]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/uid-range.c
docs/RANDOM_SEEDS: update NetBSD link
[thirdparty/systemd.git] / 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 "path-util.h"
13 #include "sort-util.h"
14 #include "stat-util.h"
15 #include "uid-range.h"
16 #include "user-util.h"
17
18 UIDRange *uid_range_free(UIDRange *range) {
19 if (!range)
20 return NULL;
21
22 free(range->entries);
23 return mfree(range);
24 }
25
26 static bool uid_range_entry_intersect(const UIDRangeEntry *a, const UIDRangeEntry *b) {
27 assert(a);
28 assert(b);
29
30 return a->start <= b->start + b->nr && a->start + a->nr >= b->start;
31 }
32
33 static int uid_range_entry_compare(const UIDRangeEntry *a, const UIDRangeEntry *b) {
34 int r;
35
36 assert(a);
37 assert(b);
38
39 r = CMP(a->start, b->start);
40 if (r != 0)
41 return r;
42
43 return CMP(a->nr, b->nr);
44 }
45
46 static void uid_range_coalesce(UIDRange *range) {
47 assert(range);
48
49 if (range->n_entries <= 0)
50 return;
51
52 typesafe_qsort(range->entries, range->n_entries, uid_range_entry_compare);
53
54 for (size_t i = 0; i < range->n_entries; i++) {
55 UIDRangeEntry *x = range->entries + i;
56
57 for (size_t j = i + 1; j < range->n_entries; j++) {
58 UIDRangeEntry *y = range->entries + j;
59 uid_t begin, end;
60
61 if (!uid_range_entry_intersect(x, y))
62 break;
63
64 begin = MIN(x->start, y->start);
65 end = MAX(x->start + x->nr, y->start + y->nr);
66
67 x->start = begin;
68 x->nr = end - begin;
69
70 if (range->n_entries > j + 1)
71 memmove(y, y + 1, sizeof(UIDRangeEntry) * (range->n_entries - j - 1));
72
73 range->n_entries--;
74 j--;
75 }
76 }
77 }
78
79 int uid_range_add_internal(UIDRange **range, uid_t start, uid_t nr, bool coalesce) {
80 _cleanup_(uid_range_freep) UIDRange *range_new = NULL;
81 UIDRange *p;
82
83 assert(range);
84
85 if (nr <= 0)
86 return 0;
87
88 if (start > UINT32_MAX - nr) /* overflow check */
89 return -ERANGE;
90
91 if (*range)
92 p = *range;
93 else {
94 range_new = new0(UIDRange, 1);
95 if (!range_new)
96 return -ENOMEM;
97
98 p = range_new;
99 }
100
101 if (!GREEDY_REALLOC(p->entries, p->n_entries + 1))
102 return -ENOMEM;
103
104 p->entries[p->n_entries++] = (UIDRangeEntry) {
105 .start = start,
106 .nr = nr,
107 };
108
109 if (coalesce)
110 uid_range_coalesce(p);
111
112 TAKE_PTR(range_new);
113 *range = p;
114
115 return 0;
116 }
117
118 int uid_range_add_str(UIDRange **range, const char *s) {
119 uid_t start, end;
120 int r;
121
122 assert(range);
123 assert(s);
124
125 r = parse_uid_range(s, &start, &end);
126 if (r < 0)
127 return r;
128
129 return uid_range_add_internal(range, start, end - start + 1, /* coalesce = */ true);
130 }
131
132 int uid_range_next_lower(const UIDRange *range, uid_t *uid) {
133 uid_t closest = UID_INVALID, candidate;
134
135 assert(range);
136 assert(uid);
137
138 if (*uid == 0)
139 return -EBUSY;
140
141 candidate = *uid - 1;
142
143 for (size_t i = 0; i < range->n_entries; i++) {
144 uid_t begin, end;
145
146 begin = range->entries[i].start;
147 end = range->entries[i].start + range->entries[i].nr - 1;
148
149 if (candidate >= begin && candidate <= end) {
150 *uid = candidate;
151 return 1;
152 }
153
154 if (end < candidate)
155 closest = end;
156 }
157
158 if (closest == UID_INVALID)
159 return -EBUSY;
160
161 *uid = closest;
162 return 1;
163 }
164
165 bool uid_range_covers(const UIDRange *range, uid_t start, uid_t nr) {
166 if (nr == 0) /* empty range? always covered... */
167 return true;
168
169 if (start > UINT32_MAX - nr) /* range overflows? definitely not covered... */
170 return false;
171
172 if (!range)
173 return false;
174
175 for (size_t i = 0; i < range->n_entries; i++)
176 if (start >= range->entries[i].start &&
177 start + nr <= range->entries[i].start + range->entries[i].nr)
178 return true;
179
180 return false;
181 }
182
183 int uid_map_read_one(FILE *f, uid_t *ret_base, uid_t *ret_shift, uid_t *ret_range) {
184 uid_t uid_base, uid_shift, uid_range;
185 int r;
186
187 assert(f);
188 assert(ret_base);
189 assert(ret_shift);
190 assert(ret_range);
191
192 errno = 0;
193 r = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT "\n", &uid_base, &uid_shift, &uid_range);
194 if (r == EOF)
195 return errno_or_else(ENOMSG);
196 assert(r >= 0);
197 if (r != 3)
198 return -EBADMSG;
199
200 *ret_base = uid_base;
201 *ret_shift = uid_shift;
202 *ret_range = uid_range;
203
204 return 0;
205 }
206
207 int uid_range_load_userns(UIDRange **ret, const char *path) {
208 _cleanup_(uid_range_freep) UIDRange *range = NULL;
209 _cleanup_fclose_ FILE *f = NULL;
210 int r;
211
212 /* If 'path' is NULL loads the UID range of the userns namespace we run. Otherwise load the data from
213 * the specified file (which can be either uid_map or gid_map, in case caller needs to deal with GID
214 * maps).
215 *
216 * To simplify things this will modify the passed array in case of later failure. */
217
218 assert(ret);
219
220 if (!path)
221 path = "/proc/self/uid_map";
222
223 f = fopen(path, "re");
224 if (!f) {
225 r = -errno;
226
227 if (r == -ENOENT && path_startswith(path, "/proc/"))
228 return proc_mounted() > 0 ? -EOPNOTSUPP : -ENOSYS;
229
230 return r;
231 }
232
233 range = new0(UIDRange, 1);
234 if (!range)
235 return -ENOMEM;
236
237 for (;;) {
238 uid_t uid_base, uid_shift, uid_range;
239
240 r = uid_map_read_one(f, &uid_base, &uid_shift, &uid_range);
241 if (r == -ENOMSG)
242 break;
243 if (r < 0)
244 return r;
245
246 r = uid_range_add_internal(&range, uid_base, uid_range, /* coalesce = */ false);
247 if (r < 0)
248 return r;
249 }
250
251 uid_range_coalesce(range);
252
253 *ret = TAKE_PTR(range);
254 return 0;
255 }