1
1
mirror of https://github.com/systemd/systemd-stable.git synced 2025-02-08 05:57:26 +03:00

Merge pull request #21448 from poettering/disk-image-purpose

encode disk image purpose in extension-release.d + os-release
This commit is contained in:
Lennart Poettering 2021-11-24 09:37:04 +01:00 committed by GitHub
commit e18dadca24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 431 additions and 101 deletions

11
TODO
View File

@ -94,6 +94,17 @@ Features:
magic into a string. Then use that to replace fstype_magic_to_name() in homed
sources, and similar code.
* man: rework os-release(5), and clearly separate our extension-release.d/ and
initrd-release parts, i.e. list explicitly which fields are about what.
* sysext: before applying a sysext, do a superficial validation run so that
things are not rearranged to wildy. I.e. protect against accidental fuckups,
such as masking out /usr/lib/ or so. We should probably refuse if existing
inodes are replaced by other types of inodes or so.
* sysext: ensure one can build a sysext that can safely apply to *any* system
(because it contains only static go binaries in /opt/ or so)
* userdb: when synthesizing NSS records, pick "best" password from defined
passwords, not just the first. i.e. if there are multiple defined, prefer
unlocked over locked and prefer non-empty over empty.

View File

@ -247,6 +247,20 @@ image. To facilitate 3 and 4 you also need to include a boot loader in the
image. As mentioned, `mkosi -b` takes care of all of that for you, but any
other image generator should work too.
The
[os-release(5)](https://www.freedesktop.org/software/systemd/man/os-release.html)
file may optionally be extended with a `PORTABLE_PREFIXES=` field listing all
supported portable service prefixes for the image (see above). This is useful
for informational purposes (as it allows recognizing portable service images
from their contents as such), but is also useful to protect the image from
being used under a wrong name and prefix. This is particularly relevant if the
images are cryptographically authenticated (via Verity or a similar mechanism)
as this way the (not necessarily authenticated) image file name can be
validated against the (authenticated) image contents. If the field is not
specified the image will work fine, but is not necessarily recognizable as
portable service image, and any set of units included in the image may be
attached, there are no restrictions enforced.
## Extension Images
Portable services can be delivered as one or multiple images that extend the base

View File

@ -407,6 +407,29 @@
<para>Examples: <literal>SYSEXT_LEVEL=2</literal>, <literal>SYSEXT_LEVEL=15.14</literal>.
</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>SYSEXT_SCOPE=</varname></term>
<listitem><para>Takes a space-separated list of one or more of the strings
<literal>system</literal>, <literal>initrd</literal> and <literal>portable</literal>. This field is
only supported in <filename>extension-release.d/</filename> files and indicates what environments
the system extension is applicable to: i.e. to regular systems, to initial RAM filesystems
("initrd") or to portable service images. If unspecified, <literal>SYSEXT_SCOPE=system
portable</literal> is implied, i.e. any system extension without this field is applicable to
regular systems and to portable service environments, but not to initrd
environments.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>PORTABLE_PREFIXES=</varname></term>
<listitem><para>Takes a space-separated list of one or more valid prefix match strings for the
<ulink url="https://systemd.io/PORTABLE_SERVICES">Portable Services</ulink> logic. This field
serves two purposes: it's informational, identifying portable service images as such (and thus
allowing them to be distinguished from other OS images, such as bootable system images); whenever a
portable service image is attached the specified or implied portable service prefix is checked
against this list, to enforce restrictions how images may be attached to a
system.</para></listitem>
</varlistentry>
</variablelist>
</refsect2>

View File

