From e580a88f4ed15b9af3ee9355a3ef5a4dc1cd82ab Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sun, 2 Feb 2014 11:32:52 -0500 Subject: [PATCH] SELinux: Ensure we label /var, and fix /etc merge wrt xattrs First, /var needs to be labeled at least once. We should probably rearrange things so that /var is only created (and labeled) on the first deployment, but this patch adds a /var/.ostree-selabeled file instead. Second, when doing the /etc merge, we compare the xattrs of the old /usr/etc versus the current /etc. The problem with that is that the policy has different labels for /usr/etc on disk than the real /etc. The correct fix for this is a bit invasive - we have to take the physical content of the old /usr/etc, but compare the labels as if they were really in /etc. Instead for now, just ignore changes to xattrs. If the file content/mode changes, then we take the new file (including any changed xattrs). Bottom line: just doing chcon -t blah_t /etc/foo.conf may be lost on upgrade (for now). --- src/libostree/ostree-diff.c | 39 +++-- src/libostree/ostree-diff.h | 8 +- src/libostree/ostree-sysroot-deploy.c | 210 +++++++++++++++++++++++--- src/ostree/ot-admin-builtin-diff.c | 3 +- src/ostree/ot-builtin-diff.c | 2 +- 5 files changed, 227 insertions(+), 35 deletions(-) diff --git a/src/libostree/ostree-diff.c b/src/libostree/ostree-diff.c index 3250fe29..6749b654 100644 --- a/src/libostree/ostree-diff.c +++ b/src/libostree/ostree-diff.c @@ -27,7 +27,8 @@ #include "libgsystem.h" static gboolean -get_file_checksum (GFile *f, +get_file_checksum (OstreeDiffFlags flags, + GFile *f, GFileInfo *f_info, char **out_checksum, GCancellable *cancellable, @@ -43,8 +44,25 @@ get_file_checksum (GFile *f, } else { - if (!ostree_checksum_file (f, OSTREE_OBJECT_TYPE_FILE, - &csum, cancellable, error)) + gs_unref_variant GVariant *xattrs = NULL; + gs_unref_object GInputStream *in = NULL; + + if (!(flags & OSTREE_DIFF_FLAGS_IGNORE_XATTRS)) + { + if (!gs_file_get_all_xattrs (f, &xattrs, cancellable, error)) + goto out; + } + + if (g_file_info_get_file_type (f_info) == G_FILE_TYPE_REGULAR) + { + in = (GInputStream*)g_file_read (f, cancellable, error); + if (!in) + goto out; + } + + if (!ostree_checksum_file_from_input (f_info, xattrs, in, + OSTREE_OBJECT_TYPE_FILE, + &csum, cancellable, error)) goto out; ret_checksum = ostree_checksum_from_bytes (csum); } @@ -101,7 +119,8 @@ diff_item_new (GFile *a, } static gboolean -diff_files (GFile *a, +diff_files (OstreeDiffFlags flags, + GFile *a, GFileInfo *a_info, GFile *b, GFileInfo *b_info, @@ -114,9 +133,9 @@ diff_files (GFile *a, gs_free char *checksum_b = NULL; OstreeDiffItem *ret_item = NULL; - if (!get_file_checksum (a, a_info, &checksum_a, cancellable, error)) + if (!get_file_checksum (flags, a, a_info, &checksum_a, cancellable, error)) goto out; - if (!get_file_checksum (b, b_info, &checksum_b, cancellable, error)) + if (!get_file_checksum (flags, b, b_info, &checksum_b, cancellable, error)) goto out; if (strcmp (checksum_a, checksum_b) != 0) @@ -184,6 +203,7 @@ diff_add_dir_recurse (GFile *d, /** * ostree_diff_dirs: + * @flags: Flags * @a: First directory path * @b: First directory path * @modified: (element-type OstreeDiffItem): Modified files @@ -194,7 +214,8 @@ diff_add_dir_recurse (GFile *d, * sets of #OstreeDiffItem in @modified, @removed, and @added. */ gboolean -ostree_diff_dirs (GFile *a, +ostree_diff_dirs (OstreeDiffFlags flags, + GFile *a, GFile *b, GPtrArray *modified, GPtrArray *removed, @@ -295,7 +316,7 @@ ostree_diff_dirs (GFile *a, { OstreeDiffItem *diff_item = NULL; - if (!diff_files (child_a, child_a_info, child_b, child_b_info, &diff_item, + if (!diff_files (flags, child_a, child_a_info, child_b, child_b_info, &diff_item, cancellable, error)) goto out; @@ -304,7 +325,7 @@ ostree_diff_dirs (GFile *a, if (child_a_type == G_FILE_TYPE_DIRECTORY) { - if (!ostree_diff_dirs (child_a, child_b, modified, + if (!ostree_diff_dirs (flags, child_a, child_b, modified, removed, added, cancellable, error)) goto out; } diff --git a/src/libostree/ostree-diff.h b/src/libostree/ostree-diff.h index 35e99f97..6a56e3ab 100644 --- a/src/libostree/ostree-diff.h +++ b/src/libostree/ostree-diff.h @@ -27,6 +27,11 @@ G_BEGIN_DECLS +typedef enum { + OSTREE_DIFF_FLAGS_NONE = 0, + OSTREE_DIFF_FLAGS_IGNORE_XATTRS = (1 << 0) +} OstreeDiffFlags; + typedef struct _OstreeDiffItem OstreeDiffItem; struct _OstreeDiffItem { @@ -47,7 +52,8 @@ void ostree_diff_item_unref (OstreeDiffItem *diffitem); GType ostree_diff_item_get_type (void); -gboolean ostree_diff_dirs (GFile *a, +gboolean ostree_diff_dirs (OstreeDiffFlags flags, + GFile *a, GFile *b, GPtrArray *modified, GPtrArray *removed, diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index cef85864..69a2b7f4 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -126,7 +126,16 @@ merge_etc_changes (GFile *orig_etc, removed = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); added = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); - if (!ostree_diff_dirs (orig_etc, modified_etc, modified, removed, added, + /* For now, ignore changes to xattrs; the problem is that + * security.selinux will be different between the /usr/etc labels + * and the ones in the real /etc, so they all show up as different. + * + * This means that if you want to change the security context of a + * file, to have that change persist across upgrades, you must also + * modify the content of the file. + */ + if (!ostree_diff_dirs (OSTREE_DIFF_FLAGS_IGNORE_XATTRS, + orig_etc, modified_etc, modified, removed, added, cancellable, error)) { g_prefix_error (error, "While computing configuration diff: "); @@ -234,7 +243,7 @@ checkout_deployment_tree (OstreeSysroot *sysroot, #ifdef HAVE_SELINUX static gboolean -get_selinux_policy_root (OstreeSysroot *sysroot, +get_selinux_policy_root (GFile *deployment_etc, GFile **out_policy_root, GCancellable *cancellable, GError **error) @@ -250,7 +259,7 @@ get_selinux_policy_root (OstreeSysroot *sysroot, const char *selinux_prefix = "SELINUX="; const char *selinuxtype_prefix = "SELINUXTYPE="; - etc_selinux_dir = g_file_resolve_relative_path (sysroot->path, "etc/selinux"); + etc_selinux_dir = g_file_get_child (deployment_etc, "selinux"); policy_config_path = g_file_get_child (etc_selinux_dir, "config"); if (g_file_query_exists (policy_config_path, NULL)) @@ -435,25 +444,30 @@ relabel_recursively (GFile *dir, #endif +typedef struct { + gboolean have_policy; +#ifdef HAVE_SELINUX + struct selabel_handle *hnd; +#endif +} OstreeLabelingContext; + static gboolean -relabel_etc (OstreeSysroot *sysroot, - GFile *deployment_etc_path, - GCancellable *cancellable, - GError **error) +init_labeling_context (GFile *deployment_etc, + OstreeLabelingContext *secontext, + GCancellable *cancellable, + GError **error) { #ifdef HAVE_SELINUX gboolean ret = FALSE; gs_unref_object GFile *policy_root = NULL; - if (!get_selinux_policy_root (sysroot, &policy_root, + if (!get_selinux_policy_root (deployment_etc, &policy_root, cancellable, error)) goto out; if (policy_root) { - struct selabel_handle *hnd; - gs_unref_ptrarray GPtrArray *path_parts = g_ptr_array_new (); - gs_unref_object GFileInfo *root_info = NULL; + secontext->have_policy = TRUE; g_print ("ostadmin: Using SELinux policy '%s'\n", gs_file_get_basename_cached (policy_root)); @@ -465,31 +479,158 @@ relabel_etc (OstreeSysroot *sysroot, strerror (errno)); goto out; } - hnd = selabel_open (SELABEL_CTX_FILE, NULL, 0); - if (!hnd) + secontext->hnd = selabel_open (SELABEL_CTX_FILE, NULL, 0); + if (!secontext->hnd) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "selabel_open(SELABEL_CTX_FILE): %s", strerror (errno)); goto out; } + } + else + secontext->have_policy = FALSE; - root_info = g_file_query_info (deployment_etc_path, OSTREE_GIO_FAST_QUERYINFO, + ret = TRUE; + out: + return ret; +#else + secontext->have_policy = FALSE; + return TRUE; +#endif +} + +static void +ostree_labeling_context_cleanup (OstreeLabelingContext *secontext) +{ + if (secontext->hnd) + selabel_close (secontext->hnd); +} + +static gboolean +selinux_relabel_dir (OstreeSysroot *sysroot, + OstreeLabelingContext *secontext, + GFile *dir, + const char *prefix, + GCancellable *cancellable, + GError **error) +{ +#ifdef HAVE_SELINUX + gboolean ret = FALSE; + gs_unref_ptrarray GPtrArray *path_parts = g_ptr_array_new (); + gs_unref_object GFileInfo *root_info = NULL; + + if (secontext->have_policy) + { + root_info = g_file_query_info (dir, OSTREE_GIO_FAST_QUERYINFO, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, cancellable, error); if (!root_info) goto out; - g_ptr_array_add (path_parts, "etc"); - if (!relabel_recursively (deployment_etc_path, root_info, path_parts, hnd, + g_ptr_array_add (path_parts, (char*)prefix); + if (!relabel_recursively (dir, root_info, path_parts, secontext->hnd, cancellable, error)) { - g_prefix_error (error, "Relabeling /etc: "); + g_prefix_error (error, "Relabeling /%s: ", prefix); goto out; } } - else - g_print ("ostadmin: No SELinux policy found\n"); + + ret = TRUE; + out: + return ret; +#else + return TRUE; +#endif +} + +static gboolean +selinux_relabel_file (OstreeLabelingContext *secontext, + GFile *path, + const char *prefix, + GCancellable *cancellable, + GError **error) +{ +#ifdef HAVE_SELINUX + gboolean ret = FALSE; + + if (secontext->have_policy) + { + gs_unref_ptrarray GPtrArray *path_parts = g_ptr_array_new (); + gs_unref_object GFileInfo *file_info = g_file_query_info (path, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, error); + if (!file_info) + goto out; + + g_ptr_array_add (path_parts, (char*)prefix); + g_ptr_array_add (path_parts, (char*)gs_file_get_basename_cached (path)); + if (!relabel_one_path (path, file_info, path_parts, secontext->hnd, + cancellable, error)) + { + g_prefix_error (error, "Relabeling /%s/%s: ", prefix, + gs_file_get_basename_cached (path)); + goto out; + } + } + + ret = TRUE; + out: + return ret; +#else + return TRUE; +#endif +} + +static gboolean +selinux_relabel_var_if_needed (OstreeSysroot *sysroot, + OstreeLabelingContext *secontext, + GFile *deployment_var_path, + GCancellable *cancellable, + GError **error) +{ +#ifdef HAVE_SELINUX + gboolean ret = FALSE; + + if (secontext->have_policy) + { + /* This is a bit of a hack; we should change the code at some + * point in the distant future to only create (and label) /var + * when doing a deployment. + */ + gs_unref_object GFile *deployment_var_labeled = + g_file_get_child (deployment_var_path, ".ostree-selabeled"); + gs_unref_object GFile *deployment_var_labeled_tmp = + g_file_get_child (deployment_var_path, ".ostree-selabeled.tmp"); + + if (!g_file_query_exists (deployment_var_labeled, NULL)) + { + g_print ("ostadmin: Didn't find '%s', relabeling /var\n", + gs_file_get_path_cached (deployment_var_labeled)); + + if (!selinux_relabel_dir (sysroot, secontext, + deployment_var_path, "var", + cancellable, error)) + { + g_prefix_error (error, "Relabeling /var: "); + goto out; + } + + if (!g_file_replace_contents (deployment_var_labeled_tmp, "", 0, NULL, FALSE, + G_FILE_CREATE_REPLACE_DESTINATION, NULL, + cancellable, error)) + goto out; + + if (!selinux_relabel_file (secontext, deployment_var_labeled_tmp, "var", + cancellable, error)) + goto out; + + if (!gs_file_rename (deployment_var_labeled_tmp, deployment_var_labeled, + cancellable, error)) + goto out; + } + } ret = TRUE; out: @@ -503,9 +644,9 @@ static gboolean merge_configuration (OstreeSysroot *sysroot, OstreeDeployment *previous_deployment, OstreeDeployment *deployment, - GFile *deployment_path, - GCancellable *cancellable, - GError **error) + GFile *deployment_path, + GCancellable *cancellable, + GError **error) { gboolean ret = FALSE; gs_unref_object GFile *source_etc_path = NULL; @@ -560,11 +701,20 @@ merge_configuration (OstreeSysroot *sysroot, if (usretc_exists) { + __attribute__((cleanup(ostree_labeling_context_cleanup))) OstreeLabelingContext new_default_secontext = { 0, }; + + /* TODO - set out labels as we copy files */ g_assert (!etc_exists); if (!gs_shutil_cp_a (deployment_usretc_path, deployment_etc_path, cancellable, error)) goto out; - if (!relabel_etc (sysroot, deployment_etc_path, cancellable, error)) + + if (!init_labeling_context (deployment_etc_path, &new_default_secontext, + cancellable, error)) + goto out; + + if (!selinux_relabel_dir (sysroot, &new_default_secontext, deployment_etc_path, "etc", + cancellable, error)) goto out; g_print ("ostadmin: Created %s\n", gs_file_get_path_cached (deployment_etc_path)); } @@ -1352,10 +1502,13 @@ ostree_sysroot_deploy_tree (OstreeSysroot *self, { gboolean ret = FALSE; gint new_deployserial; + __attribute__((cleanup(ostree_labeling_context_cleanup))) OstreeLabelingContext secontext = { 0, }; gs_unref_object OstreeDeployment *new_deployment = NULL; gs_unref_object OstreeDeployment *merge_deployment = NULL; gs_unref_object OstreeRepo *repo = NULL; gs_unref_object GFile *osdeploydir = NULL; + gs_unref_object GFile *deployment_var = NULL; + gs_unref_object GFile *deployment_etc = NULL; gs_unref_object GFile *commit_root = NULL; gs_unref_object GFile *tree_kernel_path = NULL; gs_unref_object GFile *tree_initramfs_path = NULL; @@ -1376,6 +1529,8 @@ ostree_sysroot_deploy_tree (OstreeSysroot *self, goto out; } + deployment_var = g_file_get_child (osdeploydir, "var"); + if (!ostree_sysroot_get_repo (self, &repo, cancellable, error)) goto out; @@ -1436,6 +1591,15 @@ ostree_sysroot_deploy_tree (OstreeSysroot *self, goto out; } + deployment_etc = g_file_get_child (new_deployment_path, "etc"); + + if (!init_labeling_context (deployment_etc, &secontext, cancellable, error)) + goto out; + + if (!selinux_relabel_var_if_needed (self, &secontext, deployment_var, + cancellable, error)) + goto out; + /* After this, install_deployment_kernel() will set the other boot * options and write it out to disk. */ diff --git a/src/ostree/ot-admin-builtin-diff.c b/src/ostree/ot-admin-builtin-diff.c index 7497e1a5..6623b512 100644 --- a/src/ostree/ot-admin-builtin-diff.c +++ b/src/ostree/ot-admin-builtin-diff.c @@ -83,7 +83,8 @@ ot_admin_builtin_diff (int argc, char **argv, OstreeSysroot *sysroot, GCancellab modified = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_diff_item_unref); removed = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); added = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); - if (!ostree_diff_dirs (orig_etc_path, new_etc_path, modified, removed, added, + if (!ostree_diff_dirs (OSTREE_DIFF_FLAGS_IGNORE_XATTRS, + orig_etc_path, new_etc_path, modified, removed, added, cancellable, error)) goto out; diff --git a/src/ostree/ot-builtin-diff.c b/src/ostree/ot-builtin-diff.c index 9c26e703..00de8cbb 100644 --- a/src/ostree/ot-builtin-diff.c +++ b/src/ostree/ot-builtin-diff.c @@ -170,7 +170,7 @@ ostree_builtin_diff (int argc, char **argv, OstreeRepo *repo, GCancellable *canc removed = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref); added = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref); - if (!ostree_diff_dirs (srcf, targetf, modified, removed, added, cancellable, error)) + if (!ostree_diff_dirs (OSTREE_DIFF_FLAGS_NONE, srcf, targetf, modified, removed, added, cancellable, error)) goto out; ostree_diff_print (srcf, targetf, modified, removed, added);