• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 12940458095

23 Jan 2025 10:34PM UTC coverage: 71.463% (+0.02%) from 71.444%
12940458095

push

github

web-flow
nspawn: support unpriv directory-tree containers (#35685)

So far nspawn supported unpriv containers only if backed by a DDI. This
adds dir-based unpriv containers too.

To make this work this introduces a new UID concept to systemd: the
"foreign UID range". This is a high UID range of size 64K. The idea is
that disk images that are "foreign" to the local system can use that,
and when a container or similar is invoked from it, a transiently
allocated dynamic UID range is mapped from that foreign UID range via id
mapped mounts.

This means the fully dynamic, transient UID ranges never hit the disk,
which should vastly simplify management, and does not require that uid
"subranges" are persistently delegated to any users.

The mountfsd daemon gained a new method call for acquiring an idmapped
mount fd for an mount tree owned by the foreign UID range. Access is
permitted to unpriv clients – as long as the referenced inode is located
within a dir owned by client's own uid range.

166 of 349 new or added lines in 6 files covered. (47.56%)

2432 existing lines in 65 files now uncovered.

291664 of 408131 relevant lines covered (71.46%)

709262.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.95
/src/basic/uid-range.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <errno.h>
4
#include <stdlib.h>
5
#include <string.h>
6

7
#include "alloc-util.h"
8
#include "errno-util.h"
9
#include "fd-util.h"
10
#include "format-util.h"
11
#include "macro.h"
12
#include "namespace-util.h"
13
#include "path-util.h"
14
#include "process-util.h"
15
#include "sort-util.h"
16
#include "stat-util.h"
17
#include "uid-range.h"
18
#include "user-util.h"
19

20
UIDRange *uid_range_free(UIDRange *range) {
3,900✔
21
        if (!range)
3,900✔
22
                return NULL;
23

24
        free(range->entries);
454✔
25
        return mfree(range);
454✔
26
}
27

28
static bool uid_range_entry_intersect(const UIDRangeEntry *a, const UIDRangeEntry *b) {
94✔
29
        assert(a);
94✔
30
        assert(b);
94✔
31

32
        return a->start <= b->start + b->nr && a->start + a->nr >= b->start;
94✔
33
}
34

35
static int uid_range_entry_compare(const UIDRangeEntry *a, const UIDRangeEntry *b) {
282✔
36
        int r;
282✔
37

38
        assert(a);
282✔
39
        assert(b);
282✔
40

41
        r = CMP(a->start, b->start);
282✔
42
        if (r != 0)
138✔
43
                return r;
262✔
44

45
        return CMP(a->nr, b->nr);
20✔
46
}
47

48
static void uid_range_coalesce(UIDRange *range) {
462✔
49
        assert(range);
462✔
50

51
        if (range->n_entries <= 0)
462✔
52
                return;
53

54
        typesafe_qsort(range->entries, range->n_entries, uid_range_entry_compare);
462✔
55

56
        for (size_t i = 0; i < range->n_entries; i++) {
930✔
57
                UIDRangeEntry *x = range->entries + i;
468✔
58

59
                for (size_t j = i + 1; j < range->n_entries; j++) {
556✔
60
                        UIDRangeEntry *y = range->entries + j;
94✔
61
                        uid_t begin, end;
94✔
62

63
                        if (!uid_range_entry_intersect(x, y))
94✔
64
                                break;
65

66
                        begin = MIN(x->start, y->start);
88✔
67
                        end = MAX(x->start + x->nr, y->start + y->nr);
88✔
68

69
                        x->start = begin;
88✔
70
                        x->nr = end - begin;
88✔
71

72
                        if (range->n_entries > j + 1)
88✔
73
                                memmove(y, y + 1, sizeof(UIDRangeEntry) * (range->n_entries - j - 1));
81✔
74

75
                        range->n_entries--;
88✔
76
                        j--;
88✔
77
                }
78
        }
79
}
80

81
int uid_range_add_internal(UIDRange **range, uid_t start, uid_t nr, bool coalesce) {
543✔
82
        _cleanup_(uid_range_freep) UIDRange *range_new = NULL;
543✔
83
        UIDRange *p;
543✔
84

85
        assert(range);
543✔
86

87
        if (nr <= 0)
543✔
88
                return 0;
89

90
        if (start > UINT32_MAX - nr) /* overflow check */
543✔
91
                return -ERANGE;
92

93
        if (*range)
543✔
94
                p = *range;
95
        else {
96
                range_new = new0(UIDRange, 1);
217✔
97
                if (!range_new)
217✔
98
                        return -ENOMEM;
99

100
                p = range_new;
101
        }
102

103
        if (!GREEDY_REALLOC(p->entries, p->n_entries + 1))
543✔
104
                return -ENOMEM;
105

106
        p->entries[p->n_entries++] = (UIDRangeEntry) {
543✔
107
                .start = start,
108
                .nr = nr,
109
        };
110

111
        if (coalesce)
543✔
112
                uid_range_coalesce(p);
225✔
113

114
        TAKE_PTR(range_new);
543✔
115
        *range = p;
543✔
116

117
        return 0;
543✔
118
}
119

120
int uid_range_add_str(UIDRange **range, const char *s) {
10✔
121
        uid_t start, end;
10✔
122
        int r;
10✔
123

124
        assert(range);
10✔
125
        assert(s);
10✔
126

127
        r = parse_uid_range(s, &start, &end);
10✔
128
        if (r < 0)
10✔
129
                return r;
10✔
130

131
        return uid_range_add_internal(range, start, end - start + 1, /* coalesce = */ true);
10✔
132
}
133

134
int uid_range_next_lower(const UIDRange *range, uid_t *uid) {
183✔
135
        uid_t closest = UID_INVALID, candidate;
183✔
136

137
        assert(range);
183✔
138
        assert(uid);
183✔
139

140
        if (*uid == 0)
183✔
141
                return -EBUSY;
142

143
        candidate = *uid - 1;
183✔
144

145
        for (size_t i = 0; i < range->n_entries; i++) {
229✔
146
                uid_t begin, end;
183✔
147

148
                begin = range->entries[i].start;
183✔
149
                end = range->entries[i].start + range->entries[i].nr - 1;
183✔
150

151
                if (candidate >= begin && candidate <= end) {
183✔
152
                        *uid = candidate;
137✔
153
                        return 1;
137✔
154
                }
155

156
                if (end < candidate)
46✔
157
                        closest = end;
45✔
158
        }
159

160
        if (closest == UID_INVALID)
46✔
161
                return -EBUSY;
162

163
        *uid = closest;
45✔
164
        return 1;
45✔
165
}
166

167
bool uid_range_covers(const UIDRange *range, uid_t start, uid_t nr) {
309✔
168
        if (nr == 0) /* empty range? always covered... */
309✔
169
                return true;
170

171
        if (start > UINT32_MAX - nr) /* range overflows? definitely not covered... */
308✔
172
                return false;
173

174
        if (!range)
305✔
175
                return false;
176

177
        FOREACH_ARRAY(i, range->entries, range->n_entries)
321✔
178
                if (start >= i->start &&
309✔
179
                    start + nr <= i->start + i->nr)
303✔
180
                        return true;
181

182
        return false;
183
}
184

185
int uid_map_read_one(FILE *f, uid_t *ret_base, uid_t *ret_shift, uid_t *ret_range) {
629✔
186
        uid_t uid_base, uid_shift, uid_range;
629✔
187
        int r;
629✔
188

189
        assert(f);
629✔
190

191
        errno = 0;
629✔
192
        r = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT "\n", &uid_base, &uid_shift, &uid_range);
629✔
193
        if (r == EOF)
629✔
194
                return errno_or_else(ENOMSG);
272✔
195
        assert(r >= 0);
357✔
196
        if (r != 3)
357✔
197
                return -EBADMSG;
198
        if (uid_range <= 0)
357✔
199
                return -EBADMSG;
200

201
        if (ret_base)
357✔
202
                *ret_base = uid_base;
357✔
203
        if (ret_shift)
357✔
204
                *ret_shift = uid_shift;
357✔
205
        if (ret_range)
357✔
206
                *ret_range = uid_range;
315✔
207

208
        return 0;
209
}
210

