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

systemd / systemd / 15199265962

22 May 2025 09:40PM UTC coverage: 72.061% (-0.02%) from 72.079%
15199265962

push

github

bluca
tests: fix TEST-74-AUX-UTILS.varlinkctl.sh (#37562)

per Daan's explanation:
other subtests running as testuser apparently use systemd-run --user
--machine testuser@.host which turns user tracking in logind into "by
pin" mode. when the last pinning session exits it terminates the user.

299156 of 415145 relevant lines covered (72.06%)

703915.84 hits per line

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

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

3
#include <sys/mount.h>
4

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

33
ExecSetCredential* exec_set_credential_free(ExecSetCredential *sc) {
×
34
        if (!sc)
×
35
                return NULL;
36

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

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

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

51
ExecImportCredential* exec_import_credential_free(ExecImportCredential *ic) {
4,020✔
52
        if (!ic)
4,020✔
53
                return NULL;
54

55
        free(ic->glob);
4,020✔
56
        free(ic->rename);
4,020✔
57
        return mfree(ic);
4,020✔
58
}
59

60
static void exec_import_credential_hash_func(const ExecImportCredential *ic, struct siphash *state) {
25,440✔
61
        assert(ic);
25,440✔
62
        assert(state);
25,440✔
63

64
        siphash24_compress_string(ic->glob, state);
25,440✔
65
        if (ic->rename)
25,440✔
66
                siphash24_compress_string(ic->rename, state);
2,259✔
67
}
25,440✔
68

69
static int exec_import_credential_compare_func(const ExecImportCredential *a, const ExecImportCredential *b) {
8,711✔
70
        int r;
8,711✔
71

72
        assert(a);
8,711✔
73
        assert(b);
8,711✔
74

75
        r = strcmp(a->glob, b->glob);
8,711✔
76
        if (r != 0)
8,711✔
77
                return r;
78

79
        return strcmp_ptr(a->rename, b->rename);
9✔
80
}
81

82
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
×
83
        exec_set_credential_hash_ops,
84
        char, string_hash_func, string_compare_func,
85
        ExecSetCredential, exec_set_credential_free);
86

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

92
DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
4,019✔
93
        exec_import_credential_hash_ops,
94
        ExecImportCredential,
95
        exec_import_credential_hash_func,
96
        exec_import_credential_compare_func,
97
        exec_import_credential_free);
98

99
int exec_context_put_load_credential(ExecContext *c, const char *id, const char *path, bool encrypted) {
95✔
100
        ExecLoadCredential *old;
95✔
101
        int r;
95✔
102

103
        assert(c);
95✔
104
        assert(id);
95✔
105
        assert(path);
95✔
106

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

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

117
                lc = new(ExecLoadCredential, 1);
95✔
118
                if (!lc)
95✔
119
                        return -ENOMEM;
120

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

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

134
                TAKE_PTR(lc);
95✔
135
        }
136

137
        return 0;
138
}
139

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

147
        _cleanup_free_ void *data = data_consume;
182✔
148
        ExecSetCredential *old;
182✔
149
        int r;
182✔
150

151
        /* Takes the ownership of data both on success and failure */
152

153
        assert(c);
182✔
154
        assert(id);
182✔
155
        assert(data || size == 0);
182✔
156

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

165
                sc = new(ExecSetCredential, 1);
182✔
166
                if (!sc)
182✔
167
                        return -ENOMEM;
168

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

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

183
                TAKE_PTR(sc);
182✔
184
        }
185

186
        return 0;
187
}
188

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

193
        assert(c);
11,194✔
194
        assert(glob);
11,194✔
195

196
        rename = empty_to_null(rename);
11,194✔
197

198
        ic = new(ExecImportCredential, 1);
11,194✔
199
        if (!ic)
11,194✔
200
                return -ENOMEM;
201

202
        *ic = (ExecImportCredential) {
11,194✔
203
                .glob = strdup(glob),
11,194✔
204
        };
205
        if (!ic->glob)
11,194✔
206
                return -ENOMEM;
207
        if (rename) {
11,194✔
208
                ic->rename = strdup(rename);
652✔
209
                if (!ic->rename)
652✔
210
                        return -ENOMEM;
211
        }
212

213
        if (ordered_set_contains(c->import_credentials, ic))
11,194✔
214
                return 0;
215

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

221
        TAKE_PTR(ic);
11,193✔
222

223
        return 0;
11,193✔
224
}
225

226
bool exec_params_need_credentials(const ExecParameters *p) {
23,205✔
227
        assert(p);
23,205✔
228

229
        return p->flags & (EXEC_SETUP_CREDENTIALS|EXEC_SETUP_CREDENTIALS_FRESH);
23,205✔
230
}
231

232
bool exec_context_has_credentials(const ExecContext *c) {
22,049✔
233
        assert(c);
22,049✔
234

235
        return !hashmap_isempty(c->set_credentials) ||
22,049✔
236
                !hashmap_isempty(c->load_credentials) ||
22,049✔
237
                !ordered_set_isempty(c->import_credentials);
21,699✔
238
}
239

240
bool exec_context_has_encrypted_credentials(const ExecContext *c) {
1,446✔
241
        assert(c);
1,446✔
242

243
        const ExecLoadCredential *load_cred;
1,446✔
244
        HASHMAP_FOREACH(load_cred, c->load_credentials)
1,446✔
245
                if (load_cred->encrypted)
×
246
                        return true;
×
247

248
        const ExecSetCredential *set_cred;
1,446✔
249
        HASHMAP_FOREACH(set_cred, c->set_credentials)
1,446✔
250
                if (set_cred->encrypted)
×
251
                        return true;
×
252

253
        return false;
1,446✔
254
}
255

