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

systemd / systemd / 20684862027

03 Jan 2026 10:26PM UTC coverage: 72.702% (+0.03%) from 72.677%
20684862027

push

github

web-flow
core/dynamic-user: two trivial modernizations (#40264)

2 of 4 new or added lines in 1 file covered. (50.0%)

215 existing lines in 37 files now uncovered.

310139 of 426587 relevant lines covered (72.7%)

1143601.25 hits per line

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

82.41
/src/core/dynamic-user.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <grp.h>
4
#include <sys/file.h>
5
#include <sys/stat.h>
6
#include <unistd.h>
7

8
#include "clean-ipc.h"
9
#include "dynamic-user.h"
10
#include "errno-list.h"
11
#include "extract-word.h"
12
#include "fd-util.h"
13
#include "fdset.h"
14
#include "fileio.h"
15
#include "format-util.h"
16
#include "hashmap.h"
17
#include "iovec-util.h"
18
#include "lock-util.h"
19
#include "manager.h"
20
#include "random-util.h"
21
#include "serialize.h"
22
#include "siphash24.h"
23
#include "socket-util.h"
24
#include "stat-util.h"
25
#include "stdio-util.h"
26
#include "string-util.h"
27
#include "uid-classification.h"
28
#include "user-util.h"
29

30
/* Takes a value generated randomly or by hashing and turns it into a UID in the right range */
31
#define UID_CLAMP_INTO_RANGE(rnd) (((uid_t) (rnd) % (DYNAMIC_UID_MAX - DYNAMIC_UID_MIN + 1)) + DYNAMIC_UID_MIN)
32

33
DEFINE_TRIVIAL_REF_FUNC(DynamicUser, dynamic_user);
49✔
34

35
DynamicUser* dynamic_user_free(DynamicUser *d) {
36✔
36
        if (!d)
36✔
37
                return NULL;
38

39
        if (d->manager)
5✔
40
                (void) hashmap_remove(d->manager->dynamic_users, d->name);
4✔
41

42
        safe_close_pair(d->storage_socket);
5✔
43
        return mfree(d);
5✔
44
}
45

46
static int dynamic_user_add(Manager *m, const char *name, int storage_socket[static 2], DynamicUser **ret) {
51✔
47
        DynamicUser *d;
51✔
48
        int r;
51✔
49

50
        assert(m || ret);
51✔
51
        assert(name);
51✔
52
        assert(storage_socket);
51✔
53

54
        if (m) { /* Might be called in sd-executor with no manager object */
51✔
55
                r = hashmap_ensure_allocated(&m->dynamic_users, &string_hash_ops);
4✔
56
                if (r < 0)
4✔
57
                        return r;
58
        }
59

60
        d = malloc0(offsetof(DynamicUser, name) + strlen(name) + 1);
51✔
61
        if (!d)
51✔
62
                return -ENOMEM;
63

64
        strcpy(d->name, name);
51✔
65

66
        d->storage_socket[0] = storage_socket[0];
51✔
67
        d->storage_socket[1] = storage_socket[1];
51✔
68

69
        if (m) { /* Might be called in sd-executor with no manager object */
51✔
70
                r = hashmap_put(m->dynamic_users, d->name, d);
4✔
71
                if (r < 0) {
4✔
72
                        free(d);
×
73
                        return r;
×
74
                }
75
        }
76

77
        d->manager = m;
51✔
78

79
        if (ret)
51✔
80
                *ret = d;
51✔
81

82
        return 0;
83
}
84

85
static int dynamic_user_acquire(Manager *m, const char *name, DynamicUser** ret) {
4✔
86
        _cleanup_close_pair_ int storage_socket[2] = EBADF_PAIR;
4✔
87
        DynamicUser *d;
4✔
88
        int r;
4✔
89

90
        assert(m);
4✔
91
        assert(name);
4✔
92

93
        /* Return the DynamicUser structure for a specific user name. Note that this won't actually allocate a UID for
94
         * it, but just prepare the data structure for it. The UID is allocated only on demand, when it's really
95
         * needed, and in the child process we fork off, since allocation involves NSS checks which are not OK to do
96
         * from PID 1. To allow the children and PID 1 share information about allocated UIDs we use an anonymous
97
         * AF_UNIX/SOCK_DGRAM socket (called the "storage socket") that contains at most one datagram with the
98
         * allocated UID number, plus an fd referencing the lock file for the UID
99
         * (i.e. /run/systemd/dynamic-uid/$UID). Why involve the socket pair? So that PID 1 and all its children can
100
         * share the same storage for the UID and lock fd, simply by inheriting the storage socket fds. The socket pair
101
         * may exist in three different states:
102
         *
103
         * a) no datagram stored. This is the initial state. In this case the dynamic user was never realized.
104
         *
105
         * b) a datagram containing a UID stored, but no lock fd attached to it. In this case there was already a
106
         *    statically assigned UID by the same name, which we are reusing.
107
         *
108
         * c) a datagram containing a UID stored, and a lock fd is attached to it. In this case we allocated a dynamic
109
         *    UID and locked it in the file system, using the lock fd.
110
         *
111
         * As PID 1 and various children might access the socket pair simultaneously, and pop the datagram or push it
112
         * back in any time, we also maintain a lock on the socket pair. Note one peculiarity regarding locking here:
113
         * the UID lock on disk is protected via a BSD file lock (i.e. an fd-bound lock), so that the lock is kept in
114
         * place as long as there's a reference to the fd open. The lock on the storage socket pair however is a POSIX
115
         * file lock (i.e. a process-bound lock), as all users share the same fd of this (after all it is anonymous,
116
         * nobody else could get any access to it except via our own fd) and we want to synchronize access between all
117
         * processes that have access to it. */
118

119
        d = hashmap_get(m->dynamic_users, name);
4✔
120
        if (d) {
4✔
121
                if (ret) {
×
122
                        /* We already have a structure for the dynamic user, let's increase the ref count and reuse it */
123
                        d->n_ref++;
×
124
                        *ret = d;
×
125
                }
126
                return 0;
×
127
        }
128

129
        if (!valid_user_group_name(name, VALID_USER_ALLOW_NUMERIC))
4✔
130
                return -EINVAL;
131

132
        if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, storage_socket) < 0)