211
int uid_range_load_userns(const char *path, UIDRangeUsernsMode mode, UIDRange **ret) {
237✔
212
        _cleanup_(uid_range_freep) UIDRange *range = NULL;
×
213
        _cleanup_fclose_ FILE *f = NULL;
237✔
214
        int r;
237✔
215

216
        /* If 'path' is NULL loads the UID range of the userns namespace we run. Otherwise load the data from
217
         * the specified file (which can be either uid_map or gid_map, in case caller needs to deal with GID
218
         * maps).
219
         *
220
         * To simplify things this will modify the passed array in case of later failure. */
221

222
        assert(mode >= 0);
237✔
223
        assert(mode < _UID_RANGE_USERNS_MODE_MAX);
237✔
224
        assert(ret);
237✔
225

226
        if (!path)
237✔
227
                path = IN_SET(mode, UID_RANGE_USERNS_INSIDE, UID_RANGE_USERNS_OUTSIDE) ? "/proc/self/uid_map" : "/proc/self/gid_map";
236✔
228

229
        f = fopen(path, "re");
237✔
230
        if (!f) {
237✔
231
                r = -errno;
×
232

233
                if (r == -ENOENT && path_startswith(path, "/proc/"))
×
234
                        return proc_mounted() > 0 ? -EOPNOTSUPP : -ENOSYS;
×
235

236
                return r;
237
        }
238

239
        range = new0(UIDRange, 1);
237✔
240
        if (!range)
237✔
241
                return -ENOMEM;
242

243
        for (;;) {
238✔
244
                uid_t uid_base, uid_shift, uid_range;
475✔
245

246
                r = uid_map_read_one(f, &uid_base, &uid_shift, &uid_range);
475✔
247
                if (r == -ENOMSG)
475✔
248
                        break;
249
                if (r < 0)
238✔
250
                        return r;
×
251

252
                r = uid_range_add_internal(
238✔
253
                                &range,
254
                                IN_SET(mode, UID_RANGE_USERNS_INSIDE, GID_RANGE_USERNS_INSIDE) ? uid_base : uid_shift,
238✔
255
                                uid_range,
256
                                /* coalesce = */ false);
257
                if (r < 0)
238✔
258
                        return r;
259
        }
260

261
        uid_range_coalesce(range);
237✔
262

263
        *ret = TAKE_PTR(range);
237✔
264
        return 0;
237✔
265
}
266

