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

systemd / systemd / 14630481637

23 Apr 2025 07:04PM UTC coverage: 72.178% (-0.002%) from 72.18%
14630481637

push

github

DaanDeMeyer
mkosi: Run clangd within the tools tree instead of the build container

Running within the build sandbox has a number of disadvantages:
- We have a separate clangd cache for each distribution/release combo
- It requires to build the full image before clangd can be used
- It breaks every time the image becomes out of date and requires a
  rebuild
- We can't look at system headers as we don't have the knowledge to map
  them from inside the build sandbox to the corresponding path on the host

Instead, let's have mkosi.clangd run clangd within the tools tree. We
already require building systemd for both the host and the target anyway,
and all the dependencies to build systemd are installed in the tools tree
already for that, as well as clangd since it's installed together with the
other clang tooling we install in the tools tree. Unlike the previous approach,
this approach only requires the mkosi tools tree to be built upfront, which has
a much higher chance of not invalidating its cache. We can also trivially map
system header lookups from within the sandbox to the path within mkosi.tools
on the host so that starts working as well.

297054 of 411557 relevant lines covered (72.18%)

686269.58 hits per line

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

84.82
/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 "exec-credential.h"
8
#include "execute.h"
9
#include "fileio.h"
10
#include "glob-util.h"
11
#include "io-util.h"
12
#include "iovec-util.h"
13
#include "label-util.h"
14
#include "log.h"
15
#include "mkdir-label.h"
16
#include "mount-util.h"
17
#include "mountpoint-util.h"
18
#include "process-util.h"
19
#include "random-util.h"
20
#include "recurse-dir.h"
21
#include "rm-rf.h"
22
#include "tmpfile-util.h"
23

24
ExecSetCredential* exec_set_credential_free(ExecSetCredential *sc) {
×
25
        if (!sc)
×
26
                return NULL;
27

28
        free(sc->id);
×
29
        free(sc->data);
×
30
        return mfree(sc);
×
31
}
32

33
ExecLoadCredential* exec_load_credential_free(ExecLoadCredential *lc) {
24✔
34
        if (!lc)
24✔
35
                return NULL;
36

37
        free(lc->id);
24✔
38
        free(lc->path);
24✔
39
        return mfree(lc);
24✔
40
}
41

42
ExecImportCredential* exec_import_credential_free(ExecImportCredential *ic) {
4,016✔
43
        if (!ic)
4,016✔
44
                return NULL;
45

46
        free(ic->glob);
4,016✔
47
        free(ic->rename);
4,016✔
48
        return mfree(ic);
4,016✔
49
}
50

51
static void exec_import_credential_hash_func(const ExecImportCredential *ic, struct siphash *state) {
25,308✔
52
        assert(ic);
25,308✔
53
        assert(state);
25,308✔
54

55
        siphash24_compress_string(ic->glob, state);
25,308✔
56
        if (ic->rename)
25,308✔
57
                siphash24_compress_string(ic->rename, state);
2,224✔
58
}
25,308✔
59

60
static int exec_import_credential_compare_func(const ExecImportCredential *a, const ExecImportCredential *b) {
8,549✔
61
        int r;
8,549✔
62

63
        assert(a);
8,549✔
64
        assert(b);
8,549✔
65

66
        r = strcmp(a->glob, b->glob);
8,549✔
67
        if (r != 0)
8,549✔
68
                return r;
69

70
        return strcmp_ptr(a->rename, b->rename);
5✔
71
}
72

73
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
×
74
        exec_set_credential_hash_ops,
75
        char, string_hash_func, string_compare_func,
76
        ExecSetCredential, exec_set_credential_free);
77

78
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
24✔
79
        exec_load_credential_hash_ops,
80
        char, string_hash_func, string_compare_func,
81
        ExecLoadCredential, exec_load_credential_free);
82

83
DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
4,015✔
84
        exec_import_credential_hash_ops,
85
        ExecImportCredential,
86
        exec_import_credential_hash_func,
87
        exec_import_credential_compare_func,
88
        exec_import_credential_free);
89

90
int exec_context_put_load_credential(ExecContext *c, const char *id, const char *path, bool encrypted) {
95✔
91
        ExecLoadCredential *old;
95✔
92
        int r;
95✔
93

94
        assert(c);
95✔
95
        assert(id);
95✔
96
        assert(path);
95✔
97

98
        old = hashmap_get(c->load_credentials, id);
95✔
99
        if (old) {
95✔
100
                r = free_and_strdup(&old->path, path);
×
101
                if (r < 0)
×
102
                        return r;
103

104
                old->encrypted = encrypted;
×
105
        } else {
106
                _cleanup_(exec_load_credential_freep) ExecLoadCredential *lc = NULL;
×
107

108
                lc = new(ExecLoadCredential, 1);
95✔
109
                if (!lc)
95✔
110
                        return -ENOMEM;
111

112
                *lc = (ExecLoadCredential) {
95✔
113
                        .id = strdup(id),
95✔
114
                        .path = strdup(path),
95✔
115
                        .encrypted = encrypted,
116
                };
117
                if (!lc->id || !lc->path)
95✔
118
                        return -ENOMEM;
119

120
                r = hashmap_ensure_put(&c->load_credentials, &exec_load_credential_hash_ops, lc->id, lc);
95✔
121
                assert(r != -EEXIST);
95✔
122
                if (r < 0)
95✔
123
                        return r;
124

125
                TAKE_PTR(lc);
95✔
126
        }
127

128
        return 0;
129
}
130