4✔
133
                return -errno;
×
134

135
        r = dynamic_user_add(m, name, storage_socket, &d);
4✔
136
        if (r < 0)
4✔
137
                return r;
138

139
        storage_socket[0] = storage_socket[1] = -EBADF;
4✔
140

141
        if (ret) {
4✔
142
                d->n_ref++;
4✔
143
                *ret = d;
4✔
144
        }
145

146
        return 1;
147
}
148

149
static int pick_uid(char **suggested_paths, const char *name, uid_t *ret_uid) {
43✔
150

151
        /* Find a suitable free UID. We use the following strategy to find a suitable UID:
152
         *
153
         * 1. Initially, we try to read the UID of a number of specified paths. If any of these UIDs works, we use
154
         *    them. We use in order to increase the chance of UID reuse, if StateDirectory=, CacheDirectory= or
155
         *    LogsDirectory= are used, as reusing the UID these directories are owned by saves us from having to
156
         *    recursively chown() them to new users.
157
         *
158
         * 2. If that didn't yield a currently unused UID, we hash the user name, and try to use that. This should be
159
         *    pretty good, as the use ris by default derived from the unit name, and hence the same service and same
160
         *    user should usually get the same UID as long as our hashing doesn't clash.
161
         *
162
         * 3. Finally, if that didn't work, we randomly pick UIDs, until we find one that is empty.
163
         *
164
         * Since the dynamic UID space is relatively small we'll stop trying after 100 iterations, giving up. */
165

166
        enum {
43✔
167
                PHASE_SUGGESTED,  /* the first phase, reusing directory ownership UIDs */
168
                PHASE_HASHED,     /* the second phase, deriving a UID from the username by hashing */
169
                PHASE_RANDOM,     /* the last phase, randomly picking UIDs */
170
        } phase = PHASE_SUGGESTED;
43✔
171

172
        static const uint8_t hash_key[] = {
43✔
173
                0x37, 0x53, 0x7e, 0x31, 0xcf, 0xce, 0x48, 0xf5,
174
                0x8a, 0xbb, 0x39, 0x57, 0x8d, 0xd9, 0xec, 0x59
175
        };
176

177
        unsigned n_tries = 100, current_suggested = 0;
43✔
178
        int r;
43✔
179

180
        (void) mkdir("/run/systemd/dynamic-uid", 0755);
43✔
181

182
        for (;;) {
101✔
183
                char lock_path[STRLEN("/run/systemd/dynamic-uid/") + DECIMAL_STR_MAX(uid_t) + 1];
101✔
184
                _cleanup_close_ int lock_fd = -EBADF;
101✔
185
                uid_t candidate;
101✔
186
                ssize_t l;
101✔
187

188
                if (--n_tries <= 0) /* Give up retrying eventually */
101✔
189
                        return -EBUSY;
190

191
                switch (phase) {
101✔
192

193
                case PHASE_SUGGESTED: {
58✔
194
                        struct stat st;
58✔
195

196
                        if (!suggested_paths || !suggested_paths[current_suggested]) {
58✔
197
                                /* We reached the end of the suggested paths list, let's try by hashing the name */
198
                                phase = PHASE_HASHED;
43✔
199
                                continue;
51✔
200
                        }
201

202
                        if (stat(suggested_paths[current_suggested++], &st) < 0)
15✔
203
                                continue; /* We can't read the UID of this path, but that doesn't matter, just try the next */
8✔
204

205
                        candidate = st.st_uid;
7✔
206
                        break;
7✔
207
                }
208

209
                case PHASE_HASHED:
43✔
210
                        /* A static user by this name does not exist yet. Let's find a free ID then, and use that. We
211
                         * start with a UID generated as hash from the user name. */
212
                        candidate = UID_CLAMP_INTO_RANGE(siphash24(name, strlen(name), hash_key));
43✔
213

214
                        /* If this one fails, we should proceed with random tries */
215
                        phase = PHASE_RANDOM;
43✔
216
                        break;
43✔
217

218
                case PHASE_RANDOM:
×
219

220
                        /* Pick another random UID, and see if that works for us. */
221
                        random_bytes(&candidate, sizeof(candidate));
×
222
                        candidate = UID_CLAMP_INTO_RANGE(candidate);
×
223
                        break;
×
224

225
                default:
×
226
                        assert_not_reached();
×
227
                }
228

229
                /* Make sure whatever we picked here actually is in the right range */
230
                if (!uid_is_dynamic(candidate))
50✔
231
                        continue;
7✔
232

233
                xsprintf(lock_path, "/run/systemd/dynamic-uid/" UID_FMT, candidate);
43✔
234

235
                for (;;) {
43✔
236
                        lock_fd = open(lock_path, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600);
43✔
237
                        if (lock_fd < 0)
43✔
238
                                return -errno;
×
239

240
                        r = flock(lock_fd, LOCK_EX|LOCK_NB); /* Try to get a BSD file lock on the UID lock file */
43✔
241
                        if (r < 0) {
43✔
NEW
242
                                if (errno == EAGAIN)
×
243
                                        goto next; /* already in use */
×
244

245
                                return -errno;
×
246
                        }
247

248
                        r = fd_verify_linked(lock_fd);
43✔
249
                        if (r >= 0)
43✔
250
                                break;
NEW
251
                        if (r != -EIDRM)
×
252
                                return r;
253

254
                        /* Oh, bummer, we got the lock, but the file was unlinked between the time we opened it and
255
                         * got the lock. Close it, and try again. */
256
                        lock_fd = safe_close(lock_fd);
×
257
                }
258

259
                /* Some superficial check whether this UID/GID might already be taken by some static user */
260
                if (getpwuid_malloc(candidate, /* ret= */ NULL) >= 0 ||
86✔
261
                    getgrgid_malloc((gid_t) candidate, /* ret= */ NULL) >= 0 ||
43✔
262
                    search_ipc(candidate, (gid_t) candidate) != 0) {
43✔
263
                        (void) unlink(lock_path);
×
264
                        continue;
×
265
                }
266

267
                /* Let's store the user name in the lock file, so that we can use it for looking up the username for a UID */
268
                l = pwritev(lock_fd,
129✔
269
                            (struct iovec[2]) {
43✔
270
                                    IOVEC_MAKE_STRING(name),
43✔
271
                                    IOVEC_MAKE((char[1]) { '\n' }, 1),
43✔
272
                            }, 2, 0);
273
                if (l < 0) {
43✔
274
                        r = -errno;
×
275
                        (void) unlink(lock_path);
×
276
                        return r;
×
277
                }
278

279
                (void) ftruncate(lock_fd, l);
43✔
280

281
                *ret_uid = candidate;
43✔
282
                return TAKE_FD(lock_fd);
43✔
283

284
        next:
×
285
                ;
×
286
        }
287
}
288

