]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/uid-range.c
man/systemd.mount: tmpfs automatically gains After=swap.target dep
[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_range_load_userns(UidRange **ret, const char *path) {
184 _cleanup_(uid_range_freep) UidRange *range = NULL;
185 _cleanup_fclose_ FILE *f = NULL;
186 int r;
187
188 /* If 'path' is NULL loads the UID range of the userns namespace we run. Otherwise load the data from
189 * the specified file (which can be either uid_map or gid_map, in case caller needs to deal with GID
190 * maps).
191 *
192 * To simplify things this will modify the passed array in case of later failure. */
193
194 assert(ret);
195
196 if (!path)
197 path = "/proc/self/uid_map";
198
199 f = fopen(path, "re");
200 if (!f) {
201 r = -errno;
202
203 if (r == -ENOENT && path_startswith(path, "/proc/"))
204 return proc_mounted() > 0 ? -EOPNOTSUPP : -ENOSYS;
205
206 return r;
207 }
208
209 range = new0(UidRange, 1);
210 if (!range)
211 return -ENOMEM;
212
213 for (;;) {
214 uid_t uid_base, uid_shift, uid_range;
215 int k;
216
217 errno = 0;
218 k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT "\n", &uid_base, &uid_shift, &uid_range);
219 if (k == EOF) {
220 if (ferror(f))
221 return errno_or_else(EIO);
222
223 break;
224 }
225 if (k != 3)
226 return -EBADMSG;
227
228 r = uid_range_add_internal(&range, uid_base, uid_range, /* coalesce = */ false);
229 if (r < 0)
230 return r;
231 }
232
233 uid_range_coalesce(range);
234
235 *ret = TAKE_PTR(range);
236 return 0;
237 }