diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index b350c19dcb..62c59308ef 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -61,13 +61,18 @@ paths. This is equivalent to having them listed explicitly in RequiresMountsFor=. - Similarly, units with PrivateTmp= enabled automatically get mount - unit dependencies for all mounts required to access /tmp/ and - /var/tmp/. They will also gain an automatic After= dependency - on + Units with PrivateTmp=yes automatically gain dependencies of type + Wants= and After= on all mounts required to access + /tmp/ and /var/tmp/. They will also gain an automatic + After= dependency on systemd-tmpfiles-setup.service8. + Units with PrivateTmp=disconnected automatically gain dependencies + of type Wants= and After= on the mount required to access + /var/, unless DefaultDependencies=no and/or + RootDirectory=/RootImage= are specified. + Units whose standard output or error output is connected to or (or their combinations with console output, see below) automatically acquire dependencies of type After= on @@ -1797,28 +1802,89 @@ BindReadOnlyPaths=/var/lib/systemd Takes a boolean argument, or disconnected. If enabled, a new file system namespace will be set up for the executed processes, and /tmp/ and /var/tmp/ directories inside it are not shared with processes outside of - the namespace, plus all temporary files created by a service in these directories will be removed after - the service is stopped. If true, the backing storage of the private temporary directories - will remain on the host's /tmp/ and /var/tmp/ directories. - If disconnected, the directories will be backed by a completely new tmpfs instance, - meaning that the storage is fully disconnected from the host namespace. Defaults to false. + the namespace, plus all temporary files created by a service in these directories will be removed + after the service is stopped. For this setting, the same restrictions regarding mount propagation + and privileges apply as for ReadOnlyPaths= and related calls, see above. This + setting is useful to secure access to temporary files of the process, but makes sharing between + processes via /tmp/ or /var/tmp/ impossible. If + DynamicUser= is enabled, disconnected is implied. Otherwise, + defaults to false. - This setting is useful to secure access to temporary files of the process, but makes sharing - between processes via /tmp/ or /var/tmp/ impossible. - If not set to disconnected, it is possible to run two or more units within - the same private /tmp/ and /var/tmp/ namespace by using - the JoinsNamespaceOf= directive, see - systemd.unit5 - for details. This setting is implied if DynamicUser= is set. For this setting, - the same restrictions regarding mount propagation and privileges apply as for - ReadOnlyPaths= and related calls, see above. If set to true - (as opposed to disconnected), this has the side effect of adding - Requires= and After= dependencies on all mount units necessary - to access /tmp/ and /var/tmp/ on the host. Moreover, an - implicitly After= ordering on + If true, the backing storage of the private temporary directories will + remain on the host's /tmp/ and /var/tmp/ directories. It is + possible to run two or more units within the same private /tmp/ and + /var/tmp/ namespace by using the JoinsNamespaceOf= directive, + see systemd.unit5 + for details. This has the side effect of adding Wants= and + After= dependencies on all mount units necessary to access + /tmp/ and /var/tmp/ on the host. Moreover, an implicit + After= ordering on systemd-tmpfiles-setup.service8 is added. + If disconnected, the directories will be backed by a completely new tmpfs + instance, meaning that the storage is fully disconnected from the host namespace. The tmpfs instance + does not shared with other units even if the JoinsNamespaceOf= directive is used. + If DefaultDependencies=no is specified, + RequiresMountsFor=/WantsMountsFor= for /var/ is not + specified, and RootDirectory=/RootImage= is not specified, then a new tmpfs is + mounted only on /tmp/, hence the host's + /var/tmp is still accessible from the unit. In that case, + $TMPDIR environment variable is set to /tmp to suggest the + processes in the unit to use /tmp/. This automatically adds + WantsMountsFor=/var/ dependency, unless DefaultDependencies=no + and/or RootDirectory=/RootImage= are specified. + + + Summary for <varname>PrivateTmp=disconnected</varname> + + + + + + + + Other Settings + tmpfs on /var/tmp/ + $TMPVAR + implied dependencies + + + + + (none) + yes + (unset) + WantsMountsFor=/var/ + + + RootDirectory=/RootImage= + yes + (unset) + (none) + + + DefaultDependency=no, RequiresMountsFor=/var/ + yes + (unset) + (none) + + + DefaultDependency=no, WantsMountsFor=/var/ + yes + (unset) + (none) + + + DefaultDependency=no + no + $TMPDIR=/tmp + (none) + + + +
+ Note that the implementation of this setting might be impossible (for example if mount namespaces are not available), and the unit should be written in a way that does not solely rely on this setting for security. @@ -3944,6 +4010,18 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX
+ + $TMPDIR + + The environment variable is set to /tmp when + PrivateTmp=disconnected specified with DefaultDependencies=no + and without RootDirectory=/RootImage= and + RequiresMountsFor=/WantsMountsFor= for /var/. + See explanation for PrivateTmp= above. + + + + $MAINPID diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index b0bf502576..77fc647f7b 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -1921,7 +1921,7 @@ static int build_environment( assert(cgroup_context); assert(ret); -#define N_ENV_VARS 21 +#define N_ENV_VARS 22 our_env = new0(char*, N_ENV_VARS + _EXEC_DIRECTORY_TYPE_MAX); if (!our_env) return -ENOMEM; @@ -2181,6 +2181,21 @@ static int build_environment( our_env[n_env++] = x; } + assert(c->private_var_tmp >= 0 && c->private_var_tmp < _PRIVATE_TMP_MAX); + if (c->private_tmp != c->private_var_tmp) { + assert(c->private_tmp == PRIVATE_TMP_DISCONNECTED); + assert(c->private_var_tmp == PRIVATE_TMP_NO); + + /* When private tmpfs is enabled only on /tmp/, then explicitly set $TMPDIR to suggest the + * service to use /tmp/. */ + + x = strdup("TMPDIR=/tmp"); + if (!x) + return -ENOMEM; + + our_env[n_env++] = x; + } + assert(n_env < N_ENV_VARS + _EXEC_DIRECTORY_TYPE_MAX); #undef N_ENV_VARS @@ -3589,6 +3604,7 @@ static int apply_mount_namespace( .private_ipc = needs_sandboxing && exec_needs_ipc_namespace(context), .private_pids = needs_sandboxing && exec_needs_pid_namespace(context) ? context->private_pids : PRIVATE_PIDS_NO, .private_tmp = needs_sandboxing ? context->private_tmp : PRIVATE_TMP_NO, + .private_var_tmp = needs_sandboxing ? context->private_var_tmp : PRIVATE_TMP_NO, .mount_apivfs = needs_sandboxing && exec_context_get_effective_mount_apivfs(context), .bind_log_sockets = needs_sandboxing && exec_context_get_effective_bind_log_sockets(context), diff --git a/src/core/execute-serialize.c b/src/core/execute-serialize.c index 0b71512653..18ec06b6a4 100644 --- a/src/core/execute-serialize.c +++ b/src/core/execute-serialize.c @@ -1735,6 +1735,12 @@ static int exec_context_serialize(const ExecContext *c, FILE *f) { if (r < 0) return r; + /* This must be set in unit_patch_contexts() before executing a command. */ + assert(c->private_var_tmp >= 0 && c->private_var_tmp < _PRIVATE_TMP_MAX); + r = serialize_item(f, "exec-context-private-var-tmp", private_tmp_to_string(c->private_var_tmp)); + if (r < 0) + return r; + r = serialize_bool_elide(f, "exec-context-private-devices", c->private_devices); if (r < 0) return r; @@ -2628,6 +2634,10 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { c->private_tmp = private_tmp_from_string(val); if (c->private_tmp < 0) return c->private_tmp; + } else if ((val = startswith(l, "exec-context-private-var-tmp="))) { + c->private_var_tmp = private_tmp_from_string(val); + if (c->private_var_tmp < 0) + return c->private_var_tmp; } else if ((val = startswith(l, "exec-context-private-devices="))) { r = parse_boolean(val); if (r < 0) diff --git a/src/core/execute.c b/src/core/execute.c index 4aef0ad819..417d0be575 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -635,6 +635,7 @@ void exec_context_init(ExecContext *c) { .mount_apivfs = -1, .bind_log_sockets = -1, .memory_ksm = -1, + .private_var_tmp = _PRIVATE_TMP_INVALID, .set_login_environment = -1, }; diff --git a/src/core/execute.h b/src/core/execute.h index c8e78bee24..756d78117c 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -330,6 +330,8 @@ struct ExecContext { int bind_log_sockets; int memory_ksm; PrivateTmp private_tmp; + PrivateTmp private_var_tmp; /* This is not an independent parameter, but calculated from other + * parameters in unit_patch_contexts(). */ bool private_network; bool private_devices; PrivateUsers private_users; diff --git a/src/core/fuzz-execute-serialize.c b/src/core/fuzz-execute-serialize.c index 67437549ea..48ddea10d7 100644 --- a/src/core/fuzz-execute-serialize.c +++ b/src/core/fuzz-execute-serialize.c @@ -43,6 +43,7 @@ static void exec_fuzz_one(FILE *f, FDSet *fdset) { }; exec_context_init(&exec_context); + exec_context.private_var_tmp = PRIVATE_TMP_DISCONNECTED; cgroup_context_init(&cgroup_context); (void) exec_deserialize_invocation(f, fdset, &exec_context, &command, ¶ms, &runtime, &cgroup_context); diff --git a/src/core/mount.c b/src/core/mount.c index c496375a21..d7211de024 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -1795,10 +1795,6 @@ static int mount_setup_new_unit( * attributes the deps to. */ mnt->from_proc_self_mountinfo = true; - r = mount_add_non_exec_dependencies(mnt); - if (r < 0) - return r; - /* We have only allocated the stub now, let's enqueue this unit for loading now, so that everything * else is loaded in now. */ unit_add_to_load_queue(u); @@ -1855,16 +1851,18 @@ static int mount_setup_existing_unit( if (UNIT_IS_LOAD_ERROR(u->load_state)) { /* The unit was previously not found or otherwise not loaded. Now that the unit shows up in - * /proc/self/mountinfo we should reconsider it this, hence set it to UNIT_LOADED. */ - u->load_state = UNIT_LOADED; + * /proc/self/mountinfo we should reconsider that. Hence, let's reset the load state and load + * error, and add the unit to load queue. */ + u->load_state = UNIT_STUB; u->load_error = 0; + unit_add_to_load_queue(u); flags |= MOUNT_PROC_JUST_CHANGED; } - if (FLAGS_SET(flags, MOUNT_PROC_JUST_CHANGED)) { - /* If things changed, then make sure that all deps are regenerated. Let's - * first remove all automatic deps, and then add in the new ones. */ + if (FLAGS_SET(flags, MOUNT_PROC_JUST_CHANGED) && u->load_state == UNIT_LOADED) { + /* If things changed, and we have successfully loaded the unit, then make sure that all deps + * are regenerated. Let's first remove all automatic deps, and then add in the new ones. */ r = mount_add_non_exec_dependencies(m); if (r < 0) return r; diff --git a/src/core/namespace.c b/src/core/namespace.c index 381de7528a..d80fe74afb 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -731,6 +731,102 @@ static int append_tmpfs_mounts(MountList *ml, const TemporaryFileSystem *tmpfs, return 0; } +static int append_private_tmp(MountList *ml, const NamespaceParameters *p) { + MountEntry *me; + + assert(ml); + assert(p); + assert(p->private_tmp == p->private_var_tmp || + (p->private_tmp == PRIVATE_TMP_DISCONNECTED && p->private_var_tmp == PRIVATE_TMP_NO)); + + if (p->tmp_dir) { + assert(p->private_tmp == PRIVATE_TMP_CONNECTED); + + me = mount_list_extend(ml); + if (!me) + return log_oom_debug(); + *me = (MountEntry) { + .path_const = "/tmp/", + .mode = MOUNT_PRIVATE_TMP, + .read_only = streq(p->tmp_dir, RUN_SYSTEMD_EMPTY), + .source_const = p->tmp_dir, + }; + } + + if (p->var_tmp_dir) { + assert(p->private_var_tmp == PRIVATE_TMP_CONNECTED); + + me = mount_list_extend(ml); + if (!me) + return log_oom_debug(); + *me = (MountEntry) { + .path_const = "/var/tmp/", + .mode = MOUNT_PRIVATE_TMP, + .read_only = streq(p->var_tmp_dir, RUN_SYSTEMD_EMPTY), + .source_const = p->var_tmp_dir, + }; + } + + if (p->private_tmp != PRIVATE_TMP_DISCONNECTED) + return 0; + + if (p->private_var_tmp == PRIVATE_TMP_NO) { + me = mount_list_extend(ml); + if (!me) + return log_oom_debug(); + *me = (MountEntry) { + .path_const = "/tmp/", + .mode = MOUNT_PRIVATE_TMPFS, + .options_const = "mode=0700" NESTED_TMPFS_LIMITS, + .flags = MS_NODEV|MS_STRICTATIME, + }; + + return 0; + } + + _cleanup_free_ char *tmpfs_dir = NULL, *tmp_dir = NULL, *var_tmp_dir = NULL; + tmpfs_dir = path_join(p->private_namespace_dir, "unit-private-tmp"); + tmp_dir = path_join(tmpfs_dir, "tmp"); + var_tmp_dir = path_join(tmpfs_dir, "var-tmp"); + if (!tmpfs_dir || !tmp_dir || !var_tmp_dir) + return log_oom_debug(); + + me = mount_list_extend(ml); + if (!me) + return log_oom_debug(); + *me = (MountEntry) { + .path_malloc = TAKE_PTR(tmpfs_dir), + .mode = MOUNT_PRIVATE_TMPFS, + .options_const = "mode=0700" NESTED_TMPFS_LIMITS, + .flags = MS_NODEV|MS_STRICTATIME, + .has_prefix = true, + }; + + me = mount_list_extend(ml); + if (!me) + return log_oom_debug(); + *me = (MountEntry) { + .source_malloc = TAKE_PTR(tmp_dir), + .path_const = "/tmp/", + .mode = MOUNT_BIND, + .source_dir_mode = 01777, + .create_source_dir = true, + }; + + me = mount_list_extend(ml); + if (!me) + return log_oom_debug(); + *me = (MountEntry) { + .source_malloc = TAKE_PTR(var_tmp_dir), + .path_const = "/var/tmp/", + .mode = MOUNT_BIND, + .source_dir_mode = 01777, + .create_source_dir = true, + }; + + return 0; +} + static int append_static_mounts(MountList *ml, const MountEntry *mounts, size_t n, bool ignore_protect) { assert(ml); assert(mounts || n == 0); @@ -1907,6 +2003,11 @@ static int apply_one_mount( return 1; } +static bool should_propagate_to_submounts(const MountEntry *m) { + assert(m); + return !IN_SET(m->mode, MOUNT_EMPTY_DIR, MOUNT_TMPFS, MOUNT_PRIVATE_TMPFS); +} + static int make_read_only(const MountEntry *m, char **deny_list, FILE *proc_self_mountinfo) { unsigned long new_flags = 0, flags_mask = 0; bool submounts; @@ -1935,9 +2036,7 @@ static int make_read_only(const MountEntry *m, char **deny_list, FILE *proc_self * nothing further down. Set /dev readonly, but not submounts like /dev/shm. Also, we only set the * per-mount read-only flag. We can't set it on the superblock, if we are inside a user namespace * and running Linux <= 4.17. */ - submounts = - mount_entry_read_only(m) && - !IN_SET(m->mode, MOUNT_EMPTY_DIR, MOUNT_TMPFS, MOUNT_PRIVATE_TMPFS); + submounts = mount_entry_read_only(m) && should_propagate_to_submounts(m); if (submounts) r = bind_remount_recursive_with_mountinfo(mount_entry_path(m), new_flags, flags_mask, deny_list, proc_self_mountinfo); else @@ -1977,8 +2076,7 @@ static int make_noexec(const MountEntry *m, char **deny_list, FILE *proc_self_mo if (flags_mask == 0) /* No Change? */ return 0; - submounts = !IN_SET(m->mode, MOUNT_EMPTY_DIR, MOUNT_TMPFS, MOUNT_PRIVATE_TMPFS); - + submounts = should_propagate_to_submounts(m); if (submounts) r = bind_remount_recursive_with_mountinfo(mount_entry_path(m), new_flags, flags_mask, deny_list, proc_self_mountinfo); else @@ -2002,7 +2100,7 @@ static int make_nosuid(const MountEntry *m, FILE *proc_self_mountinfo) { if (m->state != MOUNT_APPLIED) return 0; - submounts = !IN_SET(m->mode, MOUNT_EMPTY_DIR, MOUNT_TMPFS, MOUNT_PRIVATE_TMPFS); + submounts = should_propagate_to_submounts(m); if (submounts) r = bind_remount_recursive_with_mountinfo(mount_entry_path(m), MS_NOSUID, MS_NOSUID, NULL, proc_self_mountinfo); else @@ -2450,80 +2548,9 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path) { if (r < 0) return r; - /* When DynamicUser=yes enforce that /tmp/ and /var/tmp/ are disconnected from the host's - * directories, as they are world writable and ephemeral uid/gid will be used. */ - if (p->private_tmp == PRIVATE_TMP_DISCONNECTED) { - _cleanup_free_ char *tmpfs_dir = NULL, *tmp_dir = NULL, *var_tmp_dir = NULL; - MountEntry *tmpfs_entry, *tmp_entry, *var_tmp_entry; - - tmpfs_dir = path_join(p->private_namespace_dir, "unit-private-tmp"); - tmp_dir = path_join(tmpfs_dir, "tmp"); - var_tmp_dir = path_join(tmpfs_dir, "var-tmp"); - if (!tmpfs_dir || !tmp_dir || !var_tmp_dir) - return log_oom_debug(); - - tmpfs_entry = mount_list_extend(&ml); - if (!tmpfs_entry) - return log_oom_debug(); - *tmpfs_entry = (MountEntry) { - .path_malloc = TAKE_PTR(tmpfs_dir), - .mode = MOUNT_PRIVATE_TMPFS, - .options_const = "mode=0700" NESTED_TMPFS_LIMITS, - .flags = MS_NODEV|MS_STRICTATIME, - .has_prefix = true, - }; - - tmp_entry = mount_list_extend(&ml); - if (!tmp_entry) - return log_oom_debug(); - *tmp_entry = (MountEntry) { - .source_malloc = TAKE_PTR(tmp_dir), - .path_const = "/tmp", - .mode = MOUNT_BIND, - .source_dir_mode = 01777, - .create_source_dir = true, - }; - - var_tmp_entry = mount_list_extend(&ml); - if (!var_tmp_entry) - return log_oom_debug(); - *var_tmp_entry = (MountEntry) { - .source_malloc = TAKE_PTR(var_tmp_dir), - .path_const = "/var/tmp", - .mode = MOUNT_BIND, - .source_dir_mode = 01777, - .create_source_dir = true, - }; - - } else if (p->tmp_dir || p->var_tmp_dir) { - assert(p->private_tmp == PRIVATE_TMP_CONNECTED); - - if (p->tmp_dir) { - MountEntry *me = mount_list_extend(&ml); - if (!me) - return log_oom_debug(); - - *me = (MountEntry) { - .path_const = "/tmp", - .mode = MOUNT_PRIVATE_TMP, - .read_only = streq(p->tmp_dir, RUN_SYSTEMD_EMPTY), - .source_const = p->tmp_dir, - }; - } - - if (p->var_tmp_dir) { - MountEntry *me = mount_list_extend(&ml); - if (!me) - return log_oom_debug(); - - *me = (MountEntry) { - .path_const = "/var/tmp", - .mode = MOUNT_PRIVATE_TMP, - .read_only = streq(p->var_tmp_dir, RUN_SYSTEMD_EMPTY), - .source_const = p->var_tmp_dir, - }; - } - } + r = append_private_tmp(&ml, p); + if (r < 0) + return r; r = append_mount_images(&ml, p->mount_images, p->n_mount_images); if (r < 0) diff --git a/src/core/namespace.h b/src/core/namespace.h index 0ff9b4d730..f1076d0ee4 100644 --- a/src/core/namespace.h +++ b/src/core/namespace.h @@ -199,6 +199,7 @@ struct NamespaceParameters { ProtectProc protect_proc; ProcSubset proc_subset; PrivateTmp private_tmp; + PrivateTmp private_var_tmp; PrivatePIDs private_pids; }; diff --git a/src/core/swap.c b/src/core/swap.c index 6d505c2acf..668e1bcf6c 100644 --- a/src/core/swap.c +++ b/src/core/swap.c @@ -406,8 +406,6 @@ static int swap_setup_unit( s->what = strdup(what); if (!s->what) return log_oom(); - - unit_add_to_load_queue(u); } SwapParameters *p = &s->parameters_proc_swaps; @@ -418,13 +416,15 @@ static int swap_setup_unit( return log_oom(); } - /* The unit is definitely around now, mark it as loaded if it was previously referenced but - * could not be loaded. After all we can load it now, from the data in /proc/swaps. */ + /* The unit is definitely around now. When we previously fail to load the unit, let's reload the unit + * by resetting the load state and load error, and adding the unit to the load queue. */ if (UNIT_IS_LOAD_ERROR(u->load_state)) { - u->load_state = UNIT_LOADED; + u->load_state = UNIT_STUB; u->load_error = 0; } + unit_add_to_load_queue(u); + if (set_flags) { s->is_active = true; s->just_activated = !s->from_proc_swaps; diff --git a/src/core/unit.c b/src/core/unit.c index c706caae08..7e3eaa4c92 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -1270,18 +1270,30 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) { return r; } + /* This must be already set in unit_patch_contexts(). */ + assert(c->private_var_tmp >= 0 && c->private_var_tmp < _PRIVATE_TMP_MAX); + if (c->private_tmp == PRIVATE_TMP_CONNECTED) { - r = unit_add_mounts_for(u, "/tmp", UNIT_DEPENDENCY_FILE, UNIT_MOUNT_WANTS); + assert(c->private_var_tmp == PRIVATE_TMP_CONNECTED); + + r = unit_add_mounts_for(u, "/tmp/", UNIT_DEPENDENCY_FILE, UNIT_MOUNT_WANTS); if (r < 0) return r; - r = unit_add_mounts_for(u, "/var/tmp", UNIT_DEPENDENCY_FILE, UNIT_MOUNT_WANTS); + r = unit_add_mounts_for(u, "/var/tmp/", UNIT_DEPENDENCY_FILE, UNIT_MOUNT_WANTS); if (r < 0) return r; r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_TMPFILES_SETUP_SERVICE, true, UNIT_DEPENDENCY_FILE); if (r < 0) return r; + + } else if (c->private_var_tmp == PRIVATE_TMP_DISCONNECTED && !exec_context_with_rootfs(c)) { + /* Even if PrivateTmp=disconnected, we still require /var/tmp/ mountpoint to be present, + * i.e. /var/ needs to be mounted. See comments in unit_patch_contexts(). */ + r = unit_add_mounts_for(u, "/var/", UNIT_DEPENDENCY_FILE, UNIT_MOUNT_WANTS); + if (r < 0) + return r; } if (c->root_image) { @@ -1293,18 +1305,8 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) { return r; } - if (!IN_SET(c->std_output, - EXEC_OUTPUT_JOURNAL, EXEC_OUTPUT_JOURNAL_AND_CONSOLE, - EXEC_OUTPUT_KMSG, EXEC_OUTPUT_KMSG_AND_CONSOLE) && - !IN_SET(c->std_error, - EXEC_OUTPUT_JOURNAL, EXEC_OUTPUT_JOURNAL_AND_CONSOLE, - EXEC_OUTPUT_KMSG, EXEC_OUTPUT_KMSG_AND_CONSOLE) && - !c->log_namespace) - return 0; - /* If syslog or kernel logging is requested (or log namespacing is), make sure our own logging daemon * is run first. */ - if (c->log_namespace) { static const struct { const char *template; @@ -1326,7 +1328,11 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) { if (r < 0) return r; } - } else { + } else if (IN_SET(c->std_output, EXEC_OUTPUT_JOURNAL, EXEC_OUTPUT_JOURNAL_AND_CONSOLE, + EXEC_OUTPUT_KMSG, EXEC_OUTPUT_KMSG_AND_CONSOLE) || + IN_SET(c->std_error, EXEC_OUTPUT_JOURNAL, EXEC_OUTPUT_JOURNAL_AND_CONSOLE, + EXEC_OUTPUT_KMSG, EXEC_OUTPUT_KMSG_AND_CONSOLE)) { + r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_JOURNALD_SOCKET, true, UNIT_DEPENDENCY_FILE); if (r < 0) return r; @@ -4256,6 +4262,51 @@ static int unit_verify_contexts(const Unit *u) { return 0; } +static PrivateTmp unit_get_private_var_tmp(const Unit *u, const ExecContext *c) { + assert(u); + assert(c); + assert(c->private_tmp >= 0 && c->private_tmp < _PRIVATE_TMP_MAX); + + /* Disable disconnected private tmpfs on /var/tmp/ when DefaultDependencies=no and + * RootImage=/RootDirectory= are not set, as /var/ may be a separated partition. + * See issue #37258. */ + + /* PrivateTmp=yes/no also enables/disables private tmpfs on /var/tmp/. */ + if (c->private_tmp != PRIVATE_TMP_DISCONNECTED) + return c->private_tmp; + + /* When DefaultDependencies=yes, disconnected tmpfs is also enabled on /var/tmp/, and an explicit + * dependency to the mount on /var/ will be added in unit_add_exec_dependencies(). */ + if (u->default_dependencies) + return PRIVATE_TMP_DISCONNECTED; + + /* When RootImage=/RootDirectory= is enabled, /var/ should be prepared by the image or directory, + * hence we can mount a disconnected tmpfs on /var/tmp/. */ + if (exec_context_with_rootfs(c)) + return PRIVATE_TMP_DISCONNECTED; + + /* Even if DefaultDependencies=no, enable disconnected tmpfs when + * RequiresMountsFor=/WantsMountsFor=/var/ is explicitly set. */ + for (UnitMountDependencyType t = 0; t < _UNIT_MOUNT_DEPENDENCY_TYPE_MAX; t++) + if (hashmap_contains(u->mounts_for[t], "/var/")) + return PRIVATE_TMP_DISCONNECTED; + + /* Check the same but for After= with Requires=/Requisite=/Wants= or friends. */ + Unit *m = manager_get_unit(u->manager, "var.mount"); + if (!m) + return PRIVATE_TMP_NO; + + if (!unit_has_dependency(u, UNIT_ATOM_AFTER, m)) + return PRIVATE_TMP_NO; + + if (unit_has_dependency(u, UNIT_ATOM_PULL_IN_START, m) || + unit_has_dependency(u, UNIT_ATOM_PULL_IN_VERIFY, m) || + unit_has_dependency(u, UNIT_ATOM_PULL_IN_START_IGNORED, m)) + return PRIVATE_TMP_DISCONNECTED; + + return PRIVATE_TMP_NO; +} + int unit_patch_contexts(Unit *u) { CGroupContext *cc; ExecContext *ec; @@ -4332,6 +4383,8 @@ int unit_patch_contexts(Unit *u) { ec->restrict_suid_sgid = true; } + ec->private_var_tmp = unit_get_private_var_tmp(u, ec); + FOREACH_ARRAY(d, ec->directories, _EXEC_DIRECTORY_TYPE_MAX) exec_directory_sort(d); } diff --git a/src/test/test-bpf-firewall.c b/src/test/test-bpf-firewall.c index 8ba3a888ec..8f3c7b41d8 100644 --- a/src/test/test-bpf-firewall.c +++ b/src/test/test-bpf-firewall.c @@ -168,6 +168,7 @@ int main(int argc, char *argv[]) { ASSERT_OK(r); + ASSERT_OK(unit_patch_contexts(u)); ASSERT_OK(unit_start(u, NULL)); while (!IN_SET(SERVICE(u)->state, SERVICE_DEAD, SERVICE_FAILED)) @@ -193,6 +194,7 @@ int main(int argc, char *argv[]) { SERVICE(u)->type = SERVICE_ONESHOT; u->load_state = UNIT_LOADED; + ASSERT_OK(unit_patch_contexts(u)); ASSERT_OK(unit_start(u, NULL)); while (!IN_SET(SERVICE(u)->state, SERVICE_DEAD, SERVICE_FAILED)) diff --git a/src/test/test-bpf-foreign-programs.c b/src/test/test-bpf-foreign-programs.c index 337243972c..1b1f1762c9 100644 --- a/src/test/test-bpf-foreign-programs.c +++ b/src/test/test-bpf-foreign-programs.c @@ -246,6 +246,7 @@ static int test_bpf_cgroup_programs(Manager *m, const char *unit_name, const Tes SERVICE(u)->type = SERVICE_ONESHOT; u->load_state = UNIT_LOADED; + ASSERT_OK(unit_patch_contexts(u)); r = unit_start(u, NULL); if (r < 0) return log_error_errno(r, "Unit start failed %m"); diff --git a/src/test/test-bpf-restrict-fs.c b/src/test/test-bpf-restrict-fs.c index 31fdd7f0e2..522bb010e7 100644 --- a/src/test/test-bpf-restrict-fs.c +++ b/src/test/test-bpf-restrict-fs.c @@ -39,6 +39,7 @@ static int test_restrict_filesystems(Manager *m, const char *unit_name, const ch SERVICE(u)->type = SERVICE_ONESHOT; u->load_state = UNIT_LOADED; + ASSERT_OK(unit_patch_contexts(u)); r = unit_start(u, NULL); if (r < 0) return log_error_errno(r, "Unit start failed %m"); diff --git a/src/test/test-execute.c b/src/test/test-execute.c index 5ba680d571..1eddf11dd1 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -491,6 +491,20 @@ static void test_exec_privatetmp(Manager *m) { if (MANAGER_IS_SYSTEM(m) || have_userns_privileges()) { test(m, "exec-privatetmp-yes.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED); test(m, "exec-privatetmp-disabled-by-prefix.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED); + + FOREACH_STRING(s, + "exec-privatetmp-disconnected.service", + "exec-privatetmp-disconnected-defaultdependencies-no.service", + "exec-privatetmp-disconnected-requires-mounts-for-var.service", + "exec-privatetmp-disconnected-wants-mounts-for-var.service", + "exec-privatetmp-disconnected-after-and-requires-for-var.service", + "exec-privatetmp-disconnected-after-and-wants-for-var.service") { + (void) unlink("/tmp/test-exec_privatetmp_disconnected"); + (void) unlink("/var/tmp/test-exec_privatetmp_disconnected"); + test(m, s, can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED); + ASSERT_FAIL(access("/tmp/test-exec_privatetmp_disconnected", F_OK)); + ASSERT_FAIL(access("/var/tmp/test-exec_privatetmp_disconnected", F_OK)); + } } test(m, "exec-privatetmp-no.service", 0, CLD_EXITED); diff --git a/src/test/test-socket-bind.c b/src/test/test-socket-bind.c index 63552013b2..d0e448daa1 100644 --- a/src/test/test-socket-bind.c +++ b/src/test/test-socket-bind.c @@ -76,6 +76,7 @@ static int test_socket_bind( SERVICE(u)->type = SERVICE_ONESHOT; u->load_state = UNIT_LOADED; + ASSERT_OK(unit_patch_contexts(u)); r = unit_start(u, NULL); if (r < 0) return log_error_errno(r, "Unit start failed %m"); diff --git a/test/test-execute/exec-privatetmp-disconnected-after-and-requires-for-var.service b/test/test-execute/exec-privatetmp-disconnected-after-and-requires-for-var.service new file mode 100644 index 0000000000..39e49c698a --- /dev/null +++ b/test/test-execute/exec-privatetmp-disconnected-after-and-requires-for-var.service @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Test for PrivateTmp=disconnected with After=/Requires=var.mount +DefaultDependencies=no +After=var.mount +Requires=var.mount + +[Service] +ExecStart=sh -x -c 'test ! -f /tmp/test-exec_privatetmp' +ExecStart=touch /tmp/test-exec_privatetmp_disconnected +ExecStart=touch /var/tmp/test-exec_privatetmp_disconnected +ExecStart=sh -x -c 'test x$$TMPDIR = x' +Type=oneshot +ProtectSystem=strict +PrivateTmp=disconnected diff --git a/test/test-execute/exec-privatetmp-disconnected-after-and-wants-for-var.service b/test/test-execute/exec-privatetmp-disconnected-after-and-wants-for-var.service new file mode 100644 index 0000000000..b11302f5f2 --- /dev/null +++ b/test/test-execute/exec-privatetmp-disconnected-after-and-wants-for-var.service @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Test for PrivateTmp=disconnected with After=/Wants=var.mount +DefaultDependencies=no +After=var.mount +Wants=var.mount + +[Service] +ExecStart=sh -x -c 'test ! -f /tmp/test-exec_privatetmp' +ExecStart=touch /tmp/test-exec_privatetmp_disconnected +ExecStart=touch /var/tmp/test-exec_privatetmp_disconnected +ExecStart=sh -x -c 'test x$$TMPDIR = x' +Type=oneshot +ProtectSystem=strict +PrivateTmp=disconnected diff --git a/test/test-execute/exec-privatetmp-disconnected-defaultdependencies-no.service b/test/test-execute/exec-privatetmp-disconnected-defaultdependencies-no.service new file mode 100644 index 0000000000..2208b8e1b5 --- /dev/null +++ b/test/test-execute/exec-privatetmp-disconnected-defaultdependencies-no.service @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Test for PrivateTmp=disconnected with DefaultDependencies=no +DefaultDependencies=no + +[Service] +ExecStart=sh -x -c 'test ! -f /tmp/test-exec_privatetmp' +ExecStart=touch /tmp/test-exec_privatetmp_disconnected +ExecStart=sh -x -c '(! touch /var/tmp/test-exec_privatetmp_disconnected)' +ExecStart=sh -x -c 'test $$TMPDIR = /tmp' +Type=oneshot +ProtectSystem=strict +PrivateTmp=disconnected diff --git a/test/test-execute/exec-privatetmp-disconnected-requires-mounts-for-var.service b/test/test-execute/exec-privatetmp-disconnected-requires-mounts-for-var.service new file mode 100644 index 0000000000..349c137984 --- /dev/null +++ b/test/test-execute/exec-privatetmp-disconnected-requires-mounts-for-var.service @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Test for PrivateTmp=disconnected with RequiresMountsFor=/var/ +DefaultDependencies=no +RequiresMountsFor=/var/ + +[Service] +ExecStart=sh -x -c 'test ! -f /tmp/test-exec_privatetmp' +ExecStart=touch /tmp/test-exec_privatetmp_disconnected +ExecStart=touch /var/tmp/test-exec_privatetmp_disconnected +ExecStart=sh -x -c 'test x$$TMPDIR = x' +Type=oneshot +ProtectSystem=strict +PrivateTmp=disconnected diff --git a/test/test-execute/exec-privatetmp-disconnected-wants-mounts-for-var.service b/test/test-execute/exec-privatetmp-disconnected-wants-mounts-for-var.service new file mode 100644 index 0000000000..09ac3495b8 --- /dev/null +++ b/test/test-execute/exec-privatetmp-disconnected-wants-mounts-for-var.service @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Test for PrivateTmp=disconnected with WantsMountsFor=/var/ +DefaultDependencies=no +WantsMountsFor=/var/ + +[Service] +ExecStart=sh -x -c 'test ! -f /tmp/test-exec_privatetmp' +ExecStart=touch /tmp/test-exec_privatetmp_disconnected +ExecStart=touch /var/tmp/test-exec_privatetmp_disconnected +ExecStart=sh -x -c 'test x$$TMPDIR = x' +Type=oneshot +ProtectSystem=strict +PrivateTmp=disconnected diff --git a/test/test-execute/exec-privatetmp-disconnected.service b/test/test-execute/exec-privatetmp-disconnected.service new file mode 100644 index 0000000000..fbee568d74 --- /dev/null +++ b/test/test-execute/exec-privatetmp-disconnected.service @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Test for PrivateTmp=disconnected + +[Service] +ExecStart=sh -x -c 'test ! -f /tmp/test-exec_privatetmp' +ExecStart=touch /tmp/test-exec_privatetmp_disconnected +ExecStart=touch /var/tmp/test-exec_privatetmp_disconnected +ExecStart=sh -x -c 'test x$$TMPDIR = x' +Type=oneshot +ProtectSystem=strict +PrivateTmp=disconnected