]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
discover-image: imply that hidden images are read-only
authorLennart Poettering <lennart@poettering.net>
Mon, 25 Aug 2025 10:26:53 +0000 (12:26 +0200)
committerLennart Poettering <lennart@poettering.net>
Fri, 24 Oct 2025 15:51:00 +0000 (17:51 +0200)
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).

src/dissect/dissect.c
src/import/importd.c
src/machine/image-dbus.c
src/machine/machined-dbus.c
src/machine/machined-varlink.c
src/nspawn/nspawn.c
src/portable/portabled-bus.c
src/portable/portabled-image-bus.c
src/shared/discover-image.c
src/shared/discover-image.h

index ca69b36c22849f7b0bd110ac6c1f40d4bb636fb5..4025b4a2cb917bedefd8c1b3e0e2fd4e562b6450 100644 (file)
@@ -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,
index 99b8af580d178e47817ffc58cd83d026c5fc1136..91cd7ce6488183ea710ea37f401885e4a2da9a07 100644 (file)
@@ -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,
index 9d4db0521fc191e776c8940c7472586abd1f9f73..7fe7cad77ef60587a6d05582ce1f772372ab2917 100644 (file)
 
 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),
index ec34cbac3658697c0c0eb452a9cb0daabc67eee0..1619d3fd6b8c9584227f4b2451f2c48ba7990ac5 100644 (file)
@@ -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,
index a00dda1b732b1362ebf595fb39da68e57a7179a9..13cf4713cf18848d5a61025e71686086a36c743d 100644 (file)
@@ -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),
index bc1ed4c9eb5407d9bd68697e908e815484685701..fe622b26cee2459b308343bf23859ba8c0647aa3 100644 (file)
@@ -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)
index d83e073023fa56ef59e2be728a6bd8da35fe8ec1..7f3d496a6eb4b01c51a203044cc49d430c3ccc4e 100644 (file)
@@ -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,
index 68cf91692466a2f9aa4bba35020e272c7decc842..17032c5dde29c4157cc368ed0bdb7183fcbd2365 100644 (file)
 
 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),
index 53938a1256761f22c6e741948a970b50f04db8c8..f903e3a6bd27e452a5c641a6f660bcc6adf7ed7d 100644 (file)
@@ -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)),
index a7370450a839cc085d9d86a37c0fc4cd2bf6c806..e4bc73390a2d036a0ba680b78520ea20bc596da1 100644 (file)
@@ -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);