289
static int dynamic_user_pop(DynamicUser *d, uid_t *ret_uid, int *ret_lock_fd) {
104✔
290
        uid_t uid = UID_INVALID;
104✔
291
        struct iovec iov = IOVEC_MAKE(&uid, sizeof(uid));
104✔
292
        int lock_fd;
104✔
293
        ssize_t k;
104✔
294

295
        assert(d);
104✔
296
        assert(ret_uid);
104✔
297
        assert(ret_lock_fd);
104✔
298

299
        /* Read the UID and lock fd that is stored in the storage AF_UNIX socket. This should be called with
300
         * the lock on the socket taken. */
301

302
        k = receive_one_fd_iov(d->storage_socket[0], &iov, 1, MSG_DONTWAIT, &lock_fd);
104✔
303
        if (k < 0) {
104✔
304
                assert(errno_is_valid(-k));
86✔
305
                return (int) k;
86✔
306
        }
307

308
        *ret_uid = uid;
18✔
309
        *ret_lock_fd = lock_fd;
18✔
310

311
        return 0;
18✔
312
}
313

314
static int dynamic_user_push(DynamicUser *d, uid_t uid, int lock_fd) {
57✔
315
        struct iovec iov = IOVEC_MAKE(&uid, sizeof(uid));
57✔
316

317
        assert(d);
57✔
318

319
        /* Store the UID and lock_fd in the storage socket. This should be called with the socket pair lock taken. */
320
        return send_one_fd_iov(d->storage_socket[1], lock_fd, &iov, 1, MSG_DONTWAIT);
57✔
321
}
322

