diff --git a/configure.ac b/configure.ac index 7164a405..99c16303 100644 --- a/configure.ac +++ b/configure.ac @@ -139,6 +139,31 @@ AS_IF([ test x$with_libarchive != xno ], [ if test x$with_libarchive != xno; then OSTREE_FEATURES="$OSTREE_FEATURES +libarchive"; fi AM_CONDITIONAL(USE_LIBARCHIVE, test $with_libarchive != no) +dnl This is what is in RHEL7 anyways +SELINUX_DEPENDENCY="libselinux >= 2.2.1" + +AC_ARG_WITH(selinux, + AS_HELP_STRING([--without-selinux], [Do not use SELinux]), + :, with_selinux=maybe) + +AS_IF([ test x$with_selinux != xno ], [ + AC_MSG_CHECKING([for $SELINUX_DEPENDENCY]) + PKG_CHECK_EXISTS($SELINUX_DEPENDENCY, have_selinux=yes, have_selinux=no) + AC_MSG_RESULT([$have_selinux]) + AS_IF([ test x$have_selinux = xno && test x$with_selinux != xmaybe ], [ + AC_MSG_ERROR([SELinux is enabled but could not be found]) + ]) + AS_IF([ test x$have_selinux = xyes], [ + AC_DEFINE(HAVE_SELINUX, 1, [Define if we have libselinux.pc]) + PKG_CHECK_MODULES(OT_DEP_SELINUX, $SELINUX_DEPENDENCY) + with_selinux=yes + ], [ + with_selinux=no + ]) +], [ with_selinux=no ]) +if test x$with_selinux != xno; then OSTREE_FEATURES="$OSTREE_FEATURES +selinux"; fi +AM_CONDITIONAL(USE_SELINUX, test $with_selinux != no) + AC_ARG_WITH(dracut, AS_HELP_STRING([--with-dracut], [Install dracut module (default: no)]),, @@ -190,6 +215,7 @@ echo " embedded dependencies: $enable_embedded_dependencies introspection: $found_introspection libsoup (retrieve remote HTTP repositories): $with_soup + SELinux: $with_selinux libarchive (parse tar files directly): $with_libarchive gpgme (sign commits): $with_gpgme documentation: $enable_gtk_doc diff --git a/packaging/ostree.spec.in b/packaging/ostree.spec.in index 8c284677..c2596d2c 100644 --- a/packaging/ostree.spec.in +++ b/packaging/ostree.spec.in @@ -18,6 +18,7 @@ BuildRequires: pkgconfig(libsoup-2.4) BuildRequires: libattr-devel # Extras BuildRequires: pkgconfig(libarchive) +BuildRequires: pkgconfig(libselinux) BuildRequires: gpgme-devel BuildRequires: pkgconfig(systemd) BuildRequires: /usr/bin/g-ir-scanner diff --git a/src/libostree/ostree-repo-commit.c b/src/libostree/ostree-repo-commit.c index 7482db77..ad910dce 100644 --- a/src/libostree/ostree-repo-commit.c +++ b/src/libostree/ostree-repo-commit.c @@ -1613,6 +1613,7 @@ struct OstreeRepoCommitModifier { GDestroyNotify destroy_notify; OstreeRepoCommitModifierXattrCallback xattr_callback; + GDestroyNotify xattr_destroy; gpointer xattr_user_data; }; @@ -2070,6 +2071,9 @@ ostree_repo_commit_modifier_unref (OstreeRepoCommitModifier *modifier) if (modifier->destroy_notify) modifier->destroy_notify (modifier->user_data); + if (modifier->xattr_destroy) + modifier->xattr_destroy (modifier->xattr_user_data); + g_free (modifier); return; } @@ -2078,7 +2082,8 @@ ostree_repo_commit_modifier_unref (OstreeRepoCommitModifier *modifier) * ostree_repo_commit_modifier_set_xattr_callback: * @modifier: An #OstreeRepoCommitModifier * @callback: Function to be invoked, should return extended attributes for path - * @user_data: (closure): Data for @callback: + * @destroy: Destroy notification + * @user_data: Data for @callback: * * If set, this function should return extended attributes to use for * the given path. This is useful for things like SELinux, where a build @@ -2087,9 +2092,11 @@ ostree_repo_commit_modifier_unref (OstreeRepoCommitModifier *modifier) void ostree_repo_commit_modifier_set_xattr_callback (OstreeRepoCommitModifier *modifier, OstreeRepoCommitModifierXattrCallback callback, + GDestroyNotify destroy, gpointer user_data) { modifier->xattr_callback = callback; + modifier->xattr_destroy = destroy; modifier->xattr_user_data = user_data; } diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h index 9521a583..3f311f9f 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -307,6 +307,7 @@ typedef GVariant *(*OstreeRepoCommitModifierXattrCallback) (OstreeRepo *repo void ostree_repo_commit_modifier_set_xattr_callback (OstreeRepoCommitModifier *modifier, OstreeRepoCommitModifierXattrCallback callback, + GDestroyNotify destroy, gpointer user_data); OstreeRepoCommitModifier *ostree_repo_commit_modifier_ref (OstreeRepoCommitModifier *modifier); diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index 4a217401..cef85864 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -22,6 +22,11 @@ #include "config.h" +#ifdef HAVE_SELINUX +#include +#include +#endif + #include "ostree-sysroot-private.h" #include "ostree-core-private.h" #include "otutil.h" @@ -227,6 +232,273 @@ checkout_deployment_tree (OstreeSysroot *sysroot, return ret; } +#ifdef HAVE_SELINUX +static gboolean +get_selinux_policy_root (OstreeSysroot *sysroot, + GFile **out_policy_root, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + gs_unref_object GFile *etc_selinux_dir = NULL; + gs_unref_object GFile *policy_config_path = NULL; + gs_unref_object GFile *ret_policy_root = NULL; + gs_unref_object GFileInputStream *filein = NULL; + gs_unref_object GDataInputStream *datain = NULL; + gboolean enabled = FALSE; + char *policytype = NULL; + const char *selinux_prefix = "SELINUX="; + const char *selinuxtype_prefix = "SELINUXTYPE="; + + etc_selinux_dir = g_file_resolve_relative_path (sysroot->path, "etc/selinux"); + policy_config_path = g_file_get_child (etc_selinux_dir, "config"); + + if (g_file_query_exists (policy_config_path, NULL)) + { + filein = g_file_read (policy_config_path, cancellable, error); + if (!filein) + goto out; + + datain = g_data_input_stream_new ((GInputStream*)filein); + + while (TRUE) + { + gsize len; + GError *temp_error = NULL; + gs_free char *line = g_data_input_stream_read_line_utf8 (datain, &len, + cancellable, &temp_error); + + if (temp_error) + { + g_propagate_error (error, temp_error); + goto out; + } + + if (!line) + break; + + if (g_str_has_prefix (line, selinuxtype_prefix)) + { + policytype = g_strstrip (g_strdup (line + strlen (selinuxtype_prefix))); + } + else if (g_str_has_prefix (line, selinux_prefix)) + { + const char *enabled_str = line + strlen (selinux_prefix); + if (g_ascii_strncasecmp (enabled_str, "enforcing", strlen ("enforcing")) == 0 || + g_ascii_strncasecmp (enabled_str, "permissive", strlen ("permissive")) == 0) + enabled = TRUE; + } + } + } + + if (enabled) + ret_policy_root = g_file_get_child (etc_selinux_dir, policytype); + + ret = TRUE; + gs_transfer_out_value (out_policy_root, &ret_policy_root); + out: + return ret; +} + +static char * +ptrarray_path_join (GPtrArray *path) +{ + GString *path_buf; + + path_buf = g_string_new (""); + + if (path->len == 0) + g_string_append_c (path_buf, '/'); + else + { + guint i; + for (i = 0; i < path->len; i++) + { + const char *elt = path->pdata[i]; + + g_string_append_c (path_buf, '/'); + g_string_append (path_buf, elt); + } + } + + return g_string_free (path_buf, FALSE); +} + +static gboolean +relabel_one_path (GFile *path, + GFileInfo *info, + GPtrArray *path_parts, + struct selabel_handle *hnd, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + guint32 mode; + gs_free char *relpath = NULL; + char *con = NULL; + + mode = g_file_info_get_attribute_uint32 (info, "unix::mode"); + + relpath = ptrarray_path_join (path_parts); + + if (selabel_lookup_raw (hnd, &con, relpath, mode) != 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "selabel_lookup_raw(%s, %u): %s", + relpath, mode, strerror (errno)); + goto out; + } + + if (S_ISLNK (mode)) + { + if (lsetfilecon (gs_file_get_path_cached (path), con) != 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "lsetfilecon(%s): %s", + gs_file_get_path_cached (path), strerror (errno)); + goto out; + } + } + else + { + if (setfilecon (gs_file_get_path_cached (path), con) != 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "setfilecon(%s): %s", + gs_file_get_path_cached (path), strerror (errno)); + goto out; + } + } + + ret = TRUE; + out: + if (con) freecon (con); + return ret; +} + +static gboolean +relabel_recursively (GFile *dir, + GFileInfo *dir_info, + GPtrArray *path_parts, + struct selabel_handle *hnd, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + gs_unref_object GFileEnumerator *direnum = NULL; + + if (!relabel_one_path (dir, dir_info, path_parts, hnd, + cancellable, error)) + goto out; + + direnum = g_file_enumerate_children (dir, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, error); + if (!direnum) + goto out; + + while (TRUE) + { + GFileInfo *file_info; + GFile *child; + GFileType ftype; + + if (!gs_file_enumerator_iterate (direnum, &file_info, &child, + cancellable, error)) + goto out; + if (file_info == NULL) + break; + + g_ptr_array_add (path_parts, (char*)gs_file_get_basename_cached (child)); + + ftype = g_file_info_get_file_type (file_info); + if (ftype == G_FILE_TYPE_DIRECTORY) + { + if (!relabel_recursively (child, file_info, path_parts, hnd, + cancellable, error)) + goto out; + } + else + { + if (!relabel_one_path (child, file_info, path_parts, hnd, + cancellable, error)) + goto out; + } + + g_ptr_array_remove_index (path_parts, path_parts->len - 1); + } + + ret = TRUE; + out: + return ret; +} + +#endif + +static gboolean +relabel_etc (OstreeSysroot *sysroot, + GFile *deployment_etc_path, + 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, + 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; + + g_print ("ostadmin: Using SELinux policy '%s'\n", gs_file_get_basename_cached (policy_root)); + + if (selinux_set_policy_root (gs_file_get_path_cached (policy_root)) != 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "selinux_set_policy_root(%s): %s", + gs_file_get_path_cached (policy_root), + strerror (errno)); + goto out; + } + hnd = selabel_open (SELABEL_CTX_FILE, NULL, 0); + if (!hnd) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "selabel_open(SELABEL_CTX_FILE): %s", + strerror (errno)); + goto out; + } + + root_info = g_file_query_info (deployment_etc_path, 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, + cancellable, error)) + { + g_prefix_error (error, "Relabeling /etc: "); + goto out; + } + } + else + g_print ("ostadmin: No SELinux policy found\n"); + + ret = TRUE; + out: + return ret; +#else + return TRUE; +#endif +} + static gboolean merge_configuration (OstreeSysroot *sysroot, OstreeDeployment *previous_deployment, @@ -292,6 +564,8 @@ merge_configuration (OstreeSysroot *sysroot, if (!gs_shutil_cp_a (deployment_usretc_path, deployment_etc_path, cancellable, error)) goto out; + if (!relabel_etc (sysroot, deployment_etc_path, cancellable, error)) + goto out; g_print ("ostadmin: Created %s\n", gs_file_get_path_cached (deployment_etc_path)); }