From ecd0a0faee9aba2195a5391e23ef6f9ed83fa8f7 Mon Sep 17 00:00:00 2001 From: Miguel Angel Ajo Date: Tue, 13 Sep 2022 18:30:14 +0200 Subject: [PATCH 1/2] WIP/DNM / Support overlayfs whiteout character files Related-Issue: #2712 --- src/libostree/ostree-core.c | 8 +-- src/libostree/ostree-repo-checkout.c | 21 ++++++ src/libostree/ostree-repo-commit.c | 72 +++++++++++++++++-- src/libostree/ostree-repo-private.h | 9 +++ .../ostree-repo-static-delta-compilation.c | 2 +- src/libostree/ostree-repo.c | 7 +- 6 files changed, 106 insertions(+), 13 deletions(-) diff --git a/src/libostree/ostree-core.c b/src/libostree/ostree-core.c index 56b381d918..44ca8abf08 100644 --- a/src/libostree/ostree-core.c +++ b/src/libostree/ostree-core.c @@ -2014,7 +2014,7 @@ file_header_parse (GVariant *metadata, mode = GUINT32_FROM_BE (mode); g_autoptr(GFileInfo) ret_file_info = _ostree_mode_uidgid_to_gfileinfo (mode, uid, gid); - if (S_ISREG (mode)) + if (S_ISREG (mode) || S_ISCHR(mode)) { ; } @@ -2065,7 +2065,7 @@ zlib_file_header_parse (GVariant *metadata, g_autoptr(GFileInfo) ret_file_info = _ostree_mode_uidgid_to_gfileinfo (mode, uid, gid); g_file_info_set_size (ret_file_info, GUINT64_FROM_BE (size)); - if (S_ISREG (mode)) + if (S_ISREG (mode) || S_ISCHR (mode)) { ; } @@ -2368,7 +2368,7 @@ _ostree_validate_bareuseronly_mode (guint32 content_mode, return glnx_throw (error, "Content object %s: invalid mode 0%04o with bits 0%04o", checksum, content_mode, invalid_modebits); } - else if (S_ISLNK (content_mode)) + else if (S_ISLNK (content_mode) || S_ISCHR(content_mode)) ; /* Nothing */ else g_assert_not_reached (); @@ -2400,7 +2400,7 @@ gboolean ostree_validate_structureof_file_mode (guint32 mode, GError **error) { - if (!(S_ISREG (mode) || S_ISLNK (mode))) + if (!(S_ISREG (mode) || S_ISLNK (mode) || S_ISCHR(mode))) return glnx_throw (error, "Invalid file metadata mode %u; not a valid file type", mode); if (!validate_stat_mode_perms (mode, error)) diff --git a/src/libostree/ostree-repo-checkout.c b/src/libostree/ostree-repo-checkout.c index 663292a98f..ed271781e4 100644 --- a/src/libostree/ostree-repo-checkout.c +++ b/src/libostree/ostree-repo-checkout.c @@ -398,6 +398,27 @@ create_file_copy_from_input_at (OstreeRepo *repo, error)) return FALSE; } + else if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_SPECIAL) + { + guint32 file_mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); + g_assert(S_ISCHR(file_mode)); + + if (mknodat(destination_dfd, destination_name, file_mode, (dev_t)0) < 0) + return glnx_throw_errno_prefix (error, "Creating whiteout char device"); + if (options->mode != OSTREE_REPO_CHECKOUT_MODE_USER) + { + if (xattrs != NULL && + !glnx_dfd_name_set_all_xattrs(destination_dfd, destination_name, xattrs, + cancellable, error)) + return glnx_throw_errno_prefix (error, "Setting xattrs for whiteout char device"); + + if (TEMP_FAILURE_RETRY(fchownat(destination_dfd, destination_name, + g_file_info_get_attribute_uint32 (file_info, "unix::uid"), + g_file_info_get_attribute_uint32 (file_info, "unix::gid"), + AT_SYMLINK_NOFOLLOW) < 0)) + return glnx_throw_errno_prefix (error, "fchownat"); + } + } else g_assert_not_reached (); diff --git a/src/libostree/ostree-repo-commit.c b/src/libostree/ostree-repo-commit.c index c22864cd06..1bcd6d3e5a 100644 --- a/src/libostree/ostree-repo-commit.c +++ b/src/libostree/ostree-repo-commit.c @@ -213,6 +213,37 @@ _ostree_repo_commit_tmpf_final (OstreeRepo *self, return TRUE; } +gboolean +_ostree_repo_commit_bare_whiteout (OstreeRepo *self, + const char *checksum, + guint32 uid, + guint32 gid, + GVariant *xattrs, + GCancellable *cancellable, + GError **error) +{ + g_assert(self->mode == OSTREE_REPO_MODE_BARE); + + char dest_filename[_OSTREE_LOOSE_PATH_MAX]; + _ostree_loose_path (dest_filename, checksum, OSTREE_OBJECT_TYPE_FILE, self->mode); + + int dest_dfd = commit_dest_dfd (self); + if (!_ostree_repo_ensure_loose_objdir_at (dest_dfd, dest_filename, cancellable, error)) + return FALSE; + + if (mknodat(dest_dfd, dest_filename, S_IFCHR, (dev_t)0) < 0) + return glnx_throw_errno_prefix (error, "Creating whiteout char device"); + + if (xattrs != NULL && + !glnx_dfd_name_set_all_xattrs(dest_dfd, dest_filename, xattrs, cancellable, error)) + return glnx_throw_errno_prefix (error, "Setting xattrs for whiteout char device"); + + if (TEMP_FAILURE_RETRY(fchownat(dest_dfd, dest_filename, uid, gid, AT_SYMLINK_NOFOLLOW) < 0)) + return glnx_throw_errno_prefix (error, "fchownat"); + + return TRUE; +} + /* Given a dfd+path combination (may be regular file or symlink), * rename it into place. */ @@ -301,7 +332,7 @@ commit_loose_regfile_object (OstreeRepo *self, return FALSE; } else - g_assert (S_ISLNK (mode)); + g_assert (S_ISLNK (mode) || S_ISCHR(mode)); } else if (self->mode == OSTREE_REPO_MODE_BARE_USER_ONLY) { @@ -966,7 +997,9 @@ write_content_object (OstreeRepo *self, else file_input = input; + const guint32 mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); gboolean phys_object_is_symlink = FALSE; + gboolean phys_object_is_whiteout = FALSE; switch (object_file_type) { case G_FILE_TYPE_REGULAR: @@ -975,6 +1008,19 @@ write_content_object (OstreeRepo *self, if (self->mode == OSTREE_REPO_MODE_BARE || self->mode == OSTREE_REPO_MODE_BARE_USER_ONLY) phys_object_is_symlink = TRUE; break; + case G_FILE_TYPE_SPECIAL: + /* Only overlayfs whiteout char 0:0 files are supported for G_FILE_TYPE_SPECIAL */ + if (S_ISCHR(mode)) + { + /* For bare mode repositories where no side file metadata is stored we want to + * avoid the creation of an empty tmp file and later setup of permissions because + * this won't result in a char device. + */ + if (self->mode == OSTREE_REPO_MODE_BARE) + phys_object_is_whiteout = TRUE; + break; + } + return glnx_throw (error, "Unsupported file type %u with mode 0%o", object_file_type, mode); default: return glnx_throw (error, "Unsupported file type %u", object_file_type); } @@ -1021,7 +1067,8 @@ write_content_object (OstreeRepo *self, * binary with trailing garbage, creating a window on the local * system where a malicious setuid binary exists. * - * We use GLnxTmpfile for regular files, and OtCleanupUnlinkat for symlinks. + * We use GLnxTmpfile for regular files, OtCleanupUnlinkat for symlinks, + * we use no temporary for whiteout char devices in bare mode. */ g_auto(OtCleanupUnlinkat) tmp_unlinker = { commit_tmp_dfd (self), NULL }; g_auto(GLnxTmpfile) tmpf = { 0, }; @@ -1037,6 +1084,8 @@ write_content_object (OstreeRepo *self, cancellable, error)) return FALSE; } + else if (phys_object_is_whiteout) + ; else if (repo_mode != OSTREE_REPO_MODE_ARCHIVE) { if (!create_regular_tmpfile_linkable_with_content (self, size, file_input, @@ -1079,11 +1128,16 @@ write_content_object (OstreeRepo *self, unpacked_size = g_file_info_get_size (file_info); } - else + else if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_SYMBOLIC_LINK) { /* For a symlink, the size is the length of the target */ unpacked_size = strlen (g_file_info_get_symlink_target (file_info)); } + else + { + /* For char devices 0:0 whiteouts the content size is 0 */ + unpacked_size = 0; + } if (!g_output_stream_flush (temp_out, cancellable, error)) return FALSE; @@ -1151,7 +1205,6 @@ write_content_object (OstreeRepo *self, const guint32 uid = g_file_info_get_attribute_uint32 (file_info, "unix::uid"); const guint32 gid = g_file_info_get_attribute_uint32 (file_info, "unix::gid"); - const guint32 mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); /* Is it "physically" a symlink? */ if (phys_object_is_symlink) { @@ -1189,6 +1242,13 @@ write_content_object (OstreeRepo *self, &tmp_unlinker, cancellable, error)) return FALSE; } + else if (phys_object_is_whiteout) + { + if (!_ostree_repo_commit_bare_whiteout (self, actual_checksum, + uid, gid, xattrs, + cancellable, error)) + return FALSE; + } else { /* Check if a file with the same payload is present in the repository, @@ -3739,6 +3799,7 @@ write_content_to_mtree_internal (OstreeRepo *self, { case G_FILE_TYPE_SYMBOLIC_LINK: case G_FILE_TYPE_REGULAR: + case G_FILE_TYPE_SPECIAL: break; default: return glnx_throw (error, "Unsupported file type for file: '%s'", child_relpath); @@ -4090,7 +4151,8 @@ write_dfd_iter_to_mtree_internal (OstreeRepo *self, continue; } - if (S_ISREG (stbuf.st_mode)) + /* For regular files and whiteout char devices we continue */ + if (S_ISREG (stbuf.st_mode) || (S_ISCHR(stbuf.st_mode) && stbuf.st_rdev == 0)) ; else if (S_ISLNK (stbuf.st_mode)) { diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h index 0d33f7c2d0..fad94ae4bd 100644 --- a/src/libostree/ostree-repo-private.h +++ b/src/libostree/ostree-repo-private.h @@ -414,6 +414,15 @@ _ostree_repo_commit_tmpf_final (OstreeRepo *self, GCancellable *cancellable, GError **error); +gboolean +_ostree_repo_commit_bare_whiteout (OstreeRepo *self, + const char *checksum, + guint32 uid, + guint32 gid, + GVariant *xattrs, + GCancellable *cancellable, + GError **error); + typedef struct { gboolean initialized; gpointer opaque0[10]; diff --git a/src/libostree/ostree-repo-static-delta-compilation.c b/src/libostree/ostree-repo-static-delta-compilation.c index 28b421395d..c352c20991 100644 --- a/src/libostree/ostree-repo-static-delta-compilation.c +++ b/src/libostree/ostree-repo-static-delta-compilation.c @@ -516,7 +516,7 @@ process_one_object (OstreeRepo *repo, } else { - g_assert (S_ISREG (mode)); + g_assert (S_ISREG (mode) || S_ISCHR(mode)); } content_offset = current_part->payload->len; diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index 90cde65139..932eef4fac 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -4322,8 +4322,9 @@ _ostree_repo_load_file_bare (OstreeRepo *self, return glnx_throw_errno_prefix (error, "openat"); } - if (!(S_ISREG (stbuf.st_mode) || S_ISLNK (stbuf.st_mode))) - return glnx_throw (error, "Not a regular file or symlink"); + if (!(S_ISREG (stbuf.st_mode) || S_ISLNK (stbuf.st_mode) || + (S_ISCHR (stbuf.st_mode) && stbuf.st_rdev == 0))) + return glnx_throw (error, "Not a regular file, symlink or whiteout"); /* In the non-bare-user case, gather symlink info if requested */ if (self->mode != OSTREE_REPO_MODE_BARE_USER @@ -4474,7 +4475,7 @@ ostree_repo_load_file (OstreeRepo *self, if (S_ISLNK (stbuf.st_mode)) g_file_info_set_symlink_target (*out_file_info, symlink_target); else - g_assert (S_ISREG (stbuf.st_mode)); + g_assert (S_ISREG (stbuf.st_mode) || (S_ISCHR(stbuf.st_mode) && stbuf.st_rdev == 0)); } ot_transfer_out_value (out_xattrs, &ret_xattrs); From c2ff1cc19aa7eeeb0f34fb0d2d6f65b18833e4c6 Mon Sep 17 00:00:00 2001 From: Miguel Angel Ajo Date: Mon, 19 Sep 2022 14:31:36 +0200 Subject: [PATCH 2/2] Apply reviewer comments --- src/libostree/ostree-core-private.h | 2 ++ src/libostree/ostree-core.c | 21 +++++++++++++++++++-- src/libostree/ostree-repo-checkout.c | 10 +++++----- src/libostree/ostree-repo-commit.c | 4 ++-- src/libostree/ostree-repo.c | 4 ++-- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/libostree/ostree-core-private.h b/src/libostree/ostree-core-private.h index 2bd2f98487..bc416bf4aa 100644 --- a/src/libostree/ostree-core-private.h +++ b/src/libostree/ostree-core-private.h @@ -91,6 +91,8 @@ GFileInfo * _ostree_stbuf_to_gfileinfo (const struct stat *stbuf); void _ostree_gfileinfo_to_stbuf (GFileInfo *file_info, struct stat *out_stbuf); gboolean _ostree_gfileinfo_equal (GFileInfo *a, GFileInfo *b); gboolean _ostree_stbuf_equal (struct stat *stbuf_a, struct stat *stbuf_b); +gboolean _ostree_stbuf_is_whiteout(struct stat *stbuf); +gboolean _ostree_gfileinfo_is_whiteout(GFileInfo *file_info); GFileInfo * _ostree_mode_uidgid_to_gfileinfo (mode_t mode, uid_t uid, gid_t gid); static inline void diff --git a/src/libostree/ostree-core.c b/src/libostree/ostree-core.c index 44ca8abf08..c2a5867b81 100644 --- a/src/libostree/ostree-core.c +++ b/src/libostree/ostree-core.c @@ -1732,6 +1732,7 @@ _ostree_stbuf_to_gfileinfo (const struct stat *stbuf) g_file_info_set_attribute_uint32 (ret, "unix::uid", stbuf->st_uid); g_file_info_set_attribute_uint32 (ret, "unix::gid", stbuf->st_gid); g_file_info_set_attribute_uint32 (ret, "unix::mode", mode); + g_file_info_set_attribute_uint32 (ret, "unix::rdev", stbuf->st_rdev); /* those aren't stored by ostree, but used by the devino cache */ g_file_info_set_attribute_uint32 (ret, "unix::device", stbuf->st_dev); @@ -1816,6 +1817,22 @@ _ostree_stbuf_equal (struct stat *stbuf_a, struct stat *stbuf_b) return TRUE; } +/* Check if stbuf belongs to a whiteout character device */ +gboolean +_ostree_stbuf_is_whiteout(struct stat *stbuf) +{ + return S_ISCHR(stbuf->st_mode) && stbuf->st_rdev == 0; +} + +/* Check if GFileInfo belongs to a whiteout character device */ +gboolean +_ostree_gfileinfo_is_whiteout(GFileInfo *file_info) +{ + struct stat stbuf; + _ostree_gfileinfo_to_stbuf(file_info, &stbuf); + return _ostree_stbuf_is_whiteout(&stbuf); +} + /* Many parts of libostree only care about mode,uid,gid - this creates * a new GFileInfo with those fields see. */ @@ -2014,7 +2031,7 @@ file_header_parse (GVariant *metadata, mode = GUINT32_FROM_BE (mode); g_autoptr(GFileInfo) ret_file_info = _ostree_mode_uidgid_to_gfileinfo (mode, uid, gid); - if (S_ISREG (mode) || S_ISCHR(mode)) + if (S_ISREG (mode) || _ostree_gfileinfo_is_whiteout(ret_file_info)) { ; } @@ -2065,7 +2082,7 @@ zlib_file_header_parse (GVariant *metadata, g_autoptr(GFileInfo) ret_file_info = _ostree_mode_uidgid_to_gfileinfo (mode, uid, gid); g_file_info_set_size (ret_file_info, GUINT64_FROM_BE (size)); - if (S_ISREG (mode) || S_ISCHR (mode)) + if (S_ISREG (mode) || _ostree_gfileinfo_is_whiteout(ret_file_info)) { ; } diff --git a/src/libostree/ostree-repo-checkout.c b/src/libostree/ostree-repo-checkout.c index ed271781e4..d8f68f52a7 100644 --- a/src/libostree/ostree-repo-checkout.c +++ b/src/libostree/ostree-repo-checkout.c @@ -385,9 +385,9 @@ create_file_copy_from_input_at (OstreeRepo *repo, if (g_str_equal (checksum, actual_checksum)) return TRUE; - /* Otherwise, fall through and do the link, we should - * get EEXIST. - */ + /* Otherwise, fall through and do the link, we should + * get EEXIST. + */ } } break; @@ -401,10 +401,10 @@ create_file_copy_from_input_at (OstreeRepo *repo, else if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_SPECIAL) { guint32 file_mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); - g_assert(S_ISCHR(file_mode)); + g_assert(_ostree_gfileinfo_is_whiteout(file_info)); if (mknodat(destination_dfd, destination_name, file_mode, (dev_t)0) < 0) - return glnx_throw_errno_prefix (error, "Creating whiteout char device"); + return glnx_throw_errno_prefix (error, "Creating whiteout char device"); if (options->mode != OSTREE_REPO_CHECKOUT_MODE_USER) { if (xattrs != NULL && diff --git a/src/libostree/ostree-repo-commit.c b/src/libostree/ostree-repo-commit.c index 1bcd6d3e5a..595446dfb1 100644 --- a/src/libostree/ostree-repo-commit.c +++ b/src/libostree/ostree-repo-commit.c @@ -1010,7 +1010,7 @@ write_content_object (OstreeRepo *self, break; case G_FILE_TYPE_SPECIAL: /* Only overlayfs whiteout char 0:0 files are supported for G_FILE_TYPE_SPECIAL */ - if (S_ISCHR(mode)) + if (_ostree_gfileinfo_is_whiteout(file_info)) { /* For bare mode repositories where no side file metadata is stored we want to * avoid the creation of an empty tmp file and later setup of permissions because @@ -4152,7 +4152,7 @@ write_dfd_iter_to_mtree_internal (OstreeRepo *self, } /* For regular files and whiteout char devices we continue */ - if (S_ISREG (stbuf.st_mode) || (S_ISCHR(stbuf.st_mode) && stbuf.st_rdev == 0)) + if (S_ISREG (stbuf.st_mode) || _ostree_stbuf_is_whiteout(&stbuf)) ; else if (S_ISLNK (stbuf.st_mode)) { diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index 932eef4fac..013c769a7d 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -4323,7 +4323,7 @@ _ostree_repo_load_file_bare (OstreeRepo *self, } if (!(S_ISREG (stbuf.st_mode) || S_ISLNK (stbuf.st_mode) || - (S_ISCHR (stbuf.st_mode) && stbuf.st_rdev == 0))) + _ostree_stbuf_is_whiteout(&stbuf))) return glnx_throw (error, "Not a regular file, symlink or whiteout"); /* In the non-bare-user case, gather symlink info if requested */ @@ -4475,7 +4475,7 @@ ostree_repo_load_file (OstreeRepo *self, if (S_ISLNK (stbuf.st_mode)) g_file_info_set_symlink_target (*out_file_info, symlink_target); else - g_assert (S_ISREG (stbuf.st_mode) || (S_ISCHR(stbuf.st_mode) && stbuf.st_rdev == 0)); + g_assert (S_ISREG (stbuf.st_mode) || _ostree_stbuf_is_whiteout(&stbuf)); } ot_transfer_out_value (out_xattrs, &ret_xattrs);