core: make DynamicUser=1 and StateDirectory= work with TemporaryFileSystem=/var/lib

The /var/lib/private/foo -> /var/lib/foo symlink for StateDirectory and
DynamicUser is set up on the host filesystem, before the mount namespacing
is brought up. If an empty /var/lib is used, to ensure the service does not
see other services data, the symlink is then not available despite
/var/lib/private being set up as expected.

Make a list of symlinks that need to be set up, and create them after all
the namespaced filesystems have been created, but before any eventual
read-only switch is flipped.
This commit is contained in:
Luca Boccassi
2021-07-27 16:41:38 +01:00
parent d5bb2b0375
commit df61e79a5d
6 changed files with 95 additions and 3 deletions

View File

@@ -2384,7 +2384,9 @@ static int setup_exec_directory(
goto fail;
}
/* And link it up from the original place */
/* And link it up from the original place. Note that if a mount namespace is going to be
* used, then this symlink remains on the host, and a new one for the child namespace will
* be created later. */
r = symlink_idempotent(pp, p, true);
if (r < 0)
goto fail;
@@ -3148,6 +3150,49 @@ finish:
return r;
}
/* ret_symlinks will contain a list of pairs src:dest that describes
* the symlinks to create later on. For example, the symlinks needed
* to safely give private directories to DynamicUser=1 users. */
static int compile_symlinks(
const ExecContext *context,
const ExecParameters *params,
char ***ret_symlinks) {
_cleanup_strv_free_ char **symlinks = NULL;
int r;
assert(context);
assert(params);
assert(ret_symlinks);
for (ExecDirectoryType dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++) {
char **src;
if (!exec_directory_is_private(context, dt))
continue;
STRV_FOREACH(src, context->directories[dt].paths) {
_cleanup_free_ char *private_path = NULL, *path = NULL;
private_path = path_join(params->prefix[dt], "private", *src);
if (!private_path)
return -ENOMEM;
path = path_join(params->prefix[dt], *src);
if (!path)
return -ENOMEM;
r = strv_consume_pair(&symlinks, TAKE_PTR(private_path), TAKE_PTR(path));
if (r < 0)
return r;
}
}
*ret_symlinks = TAKE_PTR(symlinks);
return 0;
}
static bool insist_on_sandboxing(
const ExecContext *context,
const char *root_dir,
@@ -3194,7 +3239,7 @@ static int apply_mount_namespace(
const ExecRuntime *runtime,
char **error_path) {
_cleanup_strv_free_ char **empty_directories = NULL;
_cleanup_strv_free_ char **empty_directories = NULL, **symlinks = NULL;
const char *tmp_dir = NULL, *var_tmp_dir = NULL;
const char *root_dir = NULL, *root_image = NULL;
_cleanup_free_ char *creds_path = NULL, *incoming_dir = NULL, *propagate_dir = NULL;
@@ -3217,6 +3262,12 @@ static int apply_mount_namespace(
if (r < 0)
return r;
/* Symlinks for exec dirs are set up after other mounts, before they are
* made read-only. */
r = compile_symlinks(context, params, &symlinks);
if (r < 0)
return r;
needs_sandboxing = (params->flags & EXEC_APPLY_SANDBOXING) && !(command_flags & EXEC_COMMAND_FULLY_PRIVILEGED);
if (needs_sandboxing) {
/* The runtime struct only contains the parent of the private /tmp,
@@ -3300,6 +3351,7 @@ static int apply_mount_namespace(
needs_sandboxing ? context->exec_paths : NULL,
needs_sandboxing ? context->no_exec_paths : NULL,
empty_directories,
symlinks,
bind_mounts,
n_bind_mounts,
context->temporary_filesystems,

View File

@@ -1586,11 +1586,36 @@ static void normalize_mounts(const char *root_directory, MountEntry *mounts, siz
drop_nop(mounts, n_mounts);
}
static int create_symlinks_from_tuples(const char *root, char **strv_symlinks) {
char **src, **dst;
int r;
STRV_FOREACH_PAIR(src, dst, strv_symlinks) {
_cleanup_free_ char *src_abs = NULL, *dst_abs = NULL;
src_abs = path_join(root, *src);
dst_abs = path_join(root, *dst);
if (!src_abs || !dst_abs)
return -ENOMEM;
r = mkdir_parents_label(dst_abs, 0755);
if (r < 0)
return r;
r = symlink_idempotent(src_abs, dst_abs, true);
if (r < 0)
return r;
}
return 0;
}
static int apply_mounts(
const char *root,
const NamespaceInfo *ns_info,
MountEntry *mounts,
size_t *n_mounts,
char **exec_dir_symlinks,
char **error_path) {
_cleanup_fclose_ FILE *proc_self_mountinfo = NULL;
@@ -1657,6 +1682,14 @@ static int apply_mounts(
normalize_mounts(root, mounts, n_mounts);
}
/* Now that all filesystems have been set up, but before the
* read-only switches are flipped, create the exec dirs symlinks.
* Note that when /var/lib is not empty/tmpfs, these symlinks will already
* exist, which means this will be a no-op. */
r = create_symlinks_from_tuples(root, exec_dir_symlinks);
if (r < 0)
return r;
/* Create a deny list we can pass to bind_mount_recursive() */
deny_list = new(char*, (*n_mounts)+1);
if (!deny_list)
@@ -1820,6 +1853,7 @@ int setup_namespace(
char** exec_paths,
char** no_exec_paths,
char** empty_directories,
char** exec_dir_symlinks,
const BindMount *bind_mounts,
size_t n_bind_mounts,
const TemporaryFileSystem *temporary_filesystems,
@@ -2261,7 +2295,7 @@ int setup_namespace(
(void) base_filesystem_create(root, UID_INVALID, GID_INVALID);
/* Now make the magic happen */
r = apply_mounts(root, ns_info, mounts, &n_mounts, error_path);
r = apply_mounts(root, ns_info, mounts, &n_mounts, exec_dir_symlinks, error_path);
if (r < 0)
goto finish;

View File

@@ -121,6 +121,7 @@ int setup_namespace(
char **exec_paths,
char **no_exec_paths,
char **empty_directories,
char **exec_dir_symlinks,
const BindMount *bind_mounts,
size_t n_bind_mounts,
const TemporaryFileSystem *temporary_filesystems,

View File

@@ -166,6 +166,7 @@ static void test_protect_kernel_logs(void) {
NULL,
NULL,
NULL,
NULL,
NULL, 0,
NULL, 0,
NULL, 0,

View File

@@ -83,6 +83,7 @@ int main(int argc, char *argv[]) {
(char **) writable,
(char **) readonly,
(char **) inaccessible,
NULL,
(char **) exec,
(char **) no_exec,
NULL,

View File

@@ -10,6 +10,7 @@ systemd-analyze log-target console
systemd-run --wait -p DynamicUser=0 -p StateDirectory=zzz touch /var/lib/zzz/test
systemd-run --wait -p DynamicUser=0 -p StateDirectory=zzz test -f /var/lib/zzz/test
systemd-run --wait -p DynamicUser=0 -p StateDirectory=zzz -p TemporaryFileSystem=/var/lib test -f /var/lib/zzz/test
systemd-run --wait -p DynamicUser=0 -p StateDirectory=zzz test -f /var/lib/zzz/test-missing \
&& { echo 'unexpected success'; exit 1; }
@@ -22,6 +23,7 @@ test ! -f /var/lib/zzz/test-missing
# Convert to DynamicUser=1
systemd-run --wait -p DynamicUser=1 -p StateDirectory=zzz test -f /var/lib/zzz/test
systemd-run --wait -p DynamicUser=1 -p StateDirectory=zzz -p TemporaryFileSystem=/var/lib test -f /var/lib/zzz/test
systemd-run --wait -p DynamicUser=1 -p StateDirectory=zzz test -f /var/lib/zzz/test-missing \
&& { echo 'unexpected success'; exit 1; }
@@ -34,6 +36,7 @@ test ! -f /var/lib/zzz/test-missing
# Convert back
systemd-run --wait -p DynamicUser=0 -p StateDirectory=zzz test -f /var/lib/zzz/test
systemd-run --wait -p DynamicUser=0 -p StateDirectory=zzz -p TemporaryFileSystem=/var/lib test -f /var/lib/zzz/test
systemd-run --wait -p DynamicUser=0 -p StateDirectory=zzz test -f /var/lib/zzz/test-missing \
&& { echo 'unexpected success'; exit 1; }