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

systemd / systemd / 17992912793

24 Sep 2025 07:15PM UTC coverage: 72.205% (-0.08%) from 72.283%
17992912793

push

github

web-flow
libblkid → turn into dlopen() dependency (#39084)

Split out of #38861

153 of 207 new or added lines in 10 files covered. (73.91%)

1717 existing lines in 53 files now uncovered.

302842 of 419419 relevant lines covered (72.21%)

1052332.54 hits per line

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

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

3
#include <linux/magic.h>
4
#include <sys/statvfs.h>
5
#include <unistd.h>
6

7
#include "acl-util.h"
8
#include "alloc-util.h"
9
#include "dirent-util.h"
10
#include "errno-util.h"
11
#include "fd-util.h"
12
#include "fileio.h"
13
#include "fs-util.h"
14
#include "log.h"
15
#include "shift-uid.h"
16
#include "stat-util.h"
17
#include "string-util.h"
18
#include "user-util.h"
19

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

25
#if HAVE_ACL
26

27
static int get_acl(int fd, const char *name, acl_type_t type, acl_t *ret) {
35,846✔
28
        acl_t acl;
35,846✔
29

30
        assert(fd >= 0);
35,846✔
31
        assert(ret);
35,846✔
32

33
        if (name) {
35,846✔
34
                _cleanup_close_ int child_fd = -EBADF;
30,900✔
35

36
                child_fd = openat(fd, name, O_PATH|O_CLOEXEC|O_NOFOLLOW);
30,900✔
37
                if (child_fd < 0)
30,900✔
UNCOV
38
                        return -errno;
×
39

40
                acl = sym_acl_get_file(FORMAT_PROC_FD_PATH(child_fd), type);
30,900✔
41
        } else if (type == ACL_TYPE_ACCESS)
4,946✔
42
                acl = sym_acl_get_fd(fd);
2,473✔
43
        else
44
                acl = sym_acl_get_file(FORMAT_PROC_FD_PATH(fd), type);
2,473✔
45
        if (!acl)
35,846✔
UNCOV
46
                return -errno;
×
47

48
        *ret = acl;
35,846✔
49
        return 0;
35,846✔
50
}
51

52
static int set_acl(int fd, const char *name, acl_type_t type, acl_t acl) {
4✔
53
        int r;
4✔
54

55
        assert(fd >= 0);
4✔
56
        assert(acl);
4✔
57

58
        if (name) {
4✔
UNCOV
59
                _cleanup_close_ int child_fd = -EBADF;
×
60

61
                child_fd = openat(fd, name, O_PATH|O_CLOEXEC|O_NOFOLLOW);
×
62
                if (child_fd < 0)
×
UNCOV
63
                        return -errno;
×
64

UNCOV
65
                r = sym_acl_set_file(FORMAT_PROC_FD_PATH(child_fd), type, acl);
×
66
        } else if (type == ACL_TYPE_ACCESS)
4✔
67
                r = sym_acl_set_fd(fd, acl);
2✔
68
        else
69
                r = sym_acl_set_file(FORMAT_PROC_FD_PATH(fd), type, acl);
2✔
70
        if (r < 0)
4✔
UNCOV
71
                return -errno;
×
72

73
        return 0;
74
}
75

76
static int shift_acl(acl_t acl, uid_t shift, acl_t *ret) {
35,846✔
77
        _cleanup_(acl_freep) acl_t copy = NULL;
35,846✔
78
        acl_entry_t i;
35,846✔
79
        int r;
35,846✔
80

81
        assert(acl);
35,846✔
82
        assert(ret);
35,846✔
83

84
        r = sym_acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
35,846✔
85
        if (r < 0)
35,846✔
UNCOV
86
                return -errno;
×
87
        while (r > 0) {
135,991✔
88
                uid_t *old_uid, new_uid;
100,145✔
89
                bool modify = false;
100,145✔
90
                acl_tag_t tag;
100,145✔
91

92
                if (sym_acl_get_tag_type(i, &tag) < 0)
100,145✔
UNCOV
93
                        return -errno;
×
94

95
                if (IN_SET(tag, ACL_USER, ACL_GROUP)) {
100,145✔
96

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

101
                        old_uid = sym_acl_get_qualifier(i);
12✔
102
                        if (!old_uid)
12✔
UNCOV
103
                                return -errno;
×
104

105
                        new_uid = shift | (*old_uid & UINT32_C(0xFFFF));
12✔
106
                        if (!uid_is_valid(new_uid))
12✔
107
                                return -EINVAL;
108

109
                        modify = new_uid != *old_uid;
12✔
110
                        if (modify && !copy) {
12✔
111
                                int n;
4✔
112

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

116
                                n = sym_acl_entries(acl);
4✔
117
                                if (n < 0)
4✔
UNCOV
118
                                        return -errno;
×
119

120
                                copy = sym_acl_init(n);
4✔
121
                                if (!copy)
4✔
UNCOV
122
                                        return -errno;
×
123

124
                                /* Seek back to the beginning */
125
                                r = sym_acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
4✔
126
                                if (r < 0)
4✔
UNCOV
127
                                        return -errno;
×
128
                                continue;
4✔
129
                        }
130
                }
131

132
                if (copy) {
100,141✔
133
                        acl_entry_t new_entry;
24✔
134

135
                        if (sym_acl_create_entry(&copy, &new_entry) < 0)
24✔
UNCOV
136
                                return -errno;
×
137

138
                        if (sym_acl_copy_entry(new_entry, i) < 0)
24✔
UNCOV
139
                                return -errno;
×
140

141
                        if (modify)
24✔
142
                                if (sym_acl_set_qualifier(new_entry, &new_uid) < 0)
8✔
UNCOV
143
                                        return -errno;
×
144
                }
145

146
                r = sym_acl_get_entry(acl, ACL_NEXT_ENTRY, &i);
100,141✔
147
                if (r < 0)
100,141✔
UNCOV
148
                        return -errno;
×
149
        }
150

151
        *ret = TAKE_PTR(copy);
35,846✔
152

153
        return !!*ret;
35,846✔
154
}
155

156
static int patch_acls(int fd, const char *name, const struct stat *st, uid_t shift) {
43,768✔
157
        _cleanup_(acl_freep) acl_t acl = NULL, shifted = NULL;
43,768✔
158
        bool changed = false;
43,768✔
159
        int r;
43,768✔
160

161
        assert(fd >= 0);
43,768✔
162
        assert(st);
43,768✔
163

164
        /* ACLs are not supported on symlinks, there's no point in trying */
165
        if (!inode_type_can_acl(st->st_mode))
43,768✔
166
                return 0;
167

168
        r = dlopen_libacl();
33,373✔
169
        if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
77,141✔
170
                return 0;
171
        if (r < 0)
33,373✔
172
                return r;
173

174
        r = get_acl(fd, name, ACL_TYPE_ACCESS, &acl);
33,373✔
175
        if (r == -EOPNOTSUPP)
33,373✔
176
                return 0;
177
        if (r < 0)
33,373✔
178
                return r;
179

180
        r = shift_acl(acl, shift, &shifted);
33,373✔
181
        if (r < 0)
33,373✔
182
                return r;
183
        if (r > 0) {
33,373✔
184
                r = set_acl(fd, name, ACL_TYPE_ACCESS, shifted);
2✔
185
                if (r < 0)
2✔
186
                        return r;
187

188
                changed = true;
189
        }
190

191
        if (S_ISDIR(st->st_mode)) {
33,373✔
192
                sym_acl_free(acl);
2,473✔
193

194
                if (shifted)
2,473✔
195
                        sym_acl_free(shifted);
2✔
196

197
                acl = shifted = NULL;
2,473✔
198

199
                r = get_acl(fd, name, ACL_TYPE_DEFAULT, &acl);
2,473✔
200
                if (r < 0)
2,473✔
201
                        return r;
202

203
                r = shift_acl(acl, shift, &shifted);
2,473✔
204
                if (r < 0)
2,473✔
205
                        return r;
206
                if (r > 0) {
2,473✔
207
                        r = set_acl(fd, name, ACL_TYPE_DEFAULT, shifted);
2✔
208
                        if (r < 0)
2✔
209
                                return r;
210

211
                        changed = true;
212
                }
213
        }
214

215
        return changed;
33,373✔
216
}
217

218
#else
219

220
static int patch_acls(int fd, const char *name, const struct stat *st, uid_t shift) {
221
        return 0;
222
}
223

224
#endif
225

226
static int patch_fd(int fd, const char *name, const struct stat *st, uid_t shift) {
43,768✔
227
        uid_t new_uid;
43,768✔
228
        gid_t new_gid;
43,768✔
229
        bool changed = false;
43,768✔
230
        int r;
43,768✔
231

232
        assert(fd >= 0);
43,768✔
233
        assert(st);
43,768✔
234

235
        new_uid =         shift | (st->st_uid & UINT32_C(0xFFFF));
43,768✔
236
        new_gid = (gid_t) shift | (st->st_gid & UINT32_C(0xFFFF));
43,768✔
237

238
        if (!uid_is_valid(new_uid) || !gid_is_valid(new_gid))
87,536✔
239
                return -EINVAL;
240

241
        if (st->st_uid != new_uid || st->st_gid != new_gid) {
43,768✔
242
                if (name)
41,356✔
243
                        r = fchownat(fd, name, new_uid, new_gid, AT_SYMLINK_NOFOLLOW);
38,883✔
244
                else
245
                        r = fchown(fd, new_uid, new_gid);
2,473✔
246
                if (r < 0)
41,356✔
UNCOV
247
                        return -errno;
×
248

249
                /* The Linux kernel alters the mode in some cases of chown(). Let's undo this. */
250
                if (name) {
41,356✔
251
                        if (!S_ISLNK(st->st_mode))
38,883✔
252
                                r = fchmodat(fd, name, st->st_mode, 0);
28,488✔
253
                        else /* Changing the mode of a symlink is not supported by Linux kernel. Don't bother. */
254
                                r = 0;
255
                } else
256
                        r = fchmod(fd, st->st_mode);
2,473✔
257
                if (r < 0)
30,961✔
UNCOV
258
                        return -errno;
×
259

260
                changed = true;
261
        }
262

263
        r = patch_acls(fd, name, st, shift);
43,768✔
264
        if (r < 0)
43,768✔
265
                return r;
266

267
        return r > 0 || changed;
43,768✔
268
}
269

270
/*
271
 * Check if the filesystem is fully compatible with user namespaces or
272
 * UID/GID patching. Some filesystems in this list can be fully mounted inside
273
 * user namespaces, however their inodes may relate to host resources or only
274
 * valid in the global user namespace, therefore no patching should be applied.
275
 */
276
static int is_fs_fully_userns_compatible(const struct statfs *sfs) {
2,473✔
277

278
        assert(sfs);
2,473✔
279

280
        return F_TYPE_EQUAL(sfs->f_type, BINFMTFS_MAGIC) ||
2,473✔
281
               F_TYPE_EQUAL(sfs->f_type, CGROUP_SUPER_MAGIC) ||
2,473✔
282
               F_TYPE_EQUAL(sfs->f_type, CGROUP2_SUPER_MAGIC) ||
2,473✔
283
               F_TYPE_EQUAL(sfs->f_type, DEBUGFS_MAGIC) ||
2,473✔
284
               F_TYPE_EQUAL(sfs->f_type, DEVPTS_SUPER_MAGIC) ||
2,473✔
285
               F_TYPE_EQUAL(sfs->f_type, EFIVARFS_MAGIC) ||
2,473✔
286
               F_TYPE_EQUAL(sfs->f_type, HUGETLBFS_MAGIC) ||
2,473✔
287
               F_TYPE_EQUAL(sfs->f_type, MQUEUE_MAGIC) ||
2,473✔
288
               F_TYPE_EQUAL(sfs->f_type, PROC_SUPER_MAGIC) ||
2,473✔
289
               F_TYPE_EQUAL(sfs->f_type, PSTOREFS_MAGIC) ||
2,473✔
290
               F_TYPE_EQUAL(sfs->f_type, SELINUX_MAGIC) ||
2,473✔
291
               F_TYPE_EQUAL(sfs->f_type, SMACK_MAGIC) ||
2,473✔
292
               F_TYPE_EQUAL(sfs->f_type, SECURITYFS_MAGIC) ||
2,473✔
293
               F_TYPE_EQUAL(sfs->f_type, BPF_FS_MAGIC) ||
2,473✔
294
               F_TYPE_EQUAL(sfs->f_type, TRACEFS_MAGIC) ||
4,946✔
295
               F_TYPE_EQUAL(sfs->f_type, SYSFS_MAGIC);
296
}
297

298
static int recurse_fd(int fd, const struct stat *st, uid_t shift, bool is_toplevel) {
2,473✔
299
        _cleanup_closedir_ DIR *d = NULL;
4,946✔
300
        bool changed = false;
2,473✔
301
        struct statfs sfs;
2,473✔
302
        int r;
2,473✔
303

304
        assert(fd >= 0);
2,473✔
305

306
        if (fstatfs(fd, &sfs) < 0)
2,473✔
UNCOV
307
                return -errno;
×
308

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

313
        r = is_fs_fully_userns_compatible(&sfs);
2,473✔
314
        if (r < 0)
2,473✔
315
                return r;
316
        if (r > 0) {
2,473✔
317
                r = 0; /* don't recurse */
2,473✔
318
                return r;
319
        }
320

321
        /* Also, if we hit a read-only file system, then don't bother, skip the whole subtree */
322
        if ((sfs.f_flags & ST_RDONLY) ||
4,946✔
323
            access_fd(fd, W_OK) == -EROFS)
2,473✔
UNCOV
324
                goto read_only;
×
325

326
        if (S_ISDIR(st->st_mode)) {
2,473✔
327
                d = take_fdopendir(&fd);
2,473✔
328
                if (!d)
2,473✔
UNCOV
329
                        return -errno;
×
330

331
                FOREACH_DIRENT_ALL(de, d, return -errno) {
51,178✔
332
                        struct stat fst;
48,705✔
333

334
                        if (dot_or_dot_dot(de->d_name))
48,705✔
335
                                continue;
4,946✔
336

337
                        if (fstatat(dirfd(d), de->d_name, &fst, AT_SYMLINK_NOFOLLOW) < 0)
43,759✔
338
                                return -errno;
×
339

340
                        if (S_ISDIR(fst.st_mode)) {
43,759✔
341
                                int subdir_fd;
2,464✔
342

343
                                subdir_fd = openat(dirfd(d), de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
2,464✔
344
                                if (subdir_fd < 0)
2,464✔
UNCOV
345
                                        return -errno;
×
346

347
                                r = recurse_fd(subdir_fd, &fst, shift, false);
2,464✔
348
                                if (r < 0)
2,464✔
349
                                        return r;
350
                                if (r > 0)
2,464✔
351
                                        changed = true;
2,464✔
352

353
                        } else {
354
                                r = patch_fd(dirfd(d), de->d_name, &fst, shift);
41,295✔
355
                                if (r < 0)
41,295✔
356
                                        return r;
357
                                if (r > 0)
41,295✔
358
                                        changed = true;
38,883✔
359
                        }
360
                }
361
        }
362

363
        /* After we descended, also patch the directory itself. It's key to do this in this order so that the top-level
364
         * directory is patched as very last object in the tree, so that we can use it as quick indicator whether the
365
         * tree is properly chown()ed already. */
366
        r = patch_fd(d ? dirfd(d) : fd, NULL, st, shift);
2,473✔
367
        if (r == -EROFS)
2,473✔
368
                goto read_only;
×
369
        if (r > 0)
2,473✔
370
                changed = true;
2,473✔
371

372
        return changed;
2,473✔
373

374
read_only:
×
UNCOV
375
        if (!is_toplevel) {
×
UNCOV
376
                _cleanup_free_ char *name = NULL;
×
377

378
                /* When we hit a read-only subtree we simply skip it, but log about it. */
UNCOV
379
                (void) fd_get_path(fd, &name);
×
UNCOV
380
                log_debug("Skipping read-only file or directory %s.", strna(name));
×
UNCOV
381
                r = changed;
×
382
        }
383

384
        return r;
385
}
386

387
int path_patch_uid(const char *path, uid_t shift, uid_t range) {
11✔
388
        _cleanup_close_ int fd = -EBADF;
11✔
389
        struct stat st;
11✔
390

391
        assert(path);
11✔
392

393
        fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
11✔
394
        if (fd < 0)
11✔
UNCOV
395
                return -errno;
×
396

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

402
        /* We only support containers where the shift starts at a 2^16 boundary */
403
        if ((shift & 0xFFFF) != 0)
11✔
404
                return -EOPNOTSUPP;
405

406
        if (shift == UID_BUSY_BASE)
11✔
407
                return -EINVAL;
408

409
        /* We only support containers with 16-bit UID ranges for the patching logic */
410
        if (range != 0x10000)
11✔
411
                return -EOPNOTSUPP;
412

413
        if (fstat(fd, &st) < 0)
11✔
UNCOV
414
                return -errno;
×
415

416
        /* We only support containers where the uid/gid container ID match */
417
        if ((uint32_t) st.st_uid >> 16 != (uint32_t) st.st_gid >> 16)
11✔
418
                return -EBADE;
419

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

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

429
        if ((st.st_uid & UID_BUSY_MASK) != UID_BUSY_BASE)
9✔
430
                if (fchown(fd,
9✔
431
                           UID_BUSY_BASE | (st.st_uid & ~UID_BUSY_MASK),
9✔
432
                           (gid_t) UID_BUSY_BASE | (st.st_gid & ~(gid_t) UID_BUSY_MASK)) < 0)
9✔
UNCOV
433
                        return -errno;
×
434

435
        return recurse_fd(TAKE_FD(fd), &st, shift, true);
9✔
436
}
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