@ -1240,11 +1240,9 @@ static void read_efi_var(const char *variable, char **ret) {
}
static void print_yes_no_line(bool first, bool good, const char *name) {
printf("%s%s%s%s %s\n",
printf("%s%s %s\n",
first ? " Features: " : " ",
ansi_highlight_green_red(good),
special_glyph_check_mark(good),
ansi_normal(),
COLOR_MARK_BOOL(good),
name);
}

View File

@ -1149,7 +1149,7 @@ static int mount_image(const MountEntry *m, const char *root_directory) {
r = verity_dissect_and_mount(
mount_entry_source(m), mount_entry_path(m), m->image_options,
host_os_release_id, host_os_release_version_id, host_os_release_sysext_level);
host_os_release_id, host_os_release_version_id, host_os_release_sysext_level, NULL);
if (r == -ENOENT && m->ignore)
return 0;
if (r == -ESTALE && host_os_release_id)

View File

@ -11,6 +11,7 @@
#include "chase-symlinks.h"
#include "copy.h"
#include "dissect-image.h"
#include "env-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-table.h"
@ -369,6 +370,46 @@ static int strv_pair_to_json(char **l, JsonVariant **ret) {
return json_variant_new_array_strv(ret, jl);
}
static void strv_pair_print(char **l, const char *prefix) {
char **p, **q;
assert(prefix);
STRV_FOREACH_PAIR(p, q, l) {
if (p == l)
printf("%s %s=%s\n", prefix, *p, *q);
else
printf("%*s %s=%s\n", (int) strlen(prefix), "", *p, *q);
}
}
static int get_sysext_scopes(DissectedImage *m, char ***ret_scopes) {
_cleanup_strv_free_ char **l = NULL;
const char *e;
assert(m);
assert(ret_scopes);
/* If there's no extension-release file its not a system extension. Otherwise the SYSEXT_SCOPE field
* indicates which scope it is for and it defaults to "system" + "portable" if unset. */
if (!m->extension_release) {
*ret_scopes = NULL;
return 0;
}
e = strv_env_pairs_get(m->extension_release, "SYSEXT_SCOPE");
if (e)
l = strv_split(e, WHITESPACE);
else
l = strv_new("system", "portable");
if (!l)
return -ENOMEM;
*ret_scopes = TAKE_PTR(l);
return 1;
}
static int action_dissect(DissectedImage *m, LoopDevice *d) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_(table_unrefp) Table *t = NULL;
@ -406,47 +447,51 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) {
else if (r < 0)
return log_error_errno(r, "Failed to acquire image metadata: %m");
else if (arg_json_format_flags & JSON_FORMAT_OFF) {
_cleanup_strv_free_ char **sysext_scopes = NULL;
if (m->hostname)
printf(" Hostname: %s\n", m->hostname);
if (!sd_id128_is_null(m->machine_id))
printf("Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->machine_id));
if (!strv_isempty(m->machine_info)) {
char **p, **q;
STRV_FOREACH_PAIR(p, q, m->machine_info)
printf("%s %s=%s\n",
p == m->machine_info ? "Mach. Info:" : " ",
*p, *q);
}
if (!strv_isempty(m->os_release)) {
char **p, **q;
STRV_FOREACH_PAIR(p, q, m->os_release)
printf("%s %s=%s\n",
p == m->os_release ? "OS Release:" : " ",
*p, *q);
}
if (!strv_isempty(m->extension_release)) {
char **p, **q;
STRV_FOREACH_PAIR(p, q, m->extension_release)
printf("%s %s=%s\n",
p == m->extension_release ? "Extension Release:" : " ",
*p, *q);
}
strv_pair_print(m->machine_info,
"Mach. Info:");
strv_pair_print(m->os_release,
"OS Release:");
strv_pair_print(m->extension_release,
" Ext. Rel.:");
if (m->hostname ||
!sd_id128_is_null(m->machine_id) ||
!strv_isempty(m->machine_info) ||
!strv_isempty(m->extension_release) ||
!strv_isempty(m->os_release))
!strv_isempty(m->os_release) ||
!strv_isempty(m->extension_release))
putc('\n', stdout);
printf(" Use As: %s bootable system for UEFI\n", COLOR_MARK_BOOL(m->partitions[PARTITION_ESP].found));
if (m->has_init_system >= 0)
printf(" %s bootable system for container\n", COLOR_MARK_BOOL(m->has_init_system));
printf(" %s portable service\n",
COLOR_MARK_BOOL(strv_env_pairs_get(m->os_release, "PORTABLE_PREFIXES")));
r = get_sysext_scopes(m, &sysext_scopes);
if (r < 0)
return log_error_errno(r, "Failed to parse SYSEXT_SCOPE: %m");
printf(" %s extension for system\n",
COLOR_MARK_BOOL(strv_contains(sysext_scopes, "system")));
printf(" %s extension for initrd\n",
COLOR_MARK_BOOL(strv_contains(sysext_scopes, "initrd")));
printf(" %s extension for portable service\n",
COLOR_MARK_BOOL(strv_contains(sysext_scopes, "portable")));
putc('\n', stdout);
} else {
_cleanup_(json_variant_unrefp) JsonVariant *mi = NULL, *osr = NULL, *exr = NULL;
_cleanup_strv_free_ char **sysext_scopes = NULL;
if (!strv_isempty(m->machine_info)) {
r = strv_pair_to_json(m->machine_info, &mi);
@ -466,6 +511,10 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) {
return log_oom();
}
r = get_sysext_scopes(m, &sysext_scopes);
if (r < 0)
return log_error_errno(r, "Failed to parse SYSEXT_SCOPE: %m");
r = json_build(&v, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("name", JSON_BUILD_STRING(basename(arg_image))),
JSON_BUILD_PAIR("size", JSON_BUILD_INTEGER(size)),
@ -473,7 +522,13 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) {
JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m->machine_id), "machineId", JSON_BUILD_ID128(m->machine_id)),
JSON_BUILD_PAIR_CONDITION(mi, "machineInfo", JSON_BUILD_VARIANT(mi)),
JSON_BUILD_PAIR_CONDITION(osr, "osRelease", JSON_BUILD_VARIANT(osr)),
JSON_BUILD_PAIR_CONDITION(exr, "extensionRelease", JSON_BUILD_VARIANT(exr))));
JSON_BUILD_PAIR_CONDITION(exr, "extensionRelease", JSON_BUILD_VARIANT(exr)),
JSON_BUILD_PAIR("useBootableUefi", JSON_BUILD_BOOLEAN(m->partitions[PARTITION_ESP].found)),
JSON_BUILD_PAIR_CONDITION(m->has_init_system >= 0, "useBootableContainer", JSON_BUILD_BOOLEAN(m->has_init_system)),
JSON_BUILD_PAIR("usePortableService", JSON_BUILD_BOOLEAN(strv_env_pairs_get(m->os_release, "PORTABLE_MATCHES"))),
JSON_BUILD_PAIR("useSystemExtension", JSON_BUILD_BOOLEAN(strv_contains(sysext_scopes, "system"))),
JSON_BUILD_PAIR("useInitRDExtension", JSON_BUILD_BOOLEAN(strv_contains(sysext_scopes, "initrd"))),
JSON_BUILD_PAIR("usePortableExtension", JSON_BUILD_BOOLEAN(strv_contains(sysext_scopes, "portable")))));
if (r < 0)
return log_oom();
}

