ostree/src/libostree/ostree-sepolicy.c

517 lines
14 KiB
C

/* -*- 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 "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;
gboolean runtime_enabled;
#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);
#ifdef HAVE_SELINUX
g_clear_object (&self->selinux_policy_root);
g_clear_pointer (&self->selinux_policy_name, g_free);
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);
g_autoptr(GFile) etc_selinux_dir = NULL;
g_autoptr(GFile) policy_config_path = NULL;
g_autoptr(GFile) policy_root = NULL;
g_autoptr(GFileInputStream) filein = NULL;
g_autoptr(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;
g_autofree 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)
{
self->runtime_enabled = is_selinux_enabled () == 1;
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)
{
if (errno != ENOENT)
{
glnx_set_error_from_errno (error);
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;
g_autoptr(GFileInfo) src_info = NULL;
g_autofree 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)
{
glnx_set_error_from_errno (error);
goto out;
}
}
}
ret = TRUE;
if (out_new_label)
*out_new_label = g_steal_pointer (&label);
out:
return ret;
#else
return TRUE;
#endif
}
/**
* ostree_sepolicy_setfscreatecon:
* @self: Policy
* @path: Use this path to determine a label
* @mode: Used along with @path
* @error: Error
*
*/
gboolean
ostree_sepolicy_setfscreatecon (OstreeSePolicy *self,
const char *path,
guint32 mode,
GError **error)
{
#ifdef HAVE_SELINUX
gboolean ret = FALSE;
g_autofree char *label = NULL;
/* setfscreatecon() will bomb out if the host has SELinux disabled,
* but we're enabled for the target system. This is kind of a
* broken scenario...for now, we'll silently ignore the label
* request. To correctly handle the case of disabled host but
* enabled target will require nontrivial work.
*/
if (!self->runtime_enabled)
return TRUE;
if (!ostree_sepolicy_get_label (self, path, mode, &label, NULL, error))
goto out;
if (setfscreatecon_raw (label) != 0)
{
glnx_set_error_from_errno (error);
return FALSE;
}
ret = TRUE;
out:
return ret;
#else
return TRUE;
#endif
}
/**
* ostree_sepolicy_fscreatecon_cleanup:
*
* Cleanup function for ostree_sepolicy_setfscreatecon().
*/
void
ostree_sepolicy_fscreatecon_cleanup (void **unused)
{
#ifdef HAVE_SELINUX
setfscreatecon (NULL);
#endif
}