131
int exec_context_put_set_credential(
182✔
132
                ExecContext *c,
133
                const char *id,
134
                void *data_consume,
135
                size_t size,
136
                bool encrypted) {
137

138
        _cleanup_free_ void *data = data_consume;
182✔
139
        ExecSetCredential *old;
182✔
140
        int r;
182✔
141

142
        /* Takes the ownership of data both on success and failure */
143

144
        assert(c);
182✔
145
        assert(id);
182✔
146
        assert(data || size == 0);
182✔
147

148
        old = hashmap_get(c->set_credentials, id);
182✔
149
        if (old) {
182✔
150
                free_and_replace(old->data, data);
×
151
                old->size = size;
×
152
                old->encrypted = encrypted;
×
153
        } else {
154
                _cleanup_(exec_set_credential_freep) ExecSetCredential *sc = NULL;
×
155

156
                sc = new(ExecSetCredential, 1);
182✔
157
                if (!sc)
182✔
158
                        return -ENOMEM;
159

160
                *sc = (ExecSetCredential) {
182✔
161
                        .id = strdup(id),
182✔
162
                        .data = TAKE_PTR(data),
182✔
163
                        .size = size,
164
                        .encrypted = encrypted,
165
                };
166
                if (!sc->id)
182✔
167
                        return -ENOMEM;
168

169
                r = hashmap_ensure_put(&c->set_credentials, &exec_set_credential_hash_ops, sc->id, sc);
182✔
170
                assert(r != -EEXIST);
182✔
171
                if (r < 0)
182✔
172
                        return r;
173

174
                TAKE_PTR(sc);
182✔
175
        }
176

177
        return 0;
178
}
179

180
int exec_context_put_import_credential(ExecContext *c, const char *glob, const char *rename) {
11,147✔
181
        _cleanup_(exec_import_credential_freep) ExecImportCredential *ic = NULL;
11,147✔
182
        int r;
11,147✔
183

184
        assert(c);
11,147✔
185
        assert(glob);
11,147✔
186

187
        rename = empty_to_null(rename);
11,147✔
188

189
        ic = new(ExecImportCredential, 1);
11,147✔
190
        if (!ic)
11,147✔
191
                return -ENOMEM;
192

193
        *ic = (ExecImportCredential) {
11,147✔
194
                .glob = strdup(glob),
11,147✔
195
        };
196
        if (!ic->glob)
11,147✔
197
                return -ENOMEM;
198
        if (rename) {
11,147✔
199
                ic->rename = strdup(rename);
642✔
200
                if (!ic->rename)
642✔
201
                        return -ENOMEM;
202
        }
203

204
        if (ordered_set_contains(c->import_credentials, ic))
11,147✔
205
                return 0;
206

207
        r = ordered_set_ensure_put(&c->import_credentials, &exec_import_credential_hash_ops, ic);
11,146✔
208
        assert(r != -EEXIST);
11,146✔
209
        if (r < 0)
11,146✔
210
                return r;
211

212
        TAKE_PTR(ic);
11,146✔
213

214
        return 0;
11,146✔
215
}
216

217
bool exec_params_need_credentials(const ExecParameters *p) {
23,058✔
218
        assert(p);
23,058✔
219

220
        return p->flags & (EXEC_SETUP_CREDENTIALS|EXEC_SETUP_CREDENTIALS_FRESH);
23,058✔
221
}
222

223
bool exec_context_has_credentials(const ExecContext *c) {
21,906✔
224
        assert(c);
21,906✔
225

226
        return !hashmap_isempty(c->set_credentials) ||
21,906✔
227
                !hashmap_isempty(c->load_credentials) ||
21,906✔
228
                !ordered_set_isempty(c->import_credentials);
21,556✔
229
}
230

231
bool exec_context_has_encrypted_credentials(const ExecContext *c) {
1,458✔
232
        assert(c);
1,458✔
233

234
        const ExecLoadCredential *load_cred;
1,458✔
235
        HASHMAP_FOREACH(load_cred, c->load_credentials)
1,458✔
236
                if (load_cred->encrypted)
×
237
                        return true;
×
238

239
        const ExecSetCredential *set_cred;
1,458✔
240
        HASHMAP_FOREACH(set_cred, c->set_credentials)
1,458✔
241
                if (set_cred->encrypted)
×
242
                        return true;
×
243

244
        return false;
1,458✔
245
}
246

247
bool mount_point_is_credentials(const char *runtime_prefix, const char *path) {
83,682✔
248
        const char *e;
83,682✔
249

250
        assert(runtime_prefix);
83,682✔
251
        assert(path);
83,682✔
252

253
        e = path_startswith(path, runtime_prefix);
83,682✔
254
        if (!e)
83,682✔
255
                return false;
256

257
        return path_startswith(e, "credentials");
5,968✔
258
}
259

260
static int get_credential_directory(
13,423✔
261
                const char *runtime_prefix,
262
                const char *unit,
263
                char **ret) {
264

265
        char *p;
13,423✔
266

267
        assert(ret);
13,423✔
268

269
        if (!runtime_prefix || !unit) {
13,423✔
270
                *ret = NULL;
×
271
                return 0;
×
272
        }
273

274
        p = path_join(runtime_prefix, "credentials", unit);
13,423✔
275
        if (!p)
13,423✔
276
                return -ENOMEM;
277

278
        *ret = p;
13,423✔
279
        return 1;
13,423✔
280
}
281

