libostree: Split off SELinux OstreeSePolicy class
It's better if this is independent from the OstreeSysroot; for example, a policy is active in a given deployment root at once, not for a sysroot globally. We can also collect SELinux-related API in one place. Unfortunately at the moment there can be only one instance of this class per process.
This commit is contained in:
1 parent
cc49096044
commit
3337334be5
8 files changed
+721
-298
No files matched your search
@@ -0,0 +1,456 @@
|
||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
|
||||
*
|
||||
* Copyright (C) 2014 Colin Walters <walters@verbum.org>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_SELINUX
|
||||
#include <selinux/selinux.h>
|
||||
#include <selinux/label.h>
|
||||
#endif
|
||||
|
||||
#include "otutil.h"
|
||||
#include "libgsystem.h"
|
||||
|
||||
#include "ostree-sepolicy.h"
|
||||
#include "ostree-bootloader-uboot.h"
|
||||
#include "ostree-bootloader-syslinux.h"
|
||||
|
||||
/**
|
||||
* SECTION:libostree-sepolicy
|
||||
* @title: SELinux policy management
|
||||
* @short_description: Read SELinux policy and manage filesystem labels
|
||||
*
|
||||
* A #OstreeSePolicy object can load the SELinux policy from a given
|
||||
* root and perform labeling.
|
||||
*/
|
||||
struct OstreeSePolicy {
|
||||
GObject parent;
|
||||
|
||||
GFile *path;
|
||||
|
||||
#ifdef HAVE_SELINUX
|
||||
GFile *selinux_policy_root;
|
||||
struct selabel_handle *selinux_hnd;
|
||||
char *selinux_policy_name;
|
||||
#endif
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
GObjectClass parent_class;
|
||||
} OstreeSePolicyClass;
|
||||
|
||||
static void initable_iface_init (GInitableIface *initable_iface);
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
|
||||
PROP_PATH
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (OstreeSePolicy, ostree_sepolicy, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init))
|
||||
|
||||
static void
|
||||
ostree_sepolicy_finalize (GObject *object)
|
||||
{
|
||||
OstreeSePolicy *self = OSTREE_SEPOLICY (object);
|
||||
|
||||
g_clear_object (&self->path);
|
||||
g_clear_object (&self->selinux_policy_root);
|
||||
g_clear_pointer (&self->selinux_policy_name, g_free);
|
||||
#ifdef HAVE_SELINUX
|
||||
if (self->selinux_hnd)
|
||||
{
|
||||
selabel_close (self->selinux_hnd);
|
||||
self->selinux_hnd = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
G_OBJECT_CLASS (ostree_sepolicy_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
ostree_sepolicy_set_property(GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
OstreeSePolicy *self = OSTREE_SEPOLICY (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_PATH:
|
||||
/* Canonicalize */
|
||||
self->path = g_file_new_for_path (gs_file_get_path_cached (g_value_get_object (value)));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
ostree_sepolicy_get_property(GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
OstreeSePolicy *self = OSTREE_SEPOLICY (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_PATH:
|
||||
g_value_set_object (value, self->path);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
ostree_sepolicy_constructed (GObject *object)
|
||||
{
|
||||
OstreeSePolicy *self = OSTREE_SEPOLICY (object);
|
||||
|
||||
g_assert (self->path != NULL);
|
||||
|
||||
G_OBJECT_CLASS (ostree_sepolicy_parent_class)->constructed (object);
|
||||
}
|
||||
|
||||
static void
|
||||
ostree_sepolicy_class_init (OstreeSePolicyClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->constructed = ostree_sepolicy_constructed;
|
||||
object_class->get_property = ostree_sepolicy_get_property;
|
||||
object_class->set_property = ostree_sepolicy_set_property;
|
||||
object_class->finalize = ostree_sepolicy_finalize;
|
||||
|
||||
g_object_class_install_property (object_class,
|
||||
PROP_PATH,
|
||||
g_param_spec_object ("path",
|
||||
"",
|
||||
"",
|
||||
G_TYPE_FILE,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
initable_init (GInitable *initable,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
#ifdef HAVE_SELINUX
|
||||
gboolean ret = FALSE;
|
||||
OstreeSePolicy *self = OSTREE_SEPOLICY (initable);
|
||||
gs_unref_object GFile *etc_selinux_dir = NULL;
|
||||
gs_unref_object GFile *policy_config_path = NULL;
|
||||
gs_unref_object GFile *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 (self->path, "etc/selinux");
|
||||
if (!g_file_query_exists (etc_selinux_dir, NULL))
|
||||
{
|
||||
g_object_unref (etc_selinux_dir);
|
||||
etc_selinux_dir = g_file_resolve_relative_path (self->path, "usr/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)));
|
||||
policy_root = g_file_get_child (etc_selinux_dir, policytype);
|
||||
}
|
||||
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)
|
||||
{
|
||||
g_setenv ("LIBSELINUX_DISABLE_PCRE_PRECOMPILED", "1", FALSE);
|
||||
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 (etc_selinux_dir),
|
||||
strerror (errno));
|
||||
goto out;
|
||||
}
|
||||
|
||||
self->selinux_hnd = selabel_open (SELABEL_CTX_FILE, NULL, 0);
|
||||
if (!self->selinux_hnd)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"With policy root '%s': selabel_open(SELABEL_CTX_FILE): %s",
|
||||
gs_file_get_path_cached (etc_selinux_dir),
|
||||
strerror (errno));
|
||||
goto out;
|
||||
}
|
||||
|
||||
{
|
||||
char *con = NULL;
|
||||
if (selabel_lookup_raw (self->selinux_hnd, &con, "/", 0755) != 0)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"With policy root '%s': Failed to look up context of /: %s",
|
||||
gs_file_get_path_cached (etc_selinux_dir),
|
||||
strerror (errno));
|
||||
goto out;
|
||||
}
|
||||
freecon (con);
|
||||
}
|
||||
|
||||
self->selinux_policy_name = g_strdup (policytype);
|
||||
self->selinux_policy_root = g_object_ref (etc_selinux_dir);
|
||||
}
|
||||
|
||||
ret = TRUE;
|
||||
out:
|
||||
return ret;
|
||||
#else
|
||||
return TRUE;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
ostree_sepolicy_init (OstreeSePolicy *self)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
initable_iface_init (GInitableIface *initable_iface)
|
||||
{
|
||||
initable_iface->init = initable_init;
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sepolicy_new:
|
||||
* @path: Path to a root directory
|
||||
*
|
||||
* Returns: (transfer full): An accessor object for SELinux policy in root located at @path
|
||||
*/
|
||||
OstreeSePolicy*
|
||||
ostree_sepolicy_new (GFile *path,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
return g_initable_new (OSTREE_TYPE_SEPOLICY, cancellable, error, "path", path, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sepolicy_get_path:
|
||||
* @self:
|
||||
*
|
||||
* Returns: (transfer none): Path to rootfs
|
||||
*/
|
||||
GFile *
|
||||
ostree_sepolicy_get_path (OstreeSePolicy *self)
|
||||
{
|
||||
return self->path;
|
||||
}
|
||||
|
||||
const char *
|
||||
ostree_sepolicy_get_name (OstreeSePolicy *self)
|
||||
{
|
||||
#ifdef HAVE_SELINUX
|
||||
return self->selinux_policy_name;
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sepolicy_get_label:
|
||||
* @self: Self
|
||||
* @relpath: Path
|
||||
* @unix_mode: Unix mode
|
||||
* @out_label: (allow-none) (out) (transfer full): Return location for security context
|
||||
* @cancellable: Cancellable
|
||||
* @error: Error
|
||||
*
|
||||
* Store in @out_label the security context for the given @relpath and
|
||||
* mode @unix_mode. If the policy does not specify a label, %NULL
|
||||
* will be returned.
|
||||
*/
|
||||
gboolean
|
||||
ostree_sepolicy_get_label (OstreeSePolicy *self,
|
||||
const char *relpath,
|
||||
guint32 unix_mode,
|
||||
char **out_label,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
#ifdef HAVE_SELINUX
|
||||
gboolean ret = FALSE;
|
||||
int res;
|
||||
char *con = NULL;
|
||||
|
||||
if (self->selinux_hnd)
|
||||
{
|
||||
res = selabel_lookup_raw (self->selinux_hnd, &con, relpath, unix_mode);
|
||||
if (res != 0)
|
||||
{
|
||||
int errsv = errno;
|
||||
if (errsv != ENOENT)
|
||||
{
|
||||
ot_util_set_error_from_errno (error, errsv);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Ensure we consistently allocate with g_malloc */
|
||||
*out_label = g_strdup (con);
|
||||
freecon (con);
|
||||
}
|
||||
}
|
||||
|
||||
ret = TRUE;
|
||||
out:
|
||||
return ret;
|
||||
#else
|
||||
return TRUE;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sepolicy_restorecon:
|
||||
* @self: Self
|
||||
* @path: Path string to use for policy lookup
|
||||
* @info: (allow-none): File attributes
|
||||
* @target: Physical path to target file
|
||||
* @flags: Flags controlling behavior
|
||||
* @out_new_label: (allow-none) (out): New label, or %NULL if unchanged
|
||||
* @cancellable: Cancellable
|
||||
* @error: Error
|
||||
*
|
||||
* Reset the security context of @target based on the SELinux policy.
|
||||
*/
|
||||
gboolean
|
||||
ostree_sepolicy_restorecon (OstreeSePolicy *self,
|
||||
const char *path,
|
||||
GFileInfo *info,
|
||||
GFile *target,
|
||||
OstreeSePolicyRestoreconFlags flags,
|
||||
char **out_new_label,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
#ifdef HAVE_SELINUX
|
||||
gboolean ret = FALSE;
|
||||
gs_unref_object GFileInfo *src_info = NULL;
|
||||
gs_free char *label = NULL;
|
||||
gboolean do_relabel = TRUE;
|
||||
|
||||
if (info != NULL)
|
||||
src_info = g_object_ref (info);
|
||||
else
|
||||
{
|
||||
src_info = g_file_query_info (target, "unix::mode",
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable, error);
|
||||
if (!src_info)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (flags & OSTREE_SEPOLICY_RESTORECON_FLAGS_KEEP_EXISTING)
|
||||
{
|
||||
char *existing_con = NULL;
|
||||
if (lgetfilecon_raw (gs_file_get_path_cached (target), &existing_con) > 0
|
||||
&& existing_con)
|
||||
{
|
||||
do_relabel = FALSE;
|
||||
freecon (existing_con);
|
||||
}
|
||||
}
|
||||
|
||||
if (do_relabel)
|
||||
{
|
||||
if (!ostree_sepolicy_get_label (self, path,
|
||||
g_file_info_get_attribute_uint32 (src_info, "unix::mode"),
|
||||
&label,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
|
||||
if (!label)
|
||||
{
|
||||
if (!(flags & OSTREE_SEPOLICY_RESTORECON_FLAGS_ALLOW_NOLABEL))
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"No label found for '%s'", path);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int res = lsetfilecon (gs_file_get_path_cached (target), label);
|
||||
if (res != 0)
|
||||
{
|
||||
ot_util_set_error_from_errno (error, errno);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret = TRUE;
|
||||
gs_transfer_out_value (out_new_label, &label);
|
||||
out:
|
||||
return ret;
|
||||
#else
|
||||
return TRUE;
|
||||
#endif
|
||||
}
|
||||
Reference in new issue
Block a user