Rewrite the relabel API to read the policy configured in the guest,
invoking setfiles (added as part of the appliance, as part of
policycoreutils) to relabel the specified root. In case of failure at
any point of the process, a touch of .autorelabel in the root is tried
as last-attempt measure to do the relabel.
Considering that running SELinux tools in the appliance might be
affected by the SELinux state (leading to wrong results),
selinux_relabel now bails out if SELinux is enabled in the appliance.
As a result of this, virt-builder and virt-customize explicitly disable
it if the relabel is enabled.
---
appliance/packagelist.in | 1 +
builder/builder.ml | 5 +-
customize/customize_main.ml | 5 +-
daemon/selinux.c | 198 ++++++++++++++++++++++++++++++++++++++++----
generator/actions.ml | 7 ++
5 files changed, 197 insertions(+), 19 deletions(-)
diff --git a/appliance/packagelist.in b/appliance/packagelist.in
index e4a9c8b..6336048 100644
--- a/appliance/packagelist.in
+++ b/appliance/packagelist.in
@@ -217,6 +217,7 @@ dnl Enabling this pulls out 140 extra packages
dnl into the appliance:
dnl ocfs2-tools
parted
+policycoreutils
procps
procps-ng
psmisc
diff --git a/builder/builder.ml b/builder/builder.ml
index e7be901..a0ef6d7 100644
--- a/builder/builder.ml
+++ b/builder/builder.ml
@@ -585,7 +585,10 @@ let main () =
(match smp with None -> () | Some smp -> g#set_smp smp);
g#set_network network;
- g#set_selinux ops.flags.selinux_relabel;
+ (* If a relabel is needed, make sure to turn SELinux off to avoid
+ * awkward interactions with the relabel process.
+ *)
+ if ops.flags.selinux_relabel then g#set_selinux false;
(* The output disk is being created, so use cache=unsafe here. *)
g#add_drive_opts ~format:output_format ~cachemode:"unsafe"
output_filename;
diff --git a/customize/customize_main.ml b/customize/customize_main.ml
index 00d5bae..a6c6643 100644
--- a/customize/customize_main.ml
+++ b/customize/customize_main.ml
@@ -193,7 +193,10 @@ read the man page virt-customize(1).
(match memsize with None -> () | Some memsize -> g#set_memsize memsize);
(match smp with None -> () | Some smp -> g#set_smp smp);
g#set_network network;
- g#set_selinux ops.flags.selinux_relabel;
+ (* If a relabel is needed, make sure to turn SELinux off to avoid
+ * awkward interactions with the relabel process.
+ *)
+ if ops.flags.selinux_relabel then g#set_selinux false;
(* Add disks. *)
add g dryrun;
diff --git a/daemon/selinux.c b/daemon/selinux.c
index d0a3832..879bc61 100644
--- a/daemon/selinux.c
+++ b/daemon/selinux.c
@@ -21,6 +21,8 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
#ifdef HAVE_SELINUX_SELINUX_H
#include <selinux/selinux.h>
@@ -31,6 +33,8 @@
#include "actions.h"
#include "optgroups.h"
+GUESTFSD_EXT_CMD(str_setfiles, setfiles);
+
#if defined(HAVE_LIBSELINUX)
int
@@ -92,28 +96,188 @@ OPTGROUP_SELINUX_NOT_AVAILABLE
#endif /* !HAVE_LIBSELINUX */
int
-do_selinux_relabel (const char *root)
+optgroup_selinuxtools_available (void)
+{
+ return prog_exists (str_setfiles);
+}
+
+#define SELINUXTYPE "SELINUXTYPE"
+
+static int
+has_selinux_mountpoint (void)
+{
+ static int has_mp = -1;
+ if (has_mp == -1) {
+ struct stat sb;
+ dev_t root_dev;
+
+ if (stat ("/", &sb) == -1)
+ return -1;
+ root_dev = sb.st_dev;
+
+ if (stat ("/sys/fs/selinux", &sb) == 0 && sb.st_dev !=
root_dev) {
+ has_mp = 1;
+ return has_mp;
+ }
+
+ if (stat ("/selinux", &sb) == 0 && sb.st_dev != root_dev) {
+ has_mp = 1;
+ return has_mp;
+ }
+ }
+ return has_mp;
+}
+
+static int
+read_selinux_policy (const char *file, char **policy)
+{
+ FILE *f;
+ ssize_t r;
+ CLEANUP_FREE char *line = NULL;
+ size_t len;
+
+ f = fopen (file, "r");
+ if (f == NULL)
+ return -1;
+
+ while ((r = getline (&line, &len, f)) != -1) {
+ if ((size_t) r >= sizeof (SELINUXTYPE) + 1
+ && STRPREFIX (line, SELINUXTYPE "=")) {
+ *policy = strndup (line + sizeof (SELINUXTYPE), r - sizeof (SELINUXTYPE) - 1);
+ if (*policy == NULL) {
+ int errno_save = errno;
+ fclose (f);
+ errno = errno_save;
+ return -1;
+ }
+ trim (*policy);
+ break;
+ }
+ }
+
+ fclose (f);
+
+ return 0;
+}
+
+static int
+length_without_training_slash (const char *path)
{
- CLEANUP_FREE char *cmd = NULL, *out = NULL;
- const char cmd_fmt[] =
- "if load_policy && fixfiles restore; then\n"
- " rm -f %.*s/.autorelabel\n"
- "else\n"
- " touch %.*s/.autorelabel\n"
- " echo 'SELinux relabelling failed, will relabel at boot
instead.'\n"
- "fi\n";
- int len = strlen (root);
-
- if (root[len - 1] == '/')
+ int len = strlen (path);
+ if (path[len - 1] == '/')
--len;
+ return len;
+}
+
+int
+do_selinux_relabel (const char *root)
+{
+ CLEANUP_FREE char *selinux_config = NULL;
+ CLEANUP_FREE char *policy = NULL;
+ CLEANUP_FREE char *sys_rootpath = NULL;
+ CLEANUP_FREE char *sys_filecontextpath = NULL;
+ CLEANUP_FREE char *sys_autorelabelpath = NULL;
+ CLEANUP_FREE char *err = NULL;
+ CLEANUP_FREE char *out = NULL;
+ int r;
+ int len;
+ const size_t MAX_ARGS = 16;
+ const char *argv[MAX_ARGS];
+ size_t i = 0;
- if (asprintf (&cmd, cmd_fmt, len, root, len, root) == -1) {
- reply_with_perror ("asprintf");
+ if (has_selinux_mountpoint () > 0) {
+ reply_with_error ("cannot work when SELinux is enabled in the "
+ "appliance\n");
return -1;
}
- out = do_sh (cmd);
+ len = length_without_training_slash (root);
+
+ if (asprintf (&selinux_config, "%s%.*s/etc/selinux/config",
+ sysroot, len, root) == -1) {
+ if (verbose)
+ fprintf (stderr, "asprintf/selinux_config failed\n");
+ goto do_autorelabel;
+ }
+
+ r = read_selinux_policy (selinux_config, &policy);
+ if (r == -1) {
+ if (verbose)
+ fprintf (stderr, "cannot read policy from %s\n", selinux_config);
+ goto do_autorelabel;
+ }
+ if (verbose)
+ fprintf (stderr, "policy in %s: %s\n", root, policy);
+
+ if (policy[0] == '\0')
+ goto do_autorelabel;
+
+ /* Make the root path relative to /sysroot. */
+ sys_rootpath = sysroot_path (root);
+ if (sys_rootpath == NULL) {
+ if (verbose)
+ fprintf (stderr, "sysroot_path failed\n");
+ goto do_autorelabel;
+ }
+
+ len = length_without_training_slash (sys_rootpath);
+
+ if (asprintf (&sys_filecontextpath,
+ "%.*s/etc/selinux/%s/contexts/files/file_contexts",
+ len, sys_rootpath, policy) == -1) {
+ if (verbose)
+ fprintf (stderr, "asprintf/file_contexts failed\n");
+ goto do_autorelabel;
+ }
+
+ ADD_ARG (argv, i, str_setfiles);
+ ADD_ARG (argv, i, "-W");
if (verbose)
- fprintf (stderr, "%s\n", out);
- return out == NULL ? -1 : 0;
+ ADD_ARG (argv, i, "-v");
+ ADD_ARG (argv, i, "-r");
+ ADD_ARG (argv, i, sys_rootpath);
+ ADD_ARG (argv, i, sys_filecontextpath);
+ ADD_ARG (argv, i, sys_rootpath);
+ ADD_ARG (argv, i, NULL);
+
+ r = commandv (verbose ? &out : NULL, &err, argv);
+ if (r == -1) {
+ if (verbose)
+ fprintf (stderr, "failed setfiles %s for %s: %s\n", policy, root, err);
+ goto do_autorelabel;
+ }
+ if (verbose)
+ fprintf (stderr, "output from setfiles:\n%s\n", out);
+
+ if (asprintf (&sys_autorelabelpath, "%.*s/.autorelabel",
+ len, sys_rootpath) == -1) {
+ if (verbose)
+ fprintf (stderr, "asprintf/autorelabelpath failed\n");
+ /* We can safely exit in this case as well, since at most there is a
+ * ~/.autorelabel left in the guest.
+ */
+ return 0;
+ }
+
+ unlink (sys_autorelabelpath);
+
+ return 0;
+
+ do_autorelabel:
+ /* All of the above failed -- try to touch .autorelabel in the root
+ * and hope for the best.
+ */
+ if (verbose)
+ fprintf (stderr, "trying /.autorelabel\n");
+
+ len = length_without_training_slash (root);
+
+ if (asprintf (&sys_autorelabelpath, "%.*s/.autorelabel",
+ len, root) == -1) {
+ if (verbose)
+ fprintf (stderr, "asprintf/autorelabelpath failed\n");
+ return -1;
+ }
+
+ return do_touch (sys_autorelabelpath);
}
diff --git a/generator/actions.ml b/generator/actions.ml
index a59fe31..3bb91c8 100644
--- a/generator/actions.ml
+++ b/generator/actions.ml
@@ -11928,12 +11928,19 @@ New (SVR4) portable format with a checksum.
name = "selinux_relabel";
style = RErr, [Pathname "root"], [];
proc_nr = Some 420;
+ optional = Some "selinuxtools";
shortdesc = "do the SELinux relabel of the files";
longdesc = "\
This does a relabel of the files of the system under the specified
C<root> according to the SELinux policy in the system mounted in that
C<root>.
+I<Note:> the relabel does not work when SELinux is enabled (see
+C<guestfs_set_selinux>), as the kernel and the policy in the
+appliance might not be compatible with the policy found inside the
+guest mounted at C<root>; hence, this function will error out in this
+case.
+
See the documentation about SELINUX in L<guestfs(3)>." };
]
--
1.9.3