282
int exec_context_get_credential_directory(
11,564✔
283
                const ExecContext *context,
284
                const ExecParameters *params,
285
                const char *unit,
286
                char **ret) {
287

288
        assert(context);
11,564✔
289
        assert(params);
11,564✔
290
        assert(unit);
11,564✔
291
        assert(ret);
11,564✔
292

293
        if (!exec_params_need_credentials(params) || !exec_context_has_credentials(context)) {
11,564✔
294
                *ret = NULL;
9,022✔
295
                return 0;
9,022✔
296
        }
297

298
        return get_credential_directory(params->prefix[EXEC_DIRECTORY_RUNTIME], unit, ret);
2,542✔
299
}
300

301
int exec_context_destroy_credentials(const ExecContext *c, const char *runtime_prefix, const char *unit) {
10,881✔
302
        _cleanup_free_ char *p = NULL;
10,881✔
303
        int r;
10,881✔
304

305
        assert(c);
10,881✔
306

307
        r = get_credential_directory(runtime_prefix, unit, &p);
10,881✔
308
        if (r <= 0)
10,881✔
309
                return r;
310

311
        /* This is either a tmpfs/ramfs of its own, or a plain directory. Either way, let's first try to
312
         * unmount it, and afterwards remove the mount point */
313
        (void) umount2(p, MNT_DETACH|UMOUNT_NOFOLLOW);
10,881✔
314
        (void) rm_rf(p, REMOVE_ROOT|REMOVE_CHMOD);
10,881✔
315

316
        return 0;
317
}
318

319
static int write_credential(
1,116✔
320
                int dfd,
321
                const char *id,
322
                const void *data,
323
                size_t size,
324
                uid_t uid,
325
                gid_t gid,
326
                bool ownership_ok) {
327

328
        _cleanup_free_ char *tmp = NULL;
1,116✔
329
        _cleanup_close_ int fd = -EBADF;
1,116✔
330
        int r;
1,116✔
331

332
        assert(dfd >= 0);
1,116✔
333
        assert(id);
1,116✔
334
        assert(data || size == 0);
1,116✔
335

336
        r = tempfn_random_child("", "cred", &tmp);
1,116✔
337
        if (r < 0)
1,116✔
338
                return r;
339

340
        fd = openat(dfd, tmp, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL|O_NOFOLLOW|O_NOCTTY, 0600);
1,116✔
341
        if (fd < 0)
1,116✔
342
                return -errno;
×
343

344
        r = loop_write(fd, data, size);
1,116✔
345
        if (r < 0)
1,116✔
346
                goto fail;
×
347

348
        r = RET_NERRNO(fchmod(fd, 0400)); /* Take away "w" bit */
1,116✔
349
        if (r < 0)
×
350
                goto fail;
×
351

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

363
                        r = RET_NERRNO(fchown(fd, uid, gid));
×
364
                        if (r < 0)
×
365
                                goto fail;
×
366
                }
367
        }
368

369
        r = RET_NERRNO(renameat(dfd, tmp, dfd, id));
1,116✔
370
        if (r < 0)
×
371
                goto fail;
×
372

373
        return 0;
374

375
fail:
×
376
        (void) unlinkat(dfd, tmp, /* flags = */ 0);
×
377
        return r;
×
378
}
379

380
typedef enum CredentialSearchPath {
381
        CREDENTIAL_SEARCH_PATH_TRUSTED,
382
        CREDENTIAL_SEARCH_PATH_ENCRYPTED,
383
        CREDENTIAL_SEARCH_PATH_ALL,
384
        _CREDENTIAL_SEARCH_PATH_MAX,
385
        _CREDENTIAL_SEARCH_PATH_INVALID = -EINVAL,
386
} CredentialSearchPath;
387

388
static int credential_search_path(const ExecParameters *params, CredentialSearchPath path, char ***ret) {
7,148✔
389
        _cleanup_strv_free_ char **l = NULL;
7,148✔
390
        int r;
7,148✔
391

392
        assert(params);
7,148✔
393
        assert(path >= 0 && path < _CREDENTIAL_SEARCH_PATH_MAX);
7,148✔
394
        assert(ret);
7,148✔
395

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

400
        if (IN_SET(path, CREDENTIAL_SEARCH_PATH_ENCRYPTED, CREDENTIAL_SEARCH_PATH_ALL)) {
7,148✔
401
                r = strv_extend(&l, params->received_encrypted_credentials_directory);
3,580✔
402
                if (r < 0)
3,580✔
403
                        return r;
×
404

405
                _cleanup_strv_free_ char **add = NULL;
3,580✔
406
                r = credential_store_path_encrypted(params->runtime_scope, &add);
3,580✔
407
                if (r < 0)
3,580✔
408
                        return r;
409

410
                r = strv_extend_strv_consume(&l, TAKE_PTR(add), /* filter_duplicates= */ false);
3,580✔
411
                if (r < 0)
3,580✔
412
                        return r;
413
        }
414

415
        if (IN_SET(path, CREDENTIAL_SEARCH_PATH_TRUSTED, CREDENTIAL_SEARCH_PATH_ALL)) {
7,148✔
416
                r = strv_extend(&l, params->received_credentials_directory);
3,580✔
417
                if (r < 0)
3,580✔
418
                        return r;
×
419

420
                _cleanup_strv_free_ char **add = NULL;
3,580✔
421
                r = credential_store_path(params->runtime_scope, &add);
3,580✔
422
                if (r < 0)
3,580✔
423
                        return r;
424

425
                r = strv_extend_strv_consume(&l, TAKE_PTR(add), /* filter_duplicates= */ false);
3,580✔
426
                if (r < 0)
3,580✔
427
                        return r;
428
        }
429

430
        if (DEBUG_LOGGING) {
7,148✔
431
                _cleanup_free_ char *t = strv_join(l, ":");
14,296✔
432
                log_debug("Credential search path is: %s", strempty(t));
7,148✔
433
        }
434

435
        *ret = TAKE_PTR(l);
7,148✔
436
        return 0;
7,148✔
437
}
438

