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

systemd / systemd / 17813902210

17 Sep 2025 11:56PM UTC coverage: 72.24% (-0.04%) from 72.281%
17813902210

push

github

web-flow
sysupdate: use conf_files_list_strv_full() where possible (#38198)

37 of 44 new or added lines in 1 file covered. (84.09%)

6236 existing lines in 79 files now uncovered.

302695 of 419015 relevant lines covered (72.24%)

1046897.1 hits per line

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

87.23
/src/core/exec-credential.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <sys/mount.h>
4
#include <unistd.h>
5

6
#include "acl-util.h"
7
#include "cgroup.h"
8
#include "creds-util.h"
9
#include "errno-util.h"
10
#include "exec-credential.h"
11
#include "execute.h"
12
#include "fileio.h"
13
#include "fs-util.h"
14
#include "glob-util.h"
15
#include "io-util.h"
16
#include "iovec-util.h"
17
#include "label-util.h"
18
#include "log.h"
19
#include "mkdir-label.h"
20
#include "mount-util.h"
21
#include "mountpoint-util.h"
22
#include "ordered-set.h"
23
#include "path-lookup.h"
24
#include "path-util.h"
25
#include "process-util.h"
26
#include "random-util.h"
27
#include "recurse-dir.h"
28
#include "rm-rf.h"
29
#include "siphash24.h"
30
#include "stat-util.h"
31
#include "strv.h"
32
#include "tmpfile-util.h"
33
#include "user-util.h"
34

35
ExecSetCredential* exec_set_credential_free(ExecSetCredential *sc) {
×
36
        if (!sc)
×
37
                return NULL;
38

39
        free(sc->id);
×
40
        free(sc->data);
×
41
        return mfree(sc);
×
42
}
43

44
ExecLoadCredential* exec_load_credential_free(ExecLoadCredential *lc) {
24✔
45
        if (!lc)
24✔
46
                return NULL;
47

48
        free(lc->id);
24✔
49
        free(lc->path);
24✔
50
        return mfree(lc);
24✔
51
}
52

53
ExecImportCredential* exec_import_credential_free(ExecImportCredential *ic) {
4,101✔
54
        if (!ic)
4,101✔
55
                return NULL;
56

57
        free(ic->glob);
4,101✔
58
        free(ic->rename);
4,101✔
59
        return mfree(ic);
4,101✔
60
}
61

62
static void exec_import_credential_hash_func(const ExecImportCredential *ic, struct siphash *state) {
26,295✔
63
        assert(ic);
26,295✔
64
        assert(state);
26,295✔
65

66
        siphash24_compress_string(ic->glob, state);
26,295✔
67
        if (ic->rename)
26,295✔
68
                siphash24_compress_string(ic->rename, state);
2,091✔
69
}
26,295✔
70

71
static int exec_import_credential_compare_func(const ExecImportCredential *a, const ExecImportCredential *b) {
9,005✔
72
        int r;
9,005✔
73

74
        assert(a);
9,005✔
75
        assert(b);
9,005✔
76

77
        r = strcmp(a->glob, b->glob);
9,005✔
78
        if (r != 0)
9,005✔
79
                return r;
80

81
        return strcmp_ptr(a->rename, b->rename);
5✔
82
}
83

84
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
×
85
        exec_set_credential_hash_ops,
86
        char, string_hash_func, string_compare_func,
87
        ExecSetCredential, exec_set_credential_free);
88

89
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
24✔
90
        exec_load_credential_hash_ops,
91
        char, string_hash_func, string_compare_func,
92
        ExecLoadCredential, exec_load_credential_free);
93

94
DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
4,100✔
95
        exec_import_credential_hash_ops,
96
        ExecImportCredential,
97
        exec_import_credential_hash_func,
98
        exec_import_credential_compare_func,
99
        exec_import_credential_free);
100

101
int exec_context_put_load_credential(ExecContext *c, const char *id, const char *path, bool encrypted) {
85✔
102
        ExecLoadCredential *old;
85✔
103
        int r;
85✔
104

105
        assert(c);
85✔
106
        assert(id);
85✔
107
        assert(path);
85✔
108

109
        old = hashmap_get(c->load_credentials, id);
85✔
110
        if (old) {
85✔
111
                r = free_and_strdup(&old->path, path);
×
112
                if (r < 0)
×
113
                        return r;
114

115
                old->encrypted = encrypted;
×
116
        } else {
117
                _cleanup_(exec_load_credential_freep) ExecLoadCredential *lc = NULL;
×
118

119
                lc = new(ExecLoadCredential, 1);
85✔
120
                if (!lc)
85✔
121
                        return -ENOMEM;
122

123
                *lc = (ExecLoadCredential) {
85✔
124
                        .id = strdup(id),
85✔
125
                        .path = strdup(path),
85✔
126
                        .encrypted = encrypted,
127
                };
128
                if (!lc->id || !lc->path)
85✔
129
                        return -ENOMEM;
130

131
                r = hashmap_ensure_put(&c->load_credentials, &exec_load_credential_hash_ops, lc->id, lc);
85✔
132
                assert(r != -EEXIST);
85✔
133
                if (r < 0)
85✔
134
                        return r;
135

136
                TAKE_PTR(lc);
85✔
137
        }
138

139
        return 0;
140
}
141

142
int exec_context_put_set_credential(
186✔
143
                ExecContext *c,
144
                const char *id,
145
                void *data_consume,
146
                size_t size,
147
                bool encrypted) {
148

149
        _cleanup_free_ void *data = data_consume;
186✔
150
        ExecSetCredential *old;
186✔
151
        int r;
186✔
152

153
        /* Takes the ownership of data both on success and failure */
154

155
        assert(c);
186✔
156
        assert(id);
186✔
157
        assert(data || size == 0);
186✔
158

159
        old = hashmap_get(c->set_credentials, id);
186✔
160
        if (old) {
186✔
161
                free_and_replace(old->data, data);
×
162
                old->size = size;
×
163
                old->encrypted = encrypted;
×
164
        } else {
165
                _cleanup_(exec_set_credential_freep) ExecSetCredential *sc = NULL;
×
166

167
                sc = new(ExecSetCredential, 1);
186✔
168
                if (!sc)
186✔
169
                        return -ENOMEM;
170

171
                *sc = (ExecSetCredential) {
186✔
172
                        .id = strdup(id),
186✔
173
                        .data = TAKE_PTR(data),
186✔
174
                        .size = size,
175
                        .encrypted = encrypted,
176
                };
177
                if (!sc->id)
186✔
178
                        return -ENOMEM;
179

180
                r = hashmap_ensure_put(&c->set_credentials, &exec_set_credential_hash_ops, sc->id, sc);
186✔
181
                assert(r != -EEXIST);
186✔
182
                if (r < 0)
186✔
183
                        return r;
184

185
                TAKE_PTR(sc);
186✔
186
        }
187

188
        return 0;
189
}
190