256
bool mount_point_is_credentials(const char *runtime_prefix, const char *path) {
84,889✔
257
        const char *e;
84,889✔
258

259
        assert(runtime_prefix);
84,889✔
260
        assert(path);
84,889✔
261

262
        e = path_startswith(path, runtime_prefix);
84,889✔
263
        if (!e)
84,889✔
264
                return false;
265

266
        return path_startswith(e, "credentials");
6,125✔
267
}
268

269
static int get_credential_directory(
13,579✔
270
                const char *runtime_prefix,
271
                const char *unit,
272
                char **ret) {
273

274
        char *p;
13,579✔
275

276
        assert(ret);
13,579✔
277

278
        if (!runtime_prefix || !unit) {
13,579✔
279
                *ret = NULL;
×
280
                return 0;
×
281
        }
282

283
        p = path_join(runtime_prefix, "credentials", unit);
13,579✔
284
        if (!p)
13,579✔
285
                return -ENOMEM;
286

287
        *ret = p;
13,579✔
288
        return 1;
13,579✔
289
}
290

291
int exec_context_get_credential_directory(
11,636✔
292
                const ExecContext *context,
293
                const ExecParameters *params,
294
                const char *unit,
295
                char **ret) {
296

297
        assert(context);
11,636✔
298
        assert(params);
11,636✔
299
        assert(unit);
11,636✔
300
        assert(ret);
11,636✔
301

302
        if (!exec_params_need_credentials(params) || !exec_context_has_credentials(context)) {
11,636✔
303
                *ret = NULL;
9,082✔
304
                return 0;
9,082✔
305
        }
306

307
        return get_credential_directory(params->prefix[EXEC_DIRECTORY_RUNTIME], unit, ret);
2,554✔
308
}
309

310
int exec_context_destroy_credentials(const ExecContext *c, const char *runtime_prefix, const char *unit) {
11,025✔
311
        _cleanup_free_ char *p = NULL;
11,025✔
312
        int r;
11,025✔
313

314
        assert(c);
11,025✔
315

316
        r = get_credential_directory(runtime_prefix, unit, &p);
11,025✔
317
        if (r <= 0)
11,025✔
318
                return r;
319

320
        /* This is either a tmpfs/ramfs of its own, or a plain directory. Either way, let's first try to
321
         * unmount it, and afterwards remove the mount point */
322
        (void) umount2(p, MNT_DETACH|UMOUNT_NOFOLLOW);
11,025✔
323
        (void) rm_rf(p, REMOVE_ROOT|REMOVE_CHMOD);
11,025✔
324

325
        return 0;
326
}
327

328
static int write_credential(
1,124✔
329
                int dfd,
330
                const char *id,
331
                const void *data,
332
                size_t size,
333
                uid_t uid,
334
                gid_t gid,
335
                bool ownership_ok) {
336

337
        _cleanup_free_ char *tmp = NULL;
1,124✔
338
        _cleanup_close_ int fd = -EBADF;
1,124✔
339
        int r;
1,124✔
340

341
        assert(dfd >= 0);
1,124✔
342
        assert(id);
1,124✔
343
        assert(data || size == 0);
1,124✔
344

345
        r = tempfn_random_child("", "cred", &tmp);
1,124✔
346
        if (r < 0)
1,124✔
347
                return r;
348

349
        fd = openat(dfd, tmp, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL|O_NOFOLLOW|O_NOCTTY, 0600);
1,124✔
350
        if (fd < 0)
1,124✔
351
                return -errno;
×
352

353
        r = loop_write(fd, data, size);
1,124✔
354
        if (r < 0)
1,124✔
355
                goto fail;
×
356

357
        r = RET_NERRNO(fchmod(fd, 0400)); /* Take away "w" bit */
1,124✔
358
        if (r < 0)
×
359
                goto fail;
×
360

361
        if (uid_is_valid(uid) && uid != getuid()) {
1,124✔
362
                r = fd_add_uid_acl_permission(fd, uid, ACL_READ);
90✔
363
                if (r < 0) {
90✔
364
                        /* Ideally we use ACLs, since we can neatly express what we want to express:
365
                         * the user gets read access and nothing else. But if the backing fs can't
366
                         * support that (e.g. ramfs), then we can use file ownership instead. But that's
367
                         * only safe if we can then re-mount the whole thing read-only, so that the user
368
                         * can no longer chmod() the file to gain write access. */
369
                        if (!ownership_ok || (!ERRNO_IS_NOT_SUPPORTED(r) && !ERRNO_IS_PRIVILEGE(r)))
×
370
                                goto fail;
×
371

372
                        r = RET_NERRNO(fchown(fd, uid, gid));
×
373
                        if (r < 0)
×
374
                                goto fail;
×
375
                }
376
        }
377

378
        r = RET_NERRNO(renameat(dfd, tmp, dfd, id));
1,124✔
379
        if (r < 0)
×
380
                goto fail;
×
381

382
        return 0;
383

384
fail:
×
385
        (void) unlinkat(dfd, tmp, /* flags = */ 0);
×
386
        return r;
×
387
}
388

