1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
6 #include "creds-util.h"
7 #include "exec-credential.h"
10 #include "glob-util.h"
12 #include "iovec-util.h"
13 #include "label-util.h"
14 #include "mkdir-label.h"
15 #include "mount-util.h"
17 #include "mountpoint-util.h"
18 #include "process-util.h"
19 #include "random-util.h"
20 #include "recurse-dir.h"
22 #include "tmpfile-util.h"
24 ExecSetCredential
*exec_set_credential_free(ExecSetCredential
*sc
) {
33 ExecLoadCredential
*exec_load_credential_free(ExecLoadCredential
*lc
) {
42 DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
43 exec_set_credential_hash_ops
,
44 char, string_hash_func
, string_compare_func
,
45 ExecSetCredential
, exec_set_credential_free
);
47 DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
48 exec_load_credential_hash_ops
,
49 char, string_hash_func
, string_compare_func
,
50 ExecLoadCredential
, exec_load_credential_free
);
52 bool exec_params_need_credentials(const ExecParameters
*p
) {
55 return p
->flags
& (EXEC_SETUP_CREDENTIALS
|EXEC_SETUP_CREDENTIALS_FRESH
);
58 bool exec_context_has_credentials(const ExecContext
*c
) {
61 return !hashmap_isempty(c
->set_credentials
) ||
62 !hashmap_isempty(c
->load_credentials
) ||
63 !set_isempty(c
->import_credentials
);
66 bool exec_context_has_encrypted_credentials(const ExecContext
*c
) {
69 const ExecLoadCredential
*load_cred
;
70 HASHMAP_FOREACH(load_cred
, c
->load_credentials
)
71 if (load_cred
->encrypted
)
74 const ExecSetCredential
*set_cred
;
75 HASHMAP_FOREACH(set_cred
, c
->set_credentials
)
76 if (set_cred
->encrypted
)
82 static int get_credential_directory(
83 const char *runtime_prefix
,
91 if (!runtime_prefix
|| !unit
) {
96 p
= path_join(runtime_prefix
, "credentials", unit
);
104 int exec_context_get_credential_directory(
105 const ExecContext
*context
,
106 const ExecParameters
*params
,
115 if (!exec_params_need_credentials(params
) || !exec_context_has_credentials(context
)) {
120 return get_credential_directory(params
->prefix
[EXEC_DIRECTORY_RUNTIME
], unit
, ret
);
123 int unit_add_default_credential_dependencies(Unit
*u
, const ExecContext
*c
) {
124 _cleanup_free_
char *p
= NULL
, *m
= NULL
;
130 if (!exec_context_has_credentials(c
))
133 /* Let's make sure the credentials directory of this service is unmounted *after* the service itself
134 * shuts down. This only matters if mount namespacing is not used for the service, and hence the
135 * credentials mount appears on the host. */
137 r
= get_credential_directory(u
->manager
->prefix
[EXEC_DIRECTORY_RUNTIME
], u
->id
, &p
);
141 r
= unit_name_from_path(p
, ".mount", &m
);
145 return unit_add_dependency_by_name(u
, UNIT_AFTER
, m
, /* add_reference= */ true, UNIT_DEPENDENCY_FILE
);
148 int exec_context_destroy_credentials(Unit
*u
) {
149 _cleanup_free_
char *p
= NULL
;
154 r
= get_credential_directory(u
->manager
->prefix
[EXEC_DIRECTORY_RUNTIME
], u
->id
, &p
);
158 /* This is either a tmpfs/ramfs of its own, or a plain directory. Either way, let's first try to
159 * unmount it, and afterwards remove the mount point */
160 if (umount2(p
, MNT_DETACH
|UMOUNT_NOFOLLOW
) >= 0)
161 (void) mount_invalidate_state_by_path(u
->manager
, p
);
163 (void) rm_rf(p
, REMOVE_ROOT
|REMOVE_CHMOD
);
168 static int write_credential(
177 _cleanup_(unlink_and_freep
) char *tmp
= NULL
;
178 _cleanup_close_
int fd
= -EBADF
;
183 assert(data
|| size
== 0);
185 r
= tempfn_random_child("", "cred", &tmp
);
189 fd
= openat(dfd
, tmp
, O_CREAT
|O_RDWR
|O_CLOEXEC
|O_EXCL
|O_NOFOLLOW
|O_NOCTTY
, 0600);
195 r
= loop_write(fd
, data
, size
);
199 if (fchmod(fd
, 0400) < 0) /* Take away "w" bit */
202 if (uid_is_valid(uid
) && uid
!= getuid()) {
203 r
= fd_add_uid_acl_permission(fd
, uid
, ACL_READ
);
205 if (!ERRNO_IS_NOT_SUPPORTED(r
) && !ERRNO_IS_PRIVILEGE(r
))
208 if (!ownership_ok
) /* Ideally we use ACLs, since we can neatly express what we want
209 * to express: that the user gets read access and nothing
210 * else. But if the backing fs can't support that (e.g. ramfs)
211 * then we can use file ownership instead. But that's only safe if
212 * we can then re-mount the whole thing read-only, so that the
213 * user can no longer chmod() the file to gain write access. */
216 if (fchown(fd
, uid
, gid
) < 0)
221 if (renameat(dfd
, tmp
, dfd
, id
) < 0)
228 typedef enum CredentialSearchPath
{
229 CREDENTIAL_SEARCH_PATH_TRUSTED
,
230 CREDENTIAL_SEARCH_PATH_ENCRYPTED
,
231 CREDENTIAL_SEARCH_PATH_ALL
,
232 _CREDENTIAL_SEARCH_PATH_MAX
,
233 _CREDENTIAL_SEARCH_PATH_INVALID
= -EINVAL
,
234 } CredentialSearchPath
;
236 static char **credential_search_path(const ExecParameters
*params
, CredentialSearchPath path
) {
237 _cleanup_strv_free_
char **l
= NULL
;
240 assert(path
>= 0 && path
< _CREDENTIAL_SEARCH_PATH_MAX
);
242 /* Assemble a search path to find credentials in. For non-encrypted credentials, We'll look in
243 * /etc/credstore/ (and similar directories in /usr/lib/ + /run/). If we're looking for encrypted
244 * credentials, we'll look in /etc/credstore.encrypted/ (and similar dirs). */
246 if (IN_SET(path
, CREDENTIAL_SEARCH_PATH_ENCRYPTED
, CREDENTIAL_SEARCH_PATH_ALL
)) {
247 if (strv_extend(&l
, params
->received_encrypted_credentials_directory
) < 0)
250 if (strv_extend_strv(&l
, CONF_PATHS_STRV("credstore.encrypted"), /* filter_duplicates= */ true) < 0)
254 if (IN_SET(path
, CREDENTIAL_SEARCH_PATH_TRUSTED
, CREDENTIAL_SEARCH_PATH_ALL
)) {
255 if (strv_extend(&l
, params
->received_credentials_directory
) < 0)
258 if (strv_extend_strv(&l
, CONF_PATHS_STRV("credstore"), /* filter_duplicates= */ true) < 0)
263 _cleanup_free_
char *t
= strv_join(l
, ":");
265 log_debug("Credential search path is: %s", strempty(t
));
271 static int maybe_decrypt_and_write_credential(
282 _cleanup_(iovec_done_erase
) struct iovec plaintext
= {};
291 r
= decrypt_credential_and_warn(
294 /* tpm2_device= */ NULL
,
295 /* tpm2_signature_path= */ NULL
,
297 &IOVEC_MAKE(data
, size
),
298 CREDENTIAL_ANY_SCOPE
,
303 data
= plaintext
.iov_base
;
304 size
= plaintext
.iov_len
;
307 add
= strlen(id
) + size
;
311 r
= write_credential(dir_fd
, id
, data
, size
, uid
, gid
, ownership_ok
);
313 return log_debug_errno(r
, "Failed to write credential '%s': %m", id
);
319 static int load_credential_glob(
322 char * const *search_path
,
323 ReadFullFileFlags flags
,
334 assert(write_dfd
>= 0);
337 STRV_FOREACH(d
, search_path
) {
338 _cleanup_globfree_ glob_t pglob
= {};
339 _cleanup_free_
char *j
= NULL
;
341 j
= path_join(*d
, path
);
345 r
= safe_glob(j
, 0, &pglob
);
351 FOREACH_ARRAY(p
, pglob
.gl_pathv
, pglob
.gl_pathc
) {
352 _cleanup_free_
char *fn
= NULL
;
353 _cleanup_(erase_and_freep
) char *data
= NULL
;
356 /* path is absolute, hence pass AT_FDCWD as nop dir fd here */
357 r
= read_full_file_full(
361 encrypted
? CREDENTIAL_ENCRYPTED_SIZE_MAX
: CREDENTIAL_SIZE_MAX
,
366 return log_debug_errno(r
, "Failed to read credential '%s': %m", *p
);
368 r
= path_extract_filename(*p
, &fn
);
370 return log_debug_errno(r
, "Failed to extract filename from '%s': %m", *p
);
372 r
= maybe_decrypt_and_write_credential(
391 static int load_credential(
392 const ExecContext
*context
,
393 const ExecParameters
*params
,
405 ReadFullFileFlags flags
= READ_FULL_FILE_SECURE
|READ_FULL_FILE_FAIL_WHEN_LARGER
;
406 _cleanup_strv_free_
char **search_path
= NULL
;
407 _cleanup_(erase_and_freep
) char *data
= NULL
;
408 _cleanup_free_
char *bindname
= NULL
;
409 const char *source
= NULL
;
410 bool missing_ok
= true;
419 assert(read_dfd
>= 0 || read_dfd
== AT_FDCWD
);
420 assert(write_dfd
>= 0);
424 /* If a directory fd is specified, then read the file directly from that dir. In this case we
425 * won't do AF_UNIX stuff (we simply don't want to recursively iterate down a tree of AF_UNIX
426 * IPC sockets). It's OK if a file vanishes here in the time we enumerate it and intend to
429 if (!filename_is_valid(path
)) /* safety check */
435 } else if (path_is_absolute(path
)) {
436 /* If this is an absolute path, read the data directly from it, and support AF_UNIX
439 if (!path_is_valid(path
)) /* safety check */
442 flags
|= READ_FULL_FILE_CONNECT_SOCKET
;
444 /* Pass some minimal info about the unit and the credential name we are looking to acquire
445 * via the source socket address in case we read off an AF_UNIX socket. */
446 if (asprintf(&bindname
, "@%" PRIx64
"/unit/%s/%s", random_u64(), unit
, id
) < 0)
452 } else if (credential_name_valid(path
)) {
453 /* If this is a relative path, take it as credential name relative to the credentials
454 * directory we received ourselves. We don't support the AF_UNIX stuff in this mode, since we
455 * are operating on a credential store, i.e. this is guaranteed to be regular files. */
457 search_path
= credential_search_path(params
, CREDENTIAL_SEARCH_PATH_ALL
);
466 flags
|= READ_FULL_FILE_UNBASE64
;
468 maxsz
= encrypted
? CREDENTIAL_ENCRYPTED_SIZE_MAX
: CREDENTIAL_SIZE_MAX
;
471 STRV_FOREACH(d
, search_path
) {
472 _cleanup_free_
char *j
= NULL
;
474 j
= path_join(*d
, path
);
478 r
= read_full_file_full(
479 AT_FDCWD
, j
, /* path is absolute, hence pass AT_FDCWD as nop dir fd here */
489 r
= read_full_file_full(
499 if (r
== -ENOENT
&& (missing_ok
|| hashmap_contains(context
->set_credentials
, id
))) {
500 /* Make a missing inherited credential non-fatal, let's just continue. After all apps
501 * will get clear errors if we don't pass such a missing credential on as they
502 * themselves will get ENOENT when trying to read them, which should not be much
503 * worse than when we handle the error here and make it fatal.
505 * Also, if the source file doesn't exist, but a fallback is set via SetCredentials=
506 * we are fine, too. */
507 log_debug_errno(r
, "Couldn't read inherited credential '%s', skipping: %m", path
);
511 return log_debug_errno(r
, "Failed to read credential '%s': %m", path
);
513 return maybe_decrypt_and_write_credential(write_dfd
, id
, encrypted
, uid
, gid
, ownership_ok
, data
, size
, left
);
516 struct load_cred_args
{
517 const ExecContext
*context
;
518 const ExecParameters
*params
;
528 static int load_cred_recurse_dir_cb(
529 RecurseDirEvent event
,
533 const struct dirent
*de
,
534 const struct statx
*sx
,
537 struct load_cred_args
*args
= ASSERT_PTR(userdata
);
538 _cleanup_free_
char *sub_id
= NULL
;
544 if (event
!= RECURSE_DIR_ENTRY
)
545 return RECURSE_DIR_CONTINUE
;
547 if (!IN_SET(de
->d_type
, DT_REG
, DT_SOCK
))
548 return RECURSE_DIR_CONTINUE
;
550 sub_id
= strreplace(path
, "/", "_");
554 if (!credential_name_valid(sub_id
))
555 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL
), "Credential would get ID %s, which is not valid, refusing", sub_id
);
557 if (faccessat(args
->dfd
, sub_id
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0) {
558 log_debug("Skipping credential with duplicated ID %s at %s", sub_id
, path
);
559 return RECURSE_DIR_CONTINUE
;
562 return log_debug_errno(errno
, "Failed to test if credential %s exists: %m", sub_id
);
580 return RECURSE_DIR_CONTINUE
;
583 static int acquire_credentials(
584 const ExecContext
*context
,
585 const ExecParameters
*params
,
592 uint64_t left
= CREDENTIALS_TOTAL_SIZE_MAX
;
593 _cleanup_close_
int dfd
= -EBADF
;
595 ExecLoadCredential
*lc
;
596 ExecSetCredential
*sc
;
604 dfd
= open(p
, O_DIRECTORY
|O_CLOEXEC
);
608 r
= fd_acl_make_writable(dfd
); /* Add the "w" bit, if we are reusing an already set up credentials dir where it was unset */
612 /* First, load credentials off disk (or acquire via AF_UNIX socket) */
613 HASHMAP_FOREACH(lc
, context
->load_credentials
) {
614 _cleanup_close_
int sub_fd
= -EBADF
;
616 /* If this is an absolute path, then try to open it as a directory. If that works, then we'll
617 * recurse into it. If it is an absolute path but it isn't a directory, then we'll open it as
618 * a regular file. Finally, if it's a relative path we will use it as a credential name to
619 * propagate a credential passed to us from further up. */
621 if (path_is_absolute(lc
->path
)) {
622 sub_fd
= open(lc
->path
, O_DIRECTORY
|O_CLOEXEC
|O_RDONLY
);
623 if (sub_fd
< 0 && !IN_SET(errno
,
624 ENOTDIR
, /* Not a directory */
625 ENOENT
)) /* Doesn't exist? */
626 return log_debug_errno(errno
, "Failed to open '%s': %m", lc
->path
);
630 /* Regular file (incl. a credential passed in from higher up) */
646 r
= recurse_dir(sub_fd
,
647 /* path= */ lc
->id
, /* recurse_dir() will suffix the subdir paths from here to the top-level id */
649 /* n_depth_max= */ UINT_MAX
,
650 RECURSE_DIR_SORT
|RECURSE_DIR_IGNORE_DOT
|RECURSE_DIR_ENSURE_TYPE
,
651 load_cred_recurse_dir_cb
,
652 &(struct load_cred_args
) {
655 .encrypted
= lc
->encrypted
,
660 .ownership_ok
= ownership_ok
,
667 /* Next, look for system credentials and credentials in the credentials store. Note that these do not
668 * override any credentials found earlier. */
669 SET_FOREACH(ic
, context
->import_credentials
) {
670 _cleanup_free_
char **search_path
= NULL
;
672 search_path
= credential_search_path(params
, CREDENTIAL_SEARCH_PATH_TRUSTED
);
676 r
= load_credential_glob(
678 /* encrypted = */ false,
680 READ_FULL_FILE_SECURE
|READ_FULL_FILE_FAIL_WHEN_LARGER
,
689 search_path
= strv_free(search_path
);
690 search_path
= credential_search_path(params
, CREDENTIAL_SEARCH_PATH_ENCRYPTED
);
694 r
= load_credential_glob(
696 /* encrypted = */ true,
698 READ_FULL_FILE_SECURE
|READ_FULL_FILE_FAIL_WHEN_LARGER
|READ_FULL_FILE_UNBASE64
,
708 /* Finally, we add in literally specified credentials. If the credentials already exist, we'll not
709 * add them, so that they can act as a "default" if the same credential is specified multiple times. */
710 HASHMAP_FOREACH(sc
, context
->set_credentials
) {
711 _cleanup_(iovec_done_erase
) struct iovec plaintext
= {};
715 /* Note that we check ahead of time here instead of relying on O_EXCL|O_CREAT later to return
716 * EEXIST if the credential already exists. That's because the TPM2-based decryption is kinda
717 * slow and involved, hence it's nice to be able to skip that if the credential already
719 if (faccessat(dfd
, sc
->id
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
722 return log_debug_errno(errno
, "Failed to test if credential %s exists: %m", sc
->id
);
725 r
= decrypt_credential_and_warn(
728 /* tpm2_device= */ NULL
,
729 /* tpm2_signature_path= */ NULL
,
731 &IOVEC_MAKE(sc
->data
, sc
->size
),
732 CREDENTIAL_ANY_SCOPE
,
737 data
= plaintext
.iov_base
;
738 size
= plaintext
.iov_len
;
744 add
= strlen(sc
->id
) + size
;
748 r
= write_credential(dfd
, sc
->id
, data
, size
, uid
, gid
, ownership_ok
);
755 r
= fd_acl_make_read_only(dfd
); /* Now take away the "w" bit */
759 /* After we created all keys with the right perms, also make sure the credential store as a whole is
762 if (uid_is_valid(uid
) && uid
!= getuid()) {
763 r
= fd_add_uid_acl_permission(dfd
, uid
, ACL_READ
| ACL_EXECUTE
);
765 if (!ERRNO_IS_NOT_SUPPORTED(r
) && !ERRNO_IS_PRIVILEGE(r
))
771 if (fchown(dfd
, uid
, gid
) < 0)
779 static int setup_credentials_internal(
780 const ExecContext
*context
,
781 const ExecParameters
*params
,
783 const char *final
, /* This is where the credential store shall eventually end up at */
784 const char *workspace
, /* This is where we can prepare it before moving it to the final place */
785 bool reuse_workspace
, /* Whether to reuse any existing workspace mount if it already is a mount */
786 bool must_mount
, /* Whether to require that we mount something, it's not OK to use the plain directory fall back */
791 int r
, workspace_mounted
; /* negative if we don't know yet whether we have/can mount something; true
792 * if we mounted something; false if we definitely can't mount anything */
800 r
= path_is_mount_point(final
);
803 final_mounted
= r
> 0;
806 if (FLAGS_SET(params
->flags
, EXEC_SETUP_CREDENTIALS_FRESH
)) {
807 r
= umount_verbose(LOG_DEBUG
, final
, MNT_DETACH
|UMOUNT_NOFOLLOW
);
811 final_mounted
= false;
813 /* We can reuse the previous credential dir */
814 r
= dir_is_empty(final
, /* ignore_hidden_or_backup = */ false);
818 log_debug("Credential dir for unit '%s' already set up, skipping.", unit
);
824 if (reuse_workspace
) {
825 r
= path_is_mount_point(workspace
);
829 workspace_mounted
= true; /* If this is already a mount, and we are supposed to reuse
830 * it, let's keep this in mind */
832 workspace_mounted
= -1; /* We need to figure out if we can mount something to the workspace */
834 workspace_mounted
= -1; /* ditto */
836 /* If both the final place and the workspace are mounted, we have no mounts to set up, based on
837 * the assumption that they're actually the same tmpfs (but the latter with MS_RDONLY different).
838 * If the workspace is not mounted, we just bind the final place over and make it writable. */
839 must_mount
= must_mount
|| final_mounted
;
841 if (workspace_mounted
< 0) {
843 /* Nothing is mounted on the workspace yet, let's try to mount a new tmpfs if
844 * not using the final place. */
845 r
= mount_credentials_fs(workspace
, CREDENTIALS_TOTAL_SIZE_MAX
, /* ro= */ false);
846 if (final_mounted
|| r
< 0) {
847 /* If using final place or failed to mount new tmpfs, make a bind mount from
848 * the final to the workspace, so that we can make it writable there. */
849 r
= mount_nofollow_verbose(LOG_DEBUG
, final
, workspace
, NULL
, MS_BIND
|MS_REC
, NULL
);
851 if (!ERRNO_IS_PRIVILEGE(r
))
852 /* Propagate anything that isn't a permission problem. */
856 /* If it's not OK to use the plain directory fallback, propagate all
860 /* If we lack privileges to bind mount stuff, then let's gracefully proceed
861 * for compat with container envs, and just use the final dir as is.
862 * Final place must not be mounted in this case (refused by must_mount
865 workspace_mounted
= false;
867 /* Make the new bind mount writable (i.e. drop MS_RDONLY) */
868 r
= mount_nofollow_verbose(LOG_DEBUG
,
872 MS_BIND
|MS_REMOUNT
|credentials_fs_mount_flags(/* ro= */ false),
877 workspace_mounted
= true;
880 workspace_mounted
= true;
883 assert(workspace_mounted
>= 0);
884 assert(!must_mount
|| workspace_mounted
);
886 const char *where
= workspace_mounted
? workspace
: final
;
888 (void) label_fix_full(AT_FDCWD
, where
, final
, 0);
890 r
= acquire_credentials(context
, params
, unit
, where
, uid
, gid
, workspace_mounted
);
892 /* If we're using final place as workspace, and failed to acquire credentials, we might
893 * have left half-written creds there. Let's get rid of the whole mount, so future
894 * calls won't reuse it. */
896 (void) umount_verbose(LOG_DEBUG
, final
, MNT_DETACH
|UMOUNT_NOFOLLOW
);
901 if (workspace_mounted
) {
902 if (!final_mounted
) {
903 /* Make workspace read-only now, so that any bind mount we make from it defaults to
905 r
= mount_nofollow_verbose(LOG_DEBUG
, NULL
, workspace
, NULL
, MS_BIND
|MS_REMOUNT
|credentials_fs_mount_flags(/* ro= */ true), NULL
);
909 /* And mount it to the final place, read-only */
910 r
= mount_nofollow_verbose(LOG_DEBUG
, workspace
, final
, NULL
, MS_MOVE
, NULL
);
912 /* Otherwise we just get rid of the bind mount of final place */
913 r
= umount_verbose(LOG_DEBUG
, workspace
, MNT_DETACH
|UMOUNT_NOFOLLOW
);
917 _cleanup_free_
char *parent
= NULL
;
919 /* If we do not have our own mount put used the plain directory fallback, then we need to
920 * open access to the top-level credential directory and the per-service directory now */
922 r
= path_extract_directory(final
, &parent
);
925 if (chmod(parent
, 0755) < 0)
932 int exec_setup_credentials(
933 const ExecContext
*context
,
934 const ExecParameters
*params
,
939 _cleanup_free_
char *p
= NULL
, *q
= NULL
;
946 if (!exec_params_need_credentials(params
) || !exec_context_has_credentials(context
))
949 if (!params
->prefix
[EXEC_DIRECTORY_RUNTIME
])
952 /* This where we'll place stuff when we are done; this main credentials directory is world-readable,
953 * and the subdir we mount over with a read-only file system readable by the service's user */
954 q
= path_join(params
->prefix
[EXEC_DIRECTORY_RUNTIME
], "credentials");
958 r
= mkdir_label(q
, 0755); /* top-level dir: world readable/searchable */
959 if (r
< 0 && r
!= -EEXIST
)
962 p
= path_join(q
, unit
);
966 r
= mkdir_label(p
, 0700); /* per-unit dir: private to user */
967 if (r
< 0 && r
!= -EEXIST
)
970 r
= safe_fork("(sd-mkdcreds)", FORK_DEATHSIG_SIGTERM
|FORK_WAIT
|FORK_NEW_MOUNTNS
, NULL
);
972 _cleanup_(rmdir_and_freep
) char *u
= NULL
; /* remove the temporary workspace if we can */
973 _cleanup_free_
char *t
= NULL
;
975 /* If this is not a privilege or support issue then propagate the error */
976 if (!ERRNO_IS_NOT_SUPPORTED(r
) && !ERRNO_IS_PRIVILEGE(r
))
979 /* Temporary workspace, that remains inaccessible all the time. We prepare stuff there before moving
980 * it into place, so that users can't access half-initialized credential stores. */
981 t
= path_join(params
->prefix
[EXEC_DIRECTORY_RUNTIME
], "systemd/temporary-credentials");
985 /* We can't set up a mount namespace. In that case operate on a fixed, inaccessible per-unit
986 * directory outside of /run/credentials/ first, and then move it over to /run/credentials/
987 * after it is fully set up */
988 u
= path_join(t
, unit
);
992 FOREACH_STRING(i
, t
, u
) {
993 r
= mkdir_label(i
, 0700);
994 if (r
< 0 && r
!= -EEXIST
)
998 r
= setup_credentials_internal(
1002 p
, /* final mount point */
1003 u
, /* temporary workspace to overmount */
1004 true, /* reuse the workspace if it is already a mount */
1005 false, /* it's OK to fall back to a plain directory if we can't mount anything */
1011 } else if (r
== 0) {
1013 /* We managed to set up a mount namespace, and are now in a child. That's great. In this case
1014 * we can use the same directory for all cases, after turning off propagation. Question
1015 * though is: where do we turn off propagation exactly, and where do we place the workspace
1016 * directory? We need some place that is guaranteed to be a mount point in the host, and
1017 * which is guaranteed to have a subdir we can mount over. /run/ is not suitable for this,
1018 * since we ultimately want to move the resulting file system there, i.e. we need propagation
1019 * for /run/ eventually. We could use our own /run/systemd/bind mount on itself, but that
1020 * would be visible in the host mount table all the time, which we want to avoid. Hence, what
1021 * we do here instead we use /dev/ and /dev/shm/ for our purposes. We know for sure that
1022 * /dev/ is a mount point and we now for sure that /dev/shm/ exists. Hence we can turn off
1023 * propagation on the former, and then overmount the latter.
1025 * Yes it's nasty playing games with /dev/ and /dev/shm/ like this, since it does not exist
1026 * for this purpose, but there are few other candidates that work equally well for us, and
1027 * given that we do this in a privately namespaced short-lived single-threaded process that
1028 * no one else sees this should be OK to do. */
1030 /* Turn off propagation from our namespace to host */
1031 r
= mount_nofollow_verbose(LOG_DEBUG
, NULL
, "/dev", NULL
, MS_SLAVE
|MS_REC
, NULL
);
1035 r
= setup_credentials_internal(
1039 p
, /* final mount point */
1040 "/dev/shm", /* temporary workspace to overmount */
1041 false, /* do not reuse /dev/shm if it is already a mount, under no circumstances */
1042 true, /* insist that something is mounted, do not allow fallback to plain directory */
1048 _exit(EXIT_SUCCESS
);
1051 _exit(EXIT_FAILURE
);
1054 /* If the credentials dir is empty and not a mount point, then there's no point in having it. Let's
1055 * try to remove it. This matters in particular if we created the dir as mount point but then didn't
1056 * actually end up mounting anything on it. In that case we'd rather have ENOENT than EACCESS being
1057 * seen by users when trying access this inode. */