439
struct load_cred_args {
440
        const ExecContext *context;
441
        const ExecParameters *params;
442
        const char *unit;
443
        bool encrypted;
444
        int write_dfd;
445
        uid_t uid;
446
        gid_t gid;
447
        bool ownership_ok;
448
        uint64_t left;
449
};
450

451
static int maybe_decrypt_and_write_credential(
1,116✔
452
                struct load_cred_args *args,
453
                const char *id,
454
                const char *data,
455
                size_t size) {
456

457
        _cleanup_(iovec_done_erase) struct iovec plaintext = {};
1,116✔
458
        size_t add;
1,116✔
459
        int r;
1,116✔
460

461
        assert(args);
1,116✔
462
        assert(args->write_dfd >= 0);
1,116✔
463
        assert(id);
1,116✔
464
        assert(data || size == 0);
1,116✔
465

466
        if (args->encrypted) {
1,116✔
467
                switch (args->params->runtime_scope) {
7✔
468

469
                case RUNTIME_SCOPE_SYSTEM:
5✔
470
                        /* In system mode talk directly to the TPM */
471
                        r = decrypt_credential_and_warn(
5✔
472
                                        id,
473
                                        now(CLOCK_REALTIME),
474
                                        /* tpm2_device= */ NULL,
475
                                        /* tpm2_signature_path= */ NULL,
476
                                        getuid(),
477
                                        &IOVEC_MAKE(data, size),
5✔
478
                                        CREDENTIAL_ANY_SCOPE,
479
                                        &plaintext);
480
                        break;
7✔
481

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

496
                default:
×
497
                        assert_not_reached();
×
498
                }
499
                if (r < 0)
7✔
500
                        return r;
501

502
                data = plaintext.iov_base;
7✔
503
                size = plaintext.iov_len;
7✔
504
        }
505

506
        add = strlen(id) + size;
1,116✔
507
        if (add > args->left)
1,116✔
508
                return -E2BIG;
509

510
        r = write_credential(args->write_dfd, id, data, size, args->uid, args->gid, args->ownership_ok);
1,116✔
511
        if (r < 0)
1,116✔
512
                return log_debug_errno(r, "Failed to write credential '%s': %m", id);
×
513

514
        args->left -= add;
1,116✔
515

516
        return 0;
1,116✔
517
}
518

