From: Lennart Poettering Date: Mon, 25 Aug 2025 10:26:53 +0000 (+0200) Subject: discover-image: imply that hidden images are read-only X-Git-Tag: v259-rc1~239^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ee327e086e0534645d1c8cb9daa49cd8d7d68d51;p=thirdparty%2Fsystemd.git discover-image: imply that hidden images are read-only Marking a whole directory tree OS image as read-only is difficult privilege-wise, because so far we rely on the FS_IMMUTABLE_FL which is not accessible to unpriv clients. One fundamental place where we currently rely on marking images read-only is for keeping pristine copies of the originally downloaded image around, which we place in "hidden" image directories. This is probably the most relevant usecase for the read-only flag. And moreover, the only usecase for the hidden images are these read-only pristine copies. Hence, let's make this work reasonably in the unpriv case, and simply imply the read-only flag for hidden images. This is strictly speaking a change in behaviour, but effectively it shouldn't be, because for nspawn containers that are executed we insist on names that are hostname compatible, and hidden names aren't (because they start with a dot). --- diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index ca69b36c228..4025b4a2cb9 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -2006,8 +2006,8 @@ static int action_discover(void) { TABLE_SET_COLOR, startswith(img->name, ".") ? ANSI_GREY : NULL, TABLE_STRING, image_type_to_string(img->type), TABLE_STRING, image_class_to_string(img->class), - TABLE_BOOLEAN, img->read_only, - TABLE_SET_COLOR, !img->read_only ? ANSI_HIGHLIGHT_GREEN : ANSI_HIGHLIGHT_RED, + TABLE_BOOLEAN, image_is_read_only(img), + TABLE_SET_COLOR, image_is_read_only(img) ? ANSI_HIGHLIGHT_RED : ANSI_HIGHLIGHT_GREEN, TABLE_PATH, img->path, TABLE_TIMESTAMP, img->mtime != 0 ? img->mtime : img->crtime, TABLE_SIZE, img->usage, diff --git a/src/import/importd.c b/src/import/importd.c index 99b8af580d1..91cd7ce6488 100644 --- a/src/import/importd.c +++ b/src/import/importd.c @@ -1365,7 +1365,7 @@ static int method_list_images(sd_bus_message *msg, void *userdata, sd_bus_error i->name, image_type_to_string(i->type), i->path, - i->read_only, + image_is_read_only(i), i->crtime, i->mtime, i->usage, diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c index 9d4db0521fc..7fe7cad77ef 100644 --- a/src/machine/image-dbus.c +++ b/src/machine/image-dbus.c @@ -24,6 +24,20 @@ static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, image_type, ImageType); +static int property_get_read_only( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Image *image = ASSERT_PTR(userdata); + + return sd_bus_message_append(ASSERT_PTR(reply), "b", image_is_read_only(image)); +} + int bus_image_method_remove( sd_bus_message *message, void *userdata, @@ -448,7 +462,7 @@ const sd_bus_vtable image_vtable[] = { SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Image, name), 0), SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Image, path), 0), SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Image, type), 0), - SD_BUS_PROPERTY("ReadOnly", "b", bus_property_get_bool, offsetof(Image, read_only), 0), + SD_BUS_PROPERTY("ReadOnly", "b", property_get_read_only, 0, 0), SD_BUS_PROPERTY("CreationTimestamp", "t", NULL, offsetof(Image, crtime), 0), SD_BUS_PROPERTY("ModificationTimestamp", "t", NULL, offsetof(Image, mtime), 0), SD_BUS_PROPERTY("Usage", "t", NULL, offsetof(Image, usage), 0), diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index ec34cbac365..1619d3fd6b8 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -823,7 +823,7 @@ static int method_list_images(sd_bus_message *message, void *userdata, sd_bus_er r = sd_bus_message_append(reply, "(ssbttto)", image->name, image_type_to_string(image->type), - image->read_only, + image_is_read_only(image), image->crtime, image->mtime, image->usage, diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index a00dda1b732..13cf4713cf1 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -638,7 +638,7 @@ static int list_image_one_and_maybe_read_metadata(Manager *m, sd_varlink *link, JSON_BUILD_PAIR_STRING_NON_EMPTY("path", image->path), SD_JSON_BUILD_PAIR_STRING("type", image_type_to_string(image->type)), SD_JSON_BUILD_PAIR_STRING("class", image_class_to_string(image->class)), - SD_JSON_BUILD_PAIR_BOOLEAN("readOnly", image->read_only), + SD_JSON_BUILD_PAIR_BOOLEAN("readOnly", image_is_read_only(image)), JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("creationTimestamp", image->crtime), JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("modificationTimestamp", image->mtime), JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("usage", image->usage, UINT64_MAX), diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index bc1ed4c9eb5..fe622b26cee 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -3077,7 +3077,7 @@ static int determine_names(void) { return log_oom(); if (!arg_ephemeral) - arg_read_only = arg_read_only || i->read_only; + arg_read_only = arg_read_only || image_is_read_only(i); } else { r = safe_getcwd(&arg_directory); if (r < 0) diff --git a/src/portable/portabled-bus.c b/src/portable/portabled-bus.c index d83e073023f..7f3d496a6eb 100644 --- a/src/portable/portabled-bus.c +++ b/src/portable/portabled-bus.c @@ -177,7 +177,7 @@ static int method_list_images(sd_bus_message *message, void *userdata, sd_bus_er r = sd_bus_message_append(reply, "(ssbtttso)", image->name, image_type_to_string(image->type), - image->read_only, + image_is_read_only(image), image->crtime, image->mtime, image->usage, diff --git a/src/portable/portabled-image-bus.c b/src/portable/portabled-image-bus.c index 68cf9169246..17032c5dde2 100644 --- a/src/portable/portabled-image-bus.c +++ b/src/portable/portabled-image-bus.c @@ -30,6 +30,20 @@ static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, image_type, ImageType); +static int property_get_read_only( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Image *image = ASSERT_PTR(userdata); + + return sd_bus_message_append(ASSERT_PTR(reply), "b", image_is_read_only(image)); +} + int bus_image_common_get_os_release( Manager *m, sd_bus_message *message, @@ -865,7 +879,7 @@ const sd_bus_vtable image_vtable[] = { SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Image, name), 0), SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Image, path), 0), SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Image, type), 0), - SD_BUS_PROPERTY("ReadOnly", "b", bus_property_get_bool, offsetof(Image, read_only), 0), + SD_BUS_PROPERTY("ReadOnly", "b", property_get_read_only, 0, 0), SD_BUS_PROPERTY("CreationTimestamp", "t", NULL, offsetof(Image, crtime), 0), SD_BUS_PROPERTY("ModificationTimestamp", "t", NULL, offsetof(Image, mtime), 0), SD_BUS_PROPERTY("Usage", "t", NULL, offsetof(Image, usage), 0), diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index 53938a12567..f903e3a6bd2 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -1493,7 +1493,7 @@ int image_read_only(Image *i, bool b, RuntimeScope scope) { assert(i); - if (image_is_vendor(i) || image_is_host(i)) + if (image_is_vendor(i) || image_is_host(i) || image_is_hidden(i)) return -EROFS; /* Make sure we don't interfere with a running nspawn */ @@ -2005,7 +2005,7 @@ int image_to_json(const struct Image *img, sd_json_variant **ret) { SD_JSON_BUILD_PAIR_STRING("Class", image_class_to_string(img->class)), SD_JSON_BUILD_PAIR_STRING("Name", img->name), SD_JSON_BUILD_PAIR_CONDITION(!!img->path, "Path", SD_JSON_BUILD_STRING(img->path)), - SD_JSON_BUILD_PAIR_BOOLEAN("ReadOnly", img->read_only), + SD_JSON_BUILD_PAIR_BOOLEAN("ReadOnly", image_is_read_only(img)), SD_JSON_BUILD_PAIR_CONDITION(img->crtime != 0, "CreationTimestamp", SD_JSON_BUILD_UNSIGNED(img->crtime)), SD_JSON_BUILD_PAIR_CONDITION(img->mtime != 0, "ModificationTimestamp", SD_JSON_BUILD_UNSIGNED(img->mtime)), SD_JSON_BUILD_PAIR_CONDITION(img->usage != UINT64_MAX, "Usage", SD_JSON_BUILD_UNSIGNED(img->usage)), diff --git a/src/shared/discover-image.h b/src/shared/discover-image.h index a7370450a83..e4bc73390a2 100644 --- a/src/shared/discover-image.h +++ b/src/shared/discover-image.h @@ -84,16 +84,28 @@ static inline char** image_extension_release(Image *image, ImageClass class) { return NULL; } -static inline bool image_is_hidden(const struct Image *i) { +static inline bool image_is_hidden(const Image *i) { assert(i); return i->name && i->name[0] == '.'; } -bool image_is_vendor(const struct Image *i); -bool image_is_host(const struct Image *i); +static inline int image_is_read_only(const Image *i) { + assert(i); + + /* We enforce the rule that hidden images are always read-only too. If people want to change hidden + * images they should make a copy first, and make that one mutable */ + + if (image_is_hidden(i)) + return true; + + return i->read_only; +} + +bool image_is_vendor(const Image *i); +bool image_is_host(const Image *i); -int image_to_json(const struct Image *i, sd_json_variant **ret); +int image_to_json(const Image *i, sd_json_variant **ret); int image_root_pick(RuntimeScope scope, ImageClass c, bool runtime, char **ret);