323
static void unlink_uid_lock(int lock_fd, uid_t uid) {
4✔
324
        char lock_path[STRLEN("/run/systemd/dynamic-uid/") + DECIMAL_STR_MAX(uid_t) + 1];
4✔
325

326
        if (lock_fd < 0)
4✔
327
                return;
1✔
328

329
        xsprintf(lock_path, "/run/systemd/dynamic-uid/" UID_FMT, uid);
3✔
330
        (void) unlink(lock_path);
3✔
331
}
332

333
static int dynamic_user_realize(
47✔
334
                DynamicUser *d,
335
                char **suggested_dirs,
336
                uid_t *ret_uid, gid_t *ret_gid,
337
                bool is_user) {
338

339
        _cleanup_close_ int uid_lock_fd = -EBADF;
47✔
340
        _cleanup_close_ int etc_passwd_lock_fd = -EBADF;
47✔
341
        uid_t num = UID_INVALID; /* a uid if is_user, and a gid otherwise */
47✔
342
        gid_t gid = GID_INVALID; /* a gid if is_user, ignored otherwise */
47✔
343
        int r;
47✔
344

345
        assert(d);
47✔
346
        assert(is_user == !!ret_uid);
47✔
347
        assert(ret_gid);
47✔
348

349
        /* Acquire a UID for the user name. This will allocate a UID for the user name if the user doesn't exist
350
         * yet. If it already exists its existing UID/GID will be reused. */
351

352
        r = posix_lock(d->storage_socket[0], LOCK_EX);
47✔
353
        if (r < 0)
47✔
354
                return r;
355

356
        CLEANUP_POSIX_UNLOCK(d->storage_socket[0]);
47✔
357

358
        r = dynamic_user_pop(d, &num, &uid_lock_fd);
47✔
359
        if (r < 0) {
47✔
360
                int new_uid_lock_fd;
43✔
361
                uid_t new_uid;
43✔
362

363
                if (r != -EAGAIN)
43✔
364
                        return r;
×
365

366
                /* OK, nothing stored yet, let's try to find something useful. While we are working on this release the
367
                 * lock however, so that nobody else blocks on our NSS lookups. */
368
                r = posix_lock(d->storage_socket[0], LOCK_UN);
43✔
369
                if (r < 0)
43✔
370
                        return r;
371

372
                /* Let's see if a proper, static user or group by this name exists. Try to take the lock on
373
                 * /etc/passwd, if that fails with EROFS then /etc is read-only. In that case it's fine if we don't
374
                 * take the lock, given that users can't be added there anyway in this case. */
375
                r = etc_passwd_lock_fd = take_etc_passwd_lock(NULL);
43✔
376
                if (r < 0 && r != -EROFS)
43✔
377
                        return r;
378

379
                /* First, let's parse this as numeric UID */
380
                r = parse_uid(d->name, &num);
43✔
381
                if (r < 0) {
43✔
382
                        _cleanup_free_ struct passwd *p = NULL;
×
383
                        _cleanup_free_ struct group *g = NULL;
43✔
384

385
                        if (is_user) {
43✔
386
                                /* OK, this is not a numeric UID. Let's see if there's a user by this name */
387
                                if (getpwnam_malloc(d->name, &p) >= 0) {
43✔
388
                                        num = p->pw_uid;
×
389
                                        gid = p->pw_gid;
×
390
                                } else {
391
                                        /* if the user does not exist but the group with the same name exists, refuse operation */
392
                                        if (getgrnam_malloc(d->name, /* ret= */ NULL) >= 0)
43✔
393
                                                return -EILSEQ;
394
                                }
395
                        } else {
396
                                /* Let's see if there's a group by this name */
397
                                if (getgrnam_malloc(d->name, &g) >= 0)
×
398
                                        num = (uid_t) g->gr_gid;
×
399
                                else {
400
                                        /* if the group does not exist but the user with the same name exists, refuse operation */
401
                                        if (getpwnam_malloc(d->name, /* ret= */ NULL) >= 0)
×
402
                                                return -EILSEQ;
403
                                }
404
                        }
405
                }
406

407
                if (num == UID_INVALID) {
43✔
408
                        /* No static UID assigned yet, excellent. Let's pick a new dynamic one, and lock it. */
409

410
                        uid_lock_fd = pick_uid(suggested_dirs, d->name, &num);
43✔
411
                        if (uid_lock_fd < 0)
43✔
412
                                return uid_lock_fd;
413
                }
414

415
                /* So, we found a working UID/lock combination. Let's see if we actually still need it. */
416
                r = posix_lock(d->storage_socket[0], LOCK_EX);
43✔
417
                if (r < 0) {
43✔
418
                        unlink_uid_lock(uid_lock_fd, num);
×
419
                        return r;
420
                }
421

422
                r = dynamic_user_pop(d, &new_uid, &new_uid_lock_fd);
43✔
423
                if (r < 0) {
43✔
424
                        if (r != -EAGAIN) {
43✔
425
                                /* OK, something bad happened, let's get rid of the bits we acquired. */
426
                                unlink_uid_lock(uid_lock_fd, num);
×
427
                                return r;
428
                        }
429
                } else {
430
                        /* Hmm, so as it appears there's now something stored in the storage socket.
431
                         * Throw away what we acquired, and use what's stored now. */
432

433
                        unlink_uid_lock(uid_lock_fd, num);
×
434
                        safe_close(uid_lock_fd);
×
435

436
                        num = new_uid;
×
437
                        uid_lock_fd = new_uid_lock_fd;
×
438
                }
439
        } else if (is_user && !uid_is_dynamic(num)) {
4✔
440
                _cleanup_free_ struct passwd *p = NULL;
×
441

442
                /* Statically allocated user may have different uid and gid. So, let's obtain the gid. */
443
                r = getpwuid_malloc(num, &p);
×
444
                if (r < 0)
×
445
                        return r;
×
446

447
                gid = p->pw_gid;
×
448
        }
449

450
        /* If the UID/GID was already allocated dynamically, push the data we popped out back in. If it was already
451
         * allocated statically, push the UID back too, but do not push the lock fd in. If we allocated the UID
452
         * dynamically right here, push that in along with the lock fd for it. */
453
        r = dynamic_user_push(d, num, uid_lock_fd);
47✔
454
        if (r < 0)
47✔
455
                return r;
456

457
        if (is_user) {
47✔
458
                *ret_uid = num;
47✔
459
                *ret_gid = gid != GID_INVALID ? gid : num;
94✔
460
        } else
461
                *ret_gid = num;
×
462

463
        return 0;
464
}
465