519
static int load_credential_glob(
7,136✔
520
                struct load_cred_args *args,
521
                const ExecImportCredential *ic,
522
                char * const *search_path,
523
                ReadFullFileFlags flags) {
524

525
        int r;
7,136✔
526

527
        assert(args);
7,136✔
528
        assert(args->write_dfd >= 0);
7,136✔
529
        assert(ic);
7,136✔
530
        assert(search_path);
7,136✔
531

532
        STRV_FOREACH(d, search_path) {
39,245✔
533
                _cleanup_globfree_ glob_t pglob = {};
×
534
                _cleanup_free_ char *j = NULL;
32,109✔
535

536
                j = path_join(*d, ic->glob);
32,109✔
537
                if (!j)
32,109✔
538
                        return -ENOMEM;
539

540
                r = safe_glob(j, 0, &pglob);
32,109✔
541
                if (r == -ENOENT)
32,109✔
542
                        continue;
31,348✔
543
                if (r < 0)
761✔
544
                        return r;
545

546
                FOREACH_ARRAY(p, pglob.gl_pathv, pglob.gl_pathc) {
1,773✔
547
                        _cleanup_free_ char *fn = NULL;
1,012✔
548
                        _cleanup_(erase_and_freep) char *data = NULL;
1,012✔
549
                        size_t size;
1,012✔
550

551
                        r = path_extract_filename(*p, &fn);
1,012✔
552
                        if (r < 0)
1,012✔
553
                                return log_debug_errno(r, "Failed to extract filename from '%s': %m", *p);
×
554

555
                        if (ic->rename) {
1,012✔
556
                                _cleanup_free_ char *renamed = NULL;
×
557

558
                                renamed = strjoin(ic->rename, fn + strlen(ic->glob) - !!endswith(ic->glob, "*"));
170✔
559
                                if (!renamed)
170✔
560
                                        return log_oom_debug();
×
561

562
                                free_and_replace(fn, renamed);
170✔
563
                        }
564

565
                        if (!credential_name_valid(fn)) {
1,012✔
566
                                log_debug("Skipping credential with invalid name: %s", fn);
5✔
567
                                continue;
5✔
568
                        }
569

570
                        if (faccessat(args->write_dfd, fn, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) {
1,007✔
571
                                log_debug("Skipping credential with duplicated ID %s at %s", fn, *p);
10✔
572
                                continue;
10✔
573
                        }
574
                        if (errno != ENOENT)
997✔
575
                                return log_debug_errno(errno, "Failed to test if credential %s exists: %m", fn);
×
576

577
                        /* path is absolute, hence pass AT_FDCWD as nop dir fd here */
578
                        r = read_full_file_full(
997✔
579
                                        AT_FDCWD,
580
                                        *p,
581
                                        UINT64_MAX,
582
                                        args->encrypted ? CREDENTIAL_ENCRYPTED_SIZE_MAX : CREDENTIAL_SIZE_MAX,
997✔
583
                                        flags,
584
                                        NULL,
585
                                        &data, &size);
586
                        if (r < 0)
997✔
587
                                return log_debug_errno(r, "Failed to read credential '%s': %m", *p);
×
588

589
                        r = maybe_decrypt_and_write_credential(args, fn, data, size);
997✔
590
                        if (r < 0)
997✔
591
                                return r;
592
                }
593
        }
594

595
        return 0;
596
}
597

598
static int load_credential(
36✔
599
                struct load_cred_args *args,
600
                const char *id,
601
                int read_dfd,
602
                const char *path) {
603

604
        ReadFullFileFlags flags = READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER;
36✔
605
        _cleanup_strv_free_ char **search_path = NULL;
×
606
        _cleanup_free_ char *bindname = NULL;
36✔
607
        const char *source = NULL;
36✔
608
        bool missing_ok;
36✔
609
        _cleanup_(erase_and_freep) char *data = NULL;
36✔
610
        size_t size, maxsz;
36✔
611
        int r;
36✔
612

613
        assert(args);
36✔
614
        assert(args->context);
36✔
615
        assert(args->params);
36✔
616
        assert(args->unit);
36✔
617
        assert(args->write_dfd >= 0);
36✔
618
        assert(id);
36✔
619
        assert(read_dfd >= 0 || read_dfd == AT_FDCWD);
36✔
620
        assert(path);
36✔
621

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

628
                if (!filename_is_valid(path)) /* safety check */
4✔
629
                        return -EINVAL;
630

631
                missing_ok = true;
632
                source = path;
633

634
        } else if (path_is_absolute(path)) {
32✔
635
                /* If this is an absolute path, read the data directly from it, and support AF_UNIX
636
                 * sockets */
637

638
                if (!path_is_valid(path)) /* safety check */
20✔
639
                        return -EINVAL;
640

641
                flags |= READ_FULL_FILE_CONNECT_SOCKET;
20✔
642

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

648
                missing_ok = false;
649
                source = path;
650

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

656
                r = credential_search_path(args->params, CREDENTIAL_SEARCH_PATH_ALL, &search_path);
12✔
657
                if (r < 0)
12✔
658
                        return r;
659

660
                missing_ok = true;
661
        } else
662
                return -EINVAL;
663

664
        if (args->encrypted) {
36✔
665
                flags |= READ_FULL_FILE_UNBASE64;
3✔
666
                maxsz = CREDENTIAL_ENCRYPTED_SIZE_MAX;
3✔
667
        } else
668
                maxsz = CREDENTIAL_SIZE_MAX;
669

670
        if (search_path)
36✔
671
                STRV_FOREACH(d, search_path) {
93✔
672
                        _cleanup_free_ char *j = NULL;
88✔
673

674
                        j = path_join(*d, path);
88✔
675
                        if (!j)
88✔
676
                                return -ENOMEM;
×
677

678
                        r = read_full_file_full(
88✔
679
                                        AT_FDCWD, j, /* path is absolute, hence pass AT_FDCWD as nop dir fd here */
680
                                        UINT64_MAX,
681
                                        maxsz,
682
                                        flags,
683
                                        NULL,
684
                                        &data, &size);
685
                        if (r != -ENOENT)
88✔
686
                                break;
687
                }
688
        else if (source)
24✔
689
                r = read_full_file_full(
24✔
690
                                read_dfd, source,
691
                                UINT64_MAX,
692
                                maxsz,
693
                                flags,
694
                                bindname,
695
                                &data, &size);
696
        else
697
                assert_not_reached();
×
698

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

714
        return maybe_decrypt_and_write_credential(args, id, data, size);
30✔
715
}
716

