diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index c89adad66a..e7d5e63c96 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -63,15 +63,26 @@ 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 + /tmp/ and /var/tmp/ and an automatic After= dependency on - systemd-tmpfiles-setup.service8. - + systemd-tmpfiles-setup.service8, + unless DefaultDependencies=no is specified. If + DefaultDependencies=no is specified, and a + RequiresMountsFor=/tmp/, WantsMountsFor=/tmp/, + After=tmp.mount, or RootDirectory=/RootImage= + are not specified, PrivateTmp=yes is converted to + PrivateTmp=disconnected. 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. + RootDirectory=/RootImage= are specified. If + DefaultDependencies=no is specified, and a + RequiresMountsFor=/var/, WantsMountsFor=/var/, + After=var.mount, RootDirectory=/RootImage= are + not specified, the private mount on /tmp/ is reused for + /var/tmp/ by setting $TMPDIR appropriately. + Units whose standard output or error output is connected to or (or their combinations with console output, see below) automatically acquire diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 315ef41031..25c12ece25 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -2089,7 +2089,7 @@ static int build_environment( /* If this is D-Bus, tell the nss-systemd module, since it relies on being able to use blocking * Varlink calls back to us for look up dynamic users in PID 1. Break the deadlock between D-Bus and - * PID 1 by disabling use of PID1' NSS interface for looking up dynamic users. */ + * PID 1 by disabling use of PID1's NSS interface for looking up dynamic users. */ if (p->flags & EXEC_NSS_DYNAMIC_BYPASS) { r = strv_extend_with_size(&e, &n, "SYSTEMD_NSS_DYNAMIC_BYPASS=1"); if (r < 0) @@ -2097,8 +2097,8 @@ static int build_environment( } /* We query "root" if this is a system unit and User= is not specified. $USER is always set. $HOME - * could cause problem for e.g. getty, since login doesn't override $HOME, and $LOGNAME and $SHELL don't - * really make much sense since we're not logged in. Hence we conditionalize the three based on + * could cause problem for e.g. getty, since login doesn't override $HOME, and $LOGNAME and $SHELL + * don't really make much sense since we're not logged in. Hence we conditionalize the three based on * SetLoginEnvironment= switch. */ if (!username && !c->dynamic_user && p->runtime_scope == RUNTIME_SCOPE_SYSTEM) { assert(!c->user); @@ -2248,10 +2248,8 @@ static int build_environment( } assert(c->private_var_tmp >= 0 && c->private_var_tmp < _PRIVATE_TMP_MAX); - if (needs_sandboxing && c->private_tmp != c->private_var_tmp) { - assert(c->private_tmp == PRIVATE_TMP_DISCONNECTED); - assert(c->private_var_tmp == PRIVATE_TMP_NO); - + if (needs_sandboxing && + c->private_var_tmp == PRIVATE_TMP_NO && c->private_tmp != PRIVATE_TMP_NO) { /* When private tmpfs is enabled only on /tmp/, then explicitly set $TMPDIR to suggest the * service to use /tmp/. */ @@ -3907,14 +3905,16 @@ static int apply_mount_namespace( if (needs_sandboxing) { /* The runtime struct only contains the parent of the private /tmp, which is non-accessible * to world users. Inside of it there's a /tmp that is sticky, and that's the one we want to - * use here. This does not apply when we are using /run/systemd/empty as fallback. */ + * use here. This does not apply when we are using /run/systemd/empty as fallback. */ if (context->private_tmp == PRIVATE_TMP_CONNECTED && runtime->shared) { if (streq_ptr(runtime->shared->tmp_dir, RUN_SYSTEMD_EMPTY)) tmp_dir = runtime->shared->tmp_dir; else if (runtime->shared->tmp_dir) tmp_dir = strjoina(runtime->shared->tmp_dir, "/tmp"); + } + if (context->private_var_tmp == PRIVATE_TMP_CONNECTED && runtime->shared) { if (streq_ptr(runtime->shared->var_tmp_dir, RUN_SYSTEMD_EMPTY)) var_tmp_dir = runtime->shared->var_tmp_dir; else if (runtime->shared->var_tmp_dir) @@ -4624,7 +4624,7 @@ static bool exec_needs_cap_sys_admin(const ExecContext *context, const ExecParam return false; return context->private_users != PRIVATE_USERS_NO || - context->private_tmp != PRIVATE_TMP_NO || + context->private_tmp != PRIVATE_TMP_NO || /* no need to check for private_var_tmp here, private_tmp is never demoted to "no" */ context->private_devices || context->private_network || context->user_namespace_path || @@ -4833,7 +4833,7 @@ static int setup_delegated_namespaces( /* If PrivatePIDs= yes is configured, we're now running as pid 1 in a pid namespace! */ - if (exec_needs_mount_namespace(context, params, runtime) && + if (exec_needs_mount_namespace(context, params) && exec_namespace_is_delegated(context, params, have_cap_sys_admin, CLONE_NEWNS) == delegate) { _cleanup_free_ char *error_path = NULL; @@ -5829,7 +5829,7 @@ int exec_invoke( return log_error_errno(r, "Failed to connect to nsresourced: %m"); } - needs_mount_namespace = exec_needs_mount_namespace(context, params, runtime); + needs_mount_namespace = exec_needs_mount_namespace(context, params); for (ExecDirectoryType dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++) { r = setup_exec_directory(context, params, uid, gid, dt, needs_mount_namespace, exit_status); diff --git a/src/core/execute.c b/src/core/execute.c index 6a7fc65f20..a2d148fde5 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -256,11 +256,7 @@ bool exec_needs_pid_namespace(const ExecContext *context, const ExecParameters * return context->private_pids != PRIVATE_PIDS_NO && namespace_type_supported(NAMESPACE_PID); } -bool exec_needs_mount_namespace( - const ExecContext *context, - const ExecParameters *params, - const ExecRuntime *runtime) { - +bool exec_needs_mount_namespace(const ExecContext *context, const ExecParameters *params) { assert(context); if (context->root_image || @@ -295,13 +291,8 @@ bool exec_needs_mount_namespace( if (!IN_SET(context->mount_propagation_flag, 0, MS_SHARED)) return true; - if (context->private_tmp == PRIVATE_TMP_DISCONNECTED) - return true; - - if (context->private_tmp == PRIVATE_TMP_CONNECTED && runtime && runtime->shared && (runtime->shared->tmp_dir || runtime->shared->var_tmp_dir)) - return true; - if (context->private_devices || + context->private_tmp != PRIVATE_TMP_NO || /* no need to check for private_var_tmp here, private_tmp is never demoted to "no" */ context->private_mounts > 0 || (context->private_mounts < 0 && exec_needs_network_namespace(context)) || context->protect_system != PROTECT_SYSTEM_NO || @@ -2394,7 +2385,6 @@ static int exec_shared_runtime_add( if (r < 0) return r; - assert(!!rt->tmp_dir == !!rt->var_tmp_dir); /* We require both to be set together */ rt->tmp_dir = TAKE_PTR(*tmp_dir); rt->var_tmp_dir = TAKE_PTR(*var_tmp_dir); @@ -2437,16 +2427,25 @@ static int exec_shared_runtime_make( assert(id); /* It is not necessary to create ExecSharedRuntime object. */ - if (!exec_needs_network_namespace(c) && !exec_needs_ipc_namespace(c) && c->private_tmp != PRIVATE_TMP_CONNECTED) { + if (!c->user_namespace_path && !exec_needs_network_namespace(c) && !exec_needs_ipc_namespace(c) && + c->private_tmp != PRIVATE_TMP_CONNECTED && c->private_var_tmp != PRIVATE_TMP_CONNECTED) { *ret = NULL; return 0; } if (c->private_tmp == PRIVATE_TMP_CONNECTED && - !(prefixed_path_strv_contains(c->inaccessible_paths, "/tmp") && - (prefixed_path_strv_contains(c->inaccessible_paths, "/var/tmp") || - prefixed_path_strv_contains(c->inaccessible_paths, "/var")))) { - r = setup_tmp_dirs(id, &tmp_dir, &var_tmp_dir); + !prefixed_path_strv_contains(c->inaccessible_paths, "/tmp")) { + + r = setup_tmp_dir_one(id, "/tmp", &tmp_dir); + if (r < 0) + return r; + } + + if (c->private_var_tmp == PRIVATE_TMP_CONNECTED && + !prefixed_path_strv_contains(c->inaccessible_paths, "/var/tmp") && + !prefixed_path_strv_contains(c->inaccessible_paths, "/var")) { + + r = setup_tmp_dir_one(id, "/var/tmp", &var_tmp_dir); if (r < 0) return r; } diff --git a/src/core/execute.h b/src/core/execute.h index c5f3988371..3d5e92cccf 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -333,9 +333,9 @@ typedef struct ExecContext { int bind_log_sockets; int memory_ksm; MemoryTHP memory_thp; - PrivateTmp private_tmp; - PrivateTmp private_var_tmp; /* This is not an independent parameter, but calculated from other - * parameters in unit_patch_contexts(). */ + PrivateTmp private_tmp; /* Those are not independent parameters, but are calculated from */ + PrivateTmp private_var_tmp; /* other parameters in unit_patch_contexts(). */ + bool private_network; bool private_devices; PrivateUsers private_users; @@ -638,7 +638,7 @@ DECLARE_STRING_TABLE_LOOKUP(exec_resource_type, ExecDirectoryType); DECLARE_STRING_TABLE_LOOKUP(memory_thp, MemoryTHP); -bool exec_needs_mount_namespace(const ExecContext *context, const ExecParameters *params, const ExecRuntime *runtime); +bool exec_needs_mount_namespace(const ExecContext *context, const ExecParameters *params); bool exec_needs_network_namespace(const ExecContext *context); bool exec_needs_ipc_namespace(const ExecContext *context); bool exec_needs_pid_namespace(const ExecContext *context, const ExecParameters *params); diff --git a/src/core/namespace.c b/src/core/namespace.c index d363ac0973..9727d725eb 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -751,59 +751,71 @@ static int append_tmpfs_mounts(MountList *ml, const TemporaryFileSystem *tmpfs, return 0; } -static int append_private_tmp(MountList *ml, const NamespaceParameters *p) { - MountEntry *me; +static int append_private_tmp_one( + MountList *ml, + PrivateTmp mode, + const char *path, + const char *connected_source) { 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)); + assert(mode >= 0 && mode < _PRIVATE_TMP_MAX); + assert(path); - 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) + if (mode == PRIVATE_TMP_NO) return 0; - if (p->private_var_tmp == PRIVATE_TMP_NO) { - me = mount_list_extend(ml); - if (!me) - return log_oom_debug(); + if (mode == PRIVATE_TMP_CONNECTED && !connected_source) + /* Do nothing if the private tmp dir was suppressed as it would be made inaccessible anyways + * (see exec_shared_runtime_make()). */ + return 0; + + MountEntry *me = mount_list_extend(ml); + if (!me) + return log_oom_debug(); + + if (mode == PRIVATE_TMP_CONNECTED) *me = (MountEntry) { - .path_const = "/tmp/", + .path_const = path, + .mode = MOUNT_PRIVATE_TMP, + .read_only = streq(connected_source, RUN_SYSTEMD_EMPTY), + .source_const = connected_source, + }; + else + *me = (MountEntry) { + .path_const = path, .mode = MOUNT_PRIVATE_TMPFS, .options_const = "mode=0700" NESTED_TMPFS_LIMITS, .flags = MS_NODEV|MS_STRICTATIME, }; + return 0; +} + +static int append_private_tmp(MountList *ml, const NamespaceParameters *p) { + int r; + + assert(ml); + assert(p); + assert(p->private_tmp >= 0 && p->private_tmp < _PRIVATE_TMP_MAX); + assert(p->private_var_tmp >= 0 && p->private_var_tmp < _PRIVATE_TMP_MAX); + + if (p->private_tmp != PRIVATE_TMP_DISCONNECTED || p->private_var_tmp != PRIVATE_TMP_DISCONNECTED) { + r = append_private_tmp_one(ml, p->private_tmp, "/tmp/", p->tmp_dir); + if (r < 0) + return r; + + r = append_private_tmp_one(ml, p->private_var_tmp, "/var/tmp/", p->var_tmp_dir); + if (r < 0) + return r; + return 0; } + /* Fully disconnected private tmp: we mount a single tmpfs instance with two subdirs which are + * bind mounted to /tmp/ and /var/tmp/. */ + + MountEntry *me; + _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"); @@ -3333,11 +3345,30 @@ int temporary_filesystem_add( return 0; } +char* namespace_cleanup_tmpdir(char *p) { + if (!p) + return NULL; + + if (!streq(p, RUN_SYSTEMD_EMPTY)) { + _cleanup_free_ char *child = path_join(p, "tmp"); + if (!child) + log_oom_debug(); + else + (void) rmdir(child); + + (void) rmdir(p); + } + + return mfree(p); +} + static int make_tmp_prefix(const char *prefix) { _cleanup_free_ char *t = NULL; _cleanup_close_ int fd = -EBADF; int r; + assert(prefix); + /* Don't do anything unless we know the dir is actually missing */ r = access(prefix, F_OK); if (r >= 0) @@ -3369,22 +3400,21 @@ static int make_tmp_prefix(const char *prefix) { r = RET_NERRNO(rename(t, prefix)); if (r < 0) { (void) rmdir(t); - return r == -EEXIST ? 0 : r; /* it's fine if someone else created the dir by now */ + return IN_SET(r, -EEXIST, -ENOTEMPTY) ? 0 : r; /* it's fine if someone else created the dir by now */ } return 0; } -static int setup_one_tmp_dir(const char *id, const char *prefix, char **path, char **tmp_path) { - _cleanup_free_ char *x = NULL; - _cleanup_free_ char *y = NULL; +int setup_tmp_dir_one(const char *id, const char *prefix, char **ret_path) { + _cleanup_free_ char *d = NULL; sd_id128_t boot_id; bool rw = true; int r; assert(id); assert(prefix); - assert(path); + assert(ret_path); /* We include the boot id in the directory so that after a * reboot we can easily identify obsolete directories. */ @@ -3393,8 +3423,8 @@ static int setup_one_tmp_dir(const char *id, const char *prefix, char **path, ch if (r < 0) return r; - x = strjoin(prefix, "/systemd-private-", SD_ID128_TO_STRING(boot_id), "-", id, "-XXXXXX"); - if (!x) + d = strjoin(prefix, "/systemd-private-", SD_ID128_TO_STRING(boot_id), "-", id, "-XXXXXX"); + if (!d) return -ENOMEM; r = make_tmp_prefix(prefix); @@ -3402,7 +3432,7 @@ static int setup_one_tmp_dir(const char *id, const char *prefix, char **path, ch return r; WITH_UMASK(0077) - if (!mkdtemp(x)) { + if (!mkdtemp(d)) { if (errno == EROFS || ERRNO_IS_DISK_SPACE(errno)) rw = false; else @@ -3410,20 +3440,25 @@ static int setup_one_tmp_dir(const char *id, const char *prefix, char **path, ch } if (rw) { - y = strjoin(x, "/tmp"); - if (!y) + _cleanup_free_ char *inner_dir = path_join(d, "tmp"); + if (!inner_dir) { + (void) rmdir(d); return -ENOMEM; + } WITH_UMASK(0000) - if (mkdir(y, 0777 | S_ISVTX) < 0) - return -errno; - - r = label_fix_full(AT_FDCWD, y, prefix, 0); - if (r < 0) + r = RET_NERRNO(mkdir(inner_dir, 0777 | S_ISVTX)); + if (r < 0) { + (void) rmdir(d); return r; + } - if (tmp_path) - *tmp_path = TAKE_PTR(y); + r = label_fix_full(AT_FDCWD, inner_dir, prefix, 0); + if (r < 0) { + (void) rmdir(inner_dir); + (void) rmdir(d); + return r; + } } else { /* Trouble: we failed to create the directory. Instead of failing, let's simulate /tmp being * read-only. This way the service will get the EROFS result as if it was writing to the real @@ -3433,44 +3468,12 @@ static int setup_one_tmp_dir(const char *id, const char *prefix, char **path, ch if (r < 0) return r; - r = free_and_strdup(&x, RUN_SYSTEMD_EMPTY); + r = free_and_strdup(&d, RUN_SYSTEMD_EMPTY); if (r < 0) return r; } - *path = TAKE_PTR(x); - return 0; -} - -char* namespace_cleanup_tmpdir(char *p) { - PROTECT_ERRNO; - if (!streq_ptr(p, RUN_SYSTEMD_EMPTY)) - (void) rmdir(p); - return mfree(p); -} - -int setup_tmp_dirs(const char *id, char **tmp_dir, char **var_tmp_dir) { - _cleanup_(namespace_cleanup_tmpdirp) char *a = NULL; - _cleanup_(rmdir_and_freep) char *a_tmp = NULL; - char *b; - int r; - - assert(id); - assert(tmp_dir); - assert(var_tmp_dir); - - r = setup_one_tmp_dir(id, "/tmp", &a, &a_tmp); - if (r < 0) - return r; - - r = setup_one_tmp_dir(id, "/var/tmp", &b, NULL); - if (r < 0) - return r; - - a_tmp = mfree(a_tmp); /* avoid rmdir */ - *tmp_dir = TAKE_PTR(a); - *var_tmp_dir = TAKE_PTR(b); - + *ret_path = TAKE_PTR(d); return 0; } diff --git a/src/core/namespace.h b/src/core/namespace.h index 318836651c..f5d792dd77 100644 --- a/src/core/namespace.h +++ b/src/core/namespace.h @@ -232,10 +232,7 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path); char* namespace_cleanup_tmpdir(char *p); DEFINE_TRIVIAL_CLEANUP_FUNC(char*, namespace_cleanup_tmpdir); -int setup_tmp_dirs( - const char *id, - char **tmp_dir, - char **var_tmp_dir); +int setup_tmp_dir_one(const char *id, const char *prefix, char **ret_path); int setup_shareable_ns(int ns_storage_socket[static 2], unsigned long nsflag); int open_shareable_ns_path(int netns_storage_socket[static 2], const char *path, unsigned long nsflag); diff --git a/src/core/service.c b/src/core/service.c index b9efd9bdb9..51bba291e8 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -5800,7 +5800,7 @@ static int service_can_live_mount(Unit *u, sd_bus_error *reterr_error) { Service *s = ASSERT_PTR(SERVICE(u)); /* Ensure that the unit runs in a private mount namespace */ - if (!exec_needs_mount_namespace(&s->exec_context, /* params= */ NULL, s->exec_runtime)) + if (!exec_needs_mount_namespace(&s->exec_context, /* params= */ NULL)) return sd_bus_error_setf( reterr_error, SD_BUS_ERROR_INVALID_ARGS, diff --git a/src/core/unit.c b/src/core/unit.c index 1995bb7f8e..bb3430186c 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -1310,20 +1310,15 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) { assert(c->private_var_tmp >= 0 && c->private_var_tmp < _PRIVATE_TMP_MAX); if (c->private_tmp == PRIVATE_TMP_CONNECTED) { - 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; + } + if (c->private_var_tmp == PRIVATE_TMP_CONNECTED) { 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(). */ @@ -1332,6 +1327,12 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) { return r; } + if (c->private_tmp == PRIVATE_TMP_CONNECTED || c->private_var_tmp == PRIVATE_TMP_CONNECTED) { + r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_TMPFILES_SETUP_SERVICE, true, UNIT_DEPENDENCY_FILE); + if (r < 0) + return r; + } + if (c->root_image || c->root_mstack) { /* We need to wait for /dev/loopX to appear when doing RootImage=, hence let's add an * implicit dependency on udev. (And for RootMStack= we might need it) */ @@ -4323,20 +4324,23 @@ static int unit_verify_contexts(const Unit *u) { return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOEXEC), "WorkingDirectory=~ is not allowed under DynamicUser=yes. Refusing."); if (ec->working_directory && path_below_api_vfs(ec->working_directory) && - exec_needs_mount_namespace(ec, /* params= */ NULL, /* runtime= */ NULL)) + exec_needs_mount_namespace(ec, /* params= */ NULL)) return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOEXEC), "WorkingDirectory= may not be below /proc/, /sys/ or /dev/ when using mount namespacing. Refusing."); if (exec_needs_pid_namespace(ec, /* params= */ NULL) && !UNIT_VTABLE(u)->notify_pidref) return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOEXEC), "PrivatePIDs= setting is only supported for service units. Refusing."); + if ((ec->user || ec->dynamic_user || ec->group || ec->pam_name) && ec->private_users == PRIVATE_USERS_MANAGED) + return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOEXEC), "PrivateUsers=managed may not be used in combination with User=/DynamicUser=/Group=/PAMName=, refusing."); + + if (ec->user_namespace_path && ec->private_users != PRIVATE_USERS_NO) + return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOEXEC), "PrivateUsers= may not be used with custom UserNamespacePath=, refusing."); + const KillContext *kc = unit_get_kill_context(u); if (ec->pam_name && kc && !IN_SET(kc->kill_mode, KILL_CONTROL_GROUP, KILL_MIXED)) return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOEXEC), "Unit has PAM enabled. Kill mode must be set to 'control-group' or 'mixed'. Refusing."); - if ((ec->user || ec->dynamic_user || ec->group || ec->pam_name) && ec->private_users == PRIVATE_USERS_MANAGED) - return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOEXEC), "PrivateUsers=managed may not be used in combination with User=/DynamicUser=/Group=/PAMName=, refusing."); - return 0; } @@ -4346,8 +4350,8 @@ static PrivateTmp unit_get_private_var_tmp(const Unit *u, const ExecContext *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. */ + * RootImage=/RootDirectory= are not set, as /var/ may be a separate partition. + * See https://github.com/systemd/systemd/issues/37258. */ /* PrivateTmp=yes/no also enables/disables private tmpfs on /var/tmp/. */ if (c->private_tmp != PRIVATE_TMP_DISCONNECTED) @@ -4369,22 +4373,50 @@ static PrivateTmp unit_get_private_var_tmp(const Unit *u, const ExecContext *c) if (hashmap_contains(u->mounts_for[t], "/var/")) return PRIVATE_TMP_DISCONNECTED; - /* Check the same but for After= with Requires=/Requisite=/Wants= or friends. */ + /* Check the same but for After=. */ 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)) + if (m && unit_has_dependency(u, UNIT_ATOM_AFTER, m)) return PRIVATE_TMP_DISCONNECTED; return PRIVATE_TMP_NO; } +static PrivateTmp unit_get_private_tmp(const Unit *u, const ExecContext *c) { + assert(u); + assert(c); + assert(c->private_tmp >= 0 && c->private_tmp < _PRIVATE_TMP_MAX); + + /* Upgrade "PrivateTmp=yes" (a.k.a. 'connected') to 'disconnected' when + * DefaultDependencies=no and RootImage=/RootDirectory= are not set, as /tmp/ may be a + * separate partition. See https://github.com/systemd/systemd/issues/28515. + * + * Note that the change goes in the opposite direction than unit_get_private_var_tmp() + * above. For /var/tmp/, we need to disable the setting, because we don't want to create + * the /var/tmp/ directory if /var/ is a mount point. We don't have this problem with + * /tmp/ because there is no nesting. */ + + if (c->private_tmp != PRIVATE_TMP_CONNECTED || + u->default_dependencies || + exec_context_with_rootfs(c)) + return c->private_tmp; + + /* Even if DefaultDependencies=no, honour tmpfs setting when + * RequiresMountsFor=/WantsMountsFor=/tmp/ is explicitly set. */ + for (UnitMountDependencyType t = 0; t < _UNIT_MOUNT_DEPENDENCY_TYPE_MAX; t++) + if (hashmap_contains(u->mounts_for[t], "/tmp/")) + return c->private_tmp; + + /* Check the same but for After=. */ + Unit *m = manager_get_unit(u->manager, "tmp.mount"); + if (!m) + return c->private_tmp; + + if (unit_has_dependency(u, UNIT_ATOM_AFTER, m)) + return c->private_tmp; + + return PRIVATE_TMP_DISCONNECTED; +} + int unit_patch_contexts(Unit *u) { CGroupContext *cc; ExecContext *ec; @@ -4461,7 +4493,14 @@ int unit_patch_contexts(Unit *u) { ec->restrict_suid_sgid = true; } + /* Table of possible combinations: + * /var/tmp /tmp + * PrivateTmp=no no no + * PrivateTmp=connected connected connected,disconnected + * PrivateTmp=disconnected disconnected,no disconnected + */ ec->private_var_tmp = unit_get_private_var_tmp(u, ec); + ec->private_tmp = unit_get_private_tmp(u, ec); FOREACH_ARRAY(d, ec->directories, _EXEC_DIRECTORY_TYPE_MAX) exec_directory_sort(d); diff --git a/src/test/test-namespace.c b/src/test/test-namespace.c index 5b67cd9f96..899f8f6351 100644 --- a/src/test/test-namespace.c +++ b/src/test/test-namespace.c @@ -43,7 +43,8 @@ static void test_tmpdir_one(const char *id, const char *A, const char *B) { struct stat x, y; char *c, *d; - ASSERT_OK_ZERO(setup_tmp_dirs(id, &a, &b)); + ASSERT_OK(setup_tmp_dir_one(id, "/tmp", &a)); + ASSERT_OK(setup_tmp_dir_one(id, "/var/tmp", &b)); ASSERT_OK_ERRNO(stat(a, &x)); ASSERT_OK_ERRNO(stat(b, &y));