core: add ExtensionDirectories= setting

Add a new setting that follows the same principle and implementation
as ExtensionImages, but using directories as sources.
It will be used to implement support for extending portable images
with directories, since portable services can already use a directory
as root.
This commit is contained in:
Luca Boccassi
2022-01-21 22:53:12 +09:00
committed by Yu Watanabe
parent 071be9701a
commit a07b992606
17 changed files with 216 additions and 13 deletions
+5 -1
View File
@@ -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;
+7
View File
@@ -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) {
+1
View File
@@ -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;
+1
View File
@@ -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)
+118 -12
View File
@@ -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,9 +663,12 @@ 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;
@@ -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;
+1
View File
@@ -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,
+1
View File
@@ -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"))
+1
View File
@@ -207,6 +207,7 @@ TEST(protect_kernel_logs) {
NULL,
NULL,
NULL,
NULL,
NULL);
assert_se(r == 0);
+1
View File
@@ -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");