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

systemd / systemd / 14895667988

07 May 2025 08:57PM UTC coverage: 72.225% (-0.007%) from 72.232%
14895667988

push

github

yuwata
network: log_link_message_debug_errno() automatically append %m if necessary

Follow-up for d28746ef5.
Fixes CID#1609753.

0 of 1 new or added line in 1 file covered. (0.0%)

20297 existing lines in 338 files now uncovered.

297407 of 411780 relevant lines covered (72.22%)

695716.85 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 "alloc-util.h"
7
#include "dirent-util.h"
8
#include "fd-util.h"
9
#include "fileio.h"
10
#include "log.h"
11
#include "missing_magic.h"
12
#include "shift-uid.h"
13
#include "stat-util.h"
14
#include "user-util.h"
15

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

21
#if HAVE_ACL
22

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

26
        assert(fd >= 0);
31,756✔
27
        assert(ret);
31,756✔
28

29
        if (name) {
31,756✔
30
                _cleanup_close_ int child_fd = -EBADF;
27,340✔
31

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

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

44
        *ret = acl;
31,756✔
45
        return 0;
31,756✔
46
}
47

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

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

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

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

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

69
        return 0;
70
}
71

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

77
        assert(acl);
31,756✔
78
        assert(ret);
31,756✔
79

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

88
                if (acl_get_tag_type(i, &tag) < 0)
88,670✔
UNCOV
89
                        return -errno;
×
90

91
                if (IN_SET(tag, ACL_USER, ACL_GROUP)) {
88,670✔
92

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

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

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

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

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

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

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

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

128
                if (copy) {
88,666✔
129
                        acl_entry_t new_entry;
24✔
130

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

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

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

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

147
        *ret = TAKE_PTR(copy);
31,756✔
148

149
        return !!*ret;
31,756✔
150
}
151

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

157
        assert(fd >= 0);
38,668✔
158
        assert(st);
38,668✔
159

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

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

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

178
                changed = true;
179
        }
180

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

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

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

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

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

201
                        changed = true;
202
                }
203
        }
204

205
        return changed;
29,548✔
206
}
207

208
#else
209

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

214
#endif
215

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

222
        assert(fd >= 0);
38,668✔
223
        assert(st);
38,668✔
224

225
        new_uid =         shift | (st->st_uid & UINT32_C(0xFFFF));
38,668✔
226
        new_gid = (gid_t) shift | (st->st_gid & UINT32_C(0xFFFF));
38,668✔
227

228
        if (!uid_is_valid(new_uid) || !gid_is_valid(new_gid))
77,336✔
UNCOV
229
                return -EINVAL;
×
230

231
        if (st->st_uid != new_uid || st->st_gid != new_gid) {
38,668✔
232
                if (name)
36,524✔
233
                        r = fchownat(fd, name, new_uid, new_gid, AT_SYMLINK_NOFOLLOW);
34,316✔
234
                else
235
                        r = fchown(fd, new_uid, new_gid);
2,208✔
236
                if (r < 0)
36,524✔
UNCOV
237
                        return -errno;
×
238

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

250
                changed = true;
251
        }
252

253
        r = patch_acls(fd, name, st, shift);
38,668✔
254
        if (r < 0)
38,668✔
255
                return r;
256

257
        return r > 0 || changed;
38,668✔
258
}
259

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

268
        assert(sfs);
2,208✔
269

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

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

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

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

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

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

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

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

321
                FOREACH_DIRENT_ALL(de, d, return -errno) {
45,284✔
322
                        struct stat fst;
43,076✔
323

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

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

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

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

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

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

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

362
        return changed;
2,208✔
363

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

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

374
        return r;
375
}
376

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

381
        assert(path);
10✔
382

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

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

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

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

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

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

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

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

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

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

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