389
typedef enum CredentialSearchPath {
390
        CREDENTIAL_SEARCH_PATH_TRUSTED,
391
        CREDENTIAL_SEARCH_PATH_ENCRYPTED,
392
        CREDENTIAL_SEARCH_PATH_ALL,
393
        _CREDENTIAL_SEARCH_PATH_MAX,
394
        _CREDENTIAL_SEARCH_PATH_INVALID = -EINVAL,
395
} CredentialSearchPath;
396

397
static int credential_search_path(const ExecParameters *params, CredentialSearchPath path, char ***ret) {
7,206✔
398
        _cleanup_strv_free_ char **l = NULL;
7,206✔
399
        int r;
7,206✔
400

401
        assert(params);
7,206✔
402
        assert(path >= 0 && path < _CREDENTIAL_SEARCH_PATH_MAX);
7,206✔
403
        assert(ret);
7,206✔
404

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

409
        if (IN_SET(path, CREDENTIAL_SEARCH_PATH_ENCRYPTED, CREDENTIAL_SEARCH_PATH_ALL)) {
7,206✔
410
                r = strv_extend(&l, params->received_encrypted_credentials_directory);
3,609✔
411
                if (r < 0)
3,609✔
412
                        return r;
×
413

414
                _cleanup_strv_free_ char **add = NULL;
3,609✔
415
                r = credential_store_path_encrypted(params->runtime_scope, &add);
3,609✔
416
                if (r < 0)
3,609✔
417
                        return r;
418

419
                r = strv_extend_strv_consume(&l, TAKE_PTR(add), /* filter_duplicates= */ false);
3,609✔
420
                if (r < 0)
3,609✔
421
                        return r;
422
        }
423

424
        if (IN_SET(path, CREDENTIAL_SEARCH_PATH_TRUSTED, CREDENTIAL_SEARCH_PATH_ALL)) {
7,206✔
425
                r = strv_extend(&l, params->received_credentials_directory);
3,609✔
426
                if (r < 0)
3,609✔
427
                        return r;
×
428

429
                _cleanup_strv_free_ char **add = NULL;
3,609✔
430
                r = credential_store_path(params->runtime_scope, &add);
3,609✔
431
                if (r < 0)
3,609✔
432
                        return r;
433

434
                r = strv_extend_strv_consume(&l, TAKE_PTR(add), /* filter_duplicates= */ false);
3,609✔
435
                if (r < 0)
3,609✔
436
                        return r;
437
        }
438

439
        if (DEBUG_LOGGING) {
7,206✔
440
                _cleanup_free_ char *t = strv_join(l, ":");
14,412✔
441
                log_debug("Credential search path is: %s", strempty(t));
7,206✔
442
        }
443

444
        *ret = TAKE_PTR(l);
7,206✔
445
        return 0;
7,206✔
446
}
447

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

460
static int maybe_decrypt_and_write_credential(
1,124✔
461
                struct load_cred_args *args,
462
                const char *id,
463
                const char *data,
464
                size_t size) {
465

466
        _cleanup_(iovec_done_erase) struct iovec plaintext = {};
1,124✔
467
        size_t add;
1,124✔
468
        int r;
1,124✔
469

470
        assert(args);
1,124✔
471
        assert(args->write_dfd >= 0);
1,124✔
472
        assert(id);
1,124✔
473
        assert(data || size == 0);
1,124✔
474

475
        if (args->encrypted) {
1,124✔
476
                switch (args->params->runtime_scope) {
7✔
477

478
                case RUNTIME_SCOPE_SYSTEM:
5✔
479
                        /* In system mode talk directly to the TPM */
480
                        r = decrypt_credential_and_warn(
5✔
481
                                        id,
482
                                        now(CLOCK_REALTIME),
483
                                        /* tpm2_device= */ NULL,
484
                                        /* tpm2_signature_path= */ NULL,
485
                                        getuid(),
5✔
486
                                        &IOVEC_MAKE(data, size),
5✔
487
                                        CREDENTIAL_ANY_SCOPE,
488
                                        &plaintext);
489
                        break;
7✔
490

491
                case RUNTIME_SCOPE_USER:
2✔
492
                        /* In per user mode we'll not have access to the machine secret, nor to the TPM (most
493
                         * likely), hence go via the IPC service instead. Do this if we are run in root's
494
                         * per-user invocation too, to minimize differences and because isolating this logic
495
                         * into a separate process is generally a good thing anyway. */
496
                        r = ipc_decrypt_credential(
2✔
497
                                        id,
498
                                        now(CLOCK_REALTIME),
499
                                        getuid(),
2✔
500
                                        &IOVEC_MAKE(data, size),
2✔
501
                                        /* flags= */ 0, /* only allow user creds in user scope */
502
                                        &plaintext);
503
                        break;
504

505
                default:
×
506
                        assert_not_reached();
×
507
                }
508
                if (r < 0)
7✔
509
                        return r;
510

511
                data = plaintext.iov_base;
7✔
512
                size = plaintext.iov_len;
7✔
513
        }
514

515
        add = strlen(id) + size;
1,124✔
516
        if (add > args->left)
1,124✔
517
                return -E2BIG;
518

519
        r = write_credential(args->write_dfd, id, data, size, args->uid, args->gid, args->ownership_ok);
1,124✔
520
        if (r < 0)
1,124✔
521
                return log_debug_errno(r, "Failed to write credential '%s': %m", id);
×
522

523
        args->left -= add;
1,124✔
524

525
        return 0;
1,124✔
526
}
527

