]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
8530dc44 | 2 | |
a8fbdf54 TA |
3 | #include <string.h> |
4 | ||
62d74c78 | 5 | #include "alloc-util.h" |
5674aa7a LP |
6 | #include "errno-util.h" |
7 | #include "fd-util.h" | |
8 | #include "format-util.h" | |
8c2c8235 | 9 | #include "namespace-util.h" |
5674aa7a | 10 | #include "path-util.h" |
20ba086e | 11 | #include "process-util.h" |
760877e9 | 12 | #include "sort-util.h" |
5674aa7a | 13 | #include "stat-util.h" |
8530dc44 | 14 | #include "uid-range.h" |
b1d4f8e1 | 15 | #include "user-util.h" |
8530dc44 | 16 | |
eb7d7d14 | 17 | UIDRange *uid_range_free(UIDRange *range) { |
8dcc66ce YW |
18 | if (!range) |
19 | return NULL; | |
20 | ||
21 | free(range->entries); | |
22 | return mfree(range); | |
23 | } | |
24 | ||
eb7d7d14 | 25 | static bool uid_range_entry_intersect(const UIDRangeEntry *a, const UIDRangeEntry *b) { |
6cc6cd92 YW |
26 | assert(a); |
27 | assert(b); | |
8530dc44 | 28 | |
6cc6cd92 | 29 | return a->start <= b->start + b->nr && a->start + a->nr >= b->start; |
8530dc44 LP |
30 | } |
31 | ||
eb7d7d14 | 32 | static int uid_range_entry_compare(const UIDRangeEntry *a, const UIDRangeEntry *b) { |
34eeba47 YW |
33 | int r; |
34 | ||
35 | assert(a); | |
36 | assert(b); | |
37 | ||
38 | r = CMP(a->start, b->start); | |
39 | if (r != 0) | |
40 | return r; | |
41 | ||
42 | return CMP(a->nr, b->nr); | |
43 | } | |
44 | ||
eb7d7d14 | 45 | static void uid_range_coalesce(UIDRange *range) { |
8dcc66ce | 46 | assert(range); |
8530dc44 | 47 | |
8dcc66ce | 48 | if (range->n_entries <= 0) |
34eeba47 YW |
49 | return; |
50 | ||
8dcc66ce | 51 | typesafe_qsort(range->entries, range->n_entries, uid_range_entry_compare); |
34eeba47 | 52 | |
8dcc66ce | 53 | for (size_t i = 0; i < range->n_entries; i++) { |
eb7d7d14 | 54 | UIDRangeEntry *x = range->entries + i; |
b5739767 | 55 | |
8dcc66ce | 56 | for (size_t j = i + 1; j < range->n_entries; j++) { |
eb7d7d14 | 57 | UIDRangeEntry *y = range->entries + j; |
b5739767 | 58 | uid_t begin, end; |
8530dc44 | 59 | |
8dcc66ce | 60 | if (!uid_range_entry_intersect(x, y)) |
b5739767 | 61 | break; |
8530dc44 | 62 | |
b5739767 YW |
63 | begin = MIN(x->start, y->start); |
64 | end = MAX(x->start + x->nr, y->start + y->nr); | |
8530dc44 | 65 | |
b5739767 YW |
66 | x->start = begin; |
67 | x->nr = end - begin; | |
8530dc44 | 68 | |
8dcc66ce | 69 | if (range->n_entries > j + 1) |
eb7d7d14 | 70 | memmove(y, y + 1, sizeof(UIDRangeEntry) * (range->n_entries - j - 1)); |
8530dc44 | 71 | |
8dcc66ce | 72 | range->n_entries--; |
b5739767 | 73 | j--; |
8530dc44 LP |
74 | } |
75 | } | |
8530dc44 LP |
76 | } |
77 | ||
eb7d7d14 ZJS |
78 | int uid_range_add_internal(UIDRange **range, uid_t start, uid_t nr, bool coalesce) { |
79 | _cleanup_(uid_range_freep) UIDRange *range_new = NULL; | |
80 | UIDRange *p; | |
8dcc66ce YW |
81 | |
82 | assert(range); | |
8530dc44 LP |
83 | |
84 | if (nr <= 0) | |
85 | return 0; | |
86 | ||
0a5c6a57 LP |
87 | if (start > UINT32_MAX - nr) /* overflow check */ |
88 | return -ERANGE; | |
89 | ||
8dcc66ce YW |
90 | if (*range) |
91 | p = *range; | |
92 | else { | |
eb7d7d14 | 93 | range_new = new0(UIDRange, 1); |
8dcc66ce YW |
94 | if (!range_new) |
95 | return -ENOMEM; | |
96 | ||
97 | p = range_new; | |
98 | } | |
99 | ||
100 | if (!GREEDY_REALLOC(p->entries, p->n_entries + 1)) | |
f6c13f9f | 101 | return -ENOMEM; |
8530dc44 | 102 | |
eb7d7d14 | 103 | p->entries[p->n_entries++] = (UIDRangeEntry) { |
f6c13f9f YW |
104 | .start = start, |
105 | .nr = nr, | |
106 | }; | |
8530dc44 | 107 | |
f6c13f9f | 108 | if (coalesce) |
8dcc66ce YW |
109 | uid_range_coalesce(p); |
110 | ||
111 | TAKE_PTR(range_new); | |
112 | *range = p; | |
8530dc44 | 113 | |
8dcc66ce | 114 | return 0; |
8530dc44 LP |
115 | } |
116 | ||
eb7d7d14 | 117 | int uid_range_add_str(UIDRange **range, const char *s) { |
7cf337c5 | 118 | uid_t start, end; |
8530dc44 LP |
119 | int r; |
120 | ||
8dcc66ce | 121 | assert(range); |
8530dc44 LP |
122 | assert(s); |
123 | ||
7cf337c5 YW |
124 | r = parse_uid_range(s, &start, &end); |
125 | if (r < 0) | |
126 | return r; | |
8530dc44 | 127 | |
8dcc66ce | 128 | return uid_range_add_internal(range, start, end - start + 1, /* coalesce = */ true); |
8530dc44 LP |
129 | } |
130 | ||
eb7d7d14 | 131 | int uid_range_next_lower(const UIDRange *range, uid_t *uid) { |
fed1e721 | 132 | uid_t closest = UID_INVALID, candidate; |
8530dc44 | 133 | |
8dcc66ce | 134 | assert(range); |
8530dc44 LP |
135 | assert(uid); |
136 | ||
0a5c6a57 LP |
137 | if (*uid == 0) |
138 | return -EBUSY; | |
139 | ||
8530dc44 LP |
140 | candidate = *uid - 1; |
141 | ||
8dcc66ce | 142 | for (size_t i = 0; i < range->n_entries; i++) { |
8530dc44 LP |
143 | uid_t begin, end; |
144 | ||
8dcc66ce YW |
145 | begin = range->entries[i].start; |
146 | end = range->entries[i].start + range->entries[i].nr - 1; | |
8530dc44 LP |
147 | |
148 | if (candidate >= begin && candidate <= end) { | |
149 | *uid = candidate; | |
150 | return 1; | |
151 | } | |
152 | ||
153 | if (end < candidate) | |
154 | closest = end; | |
155 | } | |
156 | ||
fed1e721 | 157 | if (closest == UID_INVALID) |
8530dc44 LP |
158 | return -EBUSY; |
159 | ||
160 | *uid = closest; | |
161 | return 1; | |
162 | } | |
163 | ||
eb7d7d14 | 164 | bool uid_range_covers(const UIDRange *range, uid_t start, uid_t nr) { |
55656049 LP |
165 | if (nr == 0) /* empty range? always covered... */ |
166 | return true; | |
167 | ||
168 | if (start > UINT32_MAX - nr) /* range overflows? definitely not covered... */ | |
169 | return false; | |
8530dc44 | 170 | |
8dcc66ce YW |
171 | if (!range) |
172 | return false; | |
173 | ||
0aec92a5 MY |
174 | FOREACH_ARRAY(i, range->entries, range->n_entries) |
175 | if (start >= i->start && | |
176 | start + nr <= i->start + i->nr) | |
8530dc44 LP |
177 | return true; |
178 | ||
179 | return false; | |
180 | } | |
5674aa7a | 181 | |
7312c422 MY |
182 | int uid_map_read_one(FILE *f, uid_t *ret_base, uid_t *ret_shift, uid_t *ret_range) { |
183 | uid_t uid_base, uid_shift, uid_range; | |
184 | int r; | |
185 | ||
186 | assert(f); | |
7312c422 MY |
187 | |
188 | errno = 0; | |
189 | r = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT "\n", &uid_base, &uid_shift, &uid_range); | |
190 | if (r == EOF) | |
191 | return errno_or_else(ENOMSG); | |
192 | assert(r >= 0); | |
193 | if (r != 3) | |
194 | return -EBADMSG; | |
3d57b669 LP |
195 | if (uid_range <= 0) |
196 | return -EBADMSG; | |
7312c422 | 197 | |
3d57b669 LP |
198 | if (ret_base) |
199 | *ret_base = uid_base; | |
200 | if (ret_shift) | |
201 | *ret_shift = uid_shift; | |
202 | if (ret_range) | |
203 | *ret_range = uid_range; | |
7312c422 MY |
204 | |
205 | return 0; | |
206 | } | |
207 | ||
0c15577a DDM |
208 | unsigned uid_range_size(const UIDRange *range) { |
209 | if (!range) | |
210 | return 0; | |
211 | ||
212 | unsigned n = 0; | |
213 | ||
214 | FOREACH_ARRAY(e, range->entries, range->n_entries) | |
215 | n += e->nr; | |
216 | ||
217 | return n; | |
218 | } | |
219 | ||
220 | bool uid_range_is_empty(const UIDRange *range) { | |
221 | ||
222 | if (!range) | |
223 | return true; | |
224 | ||
225 | FOREACH_ARRAY(e, range->entries, range->n_entries) | |
226 | if (e->nr > 0) | |
227 | return false; | |
228 | ||
229 | return true; | |
230 | } | |
231 | ||
7fe28d83 | 232 | int uid_range_load_userns(const char *path, UIDRangeUsernsMode mode, UIDRange **ret) { |
eb7d7d14 | 233 | _cleanup_(uid_range_freep) UIDRange *range = NULL; |
5674aa7a LP |
234 | _cleanup_fclose_ FILE *f = NULL; |
235 | int r; | |
236 | ||
237 | /* If 'path' is NULL loads the UID range of the userns namespace we run. Otherwise load the data from | |
238 | * the specified file (which can be either uid_map or gid_map, in case caller needs to deal with GID | |
239 | * maps). | |
240 | * | |
241 | * To simplify things this will modify the passed array in case of later failure. */ | |
242 | ||
6ebb53d9 LP |
243 | assert(mode >= 0); |
244 | assert(mode < _UID_RANGE_USERNS_MODE_MAX); | |
7fe28d83 | 245 | assert(ret); |
f6c13f9f | 246 | |
5674aa7a | 247 | if (!path) |
6ebb53d9 | 248 | path = IN_SET(mode, UID_RANGE_USERNS_INSIDE, UID_RANGE_USERNS_OUTSIDE) ? "/proc/self/uid_map" : "/proc/self/gid_map"; |
5674aa7a LP |
249 | |
250 | f = fopen(path, "re"); | |
251 | if (!f) { | |
252 | r = -errno; | |
253 | ||
cdba12b3 LP |
254 | if (r == -ENOENT && path_startswith(path, "/proc/")) |
255 | return proc_mounted() > 0 ? -EOPNOTSUPP : -ENOSYS; | |
5674aa7a LP |
256 | |
257 | return r; | |
258 | } | |
259 | ||
eb7d7d14 | 260 | range = new0(UIDRange, 1); |
8dcc66ce YW |
261 | if (!range) |
262 | return -ENOMEM; | |
263 | ||
5674aa7a LP |
264 | for (;;) { |
265 | uid_t uid_base, uid_shift, uid_range; | |
5674aa7a | 266 | |
7312c422 MY |
267 | r = uid_map_read_one(f, &uid_base, &uid_shift, &uid_range); |
268 | if (r == -ENOMSG) | |
f6c13f9f | 269 | break; |
7312c422 MY |
270 | if (r < 0) |
271 | return r; | |
5674aa7a | 272 | |
6ebb53d9 LP |
273 | r = uid_range_add_internal( |
274 | &range, | |
275 | IN_SET(mode, UID_RANGE_USERNS_INSIDE, GID_RANGE_USERNS_INSIDE) ? uid_base : uid_shift, | |
276 | uid_range, | |
277 | /* coalesce = */ false); | |
5674aa7a LP |
278 | if (r < 0) |
279 | return r; | |
280 | } | |
f6c13f9f | 281 | |
8dcc66ce | 282 | uid_range_coalesce(range); |
f6c13f9f | 283 | |
8dcc66ce | 284 | *ret = TAKE_PTR(range); |
f6c13f9f | 285 | return 0; |
5674aa7a | 286 | } |
5bff40e7 | 287 | |
20ba086e | 288 | int uid_range_load_userns_by_fd(int userns_fd, UIDRangeUsernsMode mode, UIDRange **ret) { |
20ba086e | 289 | _cleanup_(sigkill_waitp) pid_t pid = 0; |
20ba086e LP |
290 | int r; |
291 | ||
292 | assert(userns_fd >= 0); | |
293 | assert(mode >= 0); | |
294 | assert(mode < _UID_RANGE_USERNS_MODE_MAX); | |
295 | assert(ret); | |
296 | ||
8c2c8235 | 297 | r = userns_enter_and_pin(userns_fd, &pid); |
20ba086e LP |
298 | if (r < 0) |
299 | return r; | |
20ba086e LP |
300 | |
301 | const char *p = procfs_file_alloca( | |
302 | pid, | |
303 | IN_SET(mode, UID_RANGE_USERNS_INSIDE, UID_RANGE_USERNS_OUTSIDE) ? "uid_map" : "gid_map"); | |
304 | ||
7fe28d83 | 305 | return uid_range_load_userns(p, mode, ret); |
20ba086e LP |
306 | } |
307 | ||
5bff40e7 LP |
308 | bool uid_range_overlaps(const UIDRange *range, uid_t start, uid_t nr) { |
309 | ||
310 | if (!range) | |
311 | return false; | |
312 | ||
313 | /* Avoid overflow */ | |
314 | if (start > UINT32_MAX - nr) | |
315 | nr = UINT32_MAX - start; | |
316 | ||
317 | if (nr == 0) | |
318 | return false; | |
319 | ||
320 | FOREACH_ARRAY(entry, range->entries, range->n_entries) | |
321 | if (start < entry->start + entry->nr && | |
322 | start + nr >= entry->start) | |
323 | return true; | |
324 | ||
325 | return false; | |
326 | } | |
afdd0efa LP |
327 | |
328 | bool uid_range_equal(const UIDRange *a, const UIDRange *b) { | |
329 | if (a == b) | |
330 | return true; | |
331 | ||
332 | if (!a || !b) | |
333 | return false; | |
334 | ||
335 | if (a->n_entries != b->n_entries) | |
336 | return false; | |
337 | ||
338 | for (size_t i = 0; i < a->n_entries; i++) { | |
339 | if (a->entries[i].start != b->entries[i].start) | |
340 | return false; | |
341 | if (a->entries[i].nr != b->entries[i].nr) | |
342 | return false; | |
343 | } | |
344 | ||
345 | return true; | |
346 | } | |
3d57b669 | 347 | |
a0e3dbde | 348 | int uid_map_search_root(pid_t pid, UIDRangeUsernsMode mode, uid_t *ret) { |
3d57b669 LP |
349 | int r; |
350 | ||
351 | assert(pid_is_valid(pid)); | |
a0e3dbde | 352 | assert(IN_SET(mode, UID_RANGE_USERNS_OUTSIDE, GID_RANGE_USERNS_OUTSIDE)); |
3d57b669 | 353 | |
a0e3dbde | 354 | const char *p = procfs_file_alloca(pid, mode == UID_RANGE_USERNS_OUTSIDE ? "uid_map" : "gid_map"); |
3d57b669 LP |
355 | _cleanup_fclose_ FILE *f = fopen(p, "re"); |
356 | if (!f) { | |
357 | if (errno != ENOENT) | |
358 | return -errno; | |
359 | ||
360 | r = proc_mounted(); | |
361 | if (r < 0) | |
362 | return -ENOENT; /* original error, if we can't determine /proc/ state */ | |
363 | ||
364 | return r ? -ENOPKG : -ENOSYS; | |
365 | } | |
366 | ||
367 | for (;;) { | |
368 | uid_t uid_base = UID_INVALID, uid_shift = UID_INVALID; | |
369 | ||
8821a312 | 370 | r = uid_map_read_one(f, &uid_base, &uid_shift, /* ret_range= */ NULL); |
3d57b669 LP |
371 | if (r < 0) |
372 | return r; | |
373 | ||
374 | if (uid_base == 0) { | |
375 | if (ret) | |
376 | *ret = uid_shift; | |
377 | return 0; | |
378 | } | |
379 | } | |
380 | } |