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));