717
static int load_cred_recurse_dir_cb(
6✔
718
                RecurseDirEvent event,
719
                const char *path,
720
                int dir_fd,
721
                int inode_fd,
722
                const struct dirent *de,
723
                const struct statx *sx,
724
                void *userdata) {
725

726
        struct load_cred_args *args = ASSERT_PTR(userdata);
6✔
727
        _cleanup_free_ char *sub_id = NULL;
6✔
728
        int r;
6✔
729

730
        assert(path);
6✔
731
        assert(de);
6✔
732

733
        if (event != RECURSE_DIR_ENTRY)
6✔
734
                return RECURSE_DIR_CONTINUE;
735

736
        if (!IN_SET(de->d_type, DT_REG, DT_SOCK))
4✔
737
                return RECURSE_DIR_CONTINUE;
738

739
        sub_id = strreplace(path, "/", "_");
4✔
740
        if (!sub_id)
4✔
741
                return -ENOMEM;
742

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

746
        if (faccessat(args->write_dfd, sub_id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) {
4✔
747
                log_debug("Skipping credential with duplicated ID %s at %s", sub_id, path);
×
748
                return RECURSE_DIR_CONTINUE;
×
749
        }
750
        if (errno != ENOENT)
4✔
751
                return log_debug_errno(errno, "Failed to test if credential %s exists: %m", sub_id);
×
752

753
        r = load_credential(args,
8✔
754
                            sub_id,
755
                            dir_fd, de->d_name);
4✔
756
        if (r < 0)
4✔
757
                return r;
×
758

759
        return RECURSE_DIR_CONTINUE;
760
}
761

762
static int acquire_credentials(
1,940✔
763
                const ExecContext *context,
764
                const ExecParameters *params,
765
                const char *unit,
766
                const char *p,
767
                uid_t uid,
768
                gid_t gid,
769
                bool ownership_ok) {
770

771
        _cleanup_close_ int dfd = -EBADF;
1,940✔
772
        int r;
1,940✔
773

774
        assert(context);
1,940✔
775
        assert(params);
1,940✔
776
        assert(unit);
1,940✔
777
        assert(p);
1,940✔
778

779
        dfd = open(p, O_DIRECTORY|O_CLOEXEC);
1,940✔
780
        if (dfd < 0)
1,940✔
781
                return -errno;
×
782

783
        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,940✔
784
        if (r < 0)
1,940✔
785
                return r;
786

787
        struct load_cred_args args = {
1,940✔
788
                .context = context,
789
                .params = params,
790
                .unit = unit,
791
                .write_dfd = dfd,
792
                .uid = uid,
793
                .gid = gid,
794
                .ownership_ok = ownership_ok,
795
                .left = CREDENTIALS_TOTAL_SIZE_MAX,
796
        };
797

798
        /* First, load credentials off disk (or acquire via AF_UNIX socket) */
799
        ExecLoadCredential *lc;
1,940✔
800
        HASHMAP_FOREACH(lc, context->load_credentials) {
3,913✔
801
                _cleanup_close_ int sub_fd = -EBADF;
1,973✔
802

803
                args.encrypted = lc->encrypted;
33✔
804

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

810
                if (path_is_absolute(lc->path)) {
33✔
811
                        sub_fd = open(lc->path, O_DIRECTORY|O_CLOEXEC);
21✔
812
                        if (sub_fd < 0 && !IN_SET(errno,
21✔
813
                                                  ENOTDIR,  /* Not a directory */
814
                                                  ENOENT))  /* Doesn't exist? */
815
                                return log_debug_errno(errno, "Failed to open credential source '%s': %m", lc->path);
×
816
                }
817

818
                if (sub_fd < 0)
20✔
819
                        /* Regular file (incl. a credential passed in from higher up) */
820
                        r = load_credential(&args,
32✔
821
                                            lc->id,
32✔
822
                                            AT_FDCWD, lc->path);
32✔
823
                else
824
                        /* Directory */
825
                        r = recurse_dir(sub_fd,
1✔
826
                                        /* path= */ lc->id, /* recurse_dir() will suffix the subdir paths from here to the top-level id */
1✔
827
                                        /* statx_mask= */ 0,
828
                                        /* n_depth_max= */ UINT_MAX,
829
                                        RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE,
830
                                        load_cred_recurse_dir_cb,
831
                                        &args);
832
                if (r < 0)
33✔
833
                        return r;
834
        }
835

836
        /* Next, look for system credentials and credentials in the credentials store. Note that these do not
837
         * override any credentials found earlier. */
838
        ExecImportCredential *ic;
1,940✔
839
        ORDERED_SET_FOREACH(ic, context->import_credentials) {
5,508✔
840
                _cleanup_free_ char **search_path = NULL;
3,568✔
841

842
                r = credential_search_path(params, CREDENTIAL_SEARCH_PATH_TRUSTED, &search_path);
3,568✔
843
                if (r < 0)
3,568✔
844
                        return r;
845

846
                args.encrypted = false;
3,568✔
847

848
                r = load_credential_glob(&args,
3,568✔
849
                                         ic,
850
                                         search_path,
851
                                         READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER);
852
                if (r < 0)
3,568✔
853
                        return r;
854

855
                search_path = strv_free(search_path);
3,568✔
856

857
                r = credential_search_path(params, CREDENTIAL_SEARCH_PATH_ENCRYPTED, &search_path);
3,568✔
858
                if (r < 0)
3,568✔
859
                        return r;
860

861
                args.encrypted = true;
3,568✔
862

863
                r = load_credential_glob(&args,
3,568✔
864
                                         ic,
865
                                         search_path,
866
                                         READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER|READ_FULL_FILE_UNBASE64);
867
                if (r < 0)
3,568✔
868
                        return r;
869
        }
870

871
        /* Finally, we add in literally specified credentials. If the credentials already exist, we'll not
872
         * add them, so that they can act as a "default" if the same credential is specified multiple times. */
873
        ExecSetCredential *sc;
1,940✔
874
        HASHMAP_FOREACH(sc, context->set_credentials) {
2,031✔
875
                args.encrypted = sc->encrypted;
91✔
876

877
                if (faccessat(dfd, sc->id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) {
91✔
878
                        log_debug("Skipping credential with duplicated ID %s", sc->id);
2✔
879
                        continue;
2✔
880
                }
881
                if (errno != ENOENT)
89✔
882
                        return log_debug_errno(errno, "Failed to test if credential %s exists: %m", sc->id);
×
883

884
                r = maybe_decrypt_and_write_credential(&args, sc->id, sc->data, sc->size);
89✔
885
                if (r < 0)
89✔
886
                        return r;
887
        }
888

889
        r = fd_acl_make_read_only(dfd); /* Now take away the "w" bit */
1,940✔
890
        if (r < 0)
1,940✔
891
                return r;
892

893
        /* After we created all keys with the right perms, also make sure the credential store as a whole is
894
         * accessible */
895

896
        if (uid_is_valid(uid) && uid != getuid()) {
1,940✔
897
                r = fd_add_uid_acl_permission(dfd, uid, ACL_READ | ACL_EXECUTE);
643✔
898
                if (r < 0) {
643✔
899
                        if (!ERRNO_IS_NOT_SUPPORTED(r) && !ERRNO_IS_PRIVILEGE(r))
×
900
                                return r;
901

902
                        if (!ownership_ok)
×
903
                                return r;
904

905
                        if (fchown(dfd, uid, gid) < 0)
×
906
                                return -errno;
×
907
                }
908
        }
909

910
        return 0;
911
}
912

913
static int setup_credentials_internal(
1,942✔
914
                const ExecContext *context,
915
                const ExecParameters *params,
916
                const char *unit,
917
                const char *final,        /* This is where the credential store shall eventually end up at */
918
                const char *workspace,    /* This is where we can prepare it before moving it to the final place */
919
                bool reuse_workspace,     /* Whether to reuse any existing workspace mount if it already is a mount */
920
                bool must_mount,          /* Whether to require that we mount something, it's not OK to use the plain directory fall back */
921
                uid_t uid,
922
                gid_t gid) {
923

924
        bool final_mounted;
1,942✔
925
        int r, workspace_mounted; /* negative if we don't know yet whether we have/can mount something; true
1,942✔
926
                                   * if we mounted something; false if we definitely can't mount anything */
927

928
        assert(context);
1,942✔
929
        assert(params);
1,942✔
930
        assert(unit);
1,942✔
931
        assert(final);
1,942✔
932
        assert(workspace);
1,942✔
933

934
        r = path_is_mount_point(final);
1,942✔
935
        if (r < 0)
1,942✔
936
                return log_debug_errno(r, "Failed to determine if '%s' is a mountpoint: %m", final);
×
937
        final_mounted = r > 0;
1,942✔
938

939
        if (final_mounted) {
1,942✔
940
                if (FLAGS_SET(params->flags, EXEC_SETUP_CREDENTIALS_FRESH)) {
6✔
941
                        r = umount_verbose(LOG_DEBUG, final, MNT_DETACH|UMOUNT_NOFOLLOW);
4✔
942
                        if (r < 0)
4✔
943
                                return r;
944

945
                        final_mounted = false;
946
                } else {
947
                        /* We can reuse the previous credential dir */
948
                        r = dir_is_empty(final, /* ignore_hidden_or_backup = */ false);
2✔
949
                        if (r < 0)
2✔
950
                                return r;
951
                        if (r == 0) {
2✔
952
                                log_debug("Credential dir for unit '%s' already set up, skipping.", unit);
2✔
953
                                return 0;
2✔
954
                        }
955
                }
956
        }
957

958
        if (reuse_workspace) {
1,940✔
959
                r = path_is_mount_point(workspace);
1✔
960
                if (r < 0)
1✔
961
                        return r;
962
                if (r > 0)
1✔
963
                        workspace_mounted = true; /* If this is already a mount, and we are supposed to reuse
964
                                                   * it, let's keep this in mind */
965
                else
966
                        workspace_mounted = -1; /* We need to figure out if we can mount something to the workspace */
1✔
967
        } else
968
                workspace_mounted = -1; /* ditto */
969

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

975
        if (workspace_mounted < 0) {
1,940✔
976
                if (!final_mounted)
1,940✔
977
                        /* Nothing is mounted on the workspace yet, let's try to mount a new tmpfs if
978
                         * not using the final place. */
979
                        r = mount_credentials_fs(workspace, CREDENTIALS_TOTAL_SIZE_MAX, /* ro= */ false);
1,940✔
980
                if (final_mounted || r < 0) {
1,940✔
981
                        /* If using final place or failed to mount new tmpfs, make a bind mount from
982
                         * the final to the workspace, so that we can make it writable there. */
983
                        r = mount_nofollow_verbose(LOG_DEBUG, final, workspace, NULL, MS_BIND|MS_REC, NULL);
1✔
984
                        if (r < 0) {
1✔
985
                                if (!ERRNO_IS_PRIVILEGE(r))
1✔
986
                                        /* Propagate anything that isn't a permission problem. */
987
                                        return r;
988

989
                                if (must_mount)
1✔
990
                                        /* If it's not OK to use the plain directory fallback, propagate all
991
                                         * errors too. */
992
                                        return r;
993

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

999
                                workspace_mounted = false;
1000
                        } else {
1001
                                /* Make the new bind mount writable (i.e. drop MS_RDONLY) */
1002
                                r = mount_nofollow_verbose(LOG_DEBUG,
×
1003
                                                           NULL,
1004
                                                           workspace,
1005
                                                           NULL,
1006
                                                           MS_BIND|MS_REMOUNT|credentials_fs_mount_flags(/* ro= */ false),
×
1007
                                                           NULL);
1008
                                if (r < 0)
×
1009
                                        return r;
1010

1011
                                workspace_mounted = true;
1012
                        }
1013
                } else
1014
                        workspace_mounted = true;
1015
        }
1016

1017
        assert(workspace_mounted >= 0);
×
1018
        assert(!must_mount || workspace_mounted);
1,940✔
1019

1020
        const char *where = workspace_mounted ? workspace : final;
1,940✔
1021

1022
        (void) label_fix_full(AT_FDCWD, where, final, 0);
1,940✔
1023

1024
        r = acquire_credentials(context, params, unit, where, uid, gid, workspace_mounted);
1,940✔
1025
        if (r < 0) {
1,940✔
1026
                /* If we're using final place as workspace, and failed to acquire credentials, we might
1027
                 * have left half-written creds there. Let's get rid of the whole mount, so future
1028
                 * calls won't reuse it. */
1029
                if (final_mounted)
×
1030
                        (void) umount_verbose(LOG_DEBUG, final, MNT_DETACH|UMOUNT_NOFOLLOW);
×
1031

1032
                return r;
×
1033
        }
1034

1035
        if (workspace_mounted) {
1,940✔
1036
                if (!final_mounted) {
1,939✔
1037
                        /* Make workspace read-only now, so that any bind mount we make from it defaults to
1038
                         * read-only too */
1039
                        r = mount_nofollow_verbose(LOG_DEBUG, NULL, workspace, NULL, MS_BIND|MS_REMOUNT|credentials_fs_mount_flags(/* ro= */ true), NULL);
1,939✔
1040
                        if (r < 0)
1,939✔
1041
                                return r;
1042

1043
                        /* And mount it to the final place, read-only */
1044
                        r = mount_nofollow_verbose(LOG_DEBUG, workspace, final, NULL, MS_MOVE, NULL);
1,939✔
1045
                } else
1046
                        /* Otherwise we just get rid of the bind mount of final place */
1047
                        r = umount_verbose(LOG_DEBUG, workspace, MNT_DETACH|UMOUNT_NOFOLLOW);
×
1048
                if (r < 0)
1,939✔
1049
                        return r;
×
1050
        } else {
1051
                _cleanup_free_ char *parent = NULL;
1✔
1052

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

1056
                r = path_extract_directory(final, &parent);
1✔
1057
                if (r < 0)
1✔
1058
                        return r;
1059
                if (chmod(parent, 0755) < 0)
1✔
1060
                        return -errno;
×
1061
        }
1062

1063
        return 0;
1064
}
1065

1066
int exec_setup_credentials(
11,494✔
1067
                const ExecContext *context,
1068
                const ExecParameters *params,
1069
                const char *unit,
1070
                uid_t uid,
1071
                gid_t gid) {
1072

1073
        _cleanup_free_ char *p = NULL, *q = NULL;
9,553✔
1074
        int r;
11,494✔
1075

1076
        assert(context);
11,494✔
1077
        assert(params);
11,494✔
1078
        assert(unit);
11,494✔
1079

1080
        if (!exec_params_need_credentials(params) || !exec_context_has_credentials(context))
11,494✔
1081
                return 0;
7,617✔
1082

1083
        if (!params->prefix[EXEC_DIRECTORY_RUNTIME])
3,877✔
1084
                return -EINVAL;
1085

1086
        /* This is where we'll place stuff when we are done; the main credentials directory is world-readable,
1087
         * and the subdir we mount over with a read-only file system readable by the service's user. */
1088
        q = path_join(params->prefix[EXEC_DIRECTORY_RUNTIME], "credentials");
3,877✔
1089
        if (!q)
3,877✔
1090
                return -ENOMEM;
1091

1092
        r = mkdir_label(q, 0755); /* top-level dir: world readable/searchable */
3,877✔
1093
        if (r < 0 && r != -EEXIST)
3,877✔
1094
                return r;
1095

1096
        p = path_join(q, unit);
3,877✔
1097
        if (!p)
3,877✔
1098
                return -ENOMEM;
1099

1100
        r = mkdir_label(p, 0700); /* per-unit dir: private to user */
3,877✔
1101
        if (r < 0 && r != -EEXIST)
3,877✔
1102
                return r;
1103

1104
        r = safe_fork("(sd-mkdcreds)", FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_NEW_MOUNTNS, NULL);
3,877✔
1105
        if (r < 0) {
3,877✔
1106
                _cleanup_(rmdir_and_freep) char *u = NULL; /* remove the temporary workspace if we can */
×
1107
                _cleanup_free_ char *t = NULL;
1✔
1108

1109
                /* If this is not a privilege or support issue then propagate the error */
1110
                if (!ERRNO_IS_NOT_SUPPORTED(r) && !ERRNO_IS_PRIVILEGE(r))
1✔
1111
                        return r;
1112

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

1119
                /* We can't set up a mount namespace. In that case operate on a fixed, inaccessible per-unit
1120
                 * directory outside of /run/credentials/ first, and then move it over to /run/credentials/
1121
                 * after it is fully set up */
1122
                u = path_join(t, unit);
1✔
1123
                if (!u)
1✔
1124
                        return -ENOMEM;
1125

1126
                FOREACH_STRING(i, t, u) {
3✔
1127
                        r = mkdir_label(i, 0700);
2✔
1128
                        if (r < 0 && r != -EEXIST)
2✔
1129
                                return log_debug_errno(r, "Failed to make directory '%s': %m", i);
×
1130
                }
1131

1132
                r = setup_credentials_internal(
1✔
1133
                                context,
1134
                                params,
1135
                                unit,
1136
                                p,       /* final mount point */
1137
                                u,       /* temporary workspace to overmount */
1138
                                true,    /* reuse the workspace if it is already a mount */
1139
                                false,   /* it's OK to fall back to a plain directory if we can't mount anything */
1140
                                uid,
1141
                                gid);
1142
                if (r < 0)
1✔
1143
                        return r;
1144

1145
        } else if (r == 0) {
3,876✔
1146

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

1164
                /* Turn off propagation from our namespace to host */
1165
                r = mount_nofollow_verbose(LOG_DEBUG, NULL, "/dev", NULL, MS_SLAVE|MS_REC, NULL);
1,941✔
1166
                if (r < 0)
1,941✔
1167
                        goto child_fail;
×
1168

1169
                r = setup_credentials_internal(
1,941✔
1170
                                context,
1171
                                params,
1172
                                unit,
1173
                                p,           /* final mount point */
1174
                                "/dev/shm",  /* temporary workspace to overmount */
1175
                                false,       /* do not reuse /dev/shm if it is already a mount, under no circumstances */
1176
                                true,        /* insist that something is mounted, do not allow fallback to plain directory */
1177
                                uid,
1178
                                gid);
1179
                if (r < 0)
1,941✔
1180
                        goto child_fail;
×
1181

1182
                _exit(EXIT_SUCCESS);
1,941✔
1183

1184
        child_fail:
×
1185
                _exit(EXIT_FAILURE);
×
1186
        }
1187

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