528
static int load_credential_glob(
7,194✔
529
                struct load_cred_args *args,
530
                const ExecImportCredential *ic,
531
                char * const *search_path,
532
                ReadFullFileFlags flags) {
533

534
        int r;
7,194✔
535

536
        assert(args);
7,194✔
537
        assert(args->write_dfd >= 0);
7,194✔
538
        assert(ic);
7,194✔
539
        assert(search_path);
7,194✔
540

541
        STRV_FOREACH(d, search_path) {
39,564✔
542
                _cleanup_globfree_ glob_t pglob = {};
×
543
                _cleanup_free_ char *j = NULL;
32,370✔
544

545
                j = path_join(*d, ic->glob);
32,370✔
546
                if (!j)
32,370✔
547
                        return -ENOMEM;
548

549
                r = safe_glob(j, 0, &pglob);
32,370✔
550
                if (r == -ENOENT)
32,370✔
551
                        continue;
31,601✔
552
                if (r < 0)
769✔
553
                        return r;
554

555
                FOREACH_ARRAY(p, pglob.gl_pathv, pglob.gl_pathc) {
1,789✔
556
                        _cleanup_free_ char *fn = NULL;
1,020✔
557
                        _cleanup_(erase_and_freep) char *data = NULL;
1,020✔
558
                        size_t size;
1,020✔
559

560
                        r = path_extract_filename(*p, &fn);
1,020✔
561
                        if (r < 0)
1,020✔
562
                                return log_debug_errno(r, "Failed to extract filename from '%s': %m", *p);
×
563

564
                        if (ic->rename) {
1,020✔
565
                                _cleanup_free_ char *renamed = NULL;
×
566

567
                                renamed = strjoin(ic->rename, fn + strlen(ic->glob) - !!endswith(ic->glob, "*"));
178✔
568
                                if (!renamed)
178✔
569
                                        return log_oom_debug();
×
570

571
                                free_and_replace(fn, renamed);
178✔
572
                        }
573

574
                        if (!credential_name_valid(fn)) {
1,020✔
575
                                log_debug("Skipping credential with invalid name: %s", fn);
5✔
576
                                continue;
5✔
577
                        }
578

579
                        if (faccessat(args->write_dfd, fn, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) {
1,015✔
580
                                log_debug("Skipping credential with duplicated ID %s at %s", fn, *p);
10✔
581
                                continue;
10✔
582
                        }
583
                        if (errno != ENOENT)
1,005✔
584
                                return log_debug_errno(errno, "Failed to test if credential %s exists: %m", fn);
×
585

586
                        /* path is absolute, hence pass AT_FDCWD as nop dir fd here */
587
                        r = read_full_file_full(
1,005✔
588
                                        AT_FDCWD,
589
                                        *p,
590
                                        UINT64_MAX,
591
                                        args->encrypted ? CREDENTIAL_ENCRYPTED_SIZE_MAX : CREDENTIAL_SIZE_MAX,
1,005✔
592
                                        flags,
593
                                        NULL,
594
                                        &data, &size);
595
                        if (r < 0)
1,005✔
596
                                return log_debug_errno(r, "Failed to read credential '%s': %m", *p);
×
597

598
                        r = maybe_decrypt_and_write_credential(args, fn, data, size);
1,005✔
599
                        if (r < 0)
1,005✔
600
                                return r;
601
                }
602
        }
603

604
        return 0;
605
}
606

607
static int load_credential(
36✔
608
                struct load_cred_args *args,
609
                const char *id,
610
                int read_dfd,
611
                const char *path) {
612

613
        ReadFullFileFlags flags = READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER;
36✔
614
        _cleanup_strv_free_ char **search_path = NULL;
×
615
        _cleanup_free_ char *bindname = NULL;
36✔
616
        const char *source = NULL;
36✔
617
        bool missing_ok;
36✔
618
        _cleanup_(erase_and_freep) char *data = NULL;
36✔
619
        size_t size, maxsz;
36✔
620
        int r;
36✔
621

622
        assert(args);
36✔
623
        assert(args->context);
36✔
624
        assert(args->params);
36✔
625
        assert(args->unit);
36✔
626
        assert(args->write_dfd >= 0);
36✔
627
        assert(id);
36✔
628
        assert(read_dfd >= 0 || read_dfd == AT_FDCWD);
36✔
629
        assert(path);
36✔
630

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

637
                if (!filename_is_valid(path)) /* safety check */
4✔
638
                        return -EINVAL;
639

640
                missing_ok = true;
641
                source = path;
642

643
        } else if (path_is_absolute(path)) {
32✔
644
                /* If this is an absolute path, read the data directly from it, and support AF_UNIX
645
                 * sockets */
646

647
                if (!path_is_valid(path)) /* safety check */
20✔
648
                        return -EINVAL;
649

650
                flags |= READ_FULL_FILE_CONNECT_SOCKET;
20✔
651

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

657
                missing_ok = false;
658
                source = path;
659

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

665
                r = credential_search_path(args->params, CREDENTIAL_SEARCH_PATH_ALL, &search_path);
12✔
666
                if (r < 0)
12✔
667
                        return r;
668

669
                missing_ok = true;
670
        } else
671
                return -EINVAL;
672

673
        if (args->encrypted) {
36✔
674
                flags |= READ_FULL_FILE_UNBASE64;
3✔
675
                maxsz = CREDENTIAL_ENCRYPTED_SIZE_MAX;
3✔
676
        } else
677
                maxsz = CREDENTIAL_SIZE_MAX;
678

679
        if (search_path)
36✔
680
                STRV_FOREACH(d, search_path) {
93✔
681
                        _cleanup_free_ char *j = NULL;
88✔
682

683
                        j = path_join(*d, path);
88✔
684
                        if (!j)
88✔
685
                                return -ENOMEM;
×
686

687
                        r = read_full_file_full(
88✔
688
                                        AT_FDCWD, j, /* path is absolute, hence pass AT_FDCWD as nop dir fd here */
689
                                        UINT64_MAX,
690
                                        maxsz,
691
                                        flags,
692
                                        NULL,
693
                                        &data, &size);
694
                        if (r != -ENOENT)
88✔
695
                                break;
696
                }
697
        else if (source)
24✔
698
                r = read_full_file_full(
24✔
699
                                read_dfd, source,
700
                                UINT64_MAX,
701
                                maxsz,
702
                                flags,
703
                                bindname,
704
                                &data, &size);
705
        else
706
                assert_not_reached();
×
707

708
        if (r == -ENOENT && (missing_ok || hashmap_contains(args->context->set_credentials, id))) {
36✔
709
                /* Make a missing inherited credential non-fatal, let's just continue. After all apps
710
                 * will get clear errors if we don't pass such a missing credential on as they
711
                 * themselves will get ENOENT when trying to read them, which should not be much
712
                 * worse than when we handle the error here and make it fatal.
713
                 *
714
                 * Also, if the source file doesn't exist, but a fallback is set via SetCredentials=
715
                 * we are fine, too. */
716
                log_full_errno(hashmap_contains(args->context->set_credentials, id) ? LOG_DEBUG : LOG_INFO,
6✔
717
                               r, "Couldn't read inherited credential '%s', skipping: %m", path);
718
                return 0;
6✔
719
        }
720
        if (r < 0)
30✔
721
                return log_debug_errno(r, "Failed to read credential '%s': %m", path);
×
722

723
        return maybe_decrypt_and_write_credential(args, id, data, size);
30✔
724
}
725