466
int dynamic_user_current(DynamicUser *d, uid_t *ret) {
10✔
467
        _cleanup_close_ int lock_fd = -EBADF;
10✔
468
        uid_t uid;
10✔
469
        int r;
10✔
470

471
        assert(d);
10✔
472

473
        /* Get the currently assigned UID for the user, if there's any. This simply pops the data from the
474
         * storage socket, and pushes it back in right-away. */
475

476
        r = posix_lock(d->storage_socket[0], LOCK_EX);
10✔
477
        if (r < 0)
10✔
478
                return r;
479

480
        CLEANUP_POSIX_UNLOCK(d->storage_socket[0]);
10✔
481

482
        r = dynamic_user_pop(d, &uid, &lock_fd);
10✔
483
        if (r < 0)
10✔
484
                return r;
485

486
        r = dynamic_user_push(d, uid, lock_fd);
10✔
487
        if (r < 0)
10✔
488
                return r;
489

490
        if (ret)
10✔
491
                *ret = uid;
10✔
492

493
        return 0;
494
}
495

496
static DynamicUser* dynamic_user_unref(DynamicUser *d) {
6✔
497
        if (!d)
6✔
498
                return NULL;
499

500
        /* Note that this doesn't actually release any resources itself. If a dynamic user should be fully
501
         * destroyed and its UID released, use dynamic_user_destroy() instead. NB: the dynamic user table may
502
         * contain entries with no references, which is commonly the case right before a daemon reload. */
503

504
        assert(d->n_ref > 0);
6✔
505
        d->n_ref--;
6✔
506

507
        return NULL;
6✔
508
}
509