UNCOV
267
int uid_range_load_userns_by_fd(int userns_fd, UIDRangeUsernsMode mode, UIDRange **ret) {
×
UNCOV
268
        _cleanup_(sigkill_waitp) pid_t pid = 0;
×
UNCOV
269
        int r;
×
270

UNCOV
271
        assert(userns_fd >= 0);
×
UNCOV
272
        assert(mode >= 0);
×
UNCOV
273
        assert(mode < _UID_RANGE_USERNS_MODE_MAX);
×
UNCOV
274
        assert(ret);
×
275

UNCOV
276
        r = userns_enter_and_pin(userns_fd, &pid);
×
UNCOV
277
        if (r < 0)
×
278
                return r;
279

UNCOV
280
        const char *p = procfs_file_alloca(
×
281
                        pid,
282
                        IN_SET(mode, UID_RANGE_USERNS_INSIDE, UID_RANGE_USERNS_OUTSIDE) ? "uid_map" : "gid_map");
283

UNCOV
284
        return uid_range_load_userns(p, mode, ret);
×
285
}
286

UNCOV
287
bool uid_range_overlaps(const UIDRange *range, uid_t start, uid_t nr) {
×
288

UNCOV
289
        if (!range)
×
290
                return false;
291

292
        /* Avoid overflow */
UNCOV
293
        if (start > UINT32_MAX - nr)
×
294
                nr = UINT32_MAX - start;
×
295

UNCOV
296
        if (nr == 0)
×
297
                return false;
298

UNCOV
299
        FOREACH_ARRAY(entry, range->entries, range->n_entries)
×
UNCOV
300
                if (start < entry->start + entry->nr &&
×
UNCOV
301
                    start + nr >= entry->start)
×
302
                        return true;
303

304
        return false;
305
}
306

307
bool uid_range_equal(const UIDRange *a, const UIDRange *b) {
4✔
308
        if (a == b)
4✔
309
                return true;
310

311
        if (!a || !b)
4✔
312
                return false;
313

314
        if (a->n_entries != b->n_entries)
3✔
315
                return false;
316

317
        for (size_t i = 0; i < a->n_entries; i++) {
5✔
318
                if (a->entries[i].start != b->entries[i].start)
3✔
319
                        return false;
320
                if (a->entries[i].nr != b->entries[i].nr)
3✔
321
                        return false;
322
        }
323

324
        return true;
325
}
326

327
int uid_map_search_root(pid_t pid, UIDRangeUsernsMode mode, uid_t *ret) {
45✔
328
        int r;
45✔
329

330
        assert(pid_is_valid(pid));
45✔
331
        assert(IN_SET(mode, UID_RANGE_USERNS_OUTSIDE, GID_RANGE_USERNS_OUTSIDE));
45✔
332

333
        const char *p = procfs_file_alloca(pid, mode == UID_RANGE_USERNS_OUTSIDE ? "uid_map" : "gid_map");
66✔
334
        _cleanup_fclose_ FILE *f = fopen(p, "re");
90✔
335
        if (!f) {
45✔
336
                if (errno != ENOENT)
×
337
                        return -errno;
×
338

339
                r = proc_mounted();
×
340
                if (r < 0)
×
341
                        return -ENOENT; /* original error, if we can't determine /proc/ state */
342

343
                return r ? -ENOPKG : -ENOSYS;
×
344
        }
345

346
        for (;;) {
×
347
                uid_t uid_base = UID_INVALID, uid_shift = UID_INVALID;
45✔
348

349
                r = uid_map_read_one(f, &uid_base, &uid_shift, /* ret_uid_range= */ NULL);
45✔
350
                if (r < 0)
45✔
351
                        return r;
45✔
352

353
                if (uid_base == 0) {
42✔
354
                        if (ret)
42✔
355
                                *ret = uid_shift;
42✔
356
                        return 0;
42✔
357
                }
358
        }
359
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc