mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 00:14:32 +09:00
portable,sysext: match extension OS ID also against host ID_LIKE
This commit is contained in:
committed by
Yu Watanabe
parent
703fbb0bba
commit
0af9937691
@@ -108,8 +108,9 @@
|
||||
follows the syntax and rules as described in the <ulink
|
||||
url="https://systemd.io/PORTABLE_SERVICES">Portable Services</ulink> page. The purpose of this
|
||||
file is to identify the extension and to allow the operating system to verify that the extension image
|
||||
matches the base OS. This is typically implemented by checking that the <varname>ID=</varname> options
|
||||
match, and either <varname>SYSEXT_LEVEL=</varname> exists and matches too, or if it is not present,
|
||||
matches the base OS. This is typically implemented by checking that the extension <varname>ID=</varname>
|
||||
option either matches the host <varname>ID=</varname> option or is included the host <varname>ID_LIKE=</varname>
|
||||
option, and either <varname>SYSEXT_LEVEL=</varname> exists and matches too, or if it is not present,
|
||||
<varname>VERSION_ID=</varname> exists and matches. This ensures ABI/API compatibility between the
|
||||
layers and prevents merging of an incompatible image in an overlay.</para>
|
||||
|
||||
|
||||
@@ -1596,6 +1596,7 @@ static int mount_image(
|
||||
r = parse_os_release(
|
||||
empty_to_root(root_directory),
|
||||
"ID", &rdata.os_release_id,
|
||||
"ID_LIKE", &rdata.os_release_id_like,
|
||||
"VERSION_ID", &rdata.os_release_version_id,
|
||||
image_class_info[IMAGE_SYSEXT].level_env, &rdata.os_release_sysext_level,
|
||||
image_class_info[IMAGE_CONFEXT].level_env, &rdata.os_release_confext_level,
|
||||
@@ -1623,9 +1624,10 @@ static int mount_image(
|
||||
return 0;
|
||||
if (r == -ESTALE && rdata.os_release_id)
|
||||
return log_error_errno(r, // FIXME: this should not be logged ad LOG_ERR, as it will result in duplicate logging.
|
||||
"Failed to mount image %s, extension-release metadata does not match the lower layer's: ID=%s%s%s%s%s%s%s",
|
||||
"Failed to mount image %s, extension-release metadata does not match the lower layer's: ID=%s ID_LIKE='%s'%s%s%s%s%s%s",
|
||||
mount_entry_source(m),
|
||||
rdata.os_release_id,
|
||||
strempty(rdata.os_release_id_like),
|
||||
rdata.os_release_version_id ? " VERSION_ID=" : "",
|
||||
strempty(rdata.os_release_version_id),
|
||||
rdata.os_release_sysext_level ? image_class_info[IMAGE_SYSEXT].level_env_print : "",
|
||||
@@ -1804,8 +1806,9 @@ static int apply_one_mount(
|
||||
break;
|
||||
|
||||
case MOUNT_EXTENSION_DIRECTORY: {
|
||||
_cleanup_free_ char *host_os_release_id = NULL, *host_os_release_version_id = NULL,
|
||||
*host_os_release_level = NULL, *extension_name = NULL;
|
||||
_cleanup_free_ char *host_os_release_id = NULL, *host_os_release_id_like = NULL,
|
||||
*host_os_release_version_id = NULL, *host_os_release_level = NULL,
|
||||
*extension_name = NULL;
|
||||
_cleanup_strv_free_ char **extension_release = NULL;
|
||||
ImageClass class = IMAGE_SYSEXT;
|
||||
|
||||
@@ -1840,6 +1843,7 @@ static int apply_one_mount(
|
||||
r = parse_os_release(
|
||||
empty_to_root(root_directory),
|
||||
"ID", &host_os_release_id,
|
||||
"ID_LIKE", &host_os_release_id_like,
|
||||
"VERSION_ID", &host_os_release_version_id,
|
||||
image_class_info[class].level_env, &host_os_release_level,
|
||||
NULL);
|
||||
@@ -1851,6 +1855,7 @@ static int apply_one_mount(
|
||||
r = extension_release_validate(
|
||||
extension_name,
|
||||
host_os_release_id,
|
||||
host_os_release_id_like,
|
||||
host_os_release_version_id,
|
||||
host_os_release_level,
|
||||
/* host_extension_scope = */ NULL, /* Leave empty, we need to accept both system and portable */
|
||||
|
||||
@@ -570,7 +570,7 @@ static int extract_image_and_extensions(
|
||||
char ***ret_valid_prefixes,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_free_ char *id = NULL, *version_id = NULL, *sysext_level = NULL, *confext_level = NULL;
|
||||
_cleanup_free_ char *id = NULL, *id_like = NULL, *version_id = NULL, *sysext_level = NULL, *confext_level = NULL;
|
||||
_cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
|
||||
_cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL, *extension_releases = NULL;
|
||||
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
|
||||
@@ -672,6 +672,7 @@ static int extract_image_and_extensions(
|
||||
|
||||
r = parse_env_file_fd(os_release->fd, os_release->name,
|
||||
"ID", &id,
|
||||
"ID_LIKE", &id_like,
|
||||
"VERSION_ID", &version_id,
|
||||
"SYSEXT_LEVEL", &sysext_level,
|
||||
"CONFEXT_LEVEL", &confext_level,
|
||||
@@ -719,9 +720,9 @@ static int extract_image_and_extensions(
|
||||
return r;
|
||||
|
||||
if (validate_extension) {
|
||||
r = extension_release_validate(ext->path, id, version_id, sysext_level, "portable", extension_release, IMAGE_SYSEXT);
|
||||
r = extension_release_validate(ext->path, id, id_like, version_id, sysext_level, "portable", extension_release, IMAGE_SYSEXT);
|
||||
if (r < 0)
|
||||
r = extension_release_validate(ext->path, id, version_id, confext_level, "portable", extension_release, IMAGE_CONFEXT);
|
||||
r = extension_release_validate(ext->path, id, id_like, version_id, confext_level, "portable", extension_release, IMAGE_CONFEXT);
|
||||
|
||||
if (r == 0)
|
||||
return sd_bus_error_set_errnof(error, ESTALE, "Image %s extension-release metadata does not match the root's", ext->path);
|
||||
|
||||
@@ -4421,6 +4421,7 @@ int verity_dissect_and_mount(
|
||||
r = extension_release_validate(
|
||||
dissected_image->image_name,
|
||||
extension_release_data->os_release_id,
|
||||
extension_release_data->os_release_id_like,
|
||||
extension_release_data->os_release_version_id,
|
||||
class == IMAGE_SYSEXT ? extension_release_data->os_release_sysext_level : extension_release_data->os_release_confext_level,
|
||||
extension_release_data->os_release_extension_scope,
|
||||
@@ -4447,6 +4448,7 @@ void extension_release_data_done(ExtensionReleaseData *data) {
|
||||
assert(data);
|
||||
|
||||
data->os_release_id = mfree(data->os_release_id);
|
||||
data->os_release_id_like = mfree(data->os_release_id_like);
|
||||
data->os_release_version_id = mfree(data->os_release_version_id);
|
||||
data->os_release_sysext_level = mfree(data->os_release_sysext_level);
|
||||
data->os_release_confext_level = mfree(data->os_release_confext_level);
|
||||
|
||||
@@ -145,6 +145,7 @@ typedef struct ImageFilter {
|
||||
|
||||
typedef struct ExtensionReleaseData {
|
||||
char *os_release_id;
|
||||
char *os_release_id_like;
|
||||
char *os_release_version_id;
|
||||
char *os_release_sysext_level;
|
||||
char *os_release_confext_level;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
int extension_release_validate(
|
||||
const char *name,
|
||||
const char *host_os_release_id,
|
||||
const char *host_os_release_id_like,
|
||||
const char *host_os_release_version_id,
|
||||
const char *host_os_extension_release_level,
|
||||
const char *host_extension_scope,
|
||||
@@ -22,6 +23,7 @@ int extension_release_validate(
|
||||
const char *extension_release_id = NULL, *extension_release_level = NULL, *extension_architecture = NULL;
|
||||
const char *extension_level = image_class == IMAGE_CONFEXT ? "CONFEXT_LEVEL" : "SYSEXT_LEVEL";
|
||||
const char *extension_scope = image_class == IMAGE_CONFEXT ? "CONFEXT_SCOPE" : "SYSEXT_SCOPE";
|
||||
_cleanup_strv_free_ char **id_like_l = NULL;
|
||||
|
||||
assert(name);
|
||||
assert(!isempty(host_os_release_id));
|
||||
@@ -78,9 +80,19 @@ int extension_release_validate(
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!streq(host_os_release_id, extension_release_id)) {
|
||||
log_debug("Extension '%s' is for OS '%s', but deployed on top of '%s'.",
|
||||
name, extension_release_id, host_os_release_id);
|
||||
/* Match extension OS ID against host OS ID or ID_LIKE */
|
||||
if (host_os_release_id_like) {
|
||||
id_like_l = strv_split(host_os_release_id_like, WHITESPACE);
|
||||
if (!id_like_l)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
if (!streq(host_os_release_id, extension_release_id) && !strv_contains(id_like_l, extension_release_id)) {
|
||||
log_debug("Extension '%s' is for OS '%s', but deployed on top of '%s'%s%s%s.",
|
||||
name, extension_release_id, host_os_release_id,
|
||||
host_os_release_id_like ? " (like '" : "",
|
||||
strempty(host_os_release_id_like),
|
||||
host_os_release_id_like ? "')" : "");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
int extension_release_validate(
|
||||
const char *name,
|
||||
const char *host_os_release_id,
|
||||
const char *host_os_release_id_like,
|
||||
const char *host_os_release_version_id,
|
||||
const char *host_os_extension_release_level,
|
||||
const char *host_extension_scope,
|
||||
|
||||
@@ -1686,7 +1686,9 @@ static int merge_subprocess(
|
||||
Hashmap *images,
|
||||
const char *workspace) {
|
||||
|
||||
_cleanup_free_ char *host_os_release_id = NULL, *host_os_release_version_id = NULL, *host_os_release_api_level = NULL, *filename = NULL;
|
||||
_cleanup_free_ char *host_os_release_id = NULL, *host_os_release_id_like = NULL,
|
||||
*host_os_release_version_id = NULL, *host_os_release_api_level = NULL,
|
||||
*filename = NULL;
|
||||
_cleanup_strv_free_ char **extensions = NULL, **extensions_v = NULL, **paths = NULL;
|
||||
size_t n_extensions = 0;
|
||||
unsigned n_ignored = 0;
|
||||
@@ -1718,6 +1720,7 @@ static int merge_subprocess(
|
||||
r = parse_os_release(
|
||||
arg_root,
|
||||
"ID", &host_os_release_id,
|
||||
"ID_LIKE", &host_os_release_id_like,
|
||||
"VERSION_ID", &host_os_release_version_id,
|
||||
image_class_info[image_class].level_env, &host_os_release_api_level);
|
||||
if (r < 0)
|
||||
@@ -1859,6 +1862,7 @@ static int merge_subprocess(
|
||||
r = extension_release_validate(
|
||||
img->name,
|
||||
host_os_release_id,
|
||||
host_os_release_id_like,
|
||||
host_os_release_version_id,
|
||||
host_os_release_api_level,
|
||||
in_initrd() ? "initrd" : "system",
|
||||
|
||||
@@ -85,6 +85,7 @@ prepare_root() {
|
||||
|
||||
{
|
||||
echo "ID=testtest"
|
||||
echo "ID_LIKE=\"foobar test_alike something-else\""
|
||||
echo "VERSION=1.2.3"
|
||||
} >"$root/usr/lib/os-release"
|
||||
|
||||
@@ -120,6 +121,38 @@ prepare_extension_image() {
|
||||
prepend_trap "rm -rf ${ext_dir@Q}"
|
||||
}
|
||||
|
||||
prepare_extension_image_with_matching_id() {
|
||||
local root=${1:-}
|
||||
local hierarchy=${2:?}
|
||||
local ext_dir ext_release name
|
||||
|
||||
name="test-extension-matching-id"
|
||||
ext_dir="$root/var/lib/extensions/$name"
|
||||
ext_release="$ext_dir/usr/lib/extension-release.d/extension-release.$name"
|
||||
mkdir -p "${ext_release%/*}"
|
||||
echo "ID=testtest" >"$ext_release"
|
||||
mkdir -p "$ext_dir/$hierarchy"
|
||||
touch "$ext_dir$hierarchy/preexisting-file-in-extension-image"
|
||||
|
||||
prepend_trap "rm -rf ${ext_dir@Q}"
|
||||
}
|
||||
|
||||
prepare_extension_image_with_matching_id_like() {
|
||||
local root=${1:-}
|
||||
local hierarchy=${2:?}
|
||||
local ext_dir ext_release name
|
||||
|
||||
name="test-extension-matching-id-like"
|
||||
ext_dir="$root/var/lib/extensions/$name"
|
||||
ext_release="$ext_dir/usr/lib/extension-release.d/extension-release.$name"
|
||||
mkdir -p "${ext_release%/*}"
|
||||
echo "ID=test_alike" >"$ext_release"
|
||||
mkdir -p "$ext_dir/$hierarchy"
|
||||
touch "$ext_dir$hierarchy/preexisting-file-in-extension-image"
|
||||
|
||||
prepend_trap "rm -rf ${ext_dir@Q}"
|
||||
}
|
||||
|
||||
prepare_extension_mutable_dir() {
|
||||
local dir=${1:?}
|
||||
|
||||
@@ -981,6 +1014,40 @@ for mutable_mode in no yes ephemeral; do
|
||||
done
|
||||
|
||||
|
||||
( init_trap
|
||||
: "Check if merging an extension with matching ID succeeds"
|
||||
fake_root=${roots_dir:+"$roots_dir/matching-id"}
|
||||
hierarchy=/opt
|
||||
|
||||
prepare_root "$fake_root" "$hierarchy"
|
||||
prepare_extension_image_with_matching_id "$fake_root" "$hierarchy"
|
||||
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
|
||||
|
||||
run_systemd_sysext "$fake_root" merge
|
||||
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
|
||||
|
||||
run_systemd_sysext "$fake_root" unmerge
|
||||
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
|
||||
)
|
||||
|
||||
|
||||
( init_trap
|
||||
: "Check if merging an extension that matches host ID_LIKE succeeds"
|
||||
fake_root=${roots_dir:+"$roots_dir/matching-id-like"}
|
||||
hierarchy=/opt
|
||||
|
||||
prepare_root "$fake_root" "$hierarchy"
|
||||
prepare_extension_image_with_matching_id_like "$fake_root" "$hierarchy"
|
||||
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
|
||||
|
||||
run_systemd_sysext "$fake_root" merge
|
||||
extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
|
||||
|
||||
run_systemd_sysext "$fake_root" unmerge
|
||||
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
|
||||
)
|
||||
|
||||
|
||||
( init_trap
|
||||
: "Check if merging fails in case of invalid mutable directory permissions"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user