510
static int dynamic_user_close(DynamicUser *d) {
4✔
511
        _cleanup_close_ int lock_fd = -EBADF;
4✔
512
        uid_t uid;
4✔
513
        int r;
4✔
514

515
        /* Release the user ID, by releasing the lock on it, and emptying the storage socket. After this the
516
         * user is unrealized again, much like it was after it the DynamicUser object was first allocated. */
517

518
        r = posix_lock(d->storage_socket[0], LOCK_EX);
4✔
519
        if (r < 0)
4✔
520
                return r;
521

522
        CLEANUP_POSIX_UNLOCK(d->storage_socket[0]);
4✔
523

524
        r = dynamic_user_pop(d, &uid, &lock_fd);
4✔
525
        if (r == -EAGAIN)
4✔
526
                /* User wasn't realized yet, nothing to do. */
527
                return 0;
528
        if (r < 0)
4✔
529
                return r;
530

531
        /* This dynamic user was realized and dynamically allocated. In this case, let's remove the lock file. */
532
        unlink_uid_lock(lock_fd, uid);
4✔
533

534
        return 1;
535
}
536

537
static DynamicUser* dynamic_user_destroy(DynamicUser *d) {
6✔
538
        if (!d)
6✔
539
                return NULL;
540

541
        /* Drop a reference to a DynamicUser object, and destroy the user completely if this was the last
542
         * reference. This is called whenever a service is shut down and wants its dynamic UID gone. Note that
543
         * dynamic_user_unref() is what is called whenever a service is simply freed, for example during a reload
544
         * cycle, where the dynamic users should not be destroyed, but our datastructures should. */
545

546
        dynamic_user_unref(d);
6✔
547

548
        if (d->n_ref > 0)
6✔
549
                return NULL;
550

551
        (void) dynamic_user_close(d);
4✔
552
        return dynamic_user_free(d);
4✔
553
}
554

555
int dynamic_user_serialize_one(DynamicUser *d, const char *key, FILE *f, FDSet *fds) {
4✔
556
        int copy0, copy1;
4✔
557

558
        assert(key);
4✔
559
        assert(f);
4✔
560
        assert(fds);
4✔
561

562
        if (!d)
4✔
563
                return 0;
564

565
        if (d->storage_socket[0] < 0 || d->storage_socket[1] < 0)
4✔
566
                return 0;
567

568
        copy0 = fdset_put_dup(fds, d->storage_socket[0]);
4✔
569
        if (copy0 < 0)
4✔
570
                return log_error_errno(copy0, "Failed to add dynamic user storage fd to serialization: %m");
×
571

572
        copy1 = fdset_put_dup(fds, d->storage_socket[1]);
4✔
573
        if (copy1 < 0)
4✔
574
                return log_error_errno(copy1, "Failed to add dynamic user storage fd to serialization: %m");
×
575

576
        (void) serialize_item_format(f, key, "%s %i %i", d->name, copy0, copy1);
4✔
577

578
        return 0;
4✔
579
}
580

581
int dynamic_user_serialize(Manager *m, FILE *f, FDSet *fds) {
70✔
582
        DynamicUser *d;
70✔
583

584
        assert(m);
70✔
585

586
        /* Dump the dynamic user database into the manager serialization, to deal with daemon reloads. */
587

588
        HASHMAP_FOREACH(d, m->dynamic_users)
70✔
589
                (void) dynamic_user_serialize_one(d, "dynamic-user", f, fds);
×
590

591
        return 0;
70✔
592
}
593

