diff --git a/src/app/rpmostree-internals-builtin-unpack.c b/src/app/rpmostree-internals-builtin-unpack.c index 08be0e50f5..7b931a7cc7 100644 --- a/src/app/rpmostree-internals-builtin-unpack.c +++ b/src/app/rpmostree-internals-builtin-unpack.c @@ -44,12 +44,22 @@ static gboolean opt_suid_fcaps = FALSE; static gboolean opt_owner = FALSE; static gboolean opt_to_ostree_repo = FALSE; static gboolean opt_selinux = FALSE; +static gboolean opt_rwx_dirs = FALSE; +static gboolean opt_ostree_convention = FALSE; static GOptionEntry option_entries[] = { - { "suid-fcaps", 0, 0, G_OPTION_ARG_NONE, &opt_suid_fcaps, "Enable setting suid/sgid and capabilities", NULL }, - { "owner", 0, 0, G_OPTION_ARG_NONE, &opt_owner, "Enable chown", NULL }, - { "to-ostree-repo", 0, 0, G_OPTION_ARG_NONE, &opt_to_ostree_repo, "Interpret TARGET as an OSTree repo", NULL }, - { "selinux", 0, 0, G_OPTION_ARG_NONE, &opt_selinux, "Enable setting SELinux labels", NULL }, + { "suid-fcaps", 0, 0, G_OPTION_ARG_NONE, &opt_suid_fcaps, + "Enable setting suid/sgid and capabilities", NULL }, + { "owner", 0, 0, G_OPTION_ARG_NONE, &opt_owner, + "Enable chown", NULL }, + { "to-ostree-repo", 0, 0, G_OPTION_ARG_NONE, &opt_to_ostree_repo, + "Interpret TARGET as an OSTree repo", NULL }, + { "selinux", 0, 0, G_OPTION_ARG_NONE, &opt_selinux, + "Enable setting SELinux labels", NULL }, + { "rwx-dirs", 0, 0, G_OPTION_ARG_NONE, &opt_rwx_dirs, + "Create all dirs with rwx owner bits", NULL }, + { "ostree-convention", 0, 0, G_OPTION_ARG_NONE, &opt_ostree_convention, + "Change file paths following ostree conventions", NULL }, { NULL } }; @@ -67,7 +77,8 @@ rpmostree_internals_builtin_unpack (int argc, const char *rpmpath; glnx_fd_close int rootfs_fd = -1; glnx_unref_object OstreeRepo *ostree_repo = NULL; - + glnx_unref_object OstreeSePolicy *sepolicy = NULL; + if (!rpmostree_option_context_parse (context, option_entries, &argc, &argv, @@ -83,13 +94,6 @@ rpmostree_internals_builtin_unpack (int argc, goto out; } - if (opt_selinux && !opt_to_ostree_repo) - { - rpmostree_usage_error (context, "--selinux currently only supported with " - "--to-ostree-repo", error); - goto out; - } - target = argv[1]; rpmpath = argv[2]; @@ -114,22 +118,25 @@ rpmostree_internals_builtin_unpack (int argc, flags |= RPMOSTREE_UNPACKER_FLAGS_OWNER; if (opt_suid_fcaps) flags |= RPMOSTREE_UNPACKER_FLAGS_SUID_FSCAPS; + if (opt_rwx_dirs) + flags |= RPMOSTREE_UNPACKER_FLAGS_RWX_DIRS; + if (opt_ostree_convention) + flags |= RPMOSTREE_UNPACKER_FLAGS_OSTREE_CONVENTION; unpacker = rpmostree_unpacker_new_at (AT_FDCWD, rpmpath, flags, error); if (!unpacker) goto out; + /* just use current policy */ + if (opt_selinux) + if (!rpmostree_prepare_rootfs_get_sepolicy (AT_FDCWD, "/", &sepolicy, + cancellable, error)) + goto out; + if (opt_to_ostree_repo) { const char *branch = rpmostree_unpacker_get_ostree_branch (unpacker); g_autofree char *checksum = NULL; - glnx_unref_object OstreeSePolicy *sepolicy = NULL; - - /* just use current policy */ - if (opt_selinux) - if (!rpmostree_prepare_rootfs_get_sepolicy (AT_FDCWD, "/", &sepolicy, - cancellable, error)) - goto out; if (!rpmostree_unpacker_unpack_to_ostree (unpacker, ostree_repo, sepolicy, &checksum, cancellable, error)) @@ -139,7 +146,8 @@ rpmostree_internals_builtin_unpack (int argc, } else { - if (!rpmostree_unpacker_unpack_to_dfd (unpacker, rootfs_fd, cancellable, error)) + if (!rpmostree_unpacker_unpack_to_dfd (unpacker, rootfs_fd, sepolicy, + cancellable, error)) goto out; } diff --git a/src/libpriv/rpmostree-core.c b/src/libpriv/rpmostree-core.c index 6d1923021e..c18a4bcc7e 100644 --- a/src/libpriv/rpmostree-core.c +++ b/src/libpriv/rpmostree-core.c @@ -1285,6 +1285,7 @@ import_one_package (OstreeRepo *ostreerepo, g_autofree char *ostree_commit = NULL; glnx_unref_object RpmOstreeUnpacker *unpacker = NULL; g_autofree char *pkg_path; + int flags = 0; if (pkg_is_local (pkg)) pkg_path = g_strdup (hif_package_get_filename (pkg)); @@ -1295,11 +1296,13 @@ import_one_package (OstreeRepo *ostreerepo, g_build_filename (hif_repo_get_location (hif_package_get_repo (pkg)), "packages", glnx_basename (pkg_location), NULL); } - + + flags = RPMOSTREE_UNPACKER_FLAGS_OWNER | + RPMOSTREE_UNPACKER_FLAGS_SUID_FSCAPS | + RPMOSTREE_UNPACKER_FLAGS_OSTREE_CONVENTION; + /* TODO - tweak the unpacker flags for containers */ - unpacker = rpmostree_unpacker_new_at (AT_FDCWD, pkg_path, - RPMOSTREE_UNPACKER_FLAGS_ALL, - error); + unpacker = rpmostree_unpacker_new_at (AT_FDCWD, pkg_path, flags, error); if (!unpacker) goto out; diff --git a/src/libpriv/rpmostree-unpacker.c b/src/libpriv/rpmostree-unpacker.c index 0efe10e85b..94f7d40a76 100644 --- a/src/libpriv/rpmostree-unpacker.c +++ b/src/libpriv/rpmostree-unpacker.c @@ -47,6 +47,7 @@ #include #include +#include typedef GObjectClass RpmOstreeUnpackerClass; @@ -247,6 +248,27 @@ path_relative (const char *src) return src; } +static char * +tweak_path_for_ostree (const char *path) +{ + path = path_relative (path); + if (g_str_has_prefix (path, "etc/")) + return g_strconcat ("usr/", path, NULL); + else if (strcmp (path, "etc") == 0) + return g_strdup ("usr/etc"); + return g_strdup (path); +} + +static char* +final_entry_pathname (RpmOstreeUnpacker *self, + const char *path) +{ + const char *path_rel = path_relative (path); + if (self->flags & RPMOSTREE_UNPACKER_FLAGS_OSTREE_CONVENTION) + return tweak_path_for_ostree (path_rel); + return g_strdup (path_rel); +} + static void build_rpmfi_overrides (RpmOstreeUnpacker *self) { @@ -263,12 +285,13 @@ build_rpmfi_overrides (RpmOstreeUnpacker *self) const char *group = rpmfiFGroup (self->fi); const char *fcaps = rpmfiFCaps (self->fi); - if (g_str_equal (user, "root") && g_str_equal (group, "root") - && !(fcaps && fcaps[0])) + if (g_str_equal (user, "root") && + g_str_equal (group, "root") && + (fcaps == NULL || fcaps[0] == '\0')) continue; g_hash_table_insert (self->rpmfi_overrides, - g_strdup (path_relative (rpmfiFN (self->fi))), + final_entry_pathname (self, rpmfiFN (self->fi)), GINT_TO_POINTER (i)); } } @@ -282,7 +305,7 @@ rpmostree_unpacker_new_fd (int fd, RpmOstreeUnpackerFlags flags, GError **error) struct archive *archive; gsize cpio_offset; - archive = rpm2cpio (fd, error); + archive = rpm2cpio (fd, error); if (archive == NULL) goto out; @@ -336,7 +359,7 @@ rpmostree_unpacker_new_at (int dfd, const char *path, RpmOstreeUnpackerFlags fla static gboolean get_rpmfi_override (RpmOstreeUnpacker *self, - const char *relpath, + const char *path, const char **out_user, const char **out_group, const char **out_fcaps) @@ -344,7 +367,7 @@ get_rpmfi_override (RpmOstreeUnpacker *self, gpointer v; /* Note: we use extended here because the value might be index 0 */ - if (!g_hash_table_lookup_extended (self->rpmfi_overrides, relpath, NULL, &v)) + if (!g_hash_table_lookup_extended (self->rpmfi_overrides, path, NULL, &v)) return FALSE; rpmfiInit (self->fi, GPOINTER_TO_INT (v)); @@ -360,13 +383,6 @@ get_rpmfi_override (RpmOstreeUnpacker *self, return TRUE; } -static gboolean -has_rpmfi_override (RpmOstreeUnpacker *self, - const char *relpath) -{ - return get_rpmfi_override (self, relpath, NULL, NULL, NULL); -} - static gboolean next_archive_entry (struct archive *archive, struct archive_entry **out_entry, @@ -389,574 +405,440 @@ next_archive_entry (struct archive *archive, return TRUE; } -gboolean -rpmostree_unpacker_unpack_to_dfd (RpmOstreeUnpacker *self, - int rootfs_fd, - GCancellable *cancellable, - GError **error) +static gboolean +set_label_for_path_at (int rootfs_dfd, + const char *path, + guint32 mode, + OstreeSePolicy *sepolicy, + GCancellable *cancellable, + GError **error) { - gboolean ret = FALSE; - g_autoptr(GHashTable) hardlinks = - g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + g_autofree char *label = NULL; - while (TRUE) - { - int r; - const char *fn; - struct archive_entry *entry; - glnx_fd_close int destfd = -1; - mode_t fmode; - uid_t owner_uid = 0; - gid_t owner_gid = 0; - const struct stat *archive_st; - const char *hardlink; + if (sepolicy == NULL) + return TRUE; - if (g_cancellable_set_error_if_cancelled (cancellable, error)) + { + g_autofree char *abspath = g_strdup_printf ("/%s", path); + if (!ostree_sepolicy_get_label (sepolicy, abspath, mode, &label, + cancellable, error)) + return FALSE; + } + + { + g_autofree char *abspath = glnx_fdrel_abspath (rootfs_dfd, path); + if (lsetfilecon (abspath, label) < 0) + { + glnx_set_prefix_error_from_errno (error, "%s", "lsetfilecon"); return FALSE; + } + } - if (!next_archive_entry (self->archive, &entry, error)) - goto out; - if (entry == NULL) - break; + return TRUE; +} - fn = path_relative (archive_entry_pathname (entry)); +static gboolean +create_parent_dirs (RpmOstreeUnpacker *self, + int rootfs_dfd, + const char *path, + OstreeSePolicy *sepolicy, + GCancellable *cancellable, + GError **error) +{ + /* There is a tricky issue with directories here. We're currently "installing" + * in an empty rootfs, which means that all the regular directories are + * missing (e.g. /usr/bin, /usr/share, ...). We will need to create them as we + * go and assign a set of default permissions. In general, this actually won't + * be an issue: during the real overlay, we can expect such directories to be + * present with the right perms&labels and thus have precedence over ours + * (assuming CHECKOUT_OVERWRITE_UNION_FILES). Only in the case that the RPM + * assumes a directory that doesn't exist during overlay will these default + * perms below matter. */ - archive_st = archive_entry_stat (entry); + gboolean ret = FALSE; + g_autoptr(GPtrArray) components = NULL; + g_autofree char *fullpath = NULL; - hardlink = archive_entry_hardlink (entry); - if (hardlink) - { - g_hash_table_insert (hardlinks, g_strdup (hardlink), g_strdup (fn)); - continue; - } + if (!rpmostree_split_path_ptrarray_validate (path, &components, error)) + goto out; - /* Don't try to mkdir parents of "" (originally /) */ - if (fn[0]) - { - char *fn_copy = strdupa (fn); /* alloca */ - const char *dname = dirname (fn_copy); + for (guint i = 0; i < components->len-1; i++) + { + gboolean newly_created = TRUE; + const char *component = components->pdata[i]; - /* Ensure parent directories exist */ - if (!glnx_shutil_mkdir_p_at (rootfs_fd, dname, 0755, cancellable, error)) + { + g_autofree char *fullpath_prev = g_steal_pointer (&fullpath); + fullpath = g_strdup_printf ("%s%s/", fullpath_prev ?: "", component); + } + + if (mkdirat (rootfs_dfd, fullpath, 0755) < 0) + { + if (errno == EEXIST) + newly_created = FALSE; + else + { + glnx_set_prefix_error_from_errno (error, "%s", "mkdirat"); goto out; + } } - fmode = archive_st->st_mode; + if (newly_created) + if (!set_label_for_path_at (rootfs_dfd, fullpath, 0755, sepolicy, + cancellable, error)) + goto out; + } - if (S_ISDIR (fmode)) - { - /* Always ensure we can write and execute directories...since - * this content should ultimately be read-only entirely, we're - * just breaking things by dropping write permissions during - * builds. - */ - fmode |= 0700; - /* Don't try to mkdir "" (originally /) */ - if (fn[0]) - { - g_assert (fn[0] != '/'); - if (!glnx_shutil_mkdir_p_at (rootfs_fd, fn, fmode, cancellable, error)) - goto out; - } - } - else if (S_ISLNK (fmode)) + ret = TRUE; +out: + return ret; +} + +static gboolean +create_dir (RpmOstreeUnpacker *self, + int rootfs_dfd, + const char *path, + mode_t mode, + GError **error) +{ + if (self->flags & RPMOSTREE_UNPACKER_FLAGS_RWX_DIRS) + mode |= 0700; + + if (mkdirat (rootfs_dfd, path, mode) < 0) + { + glnx_set_prefix_error_from_errno (error, "%s", "mkdirat"); + return FALSE; + } + + return TRUE; +} + +static gboolean +create_symlink (RpmOstreeUnpacker *self, + int rootfs_dfd, + const char *path, + struct archive_entry *entry, + GError **error) +{ + const char *symlink = archive_entry_symlink (entry); + if (symlinkat (symlink, rootfs_dfd, path) < 0) + { + glnx_set_prefix_error_from_errno (error, "%s", "symlinkat"); + return FALSE; + } + + return TRUE; +} + +static gboolean +create_file_on_disk (RpmOstreeUnpacker *self, + int rootfs_dfd, + const char *path, + off_t size, + int flags, + mode_t mode, + GError **error) +{ + gboolean ret = FALSE; + glnx_fd_close int fd = -1; + size_t remain = size; + gboolean restore_mode = FALSE; + + /* if we're creating a file, let's explicitly chmod on exit to make sure umask + * has no effect */ + restore_mode = (flags & O_EXCL) > 0; + + fd = openat (rootfs_dfd, path, flags, mode); + if (fd < 0 && errno == EACCES) + { + if (fchmodat (rootfs_dfd, path, mode | S_IWUSR, 0) < 0) { - g_assert (fn[0] != '/'); - if (symlinkat (archive_entry_symlink (entry), rootfs_fd, fn) < 0) - { - glnx_set_error_from_errno (error); - g_prefix_error (error, "Creating %s: ", fn); - goto out; - } + glnx_set_prefix_error_from_errno (error, "%s", "fchmodat"); + goto out; } - else if (S_ISREG (fmode)) + + /* we'll need to restore it on exit */ + restore_mode = TRUE; + fd = openat (rootfs_dfd, path, flags, mode); + } + + if (fd < 0) + { + glnx_set_prefix_error_from_errno (error, "%s", "openat"); + goto out; + } + + while (remain) + { + const void *buf; + size_t bufsize; + gint64 off; + + int r = archive_read_data_block (self->archive, &buf, &bufsize, &off); + if (r == ARCHIVE_EOF) + break; + if (r != ARCHIVE_OK) { - size_t remain = archive_st->st_size; - - g_assert (fn[0] != '/'); - destfd = openat (rootfs_fd, fn, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NOFOLLOW, 0600); - if (destfd < 0) - { - glnx_set_error_from_errno (error); - g_prefix_error (error, "Creating %s: ", fn); - goto out; - } - - while (remain) - { - const void *buf; - size_t size; - gint64 off; - - r = archive_read_data_block (self->archive, &buf, &size, &off); - if (r == ARCHIVE_EOF) - break; - if (r != ARCHIVE_OK) - { - propagate_libarchive_error (error, self->archive); - goto out; - } - - if (glnx_loop_write (destfd, buf, size) < 0) - { - glnx_set_error_from_errno (error); - goto out; - } - remain -= size; - } + propagate_libarchive_error (error, self->archive); + goto out; } - else + + if (glnx_loop_write (fd, buf, bufsize) < 0) { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_FAILED, - "RPM contains non-regular/non-symlink file %s", - fn); + glnx_set_error_from_errno (error); goto out; } + remain -= bufsize; + } - if (has_rpmfi_override (self, fn) - && (self->flags & RPMOSTREE_UNPACKER_FLAGS_OWNER) > 0) + if (restore_mode) + { + if (fchmodat (rootfs_dfd, path, mode, 0) < 0) { - struct passwd *pwent; - struct group *grent; - const char *user = NULL; - const char *group = NULL; - - get_rpmfi_override (self, fn, &user, &group, NULL); - - pwent = getpwnam (user); - if (pwent == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Unknown user '%s'", user); - goto out; - } - owner_uid = pwent->pw_uid; - - grent = getgrnam (group); - if (grent == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Unknown group '%s'", group); - goto out; - } - owner_gid = grent->gr_gid; - - if (fchownat (rootfs_fd, fn, owner_uid, owner_gid, AT_SYMLINK_NOFOLLOW) < 0) - { - glnx_set_error_from_errno (error); - g_prefix_error (error, "fchownat: "); - goto out; - } + glnx_set_prefix_error_from_errno (error, "%s", "fchmodat"); + goto out; } + } - if (S_ISREG (fmode)) + ret = TRUE; +out: + return ret; +} + +static gboolean +create_file (RpmOstreeUnpacker *self, + int rootfs_dfd, + const char *path, + struct archive_entry *entry, + GError **error) +{ + gboolean ret = FALSE; + const char *hardlink = archive_entry_hardlink (entry); + off_t size = (archive_entry_stat (entry))->st_size; + mode_t mode = (archive_entry_stat (entry))->st_mode; + + if (hardlink != NULL) + { + g_autofree char *final_hardlink = final_entry_pathname (self, hardlink); + if (linkat (rootfs_dfd, final_hardlink, rootfs_dfd, path, 0) < 0) { - if ((self->flags & RPMOSTREE_UNPACKER_FLAGS_SUID_FSCAPS) == 0) - fmode &= 0777; - else if (has_rpmfi_override (self, fn)) - { - const char *fcaps = NULL; - get_rpmfi_override (self, fn, NULL, NULL, &fcaps); - if (fcaps != NULL && fcaps[0]) - { - cap_t caps = cap_from_text (fcaps); - if (cap_set_fd (destfd, caps) != 0) - { - glnx_set_error_from_errno (error); - g_prefix_error (error, "Setting capabilities: "); - goto out; - } - } - } - - if (fchmod (destfd, fmode) < 0) - { - glnx_set_error_from_errno (error); - goto out; - } + glnx_set_prefix_error_from_errno (error, "%s", "linkat"); + goto out; } } - { GHashTableIter hashiter; - gpointer k,v; + /* If there are multiply-linked files in the CPIO, the first entry + * listed among them will not be marked as a hardlink. We create it + * like any regular file. However, its size will be 0: the actual payload is + * contained within the final hardlink. + * + * Thus, there are two cases where we want to create a file: (1) when the file + * is a regular file (hardlink == NULL), or (2) when the file is the final + * hardlink containing the payload (size > 0). This will also work for + * hardlinked files that are *actually* empty since the initial non-hardlink + * entry created it. + * */ + + if (hardlink == NULL || size > 0) + { + int flags = O_WRONLY | O_CREAT | O_CLOEXEC | O_NOFOLLOW; - g_hash_table_iter_init (&hashiter, hardlinks); + /* we don't expect the file to exist if this is not a hardlink */ + if (hardlink == NULL) + flags |= O_EXCL; - while (g_hash_table_iter_next (&hashiter, &k, &v)) - { - const char *src = path_relative (k); - const char *dest = path_relative (v); - - if (linkat (rootfs_fd, src, rootfs_fd, dest, 0) < 0) - { - glnx_set_error_from_errno (error); - goto out; - } - } - } + if (!create_file_on_disk (self, rootfs_dfd, path, + size, flags, mode, error)) + goto out; + } ret = TRUE; - out: +out: return ret; } -const char * -rpmostree_unpacker_get_ostree_branch (RpmOstreeUnpacker *self) -{ - if (!self->ostree_branch) - self->ostree_branch = rpmostree_get_cache_branch_header (self->hdr); - - return self->ostree_branch; -} - static gboolean -write_directory_meta (OstreeRepo *repo, - GFileInfo *file_info, /* allow-none */ - const char *dirpath, /* allow-none */ - OstreeSePolicy *sepolicy, /* allow-none */ - char **out_checksum, - GCancellable *cancellable, - GError **error) +unpack_archive_entry_to_dfd (RpmOstreeUnpacker *self, + struct archive_entry *entry, + int dfd, + OstreeSePolicy *sepolicy, + GCancellable *cancellable, + GError **error) { gboolean ret = FALSE; - g_autoptr(GVariant) dirmeta = NULL; - g_autofree guchar *csum = NULL; - glnx_unref_object GFileInfo *final_file_info = NULL; + mode_t mode = (archive_entry_stat (entry))->st_mode; + g_autofree char *path = + final_entry_pathname (self, archive_entry_pathname (entry)); - g_autoptr(GVariant) xattrs = NULL; - g_auto(GVariantBuilder) xattr_builder; - g_variant_builder_init (&xattr_builder, (GVariantType*)"a(ayay)"); + /* was this supposed to be the root directory? */ + if (path[0] == 0) + return TRUE; - if (g_cancellable_set_error_if_cancelled (cancellable, error)) - return FALSE; + if (!create_parent_dirs (self, dfd, path, sepolicy, + cancellable, error)) + goto out; - if (file_info) - final_file_info = g_object_ref (file_info); - else + if (S_ISDIR (mode)) { - /* just use default perms: root:root 0775 */ - final_file_info = g_file_info_new (); - g_file_info_set_attribute_uint32 (final_file_info, "unix::uid", 0); - g_file_info_set_attribute_uint32 (final_file_info, "unix::gid", 0); - g_file_info_set_attribute_uint32 (final_file_info, "unix::mode", 0755 | S_IFDIR); + if (!create_dir (self, dfd, path, mode, error)) + goto out; } - - if (sepolicy && dirpath) + else if (S_ISLNK (mode)) { - g_autofree char *label = NULL; - guint32 mode = g_file_info_get_attribute_uint32 (final_file_info, - "unix::mode"); - - if (!ostree_sepolicy_get_label (sepolicy, dirpath, mode, &label, - cancellable, error)) + if (!create_symlink (self, dfd, path, entry, error)) goto out; - - if (label) - g_variant_builder_add (&xattr_builder, "(@ay@ay)", - g_variant_new_bytestring ("security.selinux"), - g_variant_new_bytestring (label)); + } + else if (S_ISREG (mode)) + { + if (!create_file (self, dfd, path, entry, error)) + goto out; + } + else + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "RPM contains non-regular/non-symlink file %s", path); + goto out; } - xattrs = g_variant_ref_sink (g_variant_builder_end (&xattr_builder)); - dirmeta = ostree_create_directory_metadata (final_file_info, xattrs); - - if (!ostree_repo_write_metadata (repo, OSTREE_OBJECT_TYPE_DIR_META, NULL, - dirmeta, &csum, cancellable, error)) + if (!set_label_for_path_at (dfd, path, mode, sepolicy, cancellable, error)) goto out; ret = TRUE; - *out_checksum = ostree_checksum_from_bytes (csum); - out: +out: return ret; } -struct _cap_struct { - struct __user_cap_header_struct head; - union { - struct __user_cap_data_struct set; - __u32 flat[3]; - } u[_LINUX_CAPABILITY_U32S_2]; -}; -/* Rewritten version of _fcaps_save from libcap, since it's not - * exposed, and we need to generate the raw value. - */ -static void -cap_t_to_vfs (cap_t cap_d, struct vfs_cap_data *rawvfscap, int *out_size) +static gboolean +chown_file_at (int rootfs_dfd, + const char *path, + const char *user, + const char *group, + GError **error) { - guint32 eff_not_zero, magic; - guint tocopy, i; - - /* Hardcoded to 2. There is apparently a version 3 but it just maps - * to 2. I doubt another version would ever be implemented, and - * even if it was we'd need to be backcompatible forever. Anyways, - * setuid/fcaps binaries should go away entirely. - */ - magic = VFS_CAP_REVISION_2; - tocopy = VFS_CAP_U32_2; - *out_size = XATTR_CAPS_SZ_2; + struct passwd *pwent; + struct group *grent; - for (eff_not_zero = 0, i = 0; i < tocopy; i++) - eff_not_zero |= cap_d->u[i].flat[CAP_EFFECTIVE]; - - /* Here we're also not validating that the kernel understands - * the capabilities. - */ + pwent = getpwnam (user); + if (pwent == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Unknown user '%s'", user); + return FALSE; + } - for (i = 0; i < tocopy; i++) + grent = getgrnam (group); + if (grent == NULL) { - rawvfscap->data[i].permitted - = GUINT32_TO_LE(cap_d->u[i].flat[CAP_PERMITTED]); - rawvfscap->data[i].inheritable - = GUINT32_TO_LE(cap_d->u[i].flat[CAP_INHERITABLE]); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Unknown group '%s'", group); + return FALSE; } - if (eff_not_zero == 0) - rawvfscap->magic_etc = GUINT32_TO_LE(magic); - else - rawvfscap->magic_etc = GUINT32_TO_LE(magic|VFS_CAP_FLAGS_EFFECTIVE); -} + if (fchownat (rootfs_dfd, path, pwent->pw_uid, grent->gr_gid, + AT_SYMLINK_NOFOLLOW) < 0) + { + glnx_set_prefix_error_from_errno (error, "%s", "fchownat"); + return FALSE; + } -static char * -tweak_path_for_ostree (const char *path) -{ - path = path_relative (path); - if (g_str_has_prefix (path, "etc/")) - return g_strconcat ("usr/", path, NULL); - else if (strcmp (path, "etc") == 0) - return g_strdup ("usr"); - return g_strdup (path); + return TRUE; } static gboolean -import_one_libarchive_entry_to_ostree (RpmOstreeUnpacker *self, - OstreeRepo *repo, - OstreeSePolicy *sepolicy, - struct archive_entry *entry, - OstreeMutableTree *root, - GCancellable *cancellable, - GError **error) +setcap_file_at (int rootfs_dfd, + const char *path, + const char *fcaps, + GError **error) { - gboolean ret = FALSE; - g_autoptr(GPtrArray) pathname_parts = NULL; - g_autofree char *pathname = NULL; - glnx_unref_object OstreeMutableTree *parent = NULL; - const char *basename; - const struct stat *st; - g_auto(GVariantBuilder) xattr_builder; - - g_variant_builder_init (&xattr_builder, (GVariantType*)"a(ayay)"); + cap_t caps; + g_autofree char *abspath = glnx_fdrel_abspath (rootfs_dfd, path); - pathname = tweak_path_for_ostree (archive_entry_pathname (entry)); - st = archive_entry_stat (entry); + if (fcaps == NULL || fcaps[0] == '\0') + return TRUE; - { - const char *fcaps = NULL; - const char *relpath = path_relative (archive_entry_pathname (entry)); - - if (get_rpmfi_override (self, relpath, NULL, NULL, &fcaps) && - fcaps != NULL && fcaps[0]) - { - cap_t caps = cap_from_text (fcaps); - struct vfs_cap_data vfscap = { 0, }; - int vfscap_size; - g_autoptr(GBytes) vfsbytes = NULL; - - cap_t_to_vfs (caps, &vfscap, &vfscap_size); - vfsbytes = g_bytes_new (&vfscap, vfscap_size); - - g_variant_builder_add (&xattr_builder, "(@ay@ay)", - g_variant_new_bytestring ("security.capability"), - g_variant_new_from_bytes ((GVariantType*)"ay", - vfsbytes, - FALSE)); - } - } - - /* fetch the selinux label */ - if (sepolicy) + caps = cap_from_text (fcaps); + if (cap_set_file (abspath, caps) < 0) { - g_autofree char *label = NULL; - g_autofree char *fullpath = g_strdup_printf ("/%s", pathname); + glnx_set_prefix_error_from_errno (error, "%s", "cap_set_file"); + return FALSE; + } - if (!ostree_sepolicy_get_label (sepolicy, fullpath, - archive_entry_stat (entry)->st_mode, - &label, cancellable, error)) - goto out; + return TRUE; +} - if (label) - g_variant_builder_add (&xattr_builder, "(@ay@ay)", - g_variant_new_bytestring ("security.selinux"), - g_variant_new_bytestring (label)); - } +static gboolean +apply_rpmfi_overrides (RpmOstreeUnpacker *self, + int rootfs_dfd, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + GHashTableIter iter; + gpointer path; - if (!pathname[0]) + g_hash_table_iter_init (&iter, self->rpmfi_overrides); + while (g_hash_table_iter_next (&iter, &path, NULL)) { - parent = NULL; - basename = NULL; - } - else - { - g_autofree char *fullpath = g_strdup ("/"); - - if (!rpmostree_split_path_ptrarray_validate (pathname, &pathname_parts, - error)) - goto out; + const char *user, *group, *fcaps; + g_assert (get_rpmfi_override (self, path, &user, &group, &fcaps)); - /* walk up the path components, creating new dirs with proper labels (but - * default perms) as needed */ - parent = g_object_ref (root); - for (guint i = 0; i < pathname_parts->len-1; i++) - { - const char *subdir_name = pathname_parts->pdata[i]; - glnx_unref_object OstreeMutableTree *subdir_tree = NULL; + if (self->flags & RPMOSTREE_UNPACKER_FLAGS_OWNER) + if (!chown_file_at (rootfs_dfd, path, user, group, error)) + goto out; - { - g_autofree char *old_path = g_steal_pointer (&fullpath); - fullpath = g_strdup_printf ("%s%s/", old_path, subdir_name); - } + if (self->flags & RPMOSTREE_UNPACKER_FLAGS_SUID_FSCAPS) + if (!setcap_file_at (rootfs_dfd, path, fcaps, error)) + goto out; + } - if (!ostree_mutable_tree_lookup (parent, subdir_name, NULL, - &subdir_tree, error)) - { - if (g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) - { - g_autofree char *csum = NULL; - g_clear_error (error); - - if (!ostree_mutable_tree_ensure_dir (parent, subdir_name, - &subdir_tree, error)) - goto out; - - if (!write_directory_meta (repo, NULL, fullpath, sepolicy, - &csum, cancellable, error)) - goto out; - - ostree_mutable_tree_set_metadata_checksum (subdir_tree, csum); - } - else - goto out; - } - - g_set_object (&parent, subdir_tree); - } + ret = TRUE; +out: + return ret; +} - basename = (const char*)pathname_parts->pdata[pathname_parts->len-1]; - } +gboolean +rpmostree_unpacker_unpack_to_dfd (RpmOstreeUnpacker *self, + int rootfs_fd, + OstreeSePolicy *sepolicy, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; - if (archive_entry_hardlink (entry)) + while (TRUE) { - g_autofree char *hardlink = tweak_path_for_ostree (archive_entry_hardlink (entry)); - const char *hardlink_basename; - g_autoptr(GPtrArray) hardlink_split_path = NULL; - glnx_unref_object OstreeMutableTree *hardlink_source_parent = NULL; - glnx_unref_object OstreeMutableTree *hardlink_source_subdir = NULL; - g_autofree char *hardlink_source_checksum = NULL; - - g_assert (parent != NULL); + struct archive_entry *entry; - if (!rpmostree_split_path_ptrarray_validate (hardlink, &hardlink_split_path, error)) + if (!next_archive_entry (self->archive, &entry, error)) goto out; - if (hardlink_split_path->len == 0) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Invalid hardlink path %s", hardlink); - goto out; - } - - hardlink_basename = hardlink_split_path->pdata[hardlink_split_path->len - 1]; - - if (!ostree_mutable_tree_walk (root, hardlink_split_path, 0, &hardlink_source_parent, error)) + if (entry == NULL) + break; + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) goto out; - - if (!ostree_mutable_tree_lookup (hardlink_source_parent, hardlink_basename, - &hardlink_source_checksum, - &hardlink_source_subdir, - error)) - { - g_prefix_error (error, "While resolving hardlink target: "); - goto out; - } - - if (hardlink_source_subdir) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Hardlink %s refers to directory %s", - pathname, hardlink); - goto out; - } - g_assert (hardlink_source_checksum); - - if (!ostree_mutable_tree_replace_file (parent, - basename, - hardlink_source_checksum, - error)) + + if (!unpack_archive_entry_to_dfd (self, entry, rootfs_fd, sepolicy, + cancellable, error)) goto out; } - else - { - g_autofree char *object_checksum = NULL; - g_autoptr(GFileInfo) file_info = NULL; - glnx_unref_object OstreeMutableTree *subdir = NULL; - file_info = _rpmostree_libarchive_to_file_info (entry); - - if (S_ISDIR (st->st_mode)) - { - g_autofree char *fullpath = g_strdup_printf ("/%s", pathname); - if (!write_directory_meta (repo, file_info, fullpath, sepolicy, - &object_checksum, cancellable, error)) - goto out; - - if (parent == NULL) - { - subdir = g_object_ref (root); - } - else - { - if (!ostree_mutable_tree_ensure_dir (parent, basename, &subdir, error)) - goto out; - } - - ostree_mutable_tree_set_metadata_checksum (subdir, object_checksum); - } - else if (S_ISREG (st->st_mode) || S_ISLNK (st->st_mode)) - { - g_autofree guchar *object_csum = NULL; - - if (parent == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Can't import file as root directory"); - goto out; - } - - if (!_rpmostree_import_libarchive_entry_file (repo, self->archive, entry, file_info, - g_variant_builder_end (&xattr_builder), - &object_csum, - cancellable, error)) - goto out; - - object_checksum = ostree_checksum_from_bytes (object_csum); - if (!ostree_mutable_tree_replace_file (parent, basename, - object_checksum, - error)) - goto out; - } - else - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Unsupported file type for path '%s'", pathname); - goto out; - } - } + if (!apply_rpmfi_overrides (self, rootfs_fd, cancellable, error)) + goto out; ret = TRUE; out: return ret; } +const char * +rpmostree_unpacker_get_ostree_branch (RpmOstreeUnpacker *self) +{ + if (!self->ostree_branch) + self->ostree_branch = rpmostree_get_cache_branch_header (self->hdr); + + return self->ostree_branch; +} + static gboolean get_lead_sig_header_as_bytes (RpmOstreeUnpacker *self, GBytes **out_metadata, @@ -1005,65 +887,34 @@ get_lead_sig_header_as_bytes (RpmOstreeUnpacker *self, return ret; } -gboolean -rpmostree_unpacker_unpack_to_ostree (RpmOstreeUnpacker *self, - OstreeRepo *repo, - OstreeSePolicy *sepolicy, - char **out_commit, - GCancellable *cancellable, - GError **error) +static gboolean +build_metadata_variant (RpmOstreeUnpacker *self, + OstreeSePolicy *sepolicy, + GVariant **out_variant, + GCancellable *cancellable, + GError **error) { - gboolean ret = FALSE; - g_autoptr(GFile) root = NULL; - glnx_unref_object OstreeMutableTree *mtree = NULL; - g_autoptr(GBytes) header_bytes = NULL; - g_autofree char *commit_checksum = NULL; g_auto(GVariantBuilder) metadata_builder; - g_variant_builder_init (&metadata_builder, (GVariantType*)"a{sv}"); - if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) - goto out; - - /* There is a tricky issue with directories here. We're currently "installing" - * in an empty rootfs (really, an empty mtree), which means that all the - * regular directories are missing (e.g. /usr/bin, /usr/share, ...). We will - * need to create them as we go and assign a set of default permissions. In - * general, this actually won't be an issue: during the real overlay, we can - * expect such directories to be present with the right perms&labels and thus - * have precedence over ours (assuming CHECKOUT_OVERWRITE_UNION_FILES). Only - * in the case that the RPM assumes a directory that doesn't exist during - * overlay will these default perms below matter. */ - - mtree = ostree_mutable_tree_new (); - - { - g_autofree char *csum = NULL; - if (!write_directory_meta (repo, NULL, "/", sepolicy, &csum, - cancellable, error)) - goto out; - - ostree_mutable_tree_set_metadata_checksum (mtree, csum); - } - /* NB: We store the full header of the RPM in the commit for two reasons: * first, it holds the file security capabilities, and secondly, we'll need to * provide it to librpm when it updates the rpmdb (see * rpmostree_context_assemble_commit()). */ - { g_autoptr(GBytes) metadata = NULL; + { + g_autoptr(GBytes) metadata = NULL; if (!get_lead_sig_header_as_bytes (self, &metadata, cancellable, error)) - goto out; + return FALSE; g_variant_builder_add (&metadata_builder, "{sv}", "rpmostree.metadata", - g_variant_new_from_bytes ((GVariantType*)"ay", metadata, TRUE)); + g_variant_new_from_bytes ((GVariantType*)"ay", + metadata, TRUE)); } - /* XXX: Yes, we're storing a csum as a string instead of as bytes... To be - * discussed whether we should change the ostree API to return bytes (since it - * hasn't been released yet), or whether we should reconvert it to bytes here. - * In comparison to the full header we're already storing, this is trivial in - * total size. */ + /* The current sepolicy that was used to label the unpacked files is important + * to record. It will help us during future overlays to determine whether the + * files should be relabeled. */ if (sepolicy) g_variant_builder_add (&metadata_builder, "{sv}", "rpmostree.sepolicy", g_variant_new_string @@ -1073,27 +924,84 @@ rpmostree_unpacker_unpack_to_ostree (RpmOstreeUnpacker *self, g_variant_builder_add (&metadata_builder, "{sv}", "rpmostree.unpack_version", g_variant_new_uint32 (1)); - while (TRUE) - { - struct archive_entry *entry; + *out_variant = g_variant_builder_end (&metadata_builder); + return TRUE; +} - if (!next_archive_entry (self->archive, &entry, error)) - goto out; - if (entry == NULL) - break; +static gboolean +unpack_to_tmprootfs (RpmOstreeUnpacker *self, + OstreeRepo *repo, + OstreeSePolicy *sepolicy, + int *out_tmprootfs_dfd, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + g_autofree char *tmprootfs = g_strdup ("tmp/rpmostree-unpack-XXXXXX"); + glnx_fd_close int tmprootfs_dfd = -1; - if (!import_one_libarchive_entry_to_ostree (self, repo, sepolicy, entry, - mtree, cancellable, error)) - goto out; - } + int repo_dfd = ostree_repo_get_dfd (repo); /* borrowed */ + + if (!glnx_mkdtempat (repo_dfd, tmprootfs, 00755, error)) + goto out; + + if (!glnx_opendirat (repo_dfd, tmprootfs, FALSE, &tmprootfs_dfd, error)) + goto out; + + if (!set_label_for_path_at (tmprootfs_dfd, "", 00755, sepolicy, + cancellable, error)) + goto out; + + if (!rpmostree_unpacker_unpack_to_dfd (self, tmprootfs_dfd, sepolicy, + cancellable, error)) + goto out; + + *out_tmprootfs_dfd = glnx_steal_fd (&tmprootfs_dfd); + + ret = TRUE; +out: + if (tmprootfs_dfd != -1) + glnx_shutil_rm_rf_at (tmprootfs_dfd, ".", cancellable, NULL); + return ret; +} + +gboolean +rpmostree_unpacker_unpack_to_ostree (RpmOstreeUnpacker *self, + OstreeRepo *repo, + OstreeSePolicy *sepolicy, + char **out_commit, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + g_autoptr(GFile) root = NULL; + glnx_unref_object OstreeMutableTree *mtree = NULL; + g_autofree char *commit_checksum = NULL; + glnx_fd_close int tmprootfs_dfd = -1; + GVariant *metadata = NULL; /* floating */ + + if (!unpack_to_tmprootfs (self, repo, sepolicy, &tmprootfs_dfd, + cancellable, error)) + goto out; + + if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) + goto out; + + mtree = ostree_mutable_tree_new (); + + if (!ostree_repo_write_dfd_to_mtree (repo, tmprootfs_dfd, ".", mtree, NULL, + cancellable, error)) + goto out; if (!ostree_repo_write_mtree (repo, mtree, &root, cancellable, error)) goto out; - if (!ostree_repo_write_commit (repo, NULL, "", "", - g_variant_builder_end (&metadata_builder), - OSTREE_REPO_FILE (root), - &commit_checksum, cancellable, error)) + if (!build_metadata_variant (self, sepolicy, &metadata, cancellable, error)) + goto out; + + if (!ostree_repo_write_commit (repo, NULL, "", "", metadata, + OSTREE_REPO_FILE (root), &commit_checksum, + cancellable, error)) goto out; ostree_repo_transaction_set_ref (repo, NULL, @@ -1103,10 +1011,13 @@ rpmostree_unpacker_unpack_to_ostree (RpmOstreeUnpacker *self, if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error)) goto out; - ret = TRUE; *out_commit = g_steal_pointer (&commit_checksum); + + ret = TRUE; out: ostree_repo_abort_transaction (repo, cancellable, NULL); + if (tmprootfs_dfd != -1) + glnx_shutil_rm_rf_at (tmprootfs_dfd, ".", cancellable, NULL); return ret; } diff --git a/src/libpriv/rpmostree-unpacker.h b/src/libpriv/rpmostree-unpacker.h index 28d7566fc8..b33060e568 100644 --- a/src/libpriv/rpmostree-unpacker.h +++ b/src/libpriv/rpmostree-unpacker.h @@ -35,9 +35,10 @@ GType rpmostree_unpacker_get_type (void); typedef enum { RPMOSTREE_UNPACKER_FLAGS_SUID_FSCAPS = (1 << 0), - RPMOSTREE_UNPACKER_FLAGS_OWNER = (1 << 1) + RPMOSTREE_UNPACKER_FLAGS_OWNER = (1 << 1), + RPMOSTREE_UNPACKER_FLAGS_RWX_DIRS = (1 << 2), + RPMOSTREE_UNPACKER_FLAGS_OSTREE_CONVENTION = (1 << 3) } RpmOstreeUnpackerFlags; -#define RPMOSTREE_UNPACKER_FLAGS_ALL (RPMOSTREE_UNPACKER_FLAGS_SUID_FSCAPS | RPMOSTREE_UNPACKER_FLAGS_OWNER) RpmOstreeUnpacker *rpmostree_unpacker_new_fd (int fd, RpmOstreeUnpackerFlags flags, GError **error); @@ -45,6 +46,7 @@ RpmOstreeUnpacker *rpmostree_unpacker_new_at (int dfd, const char *path, RpmOstr gboolean rpmostree_unpacker_unpack_to_dfd (RpmOstreeUnpacker *unpacker, int dfd, + OstreeSePolicy *sepolicy, GCancellable *cancellable, GError **error);