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

systemd / systemd / 14554080340

19 Apr 2025 11:46AM UTC coverage: 72.101% (-0.03%) from 72.13%
14554080340

push

github

web-flow
Add two new paragraphs to coding style about header files (#37188)

296880 of 411754 relevant lines covered (72.1%)

687547.52 hits per line

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

83.78
/src/shared/shift-uid.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <sys/statvfs.h>
4

5
#include "acl-util.h"
6
#include "dirent-util.h"
7
#include "fd-util.h"
8
#include "fileio.h"
9
#include "log.h"
10
#include "missing_magic.h"
11
#include "shift-uid.h"
12
#include "stat-util.h"
13
#include "user-util.h"
14

15
/* While we are chmod()ing a directory tree, we set the top-level UID base to this "busy" base, so that we can always
16
 * recognize trees we are were chmod()ing recursively and got interrupted in */
17
#define UID_BUSY_BASE ((uid_t) UINT32_C(0xFFFE0000))
18
#define UID_BUSY_MASK ((uid_t) UINT32_C(0xFFFF0000))
19

20
#if HAVE_ACL
21

22
static int get_acl(int fd, const char *name, acl_type_t type, acl_t *ret) {
31,812✔
23
        acl_t acl;
31,812✔
24

25
        assert(fd >= 0);
31,812✔
26
        assert(ret);
31,812✔
27

28
        if (name) {
31,812✔
29
                _cleanup_close_ int child_fd = -EBADF;
27,396✔
30

31
                child_fd = openat(fd, name, O_PATH|O_CLOEXEC|O_NOFOLLOW);
27,396✔
32
                if (child_fd < 0)
27,396✔
33
                        return -errno;
×
34

35
                acl = acl_get_file(FORMAT_PROC_FD_PATH(child_fd), type);
27,396✔
36
        } else if (type == ACL_TYPE_ACCESS)
4,416✔
37
                acl = acl_get_fd(fd);
2,208✔
38
        else
39
                acl = acl_get_file(FORMAT_PROC_FD_PATH(fd), type);
2,208✔
40
        if (!acl)
31,812✔
41
                return -errno;
×
42

43
        *ret = acl;
31,812✔
44
        return 0;
31,812✔
45
}
46

47
static int set_acl(int fd, const char *name, acl_type_t type, acl_t acl) {
4✔
48
        int r;
4✔
49

50
        assert(fd >= 0);
4✔
51
        assert(acl);
4✔
52

53
        if (name) {
4✔
54
                _cleanup_close_ int child_fd = -EBADF;
×
55

56
                child_fd = openat(fd, name, O_PATH|O_CLOEXEC|O_NOFOLLOW);
×
57
                if (child_fd < 0)
×
58
                        return -errno;
×
59

60
                r = acl_set_file(FORMAT_PROC_FD_PATH(child_fd), type, acl);
×
61
        } else if (type == ACL_TYPE_ACCESS)
4✔
62
                r = acl_set_fd(fd, acl);
2✔
63
        else
64
                r = acl_set_file(FORMAT_PROC_FD_PATH(fd), type, acl);
2✔
65
        if (r < 0)
4✔
66
                return -errno;
×
67

68
        return 0;
69
}
70

71
static int shift_acl(acl_t acl, uid_t shift, acl_t *ret) {
31,812✔
72
        _cleanup_(acl_freep) acl_t copy = NULL;
31,812✔
73
        acl_entry_t i;
31,812✔
74
        int r;
31,812✔
75

76
        assert(acl);
31,812✔
77
        assert(ret);
31,812✔
78

79
        r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
31,812✔
80
        if (r < 0)
31,812✔
81
                return -errno;
×
82
        while (r > 0) {
120,650✔
83
                uid_t *old_uid, new_uid;
88,838✔
84
                bool modify = false;
88,838✔
85
                acl_tag_t tag;
88,838✔
86

87
                if (acl_get_tag_type(i, &tag) < 0)
88,838✔
88
                        return -errno;
×
89

90
                if (IN_SET(tag, ACL_USER, ACL_GROUP)) {
88,838✔
91

92
                        /* We don't distinguish here between uid_t and gid_t, let's make sure the compiler checks that
93
                         * this is actually OK */
94
                        assert_cc(sizeof(uid_t) == sizeof(gid_t));
12✔
95

96
                        old_uid = acl_get_qualifier(i);
12✔
97
                        if (!old_uid)
12✔
98
                                return -errno;
×
99

100
                        new_uid = shift | (*old_uid & UINT32_C(0xFFFF));
12✔
101
                        if (!uid_is_valid(new_uid))
12✔
102
                                return -EINVAL;
103

104
                        modify = new_uid != *old_uid;
12✔
105
                        if (modify && !copy) {
12✔
106
                                int n;
4✔
107

108
                                /* There's no copy of the ACL yet? if so, let's create one, and start the loop from the
109
                                 * beginning, so that we copy all entries, starting from the first, this time. */
110

111
                                n = acl_entries(acl);
4✔
112
                                if (n < 0)
4✔
113
                                        return -errno;
×
114

115
                                copy = acl_init(n);
4✔
116
                                if (!copy)
4✔
117
                                        return -errno;
×
118

119
                                /* Seek back to the beginning */
120
                                r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
4✔
121
                                if (r < 0)
4✔
122
                                        return -errno;
×
123
                                continue;
4✔
124
                        }
125
                }
126

127
                if (copy) {
88,834✔
128
                        acl_entry_t new_entry;
24✔
129

130
                        if (acl_create_entry(&copy, &new_entry) < 0)
24✔
131
                                return -errno;
×
132

133
                        if (acl_copy_entry(new_entry, i) < 0)
24✔
134
                                return -errno;
×
135

136
                        if (modify)
24✔
137
                                if (acl_set_qualifier(new_entry, &new_uid) < 0)
8✔
138
                                        return -errno;
×
139
                }
140

141
                r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i);
88,834✔
142
                if (r < 0)
88,834✔
143
                        return -errno;
×
144
        }
145

146
        *ret = TAKE_PTR(copy);
31,812✔
147

148
        return !!*ret;
31,812✔
149
}
150