594
void dynamic_user_deserialize_one(Manager *m, const char *value, FDSet *fds, DynamicUser **ret) {
47✔
595
        _cleanup_free_ char *name = NULL, *s0 = NULL, *s1 = NULL;
47✔
596
        _cleanup_close_ int fd0 = -EBADF, fd1 = -EBADF;
94✔
597
        int r;
47✔
598

599
        assert(value);
47✔
600
        assert(fds);
47✔
601

602
        /* Parse the serialization again, after a daemon reload */
603

604
        r = extract_many_words(&value, NULL, 0, &name, &s0, &s1);
47✔
605
        if (r != 3 || !isempty(value)) {
47✔
606
                log_debug("Unable to parse dynamic user line.");
×
607
                return;
×
608
        }
609

610
        fd0 = deserialize_fd(fds, s0);
47✔
611
        if (fd0 < 0)
47✔
612
                return;
613

614
        fd1 = deserialize_fd(fds, s1);
47✔
615
        if (fd1 < 0)
47✔
616
                return;
617

618
        r = dynamic_user_add(m, name, (int[]) { fd0, fd1 }, ret);
47✔
619
        if (r < 0) {
47✔
620
                log_debug_errno(r, "Failed to add dynamic user: %m");
×
621
                return;
×
622
        }
623

624
        TAKE_FD(fd0);
47✔
625
        TAKE_FD(fd1);
47✔
626

627
        if (ret) /* If the caller uses it directly, increment the refcount */
47✔
628
                (*ret)->n_ref++;
47✔
629
}
630

631
void dynamic_user_vacuum(Manager *m, bool close_user) {
1,538✔
632
        DynamicUser *d;
1,538✔
633

634
        assert(m);
1,538✔
635

636
        /* Empty the dynamic user database, optionally cleaning up orphaned dynamic users, i.e. destroy and free users
637
         * to which no reference exist. This is called after a daemon reload finished, in order to destroy users which
638
         * might not be referenced anymore. */
639

640
        HASHMAP_FOREACH(d, m->dynamic_users) {
1,538✔
641
                if (d->n_ref > 0)
×
642
                        continue;
×
643

644
                if (close_user) {
×
645
                        log_debug("Removing orphaned dynamic user %s", d->name);
×
646
                        (void) dynamic_user_close(d);
×
647
                }
648

649
                dynamic_user_free(d);
×
650
        }
651
}
1,538✔
652

653
int dynamic_user_lookup_uid(Manager *m, uid_t uid, char **ret) {
924✔
654
        char lock_path[STRLEN("/run/systemd/dynamic-uid/") + DECIMAL_STR_MAX(uid_t) + 1];
924✔
655
        _cleanup_free_ char *user = NULL;
924✔
656
        uid_t check_uid;
924✔
657
        int r;
924✔
658

659
        assert(m);
924✔
660
        assert(ret);
924✔
661

662
        /* A friendly way to translate a dynamic user's UID into a name. */
663
        if (!uid_is_dynamic(uid))
924✔
664
                return -ESRCH;
665

666
        xsprintf(lock_path, "/run/systemd/dynamic-uid/" UID_FMT, uid);
6✔
667
        r = read_one_line_file(lock_path, &user);
6✔
668
        if (IN_SET(r, -ENOENT, 0))
6✔
669
                return -ESRCH;
670
        if (r < 0)
6✔
671
                return r;
672

673
        /* The lock file might be stale, hence let's verify the data before we return it */
674
        r = dynamic_user_lookup_name(m, user, &check_uid);
6✔
675
        if (r < 0)
6✔
676
                return r;
677
        if (check_uid != uid) /* lock file doesn't match our own idea */
6✔
678
                return -ESRCH;
679

680
        *ret = TAKE_PTR(user);
6✔
681

682
        return 0;
6✔
683
}
684

685
int dynamic_user_lookup_name(Manager *m, const char *name, uid_t *ret) {
5,181✔
686
        DynamicUser *d;
5,181✔
687
        int r;
5,181✔
688

689
        assert(m);
5,181✔
690
        assert(name);
5,181✔
691

692
        /* A friendly call for translating a dynamic user's name into its UID */
693

694
        d = hashmap_get(m->dynamic_users, name);
5,181✔
695
        if (!d)
5,181✔
696
                return -ESRCH;
5,181✔
697

698
        uid_t uid;
10✔
699
        r = dynamic_user_current(d, &uid);
10✔
700
        if (r == -EAGAIN) /* not realized yet? */
10✔
701
                return -ESRCH;
702
        if (r < 0)
10✔
703
                return r;
704

705
        if (!uid_is_dynamic(uid))
10✔
706
                return -ESRCH;
707

708
        if (ret)
8✔
709
                *ret = uid;
8✔
710

711
        return 0;
712
}
713