191
int exec_context_put_import_credential(ExecContext *c, const char *glob, const char *rename) {
11,916✔
192
        _cleanup_(exec_import_credential_freep) ExecImportCredential *ic = NULL;
11,916✔
193
        int r;
11,916✔
194

195
        assert(c);
11,916✔
196
        assert(glob);
11,916✔
197

198
        rename = empty_to_null(rename);
11,916✔
199

200
        ic = new(ExecImportCredential, 1);
11,916✔
201
        if (!ic)
11,916✔
202
                return -ENOMEM;
203

204
        *ic = (ExecImportCredential) {
11,916✔
205
                .glob = strdup(glob),
11,916✔
206
        };
207
        if (!ic->glob)
11,916✔
208
                return -ENOMEM;
209
        if (rename) {
11,916✔
210
                ic->rename = strdup(rename);
604✔
211
                if (!ic->rename)
604✔
212
                        return -ENOMEM;
213
        }
214

215
        if (ordered_set_contains(c->import_credentials, ic))
11,916✔
216
                return 0;
217

218
        r = ordered_set_ensure_put(&c->import_credentials, &exec_import_credential_hash_ops, ic);
11,915✔
219
        assert(r != -EEXIST);
11,915✔
220
        if (r < 0)
11,915✔
221
                return r;
222

223
        TAKE_PTR(ic);
11,915✔
224

225
        return 0;
11,915✔
226
}
227

228
bool exec_params_need_credentials(const ExecParameters *p) {
23,644✔
229
        assert(p);
23,644✔
230

231
        return p->flags & (EXEC_SETUP_CREDENTIALS|EXEC_SETUP_CREDENTIALS_FRESH);
23,644✔
232
}
233

234
bool exec_context_has_credentials(const ExecContext *c) {
22,117✔
235
        assert(c);
22,117✔
236

237
        return !hashmap_isempty(c->set_credentials) ||
22,117✔
238
                !hashmap_isempty(c->load_credentials) ||
22,117✔
239
                !ordered_set_isempty(c->import_credentials);
21,764✔
240
}
241

242
bool mount_point_is_credentials(const char *runtime_prefix, const char *path) {
89,317✔
243
        const char *e;
89,317✔
244

245
        assert(runtime_prefix);
89,317✔
246
        assert(path);
89,317✔
247

248
        e = path_startswith(path, runtime_prefix);
89,317✔
249
        if (!e)
89,317✔
250
                return false;
251

252
        return path_startswith(e, "credentials");
6,491✔
253
}
254

255
static int get_credential_directory(
14,295✔
256
                const char *runtime_prefix,
257
                const char *unit,
258
                char **ret) {
259

260
        char *p;
14,295✔
261

262
        assert(ret);
14,295✔
263

264
        if (!runtime_prefix || !unit) {
14,295✔
265
                *ret = NULL;
×
266
                return 0;
×
267
        }
268

269
        p = path_join(runtime_prefix, "credentials", unit);
14,295✔
270
        if (!p)
14,295✔
271
                return -ENOMEM;
272

273
        *ret = p;
14,295✔
274
        return 1;
14,295✔
275
}
276

277
int exec_context_get_credential_directory(
11,798✔
278
                const ExecContext *context,
279
                const ExecParameters *params,
280
                const char *unit,
281
                char **ret) {
282

283
        assert(context);
11,798✔
284
        assert(params);
11,798✔
285
        assert(unit);
11,798✔
286
        assert(ret);
11,798✔
287

288
        if (!exec_params_need_credentials(params) || !exec_context_has_credentials(context)) {
11,798✔
289
                *ret = NULL;
9,148✔
290
                return 0;
9,148✔
291
        }
292

293
        return get_credential_directory(params->prefix[EXEC_DIRECTORY_RUNTIME], unit, ret);
2,650✔
294
}
295

296
int exec_context_destroy_credentials(const ExecContext *c, const char *runtime_prefix, const char *unit) {
11,645✔
297
        _cleanup_free_ char *p = NULL;
11,645✔
298
        int r;
11,645✔
299

300
        assert(c);
11,645✔
301

302
        r = get_credential_directory(runtime_prefix, unit, &p);
11,645✔
303
        if (r <= 0)
11,645✔
304
                return r;
305

306
        /* This is either a tmpfs/ramfs of its own, or a plain directory. Either way, let's first try to
307
         * unmount it, and afterwards remove the mount point */
308
        (void) umount2(p, MNT_DETACH|UMOUNT_NOFOLLOW);
11,645✔
309
        (void) rm_rf(p, REMOVE_ROOT|REMOVE_CHMOD);
11,645✔
310

311
        return 0;
312
}
313

314
static int write_credential(
1,137✔
315
                int dfd,
316
                const char *id,
317
                const void *data,
318
                size_t size,
319
                uid_t uid,
320
                gid_t gid,
321
                bool ownership_ok) {
322

323
        _cleanup_free_ char *tmp = NULL;
1,137✔
324
        _cleanup_close_ int fd = -EBADF;
1,137✔
325
        int r;
1,137✔
326

327
        assert(dfd >= 0);
1,137✔
328
        assert(id);
1,137✔
329
        assert(data || size == 0);
1,137✔
330

331
        r = tempfn_random_child("", "cred", &tmp);
1,137✔
332
        if (r < 0)
1,137✔
333
                return r;
334

335
        fd = openat(dfd, tmp, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL|O_NOFOLLOW|O_NOCTTY, 0600);
1,137✔
336
        if (fd < 0)
1,137✔
337
                return -errno;
×
338

339
        CLEANUP_TMPFILE_AT(dfd, tmp);
1,137✔
340

341
        r = loop_write(fd, data, size);
1,137✔
342
        if (r < 0)
1,137✔
343
                return r;
344

345
        r = RET_NERRNO(fchmod(fd, 0400)); /* Take away "w" bit */
1,137✔
UNCOV
346
        if (r < 0)
×
347
                return r;
348

349
        if (uid_is_valid(uid) && uid != getuid()) {
1,137✔
350
                r = fd_add_uid_acl_permission(fd, uid, ACL_READ);
90✔
351
                /* Ideally we use ACLs, since we can neatly express what we want to express:
352
                 * the user gets read access and nothing else. But if the backing fs can't
353
                 * support that (e.g. ramfs), then we can use file ownership instead. But that's
354
                 * only safe if we can then re-mount the whole thing read-only, so that the user
355
                 * can no longer chmod() the file to gain write access. */
356
                if ((ERRNO_IS_NEG_NOT_SUPPORTED(r) || ERRNO_IS_NEG_PRIVILEGE(r)) && ownership_ok)
90✔
UNCOV
357
                        r = RET_NERRNO(fchown(fd, uid, gid));
×
358
                if (r < 0)
90✔
359
                        return r;
360
        }
361

362
        r = RET_NERRNO(renameat(dfd, tmp, dfd, id));
1,137✔
UNCOV
363
        if (r < 0)
×
364
                return r;
365

366
        tmp = mfree(tmp); /* disarm CLEANUP_TMPFILE_AT() */
1,137✔
367
        return 0;
1,137✔
368
}
369