151
static int patch_acls(int fd, const char *name, const struct stat *st, uid_t shift) {
39,004✔
152
        _cleanup_(acl_freep) acl_t acl = NULL, shifted = NULL;
68,608✔
153
        bool changed = false;
39,004✔
154
        int r;
39,004✔
155

156
        assert(fd >= 0);
39,004✔
157
        assert(st);
39,004✔
158

159
        /* ACLs are not supported on symlinks, there's no point in trying */
160
        if (S_ISLNK(st->st_mode))
39,004✔
161
                return 0;
162

163
        r = get_acl(fd, name, ACL_TYPE_ACCESS, &acl);
29,604✔
164
        if (r == -EOPNOTSUPP)
29,604✔
165
                return 0;
166
        if (r < 0)
29,604✔
167
                return r;
168

169
        r = shift_acl(acl, shift, &shifted);
29,604✔
170
        if (r < 0)
29,604✔
171
                return r;
172
        if (r > 0) {
29,604✔
173
                r = set_acl(fd, name, ACL_TYPE_ACCESS, shifted);
2✔
174
                if (r < 0)
2✔
175
                        return r;
176

177
                changed = true;
178
        }
179

180
        if (S_ISDIR(st->st_mode)) {
29,604✔
181
                acl_free(acl);
2,208✔
182

183
                if (shifted)
2,208✔
184
                        acl_free(shifted);
2✔
185

186
                acl = shifted = NULL;
2,208✔
187

188
                r = get_acl(fd, name, ACL_TYPE_DEFAULT, &acl);
2,208✔
189
                if (r < 0)
2,208✔
190
                        return r;
191

192
                r = shift_acl(acl, shift, &shifted);
2,208✔
193
                if (r < 0)
2,208✔
194
                        return r;
195
                if (r > 0) {
2,208✔
196
                        r = set_acl(fd, name, ACL_TYPE_DEFAULT, shifted);
2✔
197
                        if (r < 0)
2✔
198
                                return r;
199

200
                        changed = true;
201
                }
202
        }
203

204
        return changed;
29,604✔
205
}
206

207
#else
208

209
static int patch_acls(int fd, const char *name, const struct stat *st, uid_t shift) {
210
        return 0;
211
}
212

213
#endif
214

215
static int patch_fd(int fd, const char *name, const struct stat *st, uid_t shift) {
39,004✔
216
        uid_t new_uid;
39,004✔
217
        gid_t new_gid;
39,004✔
218
        bool changed = false;
39,004✔
219
        int r;
39,004✔
220

221
        assert(fd >= 0);
39,004✔
222
        assert(st);
39,004✔
223

224
        new_uid =         shift | (st->st_uid & UINT32_C(0xFFFF));
39,004✔
225
        new_gid = (gid_t) shift | (st->st_gid & UINT32_C(0xFFFF));
39,004✔
226

227
        if (!uid_is_valid(new_uid) || !gid_is_valid(new_gid))
78,008✔
228
                return -EINVAL;
×
229

230
        if (st->st_uid != new_uid || st->st_gid != new_gid) {
39,004✔
231
                if (name)
36,860✔
232
                        r = fchownat(fd, name, new_uid, new_gid, AT_SYMLINK_NOFOLLOW);
34,652✔
233
                else
234
                        r = fchown(fd, new_uid, new_gid);
2,208✔
235
                if (r < 0)
36,860✔
236
                        return -errno;
×
237

238
                /* The Linux kernel alters the mode in some cases of chown(). Let's undo this. */
239
                if (name) {
36,860✔
240
                        if (!S_ISLNK(st->st_mode))
34,652✔
241
                                r = fchmodat(fd, name, st->st_mode, 0);
25,252✔
242
                        else /* Changing the mode of a symlink is not supported by Linux kernel. Don't bother. */
243
                                r = 0;
244
                } else
245
                        r = fchmod(fd, st->st_mode);
2,208✔
246
                if (r < 0)
27,460✔
247
                        return -errno;
×
248

249
                changed = true;
250
        }
251

252
        r = patch_acls(fd, name, st, shift);
39,004✔
253
        if (r < 0)
39,004✔
254
                return r;
255

256
        return r > 0 || changed;
39,004✔
257
}
258

