char **error_path) {
_cleanup_strv_free_ char **empty_directories = NULL;
- char *tmp = NULL, *var = NULL;
+ const char *tmp_dir = NULL, *var_tmp_dir = NULL;
const char *root_dir = NULL, *root_image = NULL;
NamespaceInfo ns_info;
bool needs_sandboxing;
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. */
+ * 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. */
if (context->private_tmp && runtime) {
- if (runtime->tmp_dir)
- tmp = strjoina(runtime->tmp_dir, "/tmp");
- if (runtime->var_tmp_dir)
- var = strjoina(runtime->var_tmp_dir, "/tmp");
+ if (streq_ptr(runtime->tmp_dir, RUN_SYSTEMD_EMPTY))
+ tmp_dir = runtime->tmp_dir;
+ else if (runtime->tmp_dir)
+ tmp_dir = strjoina(runtime->tmp_dir, "/tmp");
+
+ if (streq_ptr(runtime->var_tmp_dir, RUN_SYSTEMD_EMPTY))
+ var_tmp_dir = runtime->var_tmp_dir;
+ else if (runtime->tmp_dir)
+ var_tmp_dir = strjoina(runtime->var_tmp_dir, "/tmp");
}
ns_info = (NamespaceInfo) {
n_bind_mounts,
context->temporary_filesystems,
context->n_temporary_filesystems,
- tmp,
- var,
+ tmp_dir,
+ var_tmp_dir,
context->log_namespace,
needs_sandboxing ? context->protect_home : PROTECT_HOME_NO,
needs_sandboxing ? context->protect_system : PROTECT_SYSTEM_NO,
(void) hashmap_remove(rt->manager->exec_runtime_by_id, rt->id);
/* When destroy is true, then rm_rf tmp_dir and var_tmp_dir. */
- if (destroy && rt->tmp_dir) {
+
+ if (destroy && rt->tmp_dir && !streq(rt->tmp_dir, RUN_SYSTEMD_EMPTY)) {
log_debug("Spawning thread to nuke %s", rt->tmp_dir);
r = asynchronous_job(remove_tmpdir_thread, rt->tmp_dir);
- if (r < 0) {
+ if (r < 0)
log_warning_errno(r, "Failed to nuke %s: %m", rt->tmp_dir);
- free(rt->tmp_dir);
- }
-
- rt->tmp_dir = NULL;
+ else
+ rt->tmp_dir = NULL;
}
- if (destroy && rt->var_tmp_dir) {
+ if (destroy && rt->var_tmp_dir && !streq(rt->var_tmp_dir, RUN_SYSTEMD_EMPTY)) {
log_debug("Spawning thread to nuke %s", rt->var_tmp_dir);
r = asynchronous_job(remove_tmpdir_thread, rt->var_tmp_dir);
- if (r < 0) {
+ if (r < 0)
log_warning_errno(r, "Failed to nuke %s: %m", rt->var_tmp_dir);
- free(rt->var_tmp_dir);
- }
-
- rt->var_tmp_dir = NULL;
+ else
+ rt->var_tmp_dir = NULL;
}
rt->id = mfree(rt->id);
(void) exec_runtime_free(*rt, false);
}
-static int exec_runtime_allocate(ExecRuntime **ret) {
+static int exec_runtime_allocate(ExecRuntime **ret, const char *id) {
+ _cleanup_free_ char *id_copy = NULL;
ExecRuntime *n;
assert(ret);
+ id_copy = strdup(id);
+ if (!id_copy)
+ return -ENOMEM;
+
n = new(ExecRuntime, 1);
if (!n)
return -ENOMEM;
*n = (ExecRuntime) {
+ .id = TAKE_PTR(id_copy),
.netns_storage_socket = { -1, -1 },
};
static int exec_runtime_add(
Manager *m,
const char *id,
- const char *tmp_dir,
- const char *var_tmp_dir,
- const int netns_storage_socket[2],
+ char **tmp_dir,
+ char **var_tmp_dir,
+ int netns_storage_socket[2],
ExecRuntime **ret) {
_cleanup_(exec_runtime_freep) ExecRuntime *rt = NULL;
assert(m);
assert(id);
+ /* tmp_dir, var_tmp_dir, netns_storage_socket fds are donated on success */
+
r = hashmap_ensure_allocated(&m->exec_runtime_by_id, &string_hash_ops);
if (r < 0)
return r;
- r = exec_runtime_allocate(&rt);
+ r = exec_runtime_allocate(&rt, id);
if (r < 0)
return r;
- rt->id = strdup(id);
- if (!rt->id)
- return -ENOMEM;
-
- if (tmp_dir) {
- rt->tmp_dir = strdup(tmp_dir);
- if (!rt->tmp_dir)
- return -ENOMEM;
+ r = hashmap_put(m->exec_runtime_by_id, rt->id, rt);
+ if (r < 0)
+ return r;
- /* When tmp_dir is set, then we require var_tmp_dir is also set. */
- assert(var_tmp_dir);
- rt->var_tmp_dir = strdup(var_tmp_dir);
- if (!rt->var_tmp_dir)
- return -ENOMEM;
- }
+ 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);
if (netns_storage_socket) {
- rt->netns_storage_socket[0] = netns_storage_socket[0];
- rt->netns_storage_socket[1] = netns_storage_socket[1];
+ rt->netns_storage_socket[0] = TAKE_FD(netns_storage_socket[0]);
+ rt->netns_storage_socket[1] = TAKE_FD(netns_storage_socket[1]);
}
- r = hashmap_put(m->exec_runtime_by_id, rt->id, rt);
- if (r < 0)
- return r;
-
rt->manager = m;
if (ret)
*ret = rt;
-
/* do not remove created ExecRuntime object when the operation succeeds. */
- rt = NULL;
+ TAKE_PTR(rt);
return 0;
}
static int exec_runtime_make(Manager *m, const ExecContext *c, const char *id, ExecRuntime **ret) {
- _cleanup_free_ char *tmp_dir = NULL, *var_tmp_dir = NULL;
+ _cleanup_(namespace_cleanup_tmpdirp) char *tmp_dir = NULL, *var_tmp_dir = NULL;
_cleanup_close_pair_ int netns_storage_socket[2] = { -1, -1 };
int r;
return -errno;
}
- r = exec_runtime_add(m, id, tmp_dir, var_tmp_dir, netns_storage_socket, ret);
+ r = exec_runtime_add(m, id, &tmp_dir, &var_tmp_dir, netns_storage_socket, ret);
if (r < 0)
return r;
- /* Avoid cleanup */
- netns_storage_socket[0] = netns_storage_socket[1] = -1;
return 1;
}
rt = hashmap_get(u->manager->exec_runtime_by_id, u->id);
if (!rt) {
- r = exec_runtime_allocate(&rt_create);
+ r = exec_runtime_allocate(&rt_create, u->id);
if (r < 0)
return log_oom();
- rt_create->id = strdup(u->id);
- if (!rt_create->id)
- return log_oom();
-
rt = rt_create;
}
rt_create->manager = u->manager;
/* Avoid cleanup */
- rt_create = NULL;
+ TAKE_PTR(rt_create);
}
return 1;
}
-void exec_runtime_deserialize_one(Manager *m, const char *value, FDSet *fds) {
- char *id = NULL, *tmp_dir = NULL, *var_tmp_dir = NULL;
- int r, fd0 = -1, fd1 = -1;
+int exec_runtime_deserialize_one(Manager *m, const char *value, FDSet *fds) {
+ _cleanup_free_ char *tmp_dir = NULL, *var_tmp_dir = NULL;
+ char *id = NULL;
+ int r, fdpair[] = {-1, -1};
const char *p, *v = value;
size_t n;
v = startswith(p, "tmp-dir=");
if (v) {
n = strcspn(v, " ");
- tmp_dir = strndupa(v, n);
+ tmp_dir = strndup(v, n);
+ if (!tmp_dir)
+ return log_oom();
if (v[n] != ' ')
goto finalize;
p = v + n + 1;
v = startswith(p, "var-tmp-dir=");
if (v) {
n = strcspn(v, " ");
- var_tmp_dir = strndupa(v, n);
+ var_tmp_dir = strndup(v, n);
+ if (!var_tmp_dir)
+ return log_oom();
if (v[n] != ' ')
goto finalize;
p = v + n + 1;
n = strcspn(v, " ");
buf = strndupa(v, n);
- if (safe_atoi(buf, &fd0) < 0 || !fdset_contains(fds, fd0)) {
- log_debug("Unable to process exec-runtime netns fd specification.");
- return;
- }
- fd0 = fdset_remove(fds, fd0);
+ if (safe_atoi(buf, &fdpair[0]) < 0 || !fdset_contains(fds, fdpair[0]))
+ return log_debug("Unable to process exec-runtime netns fd specification.");
+ fdpair[0] = fdset_remove(fds, fdpair[0]);
if (v[n] != ' ')
goto finalize;
p = v + n + 1;
n = strcspn(v, " ");
buf = strndupa(v, n);
- if (safe_atoi(buf, &fd1) < 0 || !fdset_contains(fds, fd1)) {
- log_debug("Unable to process exec-runtime netns fd specification.");
- return;
- }
- fd1 = fdset_remove(fds, fd1);
+ if (safe_atoi(buf, &fdpair[1]) < 0 || !fdset_contains(fds, fdpair[1]))
+ return log_debug("Unable to process exec-runtime netns fd specification.");
+ fdpair[1] = fdset_remove(fds, fdpair[1]);
}
finalize:
-
- r = exec_runtime_add(m, id, tmp_dir, var_tmp_dir, (int[]) { fd0, fd1 }, NULL);
+ r = exec_runtime_add(m, id, &tmp_dir, &var_tmp_dir, fdpair, NULL);
if (r < 0)
- log_debug_errno(r, "Failed to add exec-runtime: %m");
+ return log_debug_errno(r, "Failed to add exec-runtime: %m");
+ return 0;
}
void exec_runtime_vacuum(Manager *m) {
BIND_MOUNT,
BIND_MOUNT_RECURSIVE,
PRIVATE_TMP,
+ PRIVATE_TMP_READONLY,
PRIVATE_DEV,
BIND_DEV,
EMPTY_DIR,
static bool mount_entry_read_only(const MountEntry *p) {
assert(p);
- return p->read_only || IN_SET(p->mode, READONLY, INACCESSIBLE);
+ return p->read_only || IN_SET(p->mode, READONLY, INACCESSIBLE, PRIVATE_TMP_READONLY);
}
static const char *mount_entry_source(const MountEntry *p) {
return mount_tmpfs(m);
case PRIVATE_TMP:
+ case PRIVATE_TMP_READONLY:
what = mount_entry_source(m);
make = true;
break;
goto finish;
if (tmp_dir) {
+ bool ro = streq(tmp_dir, RUN_SYSTEMD_EMPTY);
+
*(m++) = (MountEntry) {
.path_const = "/tmp",
- .mode = PRIVATE_TMP,
+ .mode = ro ? PRIVATE_TMP_READONLY : PRIVATE_TMP,
.source_const = tmp_dir,
};
}
if (var_tmp_dir) {
+ bool ro = streq(var_tmp_dir, RUN_SYSTEMD_EMPTY);
+
*(m++) = (MountEntry) {
.path_const = "/var/tmp",
- .mode = PRIVATE_TMP,
+ .mode = ro ? PRIVATE_TMP_READONLY : PRIVATE_TMP,
.source_const = var_tmp_dir,
};
}
}
-static int setup_one_tmp_dir(const char *id, const char *prefix, char **path) {
+static int make_tmp_subdir(const char *parent, char **ret) {
+ _cleanup_free_ char *y = NULL;
+
+ RUN_WITH_UMASK(0000) {
+ y = strjoin(parent, "/tmp");
+ if (!y)
+ return -ENOMEM;
+
+ if (mkdir(y, 0777 | S_ISVTX) < 0)
+ return -errno;
+ }
+
+ if (ret)
+ *ret = TAKE_PTR(y);
+ return 0;
+}
+
+static int setup_one_tmp_dir(const char *id, const char *prefix, char **path, char **tmp_path) {
_cleanup_free_ char *x = NULL;
char bid[SD_ID128_STRING_MAX];
sd_id128_t boot_id;
+ bool rw = true;
int r;
assert(id);
return r;
RUN_WITH_UMASK(0077)
- if (!mkdtemp(x))
- return -errno;
-
- RUN_WITH_UMASK(0000) {
- char *y;
+ if (!mkdtemp(x)) {
+ if (errno == EROFS || ERRNO_IS_DISK_SPACE(errno))
+ rw = false;
+ else
+ return -errno;
+ }
- y = strjoina(x, "/tmp");
+ if (rw) {
+ r = make_tmp_subdir(x, tmp_path);
+ if (r < 0)
+ 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
+ * file system. */
+ r = mkdir_p(RUN_SYSTEMD_EMPTY, 0500);
+ if (r < 0)
+ return r;
- if (mkdir(y, 0777 | S_ISVTX) < 0)
- return -errno;
+ x = strdup(RUN_SYSTEMD_EMPTY);
+ if (!x)
+ return -ENOMEM;
}
*path = TAKE_PTR(x);
-
return 0;
}
int setup_tmp_dirs(const char *id, char **tmp_dir, char **var_tmp_dir) {
- char *a, *b;
+ _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);
+ r = setup_one_tmp_dir(id, "/tmp", &a, &a_tmp);
if (r < 0)
return r;
- r = setup_one_tmp_dir(id, "/var/tmp", &b);
- if (r < 0) {
- char *t;
-
- t = strjoina(a, "/tmp");
- (void) rmdir(t);
- (void) rmdir(a);
-
- free(a);
+ r = setup_one_tmp_dir(id, "/var/tmp", &b, NULL);
+ if (r < 0)
return r;
- }
- *tmp_dir = a;
- *var_tmp_dir = b;
+ a_tmp = mfree(a_tmp); /* avoid rmdir */
+ *tmp_dir = TAKE_PTR(a);
+ *var_tmp_dir = TAKE_PTR(b);
return 0;
}
#include "util.h"
#include "virt.h"
+static void test_namespace_cleanup_tmpdir(void) {
+ {
+ _cleanup_(namespace_cleanup_tmpdirp) char *dir;
+ assert_se(dir = strdup(RUN_SYSTEMD_EMPTY));
+ }
+
+ {
+ _cleanup_(namespace_cleanup_tmpdirp) char *dir;
+ assert_se(dir = strdup("/tmp/systemd-test-namespace.XXXXXX"));
+ assert_se(mkdtemp(dir));
+ }
+}
+
static void test_tmpdir(const char *id, const char *A, const char *B) {
_cleanup_free_ char *a, *b;
struct stat x, y;
char *c, *d;
assert_se(setup_tmp_dirs(id, &a, &b) == 0);
- assert_se(startswith(a, A));
- assert_se(startswith(b, B));
assert_se(stat(a, &x) >= 0);
assert_se(stat(b, &y) >= 0);
assert_se(S_ISDIR(x.st_mode));
assert_se(S_ISDIR(y.st_mode));
- assert_se((x.st_mode & 01777) == 0700);
- assert_se((y.st_mode & 01777) == 0700);
-
- c = strjoina(a, "/tmp");
- d = strjoina(b, "/tmp");
-
- assert_se(stat(c, &x) >= 0);
- assert_se(stat(d, &y) >= 0);
-
- assert_se(S_ISDIR(x.st_mode));
- assert_se(S_ISDIR(y.st_mode));
-
- assert_se((x.st_mode & 01777) == 01777);
- assert_se((y.st_mode & 01777) == 01777);
-
- assert_se(rmdir(c) >= 0);
- assert_se(rmdir(d) >= 0);
+ if (!streq(a, RUN_SYSTEMD_EMPTY)) {
+ assert_se(startswith(a, A));
+ assert_se((x.st_mode & 01777) == 0700);
+ c = strjoina(a, "/tmp");
+ assert_se(stat(c, &x) >= 0);
+ assert_se(S_ISDIR(x.st_mode));
+ assert_se((x.st_mode & 01777) == 01777);
+ assert_se(rmdir(c) >= 0);
+ assert_se(rmdir(a) >= 0);
+ }
- assert_se(rmdir(a) >= 0);
- assert_se(rmdir(b) >= 0);
+ if (!streq(b, RUN_SYSTEMD_EMPTY)) {
+ assert_se(startswith(b, B));
+ assert_se((y.st_mode & 01777) == 0700);
+ d = strjoina(b, "/tmp");
+ assert_se(stat(d, &y) >= 0);
+ assert_se(S_ISDIR(y.st_mode));
+ assert_se((y.st_mode & 01777) == 01777);
+ assert_se(rmdir(d) >= 0);
+ assert_se(rmdir(b) >= 0);
+ }
}
static void test_netns(void) {
test_setup_logging(LOG_INFO);
+ test_namespace_cleanup_tmpdir();
+
if (!have_namespaces()) {
log_tests_skipped("Don't have namespace support");
return EXIT_TEST_SKIP;