From 5e3ee4c5495ae062a30b4490e37877a1f64a17d4 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 2 Oct 2023 12:15:19 +0200 Subject: [PATCH] composefs: Relabel /usr/etc as /etc in the composefs image This allows using /usr/etc as /etc (vie e.g. a bind-mount or an overlayfs mount) in the booted system. --- src/libostree/ostree-repo-composefs.c | 79 +++++++++++++++++++++------ 1 file changed, 62 insertions(+), 17 deletions(-) diff --git a/src/libostree/ostree-repo-composefs.c b/src/libostree/ostree-repo-composefs.c index 4075124723..aea52bc4ad 100644 --- a/src/libostree/ostree-repo-composefs.c +++ b/src/libostree/ostree-repo-composefs.c @@ -23,9 +23,12 @@ #include #include +#include "ostree-autocleanups.h" #include "ostree-core-private.h" #include "ostree-repo-file.h" #include "ostree-repo-private.h" +#include "ostree-sepolicy-private.h" +#include "ostree-sepolicy.h" #ifdef HAVE_COMPOSEFS #include @@ -268,7 +271,7 @@ _ostree_composefs_set_xattrs (struct lcfs_node_s *node, GVariant *xattrs, GCance return TRUE; } -static gboolean +static struct lcfs_node_s * checkout_one_composefs_file_at (OstreeRepo *repo, const char *checksum, struct lcfs_node_s *parent, const char *destination_name, GCancellable *cancellable, GError **error) @@ -279,15 +282,15 @@ checkout_one_composefs_file_at (OstreeRepo *repo, const char *checksum, struct l /* Validate this up front to prevent path traversal attacks */ if (!ot_util_filename_validate (destination_name, error)) - return FALSE; + return NULL; existing = lcfs_node_lookup_child (parent, destination_name); if (existing != NULL) - return glnx_throw (error, "Target checkout file already exist"); + return glnx_null_throw (error, "Target checkout file already exist"); g_autoptr (GFileInfo) source_info = NULL; if (!ostree_repo_load_file (repo, checksum, &input, &source_info, &xattrs, cancellable, error)) - return FALSE; + return NULL; const guint32 source_mode = g_file_info_get_attribute_uint32 (source_info, "unix::mode"); const guint32 source_uid = g_file_info_get_attribute_uint32 (source_info, "unix::uid"); @@ -298,13 +301,13 @@ checkout_one_composefs_file_at (OstreeRepo *repo, const char *checksum, struct l struct lcfs_node_s *node = lcfs_node_new (); if (node == NULL) - return glnx_throw (error, "Out of memory"); + return glnx_null_throw (error, "Out of memory"); /* Takes ownership on success */ if (lcfs_node_add_child (parent, node, destination_name) != 0) { lcfs_node_unref (node); - return glnx_throw_errno (error); + return glnx_null_throw_errno (error); } lcfs_node_set_mode (node, source_mode); @@ -315,14 +318,14 @@ checkout_one_composefs_file_at (OstreeRepo *repo, const char *checksum, struct l { const char *source_symlink_target = g_file_info_get_symlink_target (source_info); if (lcfs_node_set_payload (node, source_symlink_target) != 0) - return glnx_throw_errno (error); + return glnx_null_throw_errno (error); } else if (source_size != 0) { char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; _ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, OSTREE_REPO_MODE_BARE); if (lcfs_node_set_payload (node, loose_path_buf) != 0) - return glnx_throw_errno (error); + return glnx_null_throw_errno (error); guchar *known_digest = NULL; @@ -349,24 +352,41 @@ checkout_one_composefs_file_at (OstreeRepo *repo, const char *checksum, struct l if (known_digest) lcfs_node_set_fsverity_digest (node, known_digest); else if (lcfs_node_set_fsverity_from_content (node, input, _composefs_read_cb) != 0) - return glnx_throw_errno (error); + return glnx_null_throw_errno (error); } if (xattrs) { if (!_ostree_composefs_set_xattrs (node, xattrs, cancellable, error)) - return FALSE; + return NULL; } g_clear_object (&input); + return node; +} + +/* Relabel node as if it had path for_path. Used to relabel /usr/etc as /etc */ +static gboolean +composefs_node_relabel_for (struct lcfs_node_s *node, const char *for_path, guint32 mode, + OstreeSePolicy *sepolicy, GCancellable *cancellable, GError **error) +{ + g_autofree char *label = NULL; + + if (!ostree_sepolicy_get_label (sepolicy, for_path, mode, &label, cancellable, error)) + return FALSE; + + if (label && lcfs_node_set_xattr (node, "security.selinux", label, strlen (label) + 1) != 0) + return glnx_throw_errno_prefix (error, "relabeling for %s", for_path); + return TRUE; } static gboolean checkout_composefs_recurse (OstreeRepo *self, const char *dirtree_checksum, const char *dirmeta_checksum, struct lcfs_node_s *parent, - const char *name, GCancellable *cancellable, GError **error) + const char *name, const char *path, OstreeSePolicy *sepolicy, + GCancellable *cancellable, GError **error) { g_autoptr (GVariant) dirtree = NULL; g_autoptr (GVariant) dirmeta = NULL; @@ -414,6 +434,13 @@ checkout_composefs_recurse (OstreeRepo *self, const char *dirtree_checksum, if (xattrs && !_ostree_composefs_set_xattrs (directory, xattrs, cancellable, error)) return FALSE; + gboolean in_usr_etc = strcmp (path, "/usr/etc") == 0 || g_str_has_prefix (path, "/usr/etc/"); + + if (in_usr_etc + && !composefs_node_relabel_for (directory, path + strlen ("/usr"), mode, sepolicy, + cancellable, error)) + return FALSE; + /* Process files in this subdir */ { g_autoptr (GVariant) dir_file_contents = g_variant_get_child_value (dirtree, 0); @@ -426,9 +453,18 @@ checkout_composefs_recurse (OstreeRepo *self, const char *dirtree_checksum, char tmp_checksum[OSTREE_SHA256_STRING_LEN + 1]; _ostree_checksum_inplace_from_bytes_v (contents_csum_v, tmp_checksum); - if (!checkout_one_composefs_file_at (self, tmp_checksum, directory, fname, cancellable, - error)) + struct lcfs_node_s *node = checkout_one_composefs_file_at (self, tmp_checksum, directory, + fname, cancellable, error); + if (node == NULL) return FALSE; + + if (in_usr_etc) + { + g_autofree char *child_path = g_build_filename (path + strlen ("/usr"), fname, NULL); + if (!composefs_node_relabel_for (node, child_path, lcfs_node_get_mode (node), sepolicy, + cancellable, error)) + return FALSE; + } } contents_csum_v = NULL; /* iter_loop freed it */ } @@ -457,8 +493,10 @@ checkout_composefs_recurse (OstreeRepo *self, const char *dirtree_checksum, _ostree_checksum_inplace_from_bytes_v (subdirtree_csum_v, subdirtree_checksum); char subdirmeta_checksum[OSTREE_SHA256_STRING_LEN + 1]; _ostree_checksum_inplace_from_bytes_v (subdirmeta_csum_v, subdirmeta_checksum); + + g_autofree char *child_path = g_build_filename (path, dname, NULL); if (!checkout_composefs_recurse (self, subdirtree_checksum, subdirmeta_checksum, directory, - dname, cancellable, error)) + dname, child_path, sepolicy, cancellable, error)) return FALSE; } /* Freed by iter-loop */ @@ -472,7 +510,8 @@ checkout_composefs_recurse (OstreeRepo *self, const char *dirtree_checksum, /* Begin a checkout process */ static gboolean checkout_composefs_tree (OstreeRepo *self, OstreeComposefsTarget *target, OstreeRepoFile *source, - GFileInfo *source_info, GCancellable *cancellable, GError **error) + GFileInfo *source_info, OstreeSePolicy *sepolicy, + GCancellable *cancellable, GError **error) { if (g_file_info_get_file_type (source_info) != G_FILE_TYPE_DIRECTORY) return glnx_throw (error, "Root checkout of composefs must be directory"); @@ -488,7 +527,7 @@ checkout_composefs_tree (OstreeRepo *self, OstreeComposefsTarget *target, Ostree const char *dirtree_checksum = ostree_repo_file_tree_get_contents_checksum (source); const char *dirmeta_checksum = ostree_repo_file_tree_get_metadata_checksum (source); return checkout_composefs_recurse (self, dirtree_checksum, dirmeta_checksum, target->dest, "root", - cancellable, error); + "/", sepolicy, cancellable, error); } static struct lcfs_node_s * @@ -541,13 +580,19 @@ ostree_repo_checkout_composefs (OstreeRepo *self, OstreeComposefsTarget *target, int i; struct lcfs_node_s *root, *dir; + g_autoptr (GError) my_error = NULL; + g_autoptr (OstreeSePolicy) sepolicy + = ostree_sepolicy_new_from_root (self, source, cancellable, &my_error); + if (sepolicy == NULL) + return FALSE; + g_autoptr (GFileInfo) target_info = g_file_query_info (G_FILE (source), OSTREE_GIO_FAST_QUERYINFO, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, cancellable, error); if (!target_info) return FALSE; - if (!checkout_composefs_tree (self, target, source, target_info, cancellable, error)) + if (!checkout_composefs_tree (self, target, source, target_info, sepolicy, cancellable, error)) return FALSE; /* We need a root dir */