259
/*
260
 * Check if the filesystem is fully compatible with user namespaces or
261
 * UID/GID patching. Some filesystems in this list can be fully mounted inside
262
 * user namespaces, however their inodes may relate to host resources or only
263
 * valid in the global user namespace, therefore no patching should be applied.
264
 */
265
static int is_fs_fully_userns_compatible(const struct statfs *sfs) {
2,208✔
266

267
        assert(sfs);
2,208✔
268

269
        return F_TYPE_EQUAL(sfs->f_type, BINFMTFS_MAGIC) ||
2,208✔
270
               F_TYPE_EQUAL(sfs->f_type, CGROUP_SUPER_MAGIC) ||
2,208✔
271
               F_TYPE_EQUAL(sfs->f_type, CGROUP2_SUPER_MAGIC) ||
2,208✔
272
               F_TYPE_EQUAL(sfs->f_type, DEBUGFS_MAGIC) ||
2,208✔
273
               F_TYPE_EQUAL(sfs->f_type, DEVPTS_SUPER_MAGIC) ||
2,208✔
274
               F_TYPE_EQUAL(sfs->f_type, EFIVARFS_MAGIC) ||
2,208✔
275
               F_TYPE_EQUAL(sfs->f_type, HUGETLBFS_MAGIC) ||
2,208✔
276
               F_TYPE_EQUAL(sfs->f_type, MQUEUE_MAGIC) ||
2,208✔
277
               F_TYPE_EQUAL(sfs->f_type, PROC_SUPER_MAGIC) ||
2,208✔
278
               F_TYPE_EQUAL(sfs->f_type, PSTOREFS_MAGIC) ||
2,208✔
279
               F_TYPE_EQUAL(sfs->f_type, SELINUX_MAGIC) ||
2,208✔
280
               F_TYPE_EQUAL(sfs->f_type, SMACK_MAGIC) ||
2,208✔
281
               F_TYPE_EQUAL(sfs->f_type, SECURITYFS_MAGIC) ||
2,208✔
282
               F_TYPE_EQUAL(sfs->f_type, BPF_FS_MAGIC) ||
2,208✔
283
               F_TYPE_EQUAL(sfs->f_type, TRACEFS_MAGIC) ||
4,416✔
284
               F_TYPE_EQUAL(sfs->f_type, SYSFS_MAGIC);
285
}
286