726
static int load_cred_recurse_dir_cb(
6✔
727
                RecurseDirEvent event,
728
                const char *path,
729
                int dir_fd,
730
                int inode_fd,
731
                const struct dirent *de,
732
                const struct statx *sx,
733
                void *userdata) {
734

735
        struct load_cred_args *args = ASSERT_PTR(userdata);
6✔
736
        _cleanup_free_ char *sub_id = NULL;
6✔
737
        int r;
6✔
738

739
        assert(path);
6✔
740
        assert(de);
6✔
741

742
        if (event != RECURSE_DIR_ENTRY)
6✔
743
                return RECURSE_DIR_CONTINUE;
744

745
        if (!IN_SET(de->d_type, DT_REG, DT_SOCK))
4✔
746
                return RECURSE_DIR_CONTINUE;
747

748
        sub_id = strreplace(path, "/", "_");
4✔
749
        if (!sub_id)
4✔
750
                return -ENOMEM;
751

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

755
        if (faccessat(args->write_dfd, sub_id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) {
4✔
756
                log_debug("Skipping credential with duplicated ID %s at %s", sub_id, path);
×
757
                return RECURSE_DIR_CONTINUE;
×
758
        }
759
        if (errno != ENOENT)
4✔
760
                return log_debug_errno(errno, "Failed to test if credential %s exists: %m", sub_id);
×
761

762
        r = load_credential(args,
8✔
763
                            sub_id,
764
                            dir_fd, de->d_name);
4✔
765
        if (r < 0)
4✔
766
                return r;
×
767

768
        return RECURSE_DIR_CONTINUE;
769
}
770

771
static int acquire_credentials(
1,949✔
772
                const ExecContext *context,
773
                const ExecParameters *params,
774
                const char *unit,
775
                const char *p,
776
                uid_t uid,
777
                gid_t gid,
778
                bool ownership_ok) {
779

780
        _cleanup_close_ int dfd = -EBADF;
1,949✔
781
        int r;
1,949✔
782

783
        assert(context);
1,949✔
784
        assert(params);
1,949✔
785
        assert(unit);
1,949✔
786
        assert(p);
1,949✔
787

788
        dfd = open(p, O_DIRECTORY|O_CLOEXEC);
1,949✔
789
        if (dfd < 0)
1,949✔
790
                return -errno;
×
791

792
        r = fd_acl_make_writable(dfd); /* Add the "w" bit, if we are reusing an already set up credentials dir where it was unset */
1,949✔
793
        if (r < 0)
1,949✔
794
                return r;
795

796
        struct load_cred_args args = {
1,949✔
797
                .context = context,
798
                .params = params,
799
                .unit = unit,
800
                .write_dfd = dfd,
801
                .uid = uid,
802
                .gid = gid,
803
                .ownership_ok = ownership_ok,
804
                .left = CREDENTIALS_TOTAL_SIZE_MAX,
805
        };
806

807
        /* First, load credentials off disk (or acquire via AF_UNIX socket) */
808
        ExecLoadCredential *lc;
1,949✔
809
        HASHMAP_FOREACH(lc, context->load_credentials) {
3,931✔
810
                _cleanup_close_ int sub_fd = -EBADF;
1,982✔
811

812
                args.encrypted = lc->encrypted;
33✔
813

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

819
                if (path_is_absolute(lc->path)) {
33✔
820
                        sub_fd = open(lc->path, O_DIRECTORY|O_CLOEXEC);
21✔
821
                        if (sub_fd < 0 && !IN_SET(errno,
21✔
822
                                                  ENOTDIR,  /* Not a directory */
823
                                                  ENOENT))  /* Doesn't exist? */
824
                                return log_debug_errno(errno, "Failed to open credential source '%s': %m", lc->path);
×
825
                }
826

827
                if (sub_fd < 0)
20✔
828
                        /* Regular file (incl. a credential passed in from higher up) */
829
                        r = load_credential(&args,
32✔
830
                                            lc->id,
32✔
831
                                            AT_FDCWD, lc->path);
32✔
832
                else
833
                        /* Directory */
834
                        r = recurse_dir(sub_fd,
1✔
835
                                        /* path= */ lc->id, /* recurse_dir() will suffix the subdir paths from here to the top-level id */
1✔
836
                                        /* statx_mask= */ 0,
837
                                        /* n_depth_max= */ UINT_MAX,
838
                                        RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE,
839
                                        load_cred_recurse_dir_cb,
840
                                        &args);
841
                if (r < 0)
33✔
842
                        return r;
843
        }
844

845
        /* Next, look for system credentials and credentials in the credentials store. Note that these do not
846
         * override any credentials found earlier. */
847
        ExecImportCredential *ic;
1,949✔
848
        ORDERED_SET_FOREACH(ic, context->import_credentials) {
5,546✔
849
                _cleanup_free_ char **search_path = NULL;
3,597✔
850

851
                r = credential_search_path(params, CREDENTIAL_SEARCH_PATH_TRUSTED, &search_path);
3,597✔
852
                if (r < 0)
3,597✔
853
                        return r;
854

855
                args.encrypted = false;
3,597✔
856

857
                r = load_credential_glob(&args,
3,597✔
858
                                         ic,
859
                                         search_path,
860
                                         READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER);
861
                if (r < 0)
3,597✔
862
                        return r;
863

864
                search_path = strv_free(search_path);
3,597✔
865

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

870
                args.encrypted = true;
3,597✔
871

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

880
        /* Finally, we add in literally specified credentials. If the credentials already exist, we'll not
881
         * add them, so that they can act as a "default" if the same credential is specified multiple times. */
882
        ExecSetCredential *sc;
1,949✔
883
        HASHMAP_FOREACH(sc, context->set_credentials) {
2,040✔
884
                args.encrypted = sc->encrypted;
91✔
885

886
                if (faccessat(dfd, sc->id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) {
91✔
887
                        log_debug("Skipping credential with duplicated ID %s", sc->id);
2✔
888
                        continue;
2✔
889
                }
890
                if (errno != ENOENT)
89✔
891
                        return log_debug_errno(errno, "Failed to test if credential %s exists: %m", sc->id);
×
892

893
                r = maybe_decrypt_and_write_credential(&args, sc->id, sc->data, sc->size);
89✔
894
                if (r < 0)
89✔
895
                        return r;
896
        }
897

898
        r = fd_acl_make_read_only(dfd); /* Now take away the "w" bit */
1,949✔
899
        if (r < 0)
1,949✔
900
                return r;
901

902
        /* After we created all keys with the right perms, also make sure the credential store as a whole is
903
         * accessible */
904

905
        if (uid_is_valid(uid) && uid != getuid()) {
1,949✔
906
                r = fd_add_uid_acl_permission(dfd, uid, ACL_READ | ACL_EXECUTE);
649✔
907
                if (r < 0) {
649✔
908
                        if (!ERRNO_IS_NOT_SUPPORTED(r) && !ERRNO_IS_PRIVILEGE(r))
×
909
                                return r;
910

911
                        if (!ownership_ok)
×
912
                                return r;
913

914
                        if (fchown(dfd, uid, gid) < 0)
×
915
                                return -errno;
×
916
                }
917
        }
918

919
        return 0;
920
}
921

922
static int setup_credentials_internal(
1,951✔
923
                const ExecContext *context,
924
                const ExecParameters *params,
925
                const char *unit,
926
                const char *final,        /* This is where the credential store shall eventually end up at */
927
                const char *workspace,    /* This is where we can prepare it before moving it to the final place */
928
                bool reuse_workspace,     /* Whether to reuse any existing workspace mount if it already is a mount */
929
                bool must_mount,          /* Whether to require that we mount something, it's not OK to use the plain directory fall back */
930
                uid_t uid,
931
                gid_t gid) {
932

933
        bool final_mounted;
1,951✔
934
        int r, workspace_mounted; /* negative if we don't know yet whether we have/can mount something; true
1,951✔
935
                                   * if we mounted something; false if we definitely can't mount anything */
936

937
        assert(context);
1,951✔
938
        assert(params);
1,951✔
939
        assert(unit);
1,951✔
940
        assert(final);
1,951✔
941
        assert(workspace);
1,951✔
942

943
        r = path_is_mount_point(final);
1,951✔
944
        if (r < 0)
1,951✔
945
                return log_debug_errno(r, "Failed to determine if '%s' is a mountpoint: %m", final);
×
946
        final_mounted = r > 0;
1,951✔
947

948
        if (final_mounted) {
1,951✔
949
                if (FLAGS_SET(params->flags, EXEC_SETUP_CREDENTIALS_FRESH)) {
6✔
950
                        r = umount_verbose(LOG_DEBUG, final, MNT_DETACH|UMOUNT_NOFOLLOW);
4✔
951
                        if (r < 0)
4✔
952
                                return r;
953

954
                        final_mounted = false;
955
                } else {
956
                        /* We can reuse the previous credential dir */
957
                        r = dir_is_empty(final, /* ignore_hidden_or_backup = */ false);
2✔
958
                        if (r < 0)
2✔
959
                                return r;
960
                        if (r == 0) {
2✔
961
                                log_debug("Credential dir for unit '%s' already set up, skipping.", unit);
2✔
962
                                return 0;
2✔
963
                        }
964
                }
965
        }
966

967
        if (reuse_workspace) {
1,949✔
968
                r = path_is_mount_point(workspace);
1✔
969
                if (r < 0)
1✔
970
                        return r;
971
                if (r > 0)
1✔
972
                        workspace_mounted = true; /* If this is already a mount, and we are supposed to reuse
973
                                                   * it, let's keep this in mind */
974
                else
975
                        workspace_mounted = -1; /* We need to figure out if we can mount something to the workspace */
1✔
976
        } else
977
                workspace_mounted = -1; /* ditto */
978

979
        /* If both the final place and the workspace are mounted, we have no mounts to set up, based on
980
         * the assumption that they're actually the same tmpfs (but the latter with MS_RDONLY different).
981
         * If the workspace is not mounted, we just bind the final place over and make it writable. */
982
        must_mount = must_mount || final_mounted;
1,949✔
983

984
        if (workspace_mounted < 0) {
1,949✔
985
                if (!final_mounted)
1,949✔
986
                        /* Nothing is mounted on the workspace yet, let's try to mount a new tmpfs if
987
                         * not using the final place. */
988
                        r = mount_credentials_fs(workspace, CREDENTIALS_TOTAL_SIZE_MAX, /* ro= */ false);
1,949✔
989
                if (final_mounted || r < 0) {
1,949✔
990
                        /* If using final place or failed to mount new tmpfs, make a bind mount from
991
                         * the final to the workspace, so that we can make it writable there. */
992
                        r = mount_nofollow_verbose(LOG_DEBUG, final, workspace, NULL, MS_BIND|MS_REC, NULL);
1✔
993
                        if (r < 0) {
1✔
994
                                if (!ERRNO_IS_PRIVILEGE(r))
1✔
995
                                        /* Propagate anything that isn't a permission problem. */
996
                                        return r;
997

998
                                if (must_mount)
1✔
999
                                        /* If it's not OK to use the plain directory fallback, propagate all
1000
                                         * errors too. */
1001
                                        return r;
1002

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

1008
                                workspace_mounted = false;
1009
                        } else {
1010
                                /* Make the new bind mount writable (i.e. drop MS_RDONLY) */
1011
                                r = mount_nofollow_verbose(LOG_DEBUG,
×
1012
                                                           NULL,
1013
                                                           workspace,
1014
                                                           NULL,
1015
                                                           MS_BIND|MS_REMOUNT|credentials_fs_mount_flags(/* ro= */ false),
×
1016
                                                           NULL);
1017
                                if (r < 0)
×
1018
                                        return r;
1019

1020
                                workspace_mounted = true;
1021
                        }
1022
                } else
1023
                        workspace_mounted = true;
1024
        }
1025

1026
        assert(workspace_mounted >= 0);
×
1027
        assert(!must_mount || workspace_mounted);
1,949✔
1028

1029
        const char *where = workspace_mounted ? workspace : final;
1,949✔
1030

1031
        (void) label_fix_full(AT_FDCWD, where, final, 0);
1,949✔
1032

1033
        r = acquire_credentials(context, params, unit, where, uid, gid, workspace_mounted);
1,949✔
1034
        if (r < 0) {
1,949✔
1035
                /* If we're using final place as workspace, and failed to acquire credentials, we might
1036
                 * have left half-written creds there. Let's get rid of the whole mount, so future
1037
                 * calls won't reuse it. */
1038
                if (final_mounted)
×
1039
                        (void) umount_verbose(LOG_DEBUG, final, MNT_DETACH|UMOUNT_NOFOLLOW);
×
1040

1041
                return r;
×
1042
        }
1043

1044
        if (workspace_mounted) {
1,949✔
1045
                if (!final_mounted) {
1,948✔
1046
                        /* Make workspace read-only now, so that any bind mount we make from it defaults to
1047
                         * read-only too */
1048
                        r = mount_nofollow_verbose(LOG_DEBUG, NULL, workspace, NULL, MS_BIND|MS_REMOUNT|credentials_fs_mount_flags(/* ro= */ true), NULL);
1,948✔
1049
                        if (r < 0)
1,948✔
1050
                                return r;
1051

1052
                        /* And mount it to the final place, read-only */
1053
                        r = mount_nofollow_verbose(LOG_DEBUG, workspace, final, NULL, MS_MOVE, NULL);
1,948✔
1054
                } else
1055
                        /* Otherwise we just get rid of the bind mount of final place */
1056
                        r = umount_verbose(LOG_DEBUG, workspace, MNT_DETACH|UMOUNT_NOFOLLOW);
×
1057
                if (r < 0)
1,948✔
1058
                        return r;
×
1059
        } else {
1060
                _cleanup_free_ char *parent = NULL;
1✔
1061

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

1065
                r = path_extract_directory(final, &parent);
1✔
1066
                if (r < 0)
1✔
1067
                        return r;
1068
                if (chmod(parent, 0755) < 0)
1✔
1069
                        return -errno;
×
1070
        }
1071

1072
        return 0;
1073
}
1074

1075
int exec_setup_credentials(
11,569✔
1076
                const ExecContext *context,
1077
                const ExecParameters *params,
1078
                const char *unit,
1079
                uid_t uid,
1080
                gid_t gid) {
1081

1082
        _cleanup_free_ char *p = NULL, *q = NULL;
9,619✔
1083
        int r;
11,569✔
1084

1085
        assert(context);
11,569✔
1086
        assert(params);
11,569✔
1087
        assert(unit);
11,569✔
1088

1089
        if (!exec_params_need_credentials(params) || !exec_context_has_credentials(context))
11,569✔
1090
                return 0;
7,677✔
1091

1092
        if (!params->prefix[EXEC_DIRECTORY_RUNTIME])
3,892✔
1093
                return -EINVAL;
1094

1095
        /* This is where we'll place stuff when we are done; the main credentials directory is world-readable,
1096
         * and the subdir we mount over with a read-only file system readable by the service's user. */
1097
        q = path_join(params->prefix[EXEC_DIRECTORY_RUNTIME], "credentials");
3,892✔
1098
        if (!q)
3,892✔
1099
                return -ENOMEM;
1100

1101
        r = mkdir_label(q, 0755); /* top-level dir: world readable/searchable */
3,892✔
1102
        if (r < 0 && r != -EEXIST)
3,892✔
1103
                return r;
1104

1105
        p = path_join(q, unit);
3,892✔
1106
        if (!p)
3,892✔
1107
                return -ENOMEM;
1108

1109
        r = mkdir_label(p, 0700); /* per-unit dir: private to user */
3,892✔
1110
        if (r < 0 && r != -EEXIST)
3,892✔
1111
                return r;
1112

1113
        r = safe_fork("(sd-mkdcreds)", FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_NEW_MOUNTNS, NULL);
3,892✔
1114
        if (r < 0) {
3,892✔
1115
                _cleanup_(rmdir_and_freep) char *u = NULL; /* remove the temporary workspace if we can */
×
1116
                _cleanup_free_ char *t = NULL;
1✔
1117

1118
                /* If this is not a privilege or support issue then propagate the error */
1119
                if (!ERRNO_IS_NOT_SUPPORTED(r) && !ERRNO_IS_PRIVILEGE(r))
1✔
1120
                        return r;
1121

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

1128
                /* We can't set up a mount namespace. In that case operate on a fixed, inaccessible per-unit
1129
                 * directory outside of /run/credentials/ first, and then move it over to /run/credentials/
1130
                 * after it is fully set up */
1131
                u = path_join(t, unit);
1✔
1132
                if (!u)
1✔
1133
                        return -ENOMEM;
1134

1135
                FOREACH_STRING(i, t, u) {
3✔
1136
                        r = mkdir_label(i, 0700);
2✔
1137
                        if (r < 0 && r != -EEXIST)
2✔
1138
                                return log_debug_errno(r, "Failed to make directory '%s': %m", i);
×
1139
                }
1140

1141
                r = setup_credentials_internal(
1✔
1142
                                context,
1143
                                params,
1144
                                unit,
1145
                                p,       /* final mount point */
1146
                                u,       /* temporary workspace to overmount */
1147
                                true,    /* reuse the workspace if it is already a mount */
1148
                                false,   /* it's OK to fall back to a plain directory if we can't mount anything */
1149
                                uid,
1150
                                gid);
1151
                if (r < 0)
1✔
1152
                        return r;
1153

1154
        } else if (r == 0) {
3,891✔
1155

1156
                /* We managed to set up a mount namespace, and are now in a child. That's great. In this case
1157
                 * we can use the same directory for all cases, after turning off propagation. Question
1158
                 * though is: where do we turn off propagation exactly, and where do we place the workspace
1159
                 * directory? We need some place that is guaranteed to be a mount point in the host, and
1160
                 * which is guaranteed to have a subdir we can mount over. /run/ is not suitable for this,
1161
                 * since we ultimately want to move the resulting file system there, i.e. we need propagation
1162
                 * for /run/ eventually. We could use our own /run/systemd/bind mount on itself, but that
1163
                 * would be visible in the host mount table all the time, which we want to avoid. Hence, what
1164
                 * we do here instead we use /dev/ and /dev/shm/ for our purposes. We know for sure that
1165
                 * /dev/ is a mount point and we now for sure that /dev/shm/ exists. Hence we can turn off
1166
                 * propagation on the former, and then overmount the latter.
1167
                 *
1168
                 * Yes it's nasty playing games with /dev/ and /dev/shm/ like this, since it does not exist
1169
                 * for this purpose, but there are few other candidates that work equally well for us, and
1170
                 * given that we do this in a privately namespaced short-lived single-threaded process that
1171
                 * no one else sees this should be OK to do. */
1172

1173
                /* Turn off propagation from our namespace to host */
1174
                r = mount_nofollow_verbose(LOG_DEBUG, NULL, "/dev", NULL, MS_SLAVE|MS_REC, NULL);
1,950✔
1175
                if (r < 0)
1,950✔
1176
                        goto child_fail;
×
1177

1178
                r = setup_credentials_internal(
1,950✔
1179
                                context,
1180
                                params,
1181
                                unit,
1182
                                p,           /* final mount point */
1183
                                "/dev/shm",  /* temporary workspace to overmount */
1184
                                false,       /* do not reuse /dev/shm if it is already a mount, under no circumstances */
1185
                                true,        /* insist that something is mounted, do not allow fallback to plain directory */
1186
                                uid,
1187
                                gid);
1188
                if (r < 0)
1,950✔
1189
                        goto child_fail;
×
1190

1191
                _exit(EXIT_SUCCESS);
1,950✔
1192

1193
        child_fail:
×
1194
                _exit(EXIT_FAILURE);
×
1195
        }
1196

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