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