287
static int recurse_fd(int fd, const struct stat *st, uid_t shift, bool is_toplevel) {
2,208✔
288
        _cleanup_closedir_ DIR *d = NULL;
4,416✔
289
        bool changed = false;
2,208✔
290
        struct statfs sfs;
2,208✔
291
        int r;
2,208✔
292

293
        assert(fd >= 0);
2,208✔
294

295
        if (fstatfs(fd, &sfs) < 0)
2,208✔
296
                return -errno;
×
297

298
        /* We generally want to permit crossing of mount boundaries when patching the UIDs/GIDs. However, we probably
299
         * shouldn't do this for /proc and /sys if that is already mounted into place. Hence, let's stop the recursion
300
         * when we hit procfs, sysfs or some other special file systems. */
301

302
        r = is_fs_fully_userns_compatible(&sfs);
2,208✔
303
        if (r < 0)
2,208✔
304
                return r;
305
        if (r > 0) {
2,208✔
306
                r = 0; /* don't recurse */
2,208✔
307
                return r;
308
        }
309

310
        /* Also, if we hit a read-only file system, then don't bother, skip the whole subtree */
311
        if ((sfs.f_flags & ST_RDONLY) ||
4,416✔
312
            access_fd(fd, W_OK) == -EROFS)
2,208✔
313
                goto read_only;
×
314

315
        if (S_ISDIR(st->st_mode)) {
2,208✔
316
                d = take_fdopendir(&fd);
2,208✔
317
                if (!d)
2,208✔
318
                        return -errno;
×
319

320
                FOREACH_DIRENT_ALL(de, d, return -errno) {
45,620✔
321
                        struct stat fst;
43,412✔
322

323
                        if (dot_or_dot_dot(de->d_name))
43,412✔
324
                                continue;
4,416✔
325

326
                        if (fstatat(dirfd(d), de->d_name, &fst, AT_SYMLINK_NOFOLLOW) < 0)
38,996✔
327
                                return -errno;
×
328

329
                        if (S_ISDIR(fst.st_mode)) {
38,996✔
330
                                int subdir_fd;
2,200✔
331

332
                                subdir_fd = openat(dirfd(d), de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
2,200✔
333
                                if (subdir_fd < 0)
2,200✔
334
                                        return -errno;
×
335

336
                                r = recurse_fd(subdir_fd, &fst, shift, false);
2,200✔
337
                                if (r < 0)
2,200✔
338
                                        return r;
339
                                if (r > 0)
2,200✔
340
                                        changed = true;
2,200✔
341

342
                        } else {
343
                                r = patch_fd(dirfd(d), de->d_name, &fst, shift);
36,796✔
344
                                if (r < 0)
36,796✔
345
                                        return r;
346
                                if (r > 0)
36,796✔
347
                                        changed = true;
34,652✔
348
                        }
349
                }
350
        }
351

352
        /* After we descended, also patch the directory itself. It's key to do this in this order so that the top-level
353
         * directory is patched as very last object in the tree, so that we can use it as quick indicator whether the
354
         * tree is properly chown()ed already. */
355
        r = patch_fd(d ? dirfd(d) : fd, NULL, st, shift);
2,208✔
356
        if (r == -EROFS)
2,208✔
357
                goto read_only;
×
358
        if (r > 0)
2,208✔
359
                changed = true;
2,208✔
360

361
        return changed;
2,208✔
362

363
read_only:
×
364
        if (!is_toplevel) {
×
365
                _cleanup_free_ char *name = NULL;
×
366

367
                /* When we hit a read-only subtree we simply skip it, but log about it. */
368
                (void) fd_get_path(fd, &name);
×
369
                log_debug("Skipping read-only file or directory %s.", strna(name));
×
370
                r = changed;
×
371
        }
372

373
        return r;
374
}
375

376
int path_patch_uid(const char *path, uid_t shift, uid_t range) {
10✔
377
        _cleanup_close_ int fd = -EBADF;
10✔
378
        struct stat st;
10✔
379

380
        assert(path);
10✔
381

382
        fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
10✔
383
        if (fd < 0)
10✔
384
                return -errno;
×
385

386
        /* Recursively adjusts the UID/GIDs of all files of a directory tree. This is used to automatically fix up an
387
         * OS tree to the used user namespace UID range. Note that this automatic adjustment only works for UID ranges
388
         * following the concept that the upper 16-bit of a UID identify the container, and the lower 16-bit are the actual
389
         * UID within the container. */
390

391
        /* We only support containers where the shift starts at a 2^16 boundary */
392
        if ((shift & 0xFFFF) != 0)
10✔
393
                return -EOPNOTSUPP;
394

395
        if (shift == UID_BUSY_BASE)
10✔
396
                return -EINVAL;
397

398
        /* We only support containers with 16-bit UID ranges for the patching logic */
399
        if (range != 0x10000)
10✔
400
                return -EOPNOTSUPP;
401

402
        if (fstat(fd, &st) < 0)
10✔
403
                return -errno;
×
404

405
        /* We only support containers where the uid/gid container ID match */
406
        if ((uint32_t) st.st_uid >> 16 != (uint32_t) st.st_gid >> 16)
10✔
407
                return -EBADE;
408

409
        /* Try to detect if the range is already right. Of course, this a pretty drastic optimization, as we assume
410
         * that if the top-level dir has the right upper 16-bit assigned, then everything below will have too... */
411
        if (((uint32_t) (st.st_uid ^ shift) >> 16) == 0)
10✔
412
                return 0;
413

414
        /* Before we start recursively chowning, mark the top-level dir as "busy" by chowning it to the "busy"
415
         * range. Should we be interrupted in the middle of our work, we'll see it owned by this user and will start
416
         * chown()ing it again, unconditionally, as the busy UID is not a valid UID we'd everpick for ourselves. */
417

418
        if ((st.st_uid & UID_BUSY_MASK) != UID_BUSY_BASE)
8✔
419
                if (fchown(fd,
8✔
420
                           UID_BUSY_BASE | (st.st_uid & ~UID_BUSY_MASK),
8✔
421
                           (gid_t) UID_BUSY_BASE | (st.st_gid & ~(gid_t) UID_BUSY_MASK)) < 0)
8✔
422
                        return -errno;
×
423

424
        return recurse_fd(TAKE_FD(fd), &st, shift, true);
8✔
425
}
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