summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/dbus-execute.c6
-rw-r--r--src/core/execute.c7
-rw-r--r--src/core/execute.h1
-rw-r--r--src/core/load-fragment-gperf.gperf.in1
-rw-r--r--src/core/namespace.c130
-rw-r--r--src/core/namespace.h1
-rw-r--r--src/shared/bus-unit-util.c1
-rw-r--r--src/test/test-namespace.c1
-rw-r--r--src/test/test-ns.c1
9 files changed, 136 insertions, 13 deletions
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
index 5c499e5d06..8a8d9c9b2e 100644
--- a/src/core/dbus-execute.c
+++ b/src/core/dbus-execute.c
@@ -1204,6 +1204,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("RootHashSignature", "ay", property_get_root_hash_sig, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RootHashSignaturePath", "s", NULL, offsetof(ExecContext, root_hash_sig_path), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RootVerity", "s", NULL, offsetof(ExecContext, root_verity), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("ExtensionDirectories", "as", NULL, offsetof(ExecContext, extension_directories), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ExtensionImages", "a(sba(ss))", property_get_extension_images, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MountImages", "a(ssba(ss))", property_get_mount_images, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OOMScoreAdjust", "i", property_get_oom_score_adjust, 0, SD_BUS_VTABLE_PROPERTY_CONST),
@@ -3261,7 +3262,8 @@ int bus_exec_context_set_transient_property(
return 1;
} else if (STR_IN_SET(name, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories",
- "ReadWritePaths", "ReadOnlyPaths", "InaccessiblePaths", "ExecPaths", "NoExecPaths")) {
+ "ReadWritePaths", "ReadOnlyPaths", "InaccessiblePaths", "ExecPaths", "NoExecPaths",
+ "ExtensionDirectories")) {
_cleanup_strv_free_ char **l = NULL;
char ***dirs;
char **p;
@@ -3291,6 +3293,8 @@ int bus_exec_context_set_transient_property(
dirs = &c->exec_paths;
else if (streq(name, "NoExecPaths"))
dirs = &c->no_exec_paths;
+ else if (streq(name, "ExtensionDirectories"))
+ dirs = &c->extension_directories;
else /* "InaccessiblePaths" */
dirs = &c->inaccessible_paths;
diff --git a/src/core/execute.c b/src/core/execute.c
index eb25c98925..78c8d966df 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -2065,6 +2065,9 @@ bool exec_needs_mount_namespace(
if (context->n_extension_images > 0)
return true;
+ if (!strv_isempty(context->extension_directories))
+ return true;
+
if (!IN_SET(context->mount_flags, 0, MS_SHARED))
return true;
@@ -3566,6 +3569,7 @@ static int apply_mount_namespace(
context->root_verity,
context->extension_images,
context->n_extension_images,
+ context->extension_directories,
propagate_dir,
incoming_dir,
root_dir || root_image ? params->notify_socket : NULL,
@@ -5244,6 +5248,7 @@ void exec_context_done(ExecContext *c) {
c->root_hash_sig_path = mfree(c->root_hash_sig_path);
c->root_verity = mfree(c->root_verity);
c->extension_images = mount_image_free_many(c->extension_images, &c->n_extension_images);
+ c->extension_directories = strv_free(c->extension_directories);
c->tty_path = mfree(c->tty_path);
c->syslog_identifier = mfree(c->syslog_identifier);
c->user = mfree(c->user);
@@ -6120,6 +6125,8 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
strempty(o->options));
fprintf(f, "\n");
}
+
+ strv_dump(f, prefix, "ExtensionDirectories", c->extension_directories);
}
bool exec_context_maintains_privileges(const ExecContext *c) {
diff --git a/src/core/execute.h b/src/core/execute.h
index 805e9b4765..4aff50b442 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -273,6 +273,7 @@ struct ExecContext {
size_t n_mount_images;
MountImage *extension_images;
size_t n_extension_images;
+ char **extension_directories;
uint64_t capability_bounding_set;
uint64_t capability_ambient_set;
diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in
index deea540e10..cc04393c1e 100644
--- a/src/core/load-fragment-gperf.gperf.in
+++ b/src/core/load-fragment-gperf.gperf.in
@@ -9,6 +9,7 @@
{{type}}.RootHash, config_parse_exec_root_hash, 0, offsetof({{type}}, exec_context)
{{type}}.RootHashSignature, config_parse_exec_root_hash_sig, 0, offsetof({{type}}, exec_context)
{{type}}.RootVerity, config_parse_unit_path_printf, true, offsetof({{type}}, exec_context.root_verity)
+{{type}}.ExtensionDirectories, config_parse_namespace_path_strv, 0, offsetof({{type}}, exec_context.extension_directories)
{{type}}.ExtensionImages, config_parse_extension_images, 0, offsetof({{type}}, exec_context)
{{type}}.MountImages, config_parse_mount_images, 0, offsetof({{type}}, exec_context)
{{type}}.User, config_parse_user_group_compat, 0, offsetof({{type}}, exec_context.user)
diff --git a/src/core/namespace.c b/src/core/namespace.c
index ecbd23833c..088cb09ac9 100644
--- a/src/core/namespace.c
+++ b/src/core/namespace.c
@@ -63,6 +63,7 @@ typedef enum MountMode {
EXEC,
TMPFS,
RUN,
+ EXTENSION_DIRECTORIES, /* Bind-mounted outside the root directory, and used by subsequent mounts */
EXTENSION_IMAGES, /* Mounted outside the root directory, and used by subsequent mounts */
MQUEUEFS,
READWRITE_IMPLICIT, /* Should have the lowest priority. */
@@ -408,22 +409,23 @@ static int append_mount_images(MountEntry **p, const MountImage *mount_images, s
return 0;
}
-static int append_extension_images(
+static int append_extensions(
MountEntry **p,
const char *root,
const char *extension_dir,
char **hierarchies,
const MountImage *mount_images,
- size_t n) {
+ size_t n,
+ char **extension_directories) {
_cleanup_strv_free_ char **overlays = NULL;
- char **hierarchy;
+ char **hierarchy, **extension_directory;
int r;
assert(p);
assert(extension_dir);
- if (n == 0)
+ if (n == 0 && strv_isempty(extension_directories))
return 0;
/* Prepare a list of overlays, that will have as each element a string suitable for being
@@ -482,6 +484,62 @@ static int append_extension_images(
};
}
+ /* Secondly, extend the lowerdir= parameters with each ExtensionDirectory.
+ * Bind mount them in the same location as the ExtensionImages, so that we
+ * can check that they are valid trees (extension-release.d). */
+ STRV_FOREACH(extension_directory, extension_directories) {
+ _cleanup_free_ char *mount_point = NULL, *source = NULL;
+ const char *e = *extension_directory;
+ bool ignore_enoent = false;
+
+ /* Pick up the counter where the ExtensionImages left it. */
+ r = asprintf(&mount_point, "%s/%zu", extension_dir, n++);
+ if (r < 0)
+ return -ENOMEM;
+
+ /* Look for any prefixes */
+ if (startswith(e, "-")) {
+ e++;
+ ignore_enoent = true;
+ }
+ /* Ignore this for now */
+ if (startswith(e, "+"))
+ e++;
+
+ source = strdup(e);
+ if (!source)
+ return -ENOMEM;
+
+ for (size_t j = 0; hierarchies && hierarchies[j]; ++j) {
+ _cleanup_free_ char *prefixed_hierarchy = NULL, *escaped = NULL, *lowerdir = NULL;
+
+ prefixed_hierarchy = path_join(mount_point, hierarchies[j]);
+ if (!prefixed_hierarchy)
+ return -ENOMEM;
+
+ escaped = shell_escape(prefixed_hierarchy, ",:");
+ if (!escaped)
+ return -ENOMEM;
+
+ /* Note that lowerdir= parameters are in 'reverse' order, so the
+ * top-most directory in the overlay comes first in the list. */
+ lowerdir = strjoin(escaped, ":", overlays[j]);
+ if (!lowerdir)
+ return -ENOMEM;
+
+ free_and_replace(overlays[j], lowerdir);
+ }
+
+ *((*p)++) = (MountEntry) {
+ .path_malloc = TAKE_PTR(mount_point),
+ .source_const = TAKE_PTR(source),
+ .mode = EXTENSION_DIRECTORIES,
+ .ignore = ignore_enoent,
+ .has_prefix = true,
+ .read_only = true,
+ };
+ }
+
/* Then, for each hierarchy, prepare an overlay with the list of lowerdir= strings
* set up earlier. */
for (size_t i = 0; hierarchies && hierarchies[i]; ++i) {
@@ -605,11 +663,14 @@ static int append_protect_system(MountEntry **p, ProtectSystem protect_system, b
static int mount_path_compare(const MountEntry *a, const MountEntry *b) {
int d;
- /* EXTENSION_IMAGES will be used by other mounts as a base, so sort them first
+ /* ExtensionImages/Directories will be used by other mounts as a base, so sort them first
* regardless of the prefix - they are set up in the propagate directory anyway */
d = -CMP(a->mode == EXTENSION_IMAGES, b->mode == EXTENSION_IMAGES);
if (d != 0)
return d;
+ d = -CMP(a->mode == EXTENSION_DIRECTORIES, b->mode == EXTENSION_DIRECTORIES);
+ if (d != 0)
+ return d;
/* If the paths are not equal, then order prefixes first */
d = path_compare(mount_entry_path(a), mount_entry_path(b));
@@ -757,8 +818,8 @@ static void drop_outside_root(const char *root_directory, MountEntry *m, size_t
for (f = m, t = m; f < m + *n; f++) {
- /* ExtensionImages bases are opened in /run/systemd/unit-extensions on the host */
- if (f->mode != EXTENSION_IMAGES && !path_startswith(mount_entry_path(f), root_directory)) {
+ /* ExtensionImages/Directories bases are opened in /run/systemd/unit-extensions on the host */
+ if (!IN_SET(f->mode, EXTENSION_IMAGES, EXTENSION_DIRECTORIES) && !path_startswith(mount_entry_path(f), root_directory)) {
log_debug("%s is outside of root directory.", mount_entry_path(f));
mount_entry_done(f);
continue;
@@ -1296,6 +1357,47 @@ static int apply_one_mount(
what = mount_entry_path(m);
break;
+ case EXTENSION_DIRECTORIES: {
+ _cleanup_free_ char *host_os_release_id = NULL, *host_os_release_version_id = NULL,
+ *host_os_release_sysext_level = NULL, *extension_name = NULL;
+ _cleanup_strv_free_ char **extension_release = NULL;
+
+ r = path_extract_filename(mount_entry_source(m), &extension_name);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to extract extension name from %s: %m", mount_entry_source(m));
+
+ r = parse_os_release(
+ empty_to_root(root_directory),
+ "ID", &host_os_release_id,
+ "VERSION_ID", &host_os_release_version_id,
+ "SYSEXT_LEVEL", &host_os_release_sysext_level,
+ NULL);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to acquire 'os-release' data of OS tree '%s': %m", empty_to_root(root_directory));
+ if (isempty(host_os_release_id))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'ID' field not found or empty in 'os-release' data of OS tree '%s': %m", empty_to_root(root_directory));
+
+ r = load_extension_release_pairs(mount_entry_source(m), extension_name, &extension_release);
+ if (r == -ENOENT && m->ignore)
+ return 0;
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse directory %s extension-release metadata: %m", extension_name);
+
+ r = extension_release_validate(
+ extension_name,
+ host_os_release_id,
+ host_os_release_version_id,
+ host_os_release_sysext_level,
+ /* host_sysext_scope */ NULL, /* Leave empty, we need to accept both system and portable */
+ extension_release);
+ if (r == 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "Directory %s extension-release metadata does not match the root's", extension_name);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to compare directory %s extension-release metadata with the root's os-release: %m", extension_name);
+
+ _fallthrough_;
+ }
+
case BIND_MOUNT:
rbind = false;
@@ -1525,6 +1627,7 @@ static size_t namespace_calculate_mounts(
size_t n_temporary_filesystems,
size_t n_mount_images,
size_t n_extension_images,
+ size_t n_extension_directories,
size_t n_hierarchies,
const char* tmp_dir,
const char* var_tmp_dir,
@@ -1559,7 +1662,8 @@ static size_t namespace_calculate_mounts(
strv_length(empty_directories) +
n_bind_mounts +
n_mount_images +
- (n_extension_images > 0 ? n_hierarchies + n_extension_images : 0) + /* Mount each image plus an overlay per hierarchy */
+ (n_extension_images > 0 || n_extension_directories > 0 ? /* Mount each image and directory plus an overlay per hierarchy */
+ n_hierarchies + n_extension_images + n_extension_directories: 0) +
n_temporary_filesystems +
ns_info->private_dev +
(ns_info->protect_kernel_tunables ?
@@ -1655,8 +1759,8 @@ static int apply_mounts(
if (m->applied)
continue;
- /* ExtensionImages are first opened in the propagate directory, not in the root_directory */
- r = follow_symlink(m->mode != EXTENSION_IMAGES ? root : NULL, m);
+ /* ExtensionImages/Directories are first opened in the propagate directory, not in the root_directory */
+ r = follow_symlink(!IN_SET(m->mode, EXTENSION_IMAGES, EXTENSION_DIRECTORIES) ? root : NULL, m);
if (r < 0) {
if (error_path && mount_entry_path(m))
*error_path = strdup(mount_entry_path(m));
@@ -1879,6 +1983,7 @@ int setup_namespace(
const char *verity_data_path,
const MountImage *extension_images,
size_t n_extension_images,
+ char **extension_directories,
const char *propagate_dir,
const char *incoming_dir,
const char *notify_socket,
@@ -1992,7 +2097,7 @@ int setup_namespace(
require_prefix = true;
}
- if (n_extension_images > 0) {
+ if (n_extension_images > 0 || !strv_isempty(extension_directories)) {
r = parse_env_extension_hierarchies(&hierarchies);
if (r < 0)
return r;
@@ -2010,6 +2115,7 @@ int setup_namespace(
n_temporary_filesystems,
n_mount_images,
n_extension_images,
+ strv_length(extension_directories),
strv_length(hierarchies),
tmp_dir, var_tmp_dir,
creds_path,
@@ -2078,7 +2184,7 @@ int setup_namespace(
if (r < 0)
goto finish;
- r = append_extension_images(&m, root, extension_dir, hierarchies, extension_images, n_extension_images);
+ r = append_extensions(&m, root, extension_dir, hierarchies, extension_images, n_extension_images, extension_directories);
if (r < 0)
goto finish;
diff --git a/src/core/namespace.h b/src/core/namespace.h
index 62f05d7585..ae84d2b03b 100644
--- a/src/core/namespace.h
+++ b/src/core/namespace.h
@@ -142,6 +142,7 @@ int setup_namespace(
const char *root_verity,
const MountImage *extension_images,
size_t n_extension_images,
+ char **extension_directories,
const char *propagate_dir,
const char *incoming_dir,
const char *notify_socket,
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index dcce530c99..c35dd286e6 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -973,6 +973,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
"ExecPaths",
"NoExecPaths",
"ExecSearchPath",
+ "ExtensionDirectories",
"ConfigurationDirectory",
"SupplementaryGroups",
"SystemCallArchitectures"))
diff --git a/src/test/test-namespace.c b/src/test/test-namespace.c
index 8df5533d6e..09c3091641 100644
--- a/src/test/test-namespace.c
+++ b/src/test/test-namespace.c
@@ -207,6 +207,7 @@ TEST(protect_kernel_logs) {
NULL,
NULL,
NULL,
+ NULL,
NULL);
assert_se(r == 0);
diff --git a/src/test/test-ns.c b/src/test/test-ns.c
index b03eabb59b..cd455a3a5b 100644
--- a/src/test/test-ns.c
+++ b/src/test/test-ns.c
@@ -108,6 +108,7 @@ int main(int argc, char *argv[]) {
NULL,
NULL,
NULL,
+ NULL,
NULL);
if (r < 0) {
log_error_errno(r, "Failed to set up namespace: %m");