370
typedef enum CredentialSearchPath {
371
        CREDENTIAL_SEARCH_PATH_TRUSTED,
372
        CREDENTIAL_SEARCH_PATH_ENCRYPTED,
373
        CREDENTIAL_SEARCH_PATH_ALL,
374
        _CREDENTIAL_SEARCH_PATH_MAX,
375
        _CREDENTIAL_SEARCH_PATH_INVALID = -EINVAL,
376
} CredentialSearchPath;
377

378
static int credential_search_path(const ExecParameters *params, CredentialSearchPath path, char ***ret) {
7,827✔
379
        _cleanup_strv_free_ char **l = NULL;
7,827✔
380
        int r;
7,827✔
381

382
        assert(params);
7,827✔
383
        assert(path >= 0 && path < _CREDENTIAL_SEARCH_PATH_MAX);
7,827✔
384
        assert(ret);
7,827✔
385

386
        /* Assemble a search path to find credentials in. For non-encrypted credentials, We'll look in
387
         * /etc/credstore/ (and similar directories in /usr/lib/ + /run/). If we're looking for encrypted
388
         * credentials, we'll look in /etc/credstore.encrypted/ (and similar dirs). */
389

390
        if (IN_SET(path, CREDENTIAL_SEARCH_PATH_ENCRYPTED, CREDENTIAL_SEARCH_PATH_ALL)) {
7,827✔
391
                r = strv_extend(&l, params->received_encrypted_credentials_directory);
3,917✔
392
                if (r < 0)
3,917✔
UNCOV
393
                        return r;
×
394

395
                _cleanup_strv_free_ char **add = NULL;
3,917✔
396
                r = credential_store_path_encrypted(params->runtime_scope, &add);
3,917✔
397
                if (r < 0)
3,917✔
398
                        return r;
399

400
                r = strv_extend_strv_consume(&l, TAKE_PTR(add), /* filter_duplicates= */ false);
3,917✔
401
                if (r < 0)
3,917✔
402
                        return r;
403
        }
404

405
        if (IN_SET(path, CREDENTIAL_SEARCH_PATH_TRUSTED, CREDENTIAL_SEARCH_PATH_ALL)) {
7,827✔
406
                r = strv_extend(&l, params->received_credentials_directory);
3,917✔
407
                if (r < 0)
3,917✔
UNCOV
408
                        return r;
×
409

410
                _cleanup_strv_free_ char **add = NULL;
3,917✔
411
                r = credential_store_path(params->runtime_scope, &add);
3,917✔
412
                if (r < 0)
3,917✔
413
                        return r;
414

415
                r = strv_extend_strv_consume(&l, TAKE_PTR(add), /* filter_duplicates= */ false);
3,917✔
416
                if (r < 0)
3,917✔
417
                        return r;
418
        }
419

420
        if (DEBUG_LOGGING) {
7,827✔
421
                _cleanup_free_ char *t = strv_join(l, ":");
15,654✔
422
                log_debug("Credential search path is: %s", strempty(t));
7,827✔
423
        }
424

425
        *ret = TAKE_PTR(l);
7,827✔
426
        return 0;
7,827✔
427
}
428

429
static bool device_nodes_restricted(
5✔
430
                const ExecContext *c,
431
                const CGroupContext *cgroup_context) {
432

433
        assert(c);
5✔
434
        assert(cgroup_context);
5✔
435

436
        /* Returns true if we have any reason to believe we might not be able to access the TPM device
437
         * directly, even if we run as root/PID 1. This could be because /dev/ is replaced by a private
438
         * version, or because a device node access list is configured. */
439

440
        if (c->private_devices)
5✔
441
                return true;
442

443
        if (cgroup_context_has_device_policy(cgroup_context))
3✔
UNCOV
444
                return true;
×
445

446
        return false;
447
}
448

449
struct load_cred_args {
450
        const ExecContext *context;
451
        const CGroupContext *cgroup_context;
452
        const ExecParameters *params;
453
        const char *unit;
454
        bool encrypted;
455
        int write_dfd;
456
        uid_t uid;
457
        gid_t gid;
458
        bool ownership_ok;
459
        uint64_t left;
460
};
461

462
static int maybe_decrypt_and_write_credential(
1,137✔
463
                struct load_cred_args *args,
464
                const char *id,
465
                const char *data,
466
                size_t size) {
467

468
        _cleanup_(iovec_done_erase) struct iovec plaintext = {};
1,137✔
469
        size_t add;
1,137✔
470
        int r;
1,137✔
471

472
        assert(args);
1,137✔
473
        assert(args->write_dfd >= 0);
1,137✔
474
        assert(id);
1,137✔
475
        assert(data || size == 0);
1,137✔
476

477
        if (args->encrypted) {
1,137✔
478
                CredentialFlags flags = 0; /* only allow user creds in user scope */
7✔
479

480
                switch (args->params->runtime_scope) {
7✔
481

482
                case RUNTIME_SCOPE_SYSTEM:
5✔
483
                        /* In system mode talk directly to the TPM – unless we live in a device sandbox
484
                         * which might block TPM device access. */
485

486
                        flags |= CREDENTIAL_ANY_SCOPE;
5✔
487

488
                        if (!device_nodes_restricted(args->context, args->cgroup_context)) {
5✔
489
                                r = decrypt_credential_and_warn(
3✔
490
                                                id,
491
                                                now(CLOCK_REALTIME),
492
                                                /* tpm2_device= */ NULL,
493
                                                /* tpm2_signature_path= */ NULL,
494
                                                getuid(),
3✔
495
                                                &IOVEC_MAKE(data, size),
3✔
496
                                                flags,
497
                                                &plaintext);
498
                                break;
3✔
499
                        }
500

501
                        _fallthrough_;
4✔
502

503
                case RUNTIME_SCOPE_USER:
504
                        /* In per user mode we'll not have access to the machine secret, nor to the TPM (most
505
                         * likely), hence go via the IPC service instead. Do this if we are run in root's
506
                         * per-user invocation too, to minimize differences and because isolating this logic
507
                         * into a separate process is generally a good thing anyway. */
508
                        r = ipc_decrypt_credential(
4✔
509
                                        id,
510
                                        now(CLOCK_REALTIME),
511
                                        getuid(),
4✔
512
                                        &IOVEC_MAKE(data, size),
4✔
513
                                        flags,
514
                                        &plaintext);
515
                        break;
516

UNCOV
517
                default:
×
UNCOV
518
                        assert_not_reached();
×
519
                }
520
                if (r < 0)
7✔
521
                        return r;
522

523
                data = plaintext.iov_base;
7✔
524
                size = plaintext.iov_len;
7✔
525
        }
526

527
        add = strlen(id) + size;
1,137✔
528
        if (add > args->left)
1,137✔
529
                return -E2BIG;
530

531
        r = write_credential(args->write_dfd, id, data, size, args->uid, args->gid, args->ownership_ok);
1,137✔
532
        if (r < 0)
1,137✔
UNCOV
533
                return log_debug_errno(r, "Failed to write credential '%s': %m", id);
×
534

535
        args->left -= add;
1,137✔
536

537
        return 0;
1,137✔
538
}
539