714
int dynamic_creds_make(Manager *m, const char *user, const char *group, DynamicCreds **ret) {
3✔
715
        _cleanup_(dynamic_creds_unrefp) DynamicCreds *creds = NULL;
3✔
716
        int r;
3✔
717

718
        assert(m);
3✔
719
        assert(ret);
3✔
720

721
        if (!user && !group) {
3✔
UNCOV
722
                *ret = NULL;
×
UNCOV
723
                return 0;
×
724
        }
725

726
        creds = new0(DynamicCreds, 1);
3✔
727
        if (!creds)
3✔
728
                return -ENOMEM;
729

730
        /* A DynamicUser object encapsulates an allocation of both a UID and a GID for a specific name. However, some
731
         * services use different user and groups. For cases like that there's DynamicCreds containing a pair of user
732
         * and group. This call allocates a pair. */
733

734
        if (user) {
3✔
735
                r = dynamic_user_acquire(m, user, &creds->user);
3✔
736
                if (r < 0)
3✔
737
                        return r;
738
        }
739

740
        if (group && !streq_ptr(user, group)) {
3✔
741
                r = dynamic_user_acquire(m, group, &creds->group);
1✔
742
                if (r < 0)
1✔
743
                        return r;
744
        } else
745
                creds->group = ASSERT_PTR(dynamic_user_ref(creds->user));
2✔
746

747
        *ret = TAKE_PTR(creds);
3✔
748

749
        return 0;
3✔
750
}
751

752
int dynamic_creds_realize(DynamicCreds *creds, char **suggested_paths, uid_t *uid, gid_t *gid) {
47✔
753
        uid_t u = UID_INVALID;
47✔
754
        gid_t g = GID_INVALID;
47✔
755
        int r;
47✔
756

757
        assert(creds);
47✔
758
        assert(uid);
47✔
759
        assert(gid);
47✔
760

761
        /* Realize both the referenced user and group */
762

763
        if (creds->user) {
47✔
764
                r = dynamic_user_realize(creds->user, suggested_paths, &u, &g, true);
47✔
765
                if (r < 0)
47✔
766
                        return r;
47✔
767
        }
768

769
        if (creds->group && creds->group != creds->user) {
47✔
UNCOV
770
                r = dynamic_user_realize(creds->group, suggested_paths, NULL, &g, false);
×
UNCOV
771
                if (r < 0)
×
772
                        return r;
773
        }
774

775
        *uid = u;
47✔
776
        *gid = g;
47✔
777
        return 0;
47✔
778
}
779

780
DynamicCreds* dynamic_creds_unref(DynamicCreds *creds) {
171✔
781
        if (!creds)
171✔
782
                return NULL;
783

UNCOV
784
        creds->user = dynamic_user_unref(creds->user);
×
UNCOV
785
        creds->group = dynamic_user_unref(creds->group);
×
786

UNCOV
787
        return mfree(creds);
×
788
}
789

790
DynamicCreds* dynamic_creds_destroy(DynamicCreds *creds) {
55✔
791
        if (!creds)
55✔
792
                return NULL;
793

794
        creds->user = dynamic_user_destroy(creds->user);
3✔
795
        creds->group = dynamic_user_destroy(creds->group);
3✔
796

797
        return mfree(creds);
3✔
798
}
799

800
void dynamic_creds_done(DynamicCreds *creds) {
32✔
801
        if (!creds)
32✔
802
                return;
803

804
        if (creds->group != creds->user)
32✔
UNCOV
805
                dynamic_user_free(creds->group);
×
806
        creds->group = creds->user = dynamic_user_free(creds->user);
32✔
807
}
808

809
void dynamic_creds_close(DynamicCreds *creds) {
10,077✔
810
        if (!creds)
10,077✔
811
                return;
812

813
        if (creds->user)
10,077✔
814
                safe_close_pair(creds->user->storage_socket);
47✔
815

816
        if (creds->group && creds->group != creds->user)
10,077✔
UNCOV
817
                safe_close_pair(creds->group->storage_socket);
×
818
}
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