View File

@ -13,6 +13,7 @@
#include "discover-image.h"
#include "dissect-image.h"
#include "env-file.h"
#include "env-util.h"
#include "errno-list.h"
#include "escape.h"
#include "extension-release.h"
@ -509,20 +510,20 @@ static int extract_image_and_extensions(
OrderedHashmap **ret_extension_images,
PortableMetadata **ret_os_release,
Hashmap **ret_unit_files,
char ***ret_valid_prefixes,
sd_bus_error *error) {
_cleanup_free_ char *id = NULL, *version_id = NULL, *sysext_level = NULL;
_cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
_cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL;
_cleanup_hashmap_free_ Hashmap *unit_files = NULL;
_cleanup_strv_free_ char **valid_prefixes = NULL;
_cleanup_(image_unrefp) Image *image = NULL;
Image *ext;
int r;
assert(name_or_path);
assert(matches);
assert(ret_image);
assert(ret_extension_images);
r = image_find_harder(IMAGE_PORTABLE, name_or_path, NULL, &image);
if (r < 0)
@ -553,10 +554,12 @@ static int extract_image_and_extensions(
if (r < 0)
return r;
/* If we are layering extension images on top of a runtime image, check that the os-release and extension-release metadata
* match, otherwise reject it immediately as invalid, or it will fail when the units are started. */
if (validate_sysext) {
/* If we are layering extension images on top of a runtime image, check that the os-release and
* extension-release metadata match, otherwise reject it immediately as invalid, or it will fail when
* the units are started. Also, collect valid portable prefixes if caller requested that. */
if (validate_sysext || ret_valid_prefixes) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *prefixes = NULL;
r = take_fdopen_unlocked(&os_release->fd, "r", &f);
if (r < 0)
@ -565,9 +568,16 @@ static int extract_image_and_extensions(
r = parse_env_file(f, os_release->name,
"ID", &id,
"VERSION_ID", &version_id,
"SYSEXT_LEVEL", &sysext_level);
"SYSEXT_LEVEL", &sysext_level,
"PORTABLE_PREFIXES", &prefixes);
if (r < 0)
return r;
if (prefixes) {
valid_prefixes = strv_split(prefixes, WHITESPACE);
if (!valid_prefixes)
return -ENOMEM;
}
}
ORDERED_HASHMAP_FOREACH(ext, extension_images) {
@ -575,6 +585,7 @@ static int extract_image_and_extensions(
_cleanup_hashmap_free_ Hashmap *extra_unit_files = NULL;
_cleanup_strv_free_ char **extension_release = NULL;
_cleanup_fclose_ FILE *f = NULL;
const char *e;
r = portable_extract_by_path(ext->path, /* path_is_extension= */ true, matches, &extension_release_meta, &extra_unit_files, error);
if (r < 0)
@ -584,7 +595,7 @@ static int extract_image_and_extensions(
if (r < 0)
return r;
if (!validate_sysext)
if (!validate_sysext && !ret_valid_prefixes)
continue;
r = take_fdopen_unlocked(&extension_release_meta->fd, "r", &f);
@ -595,19 +606,40 @@ static int extract_image_and_extensions(
if (r < 0)
return r;
r = extension_release_validate(ext->path, id, version_id, sysext_level, extension_release);
if (r == 0)
return sd_bus_error_set_errnof(error, SYNTHETIC_ERRNO(ESTALE), "Image %s extension-release metadata does not match the root's", ext->path);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to compare image %s extension-release metadata with the root's os-release: %m", ext->path);
if (validate_sysext) {
r = extension_release_validate(ext->path, id, version_id, sysext_level, "portable", extension_release);
if (r == 0)
return sd_bus_error_set_errnof(error, SYNTHETIC_ERRNO(ESTALE), "Image %s extension-release metadata does not match the root's", ext->path);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to compare image %s extension-release metadata with the root's os-release: %m", ext->path);
}
e = strv_env_pairs_get(extension_release, "PORTABLE_PREFIXES");
if (e) {
_cleanup_strv_free_ char **l = NULL;
l = strv_split(e, WHITESPACE);
if (!l)
return -ENOMEM;
r = strv_extend_strv(&valid_prefixes, l, true);
if (r < 0)
return r;
}
}
*ret_image = TAKE_PTR(image);
*ret_extension_images = TAKE_PTR(extension_images);
strv_sort(valid_prefixes);
if (ret_image)
*ret_image = TAKE_PTR(image);
if (ret_extension_images)
*ret_extension_images = TAKE_PTR(extension_images);
if (ret_os_release)
*ret_os_release = TAKE_PTR(os_release);
if (ret_unit_files)
*ret_unit_files = TAKE_PTR(unit_files);
if (ret_valid_prefixes)
*ret_valid_prefixes = TAKE_PTR(valid_prefixes);
return 0;
}
@ -618,23 +650,29 @@ int portable_extract(
char **extension_image_paths,
PortableMetadata **ret_os_release,
Hashmap **ret_unit_files,
char ***ret_valid_prefixes,
sd_bus_error *error) {
_cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
_cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL;
_cleanup_hashmap_free_ Hashmap *unit_files = NULL;
_cleanup_(strv_freep) char **valid_prefixes = NULL;
_cleanup_(image_unrefp) Image *image = NULL;
int r;
r = extract_image_and_extensions(name_or_path,
matches,
extension_image_paths,
/* validate_sysext= */ false,
&image,
&extension_images,
&os_release,
&unit_files,
error);
assert(name_or_path);
r = extract_image_and_extensions(
name_or_path,
matches,
extension_image_paths,
/* validate_sysext= */ false,
&image,
&extension_images,
&os_release,
&unit_files,
ret_valid_prefixes ? &valid_prefixes : NULL,
error);
if (r < 0)
return r;
@ -651,8 +689,12 @@ int portable_extract(
isempty(extensions) ? "" : extensions);
}
*ret_os_release = TAKE_PTR(os_release);
*ret_unit_files = TAKE_PTR(unit_files);
if (ret_os_release)
*ret_os_release = TAKE_PTR(os_release);
if (ret_unit_files)
*ret_unit_files = TAKE_PTR(unit_files);
if (ret_valid_prefixes)
*ret_valid_prefixes = TAKE_PTR(valid_prefixes);
return 0;
}
@ -1211,6 +1253,18 @@ static int install_image_and_extensions_symlinks(
return 0;
}
static bool prefix_matches_compatible(char **matches, char **valid_prefixes) {
char **m;
/* Checks if all 'matches' are included in the list of 'valid_prefixes' */
STRV_FOREACH(m, matches)
if (!strv_contains(valid_prefixes, *m))
return false;
return true;
}
int portable_attach(
sd_bus *bus,
const char *name_or_path,
@ -1225,33 +1279,63 @@ int portable_attach(
_cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL;
_cleanup_hashmap_free_ Hashmap *unit_files = NULL;
_cleanup_(lookup_paths_free) LookupPaths paths = {};
_cleanup_strv_free_ char **valid_prefixes = NULL;
_cleanup_(image_unrefp) Image *image = NULL;
PortableMetadata *item;
int r;
r = extract_image_and_extensions(name_or_path,
matches,
extension_image_paths,
/* validate_sysext= */ true,
&image,
&extension_images,
/* os_release= */ NULL,
&unit_files,
error);
r = extract_image_and_extensions(
name_or_path,
matches,
extension_image_paths,
/* validate_sysext= */ true,
&image,
&extension_images,
/* os_release= */ NULL,
&unit_files,
&valid_prefixes,
error);
if (r < 0)
return r;
if (hashmap_isempty(unit_files)) {
_cleanup_free_ char *extensions = strv_join(extension_image_paths, ", ");
if (!extensions)
if (valid_prefixes && !prefix_matches_compatible(matches, valid_prefixes)) {
_cleanup_free_ char *matches_joined = NULL, *extensions_joined = NULL, *valid_prefixes_joined = NULL;
matches_joined = strv_join(matches, "', '");
if (!matches_joined)
return -ENOMEM;
return sd_bus_error_setf(error,
SD_BUS_ERROR_INVALID_ARGS,
"Couldn't find any matching unit files in image '%s%s%s', refusing.",
image->path,
isempty(extensions) ? "" : "' or any of its extensions '",
isempty(extensions) ? "" : extensions);
extensions_joined = strv_join(extension_image_paths, ", ");
if (!extensions_joined)
return -ENOMEM;
valid_prefixes_joined = strv_join(valid_prefixes, ", ");
if (!valid_prefixes_joined)
return -ENOMEM;
return sd_bus_error_setf(
error,
SD_BUS_ERROR_INVALID_ARGS,
"Selected matches '%s' are not compatible with portable service image '%s%s%s', refusing. (Acceptable prefix matches are: %s)",
matches_joined,
image->path,
isempty(extensions_joined) ? "" : "' or any of its extensions '",
strempty(extensions_joined),
valid_prefixes_joined);
}
if (hashmap_isempty(unit_files)) {
_cleanup_free_ char *extensions_joined = strv_join(extension_image_paths, ", ");
if (!extensions_joined)
return -ENOMEM;
return sd_bus_error_setf(
error,
SD_BUS_ERROR_INVALID_ARGS,
"Couldn't find any matching unit files in image '%s%s%s', refusing.",
image->path,
isempty(extensions_joined) ? "" : "' or any of its extensions '",
strempty(extensions_joined));
}
r = lookup_paths_init(&paths, UNIT_FILE_SYSTEM, LOOKUP_PATHS_SPLIT_USR, NULL);

View File

@ -65,7 +65,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(PortableMetadata*, portable_metadata_unref);
int portable_metadata_hashmap_to_sorted_array(Hashmap *unit_files, PortableMetadata ***ret);
int portable_extract(const char *image, char **matches, char **extension_image_paths, PortableMetadata **ret_os_release, Hashmap **ret_unit_files, sd_bus_error *error);
int portable_extract(const char *image, char **matches, char **extension_image_paths, PortableMetadata **ret_os_release, Hashmap **ret_unit_files, char ***ret_valid_prefixes, sd_bus_error *error);
int portable_attach(sd_bus *bus, const char *name_or_path, char **matches, const char *profile, char **extension_images, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
int portable_detach(sd_bus *bus, const char *name_or_path, char **extension_image_paths, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);

View File

@ -162,6 +162,7 @@ int bus_image_common_get_metadata(
extension_images,
&os_release,
&unit_files,
NULL,
error);
if (r < 0)
return r;

View File

@ -46,6 +46,7 @@
#include "hostname-setup.h"
#include "id128-util.h"
#include "import-util.h"
#include "io-util.h"
#include "mkdir-label.h"
#include "mount-util.h"
#include "mountpoint-util.h"
@ -748,10 +749,14 @@ int dissect_image(
if (r != 0)
return errno_or_else(EIO);
m = new0(DissectedImage, 1);
m = new(DissectedImage, 1);
if (!m)
return -ENOMEM;
*m = (DissectedImage) {
.has_init_system = -1,
};
r = sd_device_get_sysname(d, &sysname);
if (r < 0)
return log_debug_errno(r, "Failed to get device sysname: %m");
@ -3012,6 +3017,7 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
META_MACHINE_INFO,
META_OS_RELEASE,
META_EXTENSION_RELEASE,
META_HAS_INIT_SYSTEM,
_META_MAX,
};
@ -3021,7 +3027,8 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
[META_MACHINE_INFO] = "/etc/machine-info\0",
[META_OS_RELEASE] = ("/etc/os-release\0"
"/usr/lib/os-release\0"),
[META_EXTENSION_RELEASE] = "extension-release\0", /* Used only for logging. */
[META_EXTENSION_RELEASE] = "extension-release\0", /* Used only for logging. */
[META_HAS_INIT_SYSTEM] = "has-init-system\0", /* ditto */
};
_cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL, **extension_release = NULL;
@ -3032,6 +3039,7 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
_cleanup_free_ char *hostname = NULL;
unsigned n_meta_initialized = 0;
int fds[2 * _META_MAX], r, v;
int has_init_system = -1;
ssize_t n;
BLOCK_SIGNALS(SIGCHLD);
@ -3063,6 +3071,7 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
if (r < 0)
goto finish;
if (r == 0) {
/* Child in a new mount namespace */
error_pipe[0] = safe_close(error_pipe[0]);
r = dissected_image_mount(
@ -3092,7 +3101,9 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
fds[2*k] = safe_close(fds[2*k]);
if (k == META_EXTENSION_RELEASE) {
switch (k) {
case META_EXTENSION_RELEASE:
/* As per the os-release spec, if the image is an extension it will have a file
* named after the image name in extension-release.d/ - we use the image name
* and try to resolve it with the extension-release helpers, as sometimes
@ -3105,12 +3116,42 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
r = open_extension_release(t, m->image_name, NULL, &fd);
if (r < 0)
fd = r; /* Propagate the error. */
} else
break;
case META_HAS_INIT_SYSTEM: {
bool found = false;
const char *init;
FOREACH_STRING(init,
"/usr/lib/systemd/systemd", /* systemd on /usr merged system */
"/lib/systemd/systemd", /* systemd on /usr non-merged systems */
"/sbin/init") { /* traditional path the Linux kernel invokes */
r = chase_symlinks(init, t, CHASE_PREFIX_ROOT, NULL, NULL);
if (r < 0) {
if (r != -ENOENT)
log_debug_errno(r, "Failed to resolve %s, ignoring: %m", init);
} else {
found = true;
break;
}
}
r = loop_write(fds[2*k+1], &found, sizeof(found), false);
if (r < 0)
goto inner_fail;
continue;
}
default:
NULSTR_FOREACH(p, paths[k]) {
fd = chase_symlinks_and_open(p, t, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
if (fd >= 0)
break;
}
}
if (fd < 0) {
log_debug_errno(fd, "Failed to read %s file of image, ignoring: %m", paths[k]);
fds[2*k+1] = safe_close(fds[2*k+1]);
@ -3118,15 +3159,17 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
}
r = copy_bytes(fd, fds[2*k+1], UINT64_MAX, 0);
if (r < 0) {
(void) write(error_pipe[1], &r, sizeof(r));
_exit(EXIT_FAILURE);
}
if (r < 0)
goto inner_fail;
fds[2*k+1] = safe_close(fds[2*k+1]);
}
_exit(EXIT_SUCCESS);
inner_fail:
(void) write(error_pipe[1], &r, sizeof(r));
_exit(EXIT_FAILURE);
}
error_pipe[1] = safe_close(error_pipe[1]);
@ -3194,7 +3237,20 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
log_debug_errno(r, "Failed to read extension release file: %m");
break;
}
case META_HAS_INIT_SYSTEM: {
bool b = false;
size_t nr;
errno = 0;
nr = fread(&b, 1, sizeof(b), f);
if (nr != sizeof(b))
log_debug_errno(errno_or_else(EIO), "Failed to read has-init-system boolean: %m");
else
has_init_system = b;
break;
}}
}
r = wait_for_terminate_and_check("(sd-dissect)", child, 0);
@ -3218,6 +3274,7 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
strv_free_and_replace(m->machine_info, machine_info);
strv_free_and_replace(m->os_release, os_release);
strv_free_and_replace(m->extension_release, extension_release);
m->has_init_system = has_init_system;
finish:
for (unsigned k = 0; k < n_meta_initialized; k++)
@ -3468,7 +3525,8 @@ int verity_dissect_and_mount(
const MountOptions *options,
const char *required_host_os_release_id,
const char *required_host_os_release_version_id,
const char *required_host_os_release_sysext_level) {
const char *required_host_os_release_sysext_level,
const char *required_sysext_scope) {
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
_cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL;
@ -3554,11 +3612,12 @@ int verity_dissect_and_mount(
return log_debug_errno(r, "Failed to parse image %s extension-release metadata: %m", dissected_image->image_name);
r = extension_release_validate(
dissected_image->image_name,
required_host_os_release_id,
required_host_os_release_version_id,
required_host_os_release_sysext_level,
extension_release);
dissected_image->image_name,
required_host_os_release_id,
required_host_os_release_version_id,
required_host_os_release_sysext_level,
required_sysext_scope,
extension_release);
if (r == 0)
return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "Image %s extension-release metadata does not match the root's", dissected_image->image_name);
if (r < 0)

View File

@ -163,6 +163,7 @@ struct DissectedImage {
char **machine_info;
char **os_release;
char **extension_release;
int has_init_system;
};
struct MountOptions {
@ -227,4 +228,4 @@ bool dissected_image_verity_sig_ready(const DissectedImage *image, PartitionDesi
int mount_image_privately_interactively(const char *path, DissectImageFlags flags, char **ret_directory, LoopDevice **ret_loop_device, DecryptedImage **ret_decrypted_image);
int verity_dissect_and_mount(const char *src, const char *dest, const MountOptions *options, const char *required_host_os_release_id, const char *required_host_os_release_version_id, const char *required_host_os_release_sysext_level);
int verity_dissect_and_mount(const char *src, const char *dest, const MountOptions *options, const char *required_host_os_release_id, const char *required_host_os_release_version_id, const char *required_host_os_release_sysext_level, const char *required_sysext_scope);

View File

@ -12,6 +12,7 @@ int extension_release_validate(
const char *host_os_release_id,
const char *host_os_release_version_id,
const char *host_os_release_sysext_level,
const char *host_sysext_scope,
char **extension_release) {
const char *extension_release_id = NULL, *extension_release_sysext_level = NULL;
@ -25,6 +26,28 @@ int extension_release_validate(
return 0;
}
if (host_sysext_scope) {
_cleanup_strv_free_ char **extension_sysext_scope_list = NULL;
const char *extension_sysext_scope;
bool valid;
extension_sysext_scope = strv_env_pairs_get(extension_release, "SYSEXT_SCOPE");
if (extension_sysext_scope) {
extension_sysext_scope_list = strv_split(extension_sysext_scope, WHITESPACE);
if (!extension_sysext_scope_list)
return -ENOMEM;
}
/* by default extension are good for attachment in portable service and on the system */
valid = strv_contains(
extension_sysext_scope_list ?: STRV_MAKE("system", "portable"),
host_sysext_scope);
if (!valid) {
log_debug("Extension '%s' is not suitable for scope %s, ignoring extension.", name, host_sysext_scope);
return 0;
}
}
extension_release_id = strv_env_pairs_get(extension_release, "ID");
if (isempty(extension_release_id)) {
log_debug("Extension '%s' does not contain ID in extension-release but requested to match '%s'",

View File

@ -9,6 +9,7 @@ int extension_release_validate(
const char *host_os_release_id,
const char *host_os_release_version_id,
const char *host_os_release_sysext_level,
const char *host_sysext_scope,
char **extension_release);
/* Parse SYSTEMD_SYSEXT_HIERARCHIES and if not set, return "/usr /opt" */

View File

@ -874,7 +874,7 @@ static int mount_in_namespace(
mount_tmp_created = true;
if (is_image)
r = verity_dissect_and_mount(FORMAT_PROC_FD_PATH(chased_src_fd), mount_tmp, options, NULL, NULL, NULL);
r = verity_dissect_and_mount(FORMAT_PROC_FD_PATH(chased_src_fd), mount_tmp, options, NULL, NULL, NULL, NULL);
else
r = mount_follow_verbose(LOG_DEBUG, FORMAT_PROC_FD_PATH(chased_src_fd), mount_tmp, NULL, MS_BIND, NULL);
if (r < 0)

View File

@ -1,6 +1,9 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "glyph-util.h"
#include "terminal-util.h"
void print_separator(void);
int file_url_from_path(const char *path, char **ret);
@ -17,3 +20,23 @@ typedef enum CatFlags {
int cat_files(const char *file, char **dropins, CatFlags flags);
int conf_files_cat(const char *root, const char *name);
#define RED_CROSS_MARK_MAX (STRLEN(ANSI_HIGHLIGHT_RED) + STRLEN("✗") + STRLEN(ANSI_NORMAL) + 1)
#define GREEN_CHECK_MARK_MAX (STRLEN(ANSI_HIGHLIGHT_GREEN) + STRLEN("✓") + STRLEN(ANSI_NORMAL) + 1)
static inline const char *red_cross_mark_internal(char buffer[static RED_CROSS_MARK_MAX]) {
assert(buffer);
assert_se(stpcpy(stpcpy(stpcpy(buffer, ansi_highlight_red()), special_glyph(SPECIAL_GLYPH_CROSS_MARK)), ansi_normal()) < buffer + RED_CROSS_MARK_MAX);
return buffer;
}
static inline const char *green_check_mark_internal(char buffer[static GREEN_CHECK_MARK_MAX]) {
assert(buffer);
assert_se(stpcpy(stpcpy(stpcpy(buffer, ansi_highlight_green()), special_glyph(SPECIAL_GLYPH_CHECK_MARK)), ansi_normal()) < buffer + GREEN_CHECK_MARK_MAX);
return buffer;
}
#define RED_CROSS_MARK() red_cross_mark_internal((char[RED_CROSS_MARK_MAX]) {})
#define GREEN_CHECK_MARK() green_check_mark_internal((char[GREEN_CHECK_MARK_MAX]) {})
#define COLOR_MARK_BOOL(b) ((b) ? GREEN_CHECK_MARK() : RED_CROSS_MARK())

View File

@ -432,12 +432,17 @@ static int validate_version(
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Extension image contains /usr/lib/os-release file, which is not allowed (it may carry /etc/os-release), refusing.");
return extension_release_validate(
r = extension_release_validate(
img->name,
host_os_release_id,
host_os_release_version_id,
host_os_release_sysext_level,
in_initrd() ? "initrd" : "system",
img->extension_release);
if (r < 0)
return log_error_errno(r, "Failed to validate extension release information: %m");
return r;
}
static int merge_subprocess(Hashmap *images, const char *workspace) {

View File

@ -4,6 +4,7 @@
#include "glyph-util.h"
#include "gpt.h"
#include "log.h"
#include "pretty-print.h"
#include "strv.h"
#include "terminal-util.h"
#include "tests.h"
@ -32,11 +33,11 @@ static void test_gpt_types_against_architectures(void) {
r = gpt_partition_type_uuid_from_string(joined, &id);
if (r < 0) {
printf("%s%s%s %s\n", ansi_highlight_red(), special_glyph(SPECIAL_GLYPH_CROSS_MARK), ansi_normal(), joined);
printf("%s %s\n", RED_CROSS_MARK(), joined);
continue;
}
printf("%s%s%s %s\n", ansi_highlight_green(), special_glyph(SPECIAL_GLYPH_CHECK_MARK), ansi_normal(), joined);
printf("%s %s\n", GREEN_CHECK_MARK(), joined);
if (streq(prefix, "root-") && streq(suffix, ""))
assert_se(gpt_partition_type_is_root(id));

View File

@ -31,11 +31,25 @@ static void test_cat_files(void) {
assert_se(cat_files("/etc/fstab", STRV_MAKE("/etc/fstab", "/etc/fstab"), 0) == 0);
}
static void test_red_green_cross_check_mark(void) {
bool b = false;
printf("yeah: <%s>\n", GREEN_CHECK_MARK());
printf("nay: <%s>\n", RED_CROSS_MARK());
printf("%s → %s → %s → %s\n",
COLOR_MARK_BOOL(b),
COLOR_MARK_BOOL(!b),
COLOR_MARK_BOOL(!!b),
COLOR_MARK_BOOL(!!!b));
}
int main(int argc, char *argv[]) {
test_setup_logging(LOG_INFO);
test_terminal_urlify();
test_cat_files();
test_red_green_cross_check_mark();
print_separator();

View File

@ -594,6 +594,7 @@ install_verity_minimal() {
touch "$initdir/etc/machine-id" "$initdir/etc/resolv.conf"
touch "$initdir/opt/some_file"
echo MARKER=1 >>"$initdir/usr/lib/os-release"
echo "PORTABLE_PREFIXES=app0 minimal" >>"$initdir/usr/lib/os-release"
echo -e "[Service]\nExecStartPre=cat /usr/lib/os-release\nExecStart=sleep 120" >"$initdir/usr/lib/systemd/system/app0.service"
cp "$initdir/usr/lib/systemd/system/app0.service" "$initdir/usr/lib/systemd/system/app0-foo.service"
@ -638,7 +639,9 @@ EOF
export initdir="$TESTDIR/app1"
mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system" "$initdir/opt"
grep "^ID=" "$os_release" >"$initdir/usr/lib/extension-release.d/extension-release.app2"
echo "${version_id}" >>"$initdir/usr/lib/extension-release.d/extension-release.app2"
( echo "${version_id}"
echo "SYSEXT_SCOPE=portable"
echo "PORTABLE_PREFIXES=app1" ) >>"$initdir/usr/lib/extension-release.d/extension-release.app2"
setfattr -n user.extension-release.strict -v false "$initdir/usr/lib/extension-release.d/extension-release.app2"
cat >"$initdir/usr/lib/systemd/system/app1.service" <<EOF
[Service]

View File

@ -12,6 +12,11 @@ if [[ -v ASAN_OPTIONS || -v UBSAN_OPTIONS ]]; then
ARGS+=(--profile=trusted)
fi
systemd-dissect --no-pager /usr/share/minimal_0.raw | grep -q '✓ portable service'
systemd-dissect --no-pager /usr/share/minimal_1.raw | grep -q '✓ portable service'
systemd-dissect --no-pager /usr/share/app0.raw | grep -q '✓ extension for portable service'
systemd-dissect --no-pager /usr/share/app1.raw | grep -q '✓ extension for portable service'
export SYSTEMD_LOG_LEVEL=debug
mkdir -p /run/systemd/system/systemd-portabled.service.d/
cat <<EOF >/run/systemd/system/systemd-portabled.service.d/override.conf
@ -98,10 +103,19 @@ portablectl detach --now --runtime --extension /usr/share/app1.raw /usr/share/mi
# portablectl also works with directory paths rather than images
mkdir /tmp/rootdir /tmp/app1 /tmp/overlay
mkdir /tmp/rootdir /tmp/app1 /tmp/overlay /tmp/os-release-fix /tmp/os-release-fix/etc
mount /usr/share/app1.raw /tmp/app1
mount /usr/share/minimal_0.raw /tmp/rootdir
mount -t overlay overlay -o lowerdir=/tmp/app1:/tmp/rootdir /tmp/overlay
# Fix up os-release to drop the valid PORTABLE_SERVICES field (because we are
# bypassing the sysext logic in portabled here it will otherwise not see the
# extensions additional valid prefix)
grep -v "^PORTABLE_PREFIXES=" /tmp/rootdir/etc/os-release > /tmp/os-release-fix/etc/os-release
mount -t overlay overlay -o lowerdir=/tmp/os-release-fix:/tmp/app1:/tmp/rootdir /tmp/overlay
grep . /tmp/overlay/usr/lib/extension-release.d/*
grep . /tmp/overlay/etc/os-release
portablectl "${ARGS[@]}" attach --copy=symlink --now --runtime /tmp/overlay app1