540
static int load_credential_glob(
7,820✔
541
                struct load_cred_args *args,
542
                const ExecImportCredential *ic,
543
                char * const *search_path,
544
                ReadFullFileFlags flags) {
545

546
        int r;
7,820✔
547

548
        assert(args);
7,820✔
549
        assert(args->write_dfd >= 0);
7,820✔
550
        assert(ic);
7,820✔
551
        assert(search_path);
7,820✔
552

553
        STRV_FOREACH(d, search_path) {
43,007✔
UNCOV
554
                _cleanup_strv_free_ char **paths = NULL;
×
555
                _cleanup_free_ char *j = NULL;
35,187✔
556

557
                j = path_join(*d, ic->glob);
35,187✔
558
                if (!j)
35,187✔
559
                        return -ENOMEM;
560

561
                r = safe_glob(j, /* flags = */ 0, &paths);
35,187✔
562
                if (r == -ENOENT)
35,187✔
563
                        continue;
34,409✔
564
                if (r < 0)
778✔
565
                        return r;
566

567
                STRV_FOREACH(p, paths) {
1,809✔
568
                        _cleanup_free_ char *fn = NULL;
1,031✔
569
                        _cleanup_(erase_and_freep) char *data = NULL;
1,031✔
570
                        size_t size;
1,031✔
571

572
                        r = path_extract_filename(*p, &fn);
1,031✔
573
                        if (r < 0)
1,031✔
UNCOV
574
                                return log_debug_errno(r, "Failed to extract filename from '%s': %m", *p);
×
575

576
                        if (ic->rename) {
1,031✔
UNCOV
577
                                _cleanup_free_ char *renamed = NULL;
×
578

579
                                renamed = strjoin(ic->rename, fn + strlen(ic->glob) - !!endswith(ic->glob, "*"));
180✔
580
                                if (!renamed)
180✔
UNCOV
581
                                        return log_oom_debug();
×
582

583
                                free_and_replace(fn, renamed);
180✔
584
                        }
585

586
                        if (!credential_name_valid(fn)) {
1,031✔
587
                                log_debug("Skipping credential with invalid name: %s", fn);
5✔
588
                                continue;
5✔
589
                        }
590

591
                        if (faccessat(args->write_dfd, fn, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) {
1,026✔
592
                                log_debug("Skipping credential with duplicated ID %s at %s", fn, *p);
10✔
593
                                continue;
10✔
594
                        }
595
                        if (errno != ENOENT)
1,016✔
UNCOV
596
                                return log_debug_errno(errno, "Failed to test if credential %s exists: %m", fn);
×
597

598
                        /* path is absolute, hence pass AT_FDCWD as nop dir fd here */
599
                        r = read_full_file_full(
1,016✔
600
                                        AT_FDCWD,
601
                                        *p,
602
                                        UINT64_MAX,
603
                                        args->encrypted ? CREDENTIAL_ENCRYPTED_SIZE_MAX : CREDENTIAL_SIZE_MAX,
1,016✔
604
                                        flags,
605
                                        NULL,
606
                                        &data, &size);
607
                        if (r < 0)
1,016✔
UNCOV
608
                                return log_debug_errno(r, "Failed to read credential '%s': %m", *p);
×
609

610
                        r = maybe_decrypt_and_write_credential(args, fn, data, size);
1,016✔
611
                        if (r < 0)
1,016✔
612
                                return r;
613
                }
614
        }
615

616
        return 0;
617
}
618

619
static int load_credential(
31✔
620
                struct load_cred_args *args,
621
                const char *id,
622
                int read_dfd,
623
                const char *path) {
624

625
        ReadFullFileFlags flags = READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER;
31✔
UNCOV
626
        _cleanup_strv_free_ char **search_path = NULL;
×
627
        _cleanup_free_ char *bindname = NULL;
31✔
628
        const char *source = NULL;
31✔
629
        bool missing_ok;
31✔
630
        _cleanup_(erase_and_freep) char *data = NULL;
31✔
631
        size_t size, maxsz;
31✔
632
        int r;
31✔
633

634
        assert(args);
31✔
635
        assert(args->context);
31✔
636
        assert(args->params);
31✔
637
        assert(args->unit);
31✔
638
        assert(args->write_dfd >= 0);
31✔
639
        assert(id);
31✔
640
        assert(read_dfd >= 0 || read_dfd == AT_FDCWD);
31✔
641
        assert(path);
31✔
642

643
        if (read_dfd >= 0) {
31✔
644
                /* If a directory fd is specified, then read the file directly from that dir. In this case we
645
                 * won't do AF_UNIX stuff (we simply don't want to recursively iterate down a tree of AF_UNIX
646
                 * IPC sockets). It's OK if a file vanishes here in the time we enumerate it and intend to
647
                 * open it. */
648

649
                if (!filename_is_valid(path)) /* safety check */
4✔
650
                        return -EINVAL;
651

652
                missing_ok = true;
653
                source = path;
654

655
        } else if (path_is_absolute(path)) {
27✔
656
                /* If this is an absolute path, read the data directly from it, and support AF_UNIX
657
                 * sockets */
658

659
                if (!path_is_valid(path)) /* safety check */
20✔
660
                        return -EINVAL;
661

662
                flags |= READ_FULL_FILE_CONNECT_SOCKET;
20✔
663

664
                /* Pass some minimal info about the unit and the credential name we are looking to acquire
665
                 * via the source socket address in case we read off an AF_UNIX socket. */
666
                if (asprintf(&bindname, "@%" PRIx64 "/unit/%s/%s", random_u64(), args->unit, id) < 0)
20✔
667
                        return -ENOMEM;
668

669
                missing_ok = false;
670
                source = path;
671

672
        } else if (credential_name_valid(path)) {
7✔
673
                /* If this is a relative path, take it as credential name relative to the credentials
674
                 * directory we received ourselves. We don't support the AF_UNIX stuff in this mode, since we
675
                 * are operating on a credential store, i.e. this is guaranteed to be regular files. */
676

677
                r = credential_search_path(args->params, CREDENTIAL_SEARCH_PATH_ALL, &search_path);
7✔
678
                if (r < 0)
7✔
679
                        return r;
680

681
                missing_ok = true;
682
        } else
683
                return -EINVAL;
684

685
        if (args->encrypted) {
31✔
686
                flags |= READ_FULL_FILE_UNBASE64;
3✔
687
                maxsz = CREDENTIAL_ENCRYPTED_SIZE_MAX;
3✔
688
        } else
689
                maxsz = CREDENTIAL_SIZE_MAX;
690

691
        if (search_path)
31✔
692
                STRV_FOREACH(d, search_path) {
43✔
693
                        _cleanup_free_ char *j = NULL;
43✔
694

695
                        j = path_join(*d, path);
43✔
696
                        if (!j)
43✔
UNCOV
697
                                return -ENOMEM;
×
698

699
                        r = read_full_file_full(
43✔
700
                                        AT_FDCWD, j, /* path is absolute, hence pass AT_FDCWD as nop dir fd here */
701
                                        UINT64_MAX,
702
                                        maxsz,
703
                                        flags,
704
                                        NULL,
705
                                        &data, &size);
706
                        if (r != -ENOENT)
43✔
707
                                break;
708
                }
709
        else if (source)
24✔
710
                r = read_full_file_full(
24✔
711
                                read_dfd, source,
712
                                UINT64_MAX,
713
                                maxsz,
714
                                flags,
715
                                bindname,
716
                                &data, &size);
717
        else
UNCOV
718
                assert_not_reached();
×
719

720
        if (r == -ENOENT && (missing_ok || hashmap_contains(args->context->set_credentials, id))) {
31✔
721
                /* Make a missing inherited credential non-fatal, let's just continue. After all apps
722
                 * will get clear errors if we don't pass such a missing credential on as they
723
                 * themselves will get ENOENT when trying to read them, which should not be much
724
                 * worse than when we handle the error here and make it fatal.
725
                 *
726
                 * Also, if the source file doesn't exist, but a fallback is set via SetCredentials=
727
                 * we are fine, too. */
728
                log_full_errno(hashmap_contains(args->context->set_credentials, id) ? LOG_DEBUG : LOG_INFO,
1✔
729
                               r, "Couldn't read inherited credential '%s', skipping: %m", path);
730
                return 0;
1✔
731
        }
732
        if (r < 0)
30✔
UNCOV
733
                return log_debug_errno(r, "Failed to read credential '%s': %m", path);
×
734

735
        return maybe_decrypt_and_write_credential(args, id, data, size);
30✔
736
}
737

738
static int load_cred_recurse_dir_cb(
6✔
739
                RecurseDirEvent event,
740
                const char *path,
741
                int dir_fd,
742
                int inode_fd,
743
                const struct dirent *de,
744
                const struct statx *sx,
745
                void *userdata) {
746

747
        struct load_cred_args *args = ASSERT_PTR(userdata);
6✔
748
        _cleanup_free_ char *sub_id = NULL;
6✔
749
        int r;
6✔
750

751
        assert(path);
6✔
752
        assert(de);
6✔
753

754
        if (event != RECURSE_DIR_ENTRY)
6✔
755
                return RECURSE_DIR_CONTINUE;
756

757
        if (!IN_SET(de->d_type, DT_REG, DT_SOCK))
4✔
758
                return RECURSE_DIR_CONTINUE;
759

760
        sub_id = strreplace(path, "/", "_");
4✔
761
        if (!sub_id)
4✔
762
                return -ENOMEM;
763

764
        if (!credential_name_valid(sub_id))
4✔
UNCOV
765
                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Credential would get ID '%s', which is not valid, refusing.", sub_id);
×
766

767
        if (faccessat(args->write_dfd, sub_id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) {
4✔
UNCOV
768
                log_debug("Skipping credential with duplicated ID %s at %s", sub_id, path);
×
UNCOV
769
                return RECURSE_DIR_CONTINUE;
×
770
        }
771
        if (errno != ENOENT)
4✔
UNCOV
772
                return log_debug_errno(errno, "Failed to test if credential %s exists: %m", sub_id);
×
773

774
        r = load_credential(args,
8✔
775
                            sub_id,
776
                            dir_fd, de->d_name);
4✔
777
        if (r < 0)
4✔
UNCOV
778
                return r;
×
779

780
        return RECURSE_DIR_CONTINUE;
781
}
782

783
static int acquire_credentials(
2,024✔
784
                const ExecContext *context,
785
                const CGroupContext *cgroup_context,
786
                const ExecParameters *params,
787
                const char *unit,
788
                const char *p,
789
                uid_t uid,
790
                gid_t gid,
791
                bool ownership_ok) {
792

793
        _cleanup_close_ int dfd = -EBADF;
2,024✔
794
        int r;
2,024✔
795

796
        assert(context);
2,024✔
797
        assert(cgroup_context);
2,024✔
798
        assert(params);
2,024✔
799
        assert(unit);
2,024✔
800
        assert(p);
2,024✔
801

802
        dfd = open(p, O_DIRECTORY|O_CLOEXEC);
2,024✔
803
        if (dfd < 0)
2,024✔
UNCOV
804
                return -errno;
×
805

806
        r = fd_acl_make_writable(dfd); /* Add the "w" bit, if we are reusing an already set up credentials dir where it was unset */
2,024✔
807
        if (r < 0)
2,024✔
808
                return r;
809

810
        struct load_cred_args args = {
2,024✔
811
                .context = context,
812
                .cgroup_context = cgroup_context,
813
                .params = params,
814
                .unit = unit,
815
                .write_dfd = dfd,
816
                .uid = uid,
817
                .gid = gid,
818
                .ownership_ok = ownership_ok,
819
                .left = CREDENTIALS_TOTAL_SIZE_MAX,
820
        };
821

822
        /* First, load credentials off disk (or acquire via AF_UNIX socket) */
823
        ExecLoadCredential *lc;
2,024✔
824
        HASHMAP_FOREACH(lc, context->load_credentials) {
4,076✔
825
                _cleanup_close_ int sub_fd = -EBADF;
2,052✔
826

827
                args.encrypted = lc->encrypted;
28✔
828

829
                /* If this is an absolute path, then try to open it as a directory. If that works, then we'll
830
                 * recurse into it. If it is an absolute path but it isn't a directory, then we'll open it as
831
                 * a regular file. Finally, if it's a relative path we will use it as a credential name to
832
                 * propagate a credential passed to us from further up. */
833

834
                if (path_is_absolute(lc->path)) {
28✔
835
                        sub_fd = open(lc->path, O_DIRECTORY|O_CLOEXEC);
21✔
836
                        if (sub_fd < 0 && !IN_SET(errno,
21✔
837
                                                  ENOTDIR,  /* Not a directory */
838
                                                  ENOENT))  /* Doesn't exist? */
UNCOV
839
                                return log_debug_errno(errno, "Failed to open credential source '%s': %m", lc->path);
×
840
                }
841

842
                if (sub_fd < 0)
20✔
843
                        /* Regular file (incl. a credential passed in from higher up) */
844
                        r = load_credential(&args,
27✔
845
                                            lc->id,
27✔
846
                                            AT_FDCWD, lc->path);
27✔
847
                else
848
                        /* Directory */
849
                        r = recurse_dir(sub_fd,
1✔
850
                                        /* path= */ lc->id, /* recurse_dir() will suffix the subdir paths from here to the top-level id */
1✔
851
                                        /* statx_mask= */ 0,
852
                                        /* n_depth_max= */ UINT_MAX,
853
                                        RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE,
854
                                        load_cred_recurse_dir_cb,
855
                                        &args);
856
                if (r < 0)
28✔
857
                        return r;
858
        }
859

860
        /* Next, look for system credentials and credentials in the credentials store. Note that these do not
861
         * override any credentials found earlier. */
862
        ExecImportCredential *ic;
2,024✔
863
        ORDERED_SET_FOREACH(ic, context->import_credentials) {
5,934✔
864
                _cleanup_free_ char **search_path = NULL;
3,910✔
865

866
                r = credential_search_path(params, CREDENTIAL_SEARCH_PATH_TRUSTED, &search_path);
3,910✔
867
                if (r < 0)
3,910✔
868
                        return r;
869

870
                args.encrypted = false;
3,910✔
871

872
                r = load_credential_glob(&args,
3,910✔
873
                                         ic,
874
                                         search_path,
875
                                         READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER);
876
                if (r < 0)
3,910✔
877
                        return r;
878

879
                search_path = strv_free(search_path);
3,910✔
880

881
                r = credential_search_path(params, CREDENTIAL_SEARCH_PATH_ENCRYPTED, &search_path);
3,910✔
882
                if (r < 0)
3,910✔
883
                        return r;
884

885
                args.encrypted = true;
3,910✔
886

887
                r = load_credential_glob(&args,
3,910✔
888
                                         ic,
889
                                         search_path,
890
                                         READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER|READ_FULL_FILE_UNBASE64);
891
                if (r < 0)
3,910✔
892
                        return r;
893
        }
894

895
        /* Finally, we add in literally specified credentials. If the credentials already exist, we'll not
896
         * add them, so that they can act as a "default" if the same credential is specified multiple times. */
897
        ExecSetCredential *sc;
2,024✔
898
        HASHMAP_FOREACH(sc, context->set_credentials) {
2,117✔
899
                args.encrypted = sc->encrypted;
93✔
900

901
                if (faccessat(dfd, sc->id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) {
93✔
902
                        log_debug("Skipping credential with duplicated ID %s", sc->id);
2✔
903
                        continue;
2✔
904
                }
905
                if (errno != ENOENT)
91✔
UNCOV
906
                        return log_debug_errno(errno, "Failed to test if credential %s exists: %m", sc->id);
×
907

908
                r = maybe_decrypt_and_write_credential(&args, sc->id, sc->data, sc->size);
91✔
909
                if (r < 0)
91✔
910
                        return r;
911
        }
912

913
        r = fd_acl_make_read_only(dfd); /* Now take away the "w" bit */
2,024✔
914
        if (r < 0)
2,024✔
915
                return r;
916

917
        /* After we created all keys with the right perms, also make sure the credential store as a whole is
918
         * accessible */
919

920
        if (uid_is_valid(uid) && uid != getuid()) {
2,024✔
921
                r = fd_add_uid_acl_permission(dfd, uid, ACL_READ | ACL_EXECUTE);
667✔
922
                if (r < 0) {
667✔
UNCOV
923
                        if (!ERRNO_IS_NOT_SUPPORTED(r) && !ERRNO_IS_PRIVILEGE(r))
×
924
                                return r;
925

UNCOV
926
                        if (!ownership_ok)
×
927
                                return r;
928

UNCOV
929
                        if (fchown(dfd, uid, gid) < 0)
×
UNCOV
930
                                return -errno;
×
931
                }
932
        }
933

934
        return 0;
935
}
936

937
static int setup_credentials_internal(
2,026✔
938
                const ExecContext *context,
939
                const CGroupContext *cgroup_context,
940
                const ExecParameters *params,
941
                const char *unit,
942
                const char *final,        /* This is where the credential store shall eventually end up at */
943
                const char *workspace,    /* This is where we can prepare it before moving it to the final place */
944
                bool reuse_workspace,     /* Whether to reuse any existing workspace mount if it already is a mount */
945
                bool must_mount,          /* Whether to require that we mount something, it's not OK to use the plain directory fall back */
946
                uid_t uid,
947
                gid_t gid) {
948

949
        bool final_mounted;
2,026✔
950
        int r, workspace_mounted; /* negative if we don't know yet whether we have/can mount something; true
2,026✔
951
                                   * if we mounted something; false if we definitely can't mount anything */
952

953
        assert(context);
2,026✔
954
        assert(params);
2,026✔
955
        assert(unit);
2,026✔
956
        assert(final);
2,026✔
957
        assert(workspace);
2,026✔
958

959
        r = path_is_mount_point(final);
2,026✔
960
        if (r < 0)
2,026✔
UNCOV
961
                return log_debug_errno(r, "Failed to determine if '%s' is a mountpoint: %m", final);
×
962
        final_mounted = r > 0;
2,026✔
963

964
        if (final_mounted) {
2,026✔
965
                if (FLAGS_SET(params->flags, EXEC_SETUP_CREDENTIALS_FRESH)) {
6✔
966
                        r = umount_verbose(LOG_DEBUG, final, MNT_DETACH|UMOUNT_NOFOLLOW);
4✔
967
                        if (r < 0)
4✔
968
                                return r;
969

970
                        final_mounted = false;
971
                } else {
972
                        /* We can reuse the previous credential dir */
973
                        r = dir_is_empty(final, /* ignore_hidden_or_backup = */ false);
2✔
974
                        if (r < 0)
2✔
975
                                return r;
976
                        if (r == 0) {
2✔
977
                                log_debug("Credential dir for unit '%s' already set up, skipping.", unit);
2✔
978
                                return 0;
2✔
979
                        }
980
                }
981
        }
982

983
        if (reuse_workspace) {
2,024✔
984
                r = path_is_mount_point(workspace);
1✔
985
                if (r < 0)
1✔
986
                        return r;
987
                if (r > 0)
1✔
988
                        workspace_mounted = true; /* If this is already a mount, and we are supposed to reuse
989
                                                   * it, let's keep this in mind */
990
                else
991
                        workspace_mounted = -1; /* We need to figure out if we can mount something to the workspace */
1✔
992
        } else
993
                workspace_mounted = -1; /* ditto */
994

995
        /* If both the final place and the workspace are mounted, we have no mounts to set up, based on
996
         * the assumption that they're actually the same tmpfs (but the latter with MS_RDONLY different).
997
         * If the workspace is not mounted, we just bind the final place over and make it writable. */
998
        must_mount = must_mount || final_mounted;
2,024✔
999

1000
        if (workspace_mounted < 0) {
2,024✔
1001
                if (!final_mounted)
2,024✔
1002
                        /* Nothing is mounted on the workspace yet, let's try to mount a new tmpfs if
1003
                         * not using the final place. */
1004
                        r = mount_credentials_fs(workspace, CREDENTIALS_TOTAL_SIZE_MAX, /* ro= */ false);
2,024✔
1005
                if (final_mounted || r < 0) {
2,024✔
1006
                        /* If using final place or failed to mount new tmpfs, make a bind mount from
1007
                         * the final to the workspace, so that we can make it writable there. */
1008
                        r = mount_nofollow_verbose(LOG_DEBUG, final, workspace, NULL, MS_BIND|MS_REC, NULL);
1✔
1009
                        if (r < 0) {
1✔
1010
                                if (!ERRNO_IS_PRIVILEGE(r))
1✔
1011
                                        /* Propagate anything that isn't a permission problem. */
1012
                                        return r;
1013

1014
                                if (must_mount)
1✔
1015
                                        /* If it's not OK to use the plain directory fallback, propagate all
1016
                                         * errors too. */
1017
                                        return r;
1018

1019
                                /* If we lack privileges to bind mount stuff, then let's gracefully proceed
1020
                                 * for compat with container envs, and just use the final dir as is.
1021
                                 * Final place must not be mounted in this case (refused by must_mount
1022
                                 * above) */
1023

1024
                                workspace_mounted = false;
1025
                        } else {
1026
                                /* Make the new bind mount writable (i.e. drop MS_RDONLY) */
UNCOV
1027
                                r = mount_nofollow_verbose(LOG_DEBUG,
×
1028
                                                           NULL,
1029
                                                           workspace,
1030
                                                           NULL,
UNCOV
1031
                                                           MS_BIND|MS_REMOUNT|credentials_fs_mount_flags(/* ro= */ false),
×
1032
                                                           NULL);
UNCOV
1033
                                if (r < 0)
×
1034
                                        return r;
1035

1036
                                workspace_mounted = true;
1037
                        }
1038
                } else
1039
                        workspace_mounted = true;
1040
        }
1041

UNCOV
1042
        assert(workspace_mounted >= 0);
×
1043
        assert(!must_mount || workspace_mounted);
2,024✔
1044

1045
        const char *where = workspace_mounted ? workspace : final;
2,024✔
1046

1047
        (void) label_fix_full(AT_FDCWD, where, final, 0);
2,024✔
1048

1049
        r = acquire_credentials(context, cgroup_context, params, unit, where, uid, gid, workspace_mounted);
2,024✔
1050
        if (r < 0) {
2,024✔
1051
                /* If we're using final place as workspace, and failed to acquire credentials, we might
1052
                 * have left half-written creds there. Let's get rid of the whole mount, so future
1053
                 * calls won't reuse it. */
UNCOV
1054
                if (final_mounted)
×
UNCOV
1055
                        (void) umount_verbose(LOG_DEBUG, final, MNT_DETACH|UMOUNT_NOFOLLOW);
×
1056

UNCOV
1057
                return r;
×
1058
        }
1059

1060
        if (workspace_mounted) {
2,024✔
1061
                if (!final_mounted) {
2,023✔
1062
                        /* Make workspace read-only now, so that any bind mount we make from it defaults to
1063
                         * read-only too */
1064
                        r = mount_nofollow_verbose(LOG_DEBUG, NULL, workspace, NULL, MS_BIND|MS_REMOUNT|credentials_fs_mount_flags(/* ro= */ true), NULL);
2,023✔
1065
                        if (r < 0)
2,023✔
1066
                                return r;
1067

1068
                        /* And mount it to the final place, read-only */
1069
                        r = mount_nofollow_verbose(LOG_DEBUG, workspace, final, NULL, MS_MOVE, NULL);
2,023✔
1070
                } else
1071
                        /* Otherwise we just get rid of the bind mount of final place */
UNCOV
1072
                        r = umount_verbose(LOG_DEBUG, workspace, MNT_DETACH|UMOUNT_NOFOLLOW);
×
1073
                if (r < 0)
2,023✔
UNCOV
1074
                        return r;
×
1075
        } else {
1076
                _cleanup_free_ char *parent = NULL;
1✔
1077

1078
                /* If we do not have our own mount put used the plain directory fallback, then we need to
1079
                 * open access to the top-level credential directory and the per-service directory now */
1080

1081
                r = path_extract_directory(final, &parent);
1✔
1082
                if (r < 0)
1✔
1083
                        return r;
1084
                if (chmod(parent, 0755) < 0)
1✔
UNCOV
1085
                        return -errno;
×
1086
        }
1087

1088
        return 0;
1089
}
1090

1091
int exec_setup_credentials(
11,846✔
1092
                const ExecContext *context,
1093
                const CGroupContext *cgroup_context,
1094
                const ExecParameters *params,
1095
                const char *unit,
1096
                uid_t uid,
1097
                gid_t gid) {
1098

1099
        _cleanup_free_ char *p = NULL, *q = NULL;
9,821✔
1100
        int r;
11,846✔
1101

1102
        assert(context);
11,846✔
1103
        assert(params);
11,846✔
1104
        assert(unit);
11,846✔
1105

1106
        if (!exec_params_need_credentials(params) || !exec_context_has_credentials(context))
11,846✔
1107
                return 0;
7,801✔
1108

1109
        if (!params->prefix[EXEC_DIRECTORY_RUNTIME])
4,045✔
1110
                return -EINVAL;
1111

1112
        /* This is where we'll place stuff when we are done; the main credentials directory is world-readable,
1113
         * and the subdir we mount over with a read-only file system readable by the service's user. */
1114
        q = path_join(params->prefix[EXEC_DIRECTORY_RUNTIME], "credentials");
4,045✔
1115
        if (!q)
4,045✔
1116
                return -ENOMEM;
1117

1118
        r = mkdir_label(q, 0755); /* top-level dir: world readable/searchable */
4,045✔
1119
        if (r < 0 && r != -EEXIST)
4,045✔
1120
                return r;
1121

1122
        p = path_join(q, unit);
4,045✔
1123
        if (!p)
4,045✔
1124
                return -ENOMEM;
1125

1126
        r = mkdir_label(p, 0700); /* per-unit dir: private to user */
4,045✔
1127
        if (r < 0 && r != -EEXIST)
4,045✔
1128
                return r;
1129

1130
        r = safe_fork("(sd-mkdcreds)", FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_NEW_MOUNTNS, NULL);
4,045✔
1131
        if (r < 0) {
4,045✔
UNCOV
1132
                _cleanup_(rmdir_and_freep) char *u = NULL; /* remove the temporary workspace if we can */
×
1133
                _cleanup_free_ char *t = NULL;
1✔
1134

1135
                /* If this is not a privilege or support issue then propagate the error */
1136
                if (!ERRNO_IS_NOT_SUPPORTED(r) && !ERRNO_IS_PRIVILEGE(r))
1✔
1137
                        return r;
1138

1139
                /* Temporary workspace, that remains inaccessible all the time. We prepare stuff there before moving
1140
                 * it into place, so that users can't access half-initialized credential stores. */
1141
                t = path_join(params->prefix[EXEC_DIRECTORY_RUNTIME], "systemd/temporary-credentials");
1✔
1142
                if (!t)
1✔
1143
                        return -ENOMEM;
1144

1145
                /* We can't set up a mount namespace. In that case operate on a fixed, inaccessible per-unit
1146
                 * directory outside of /run/credentials/ first, and then move it over to /run/credentials/
1147
                 * after it is fully set up */
1148
                u = path_join(t, unit);
1✔
1149
                if (!u)
1✔
1150
                        return -ENOMEM;
1151

1152
                FOREACH_STRING(i, t, u) {
3✔
1153
                        r = mkdir_label(i, 0700);
2✔
1154
                        if (r < 0 && r != -EEXIST)
2✔
UNCOV
1155
                                return log_debug_errno(r, "Failed to make directory '%s': %m", i);
×
1156
                }
1157

1158
                r = setup_credentials_internal(
1✔
1159
                                context,
1160
                                cgroup_context,
1161
                                params,
1162
                                unit,
1163
                                p,       /* final mount point */
1164
                                u,       /* temporary workspace to overmount */
1165
                                true,    /* reuse the workspace if it is already a mount */
1166
                                false,   /* it's OK to fall back to a plain directory if we can't mount anything */
1167
                                uid,
1168
                                gid);
1169
                if (r < 0)
1✔
1170
                        return r;
1171

1172
        } else if (r == 0) {
4,044✔
1173

1174
                /* We managed to set up a mount namespace, and are now in a child. That's great. In this case
1175
                 * we can use the same directory for all cases, after turning off propagation. Question
1176
                 * though is: where do we turn off propagation exactly, and where do we place the workspace
1177
                 * directory? We need some place that is guaranteed to be a mount point in the host, and
1178
                 * which is guaranteed to have a subdir we can mount over. /run/ is not suitable for this,
1179
                 * since we ultimately want to move the resulting file system there, i.e. we need propagation
1180
                 * for /run/ eventually. We could use our own /run/systemd/bind mount on itself, but that
1181
                 * would be visible in the host mount table all the time, which we want to avoid. Hence, what
1182
                 * we do here instead we use /dev/ and /dev/shm/ for our purposes. We know for sure that
1183
                 * /dev/ is a mount point and we now for sure that /dev/shm/ exists. Hence we can turn off
1184
                 * propagation on the former, and then overmount the latter.
1185
                 *
1186
                 * Yes it's nasty playing games with /dev/ and /dev/shm/ like this, since it does not exist
1187
                 * for this purpose, but there are few other candidates that work equally well for us, and
1188
                 * given that we do this in a privately namespaced short-lived single-threaded process that
1189
                 * no one else sees this should be OK to do. */
1190

1191
                /* Turn off propagation from our namespace to host */
1192
                r = mount_nofollow_verbose(LOG_DEBUG, NULL, "/dev", NULL, MS_SLAVE|MS_REC, NULL);
2,025✔
1193
                if (r < 0)
2,025✔
UNCOV
1194
                        goto child_fail;
×
1195

1196
                r = setup_credentials_internal(
2,025✔
1197
                                context,
1198
                                cgroup_context,
1199
                                params,
1200
                                unit,
1201
                                p,           /* final mount point */
1202
                                "/dev/shm",  /* temporary workspace to overmount */
1203
                                false,       /* do not reuse /dev/shm if it is already a mount, under no circumstances */
1204
                                true,        /* insist that something is mounted, do not allow fallback to plain directory */
1205
                                uid,
1206
                                gid);
1207
                if (r < 0)
2,025✔
UNCOV
1208
                        goto child_fail;
×
1209

1210
                _exit(EXIT_SUCCESS);
2,025✔
1211

UNCOV
1212
        child_fail:
×
1213
                _exit(EXIT_FAILURE);
×
1214
        }
1215

1216
        /* If the credentials dir is empty and not a mount point, then there's no point in having it. Let's
1217
         * try to remove it. This matters in particular if we created the dir as mount point but then didn't
1218
         * actually end up mounting anything on it. In that case we'd rather have ENOENT than EACCESS being
1219
         * seen by users when trying access this inode. */
1220
        (void) rmdir(p);
2,020✔
1221
        return 0;
2,020✔
1222
}
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