(root)/
util-linux-2.39/
libmount/
src/
hook_selinux.c
       1  /* SPDX-License-Identifier: LGPL-2.1-or-later */
       2  /*
       3   * This file is part of libmount from util-linux project.
       4   *
       5   * Copyright (C) 2023 Karel Zak <kzak@redhat.com>
       6   *
       7   * libmount is free software; you can redistribute it and/or modify it
       8   * under the terms of the GNU Lesser General Public License as published by
       9   * the Free Software Foundation; either version 2.1 of the License, or
      10   * (at your option) any later version.
      11   *
      12   *
      13   * Please, see the comment in libmount/src/hooks.c to understand how hooks work.
      14   */
      15  #ifdef HAVE_LIBSELINUX
      16  #include <selinux/selinux.h>
      17  #include <selinux/context.h>
      18  
      19  #include "mountP.h"
      20  #include "fileutils.h"
      21  #include "linux_version.h"
      22  
      23  static int hookset_deinit(struct libmnt_context *cxt, const struct libmnt_hookset *hs)
      24  {
      25  	void *data = NULL;
      26  
      27  	DBG(HOOK, ul_debugobj(hs, "deinit '%s'", hs->name));
      28  
      29  	/* remove all our hooks and free hook data */
      30  	while (mnt_context_remove_hook(cxt, hs, 0, &data) == 0) {
      31  		if (data)
      32  			free(data);
      33  		data = NULL;
      34  	}
      35  
      36  	return 0;
      37  }
      38  
      39  static inline int is_option(const char *name, const char *const *names)
      40  {
      41  	const char *const *p;
      42  
      43  	for (p = names; p && *p; p++) {
      44  		if (strcmp(name, *p) == 0)
      45  			return 1;
      46  	}
      47  	return 0;
      48  }
      49  
      50  /* Converts rootcontext=@target to the real selinxu context
      51   */
      52  static int hook_selinux_target(
      53  			struct libmnt_context *cxt,
      54  			const struct libmnt_hookset *hs,
      55  			void *data __attribute__((__unused__)))
      56  {
      57  	struct libmnt_optlist *ol;
      58  	struct libmnt_opt *opt;
      59  	const char *tgt, *val;
      60  	char *raw = NULL;
      61  	int rc = 0;
      62  
      63  	assert(cxt);
      64  
      65  	tgt = mnt_fs_get_target(cxt->fs);
      66  	if (!tgt)
      67  		return 0;
      68  	if (cxt->action != MNT_ACT_MOUNT)
      69  		return 0;
      70  	ol = mnt_context_get_optlist(cxt);
      71  	if (!ol)
      72  		return -EINVAL;
      73  
      74  	opt = mnt_optlist_get_named(ol, "rootcontext", NULL);
      75  	if (!opt)
      76  		return 0;
      77  
      78  	val = mnt_opt_get_value(opt);
      79  	if (!val || strcmp(val, "@target") != 0)
      80  		return 0;
      81  
      82  
      83  	rc = getfilecon_raw(tgt, &raw);
      84  	if (rc <= 0 || !raw) {
      85  		rc = errno ? -errno : -EINVAL;
      86  		DBG(HOOK, ul_debugobj(hs, " SELinux fix @target failed [rc=%d]", rc));
      87  	} else {
      88  		DBG(HOOK, ul_debugobj(hs, " SELinux fix @target to %s", raw));
      89  		rc = 0;	/* getfilecon_raw(3) returns the size of the extended attribute value */
      90  	}
      91  	if (!rc)
      92  		rc = mnt_opt_set_quoted_value(opt, raw);
      93  	if (raw)
      94  		freecon(raw);
      95  
      96  	return rc != 0 ? -MNT_ERR_MOUNTOPT : 0;
      97  }
      98  
      99  static int hook_prepare_options(
     100  			struct libmnt_context *cxt,
     101  			const struct libmnt_hookset *hs,
     102  			void *data __attribute__((__unused__)))
     103  {
     104  	int rc = 0, se_fix = 0, se_rem = 0;
     105  	struct libmnt_optlist *ol;
     106  
     107  	assert(cxt);
     108  
     109  	ol = mnt_context_get_optlist(cxt);
     110  	if (!ol)
     111  		return -EINVAL;
     112  
     113  	if (!is_selinux_enabled())
     114  		/* Always remove SELinux garbage if SELinux disabled */
     115  		se_rem = 1;
     116  	else if (mnt_optlist_is_remount(ol))
     117  		/*
     118  		 * Linux kernel < 2.6.39 does not support remount operation
     119  		 * with any selinux specific mount options.
     120  		 *
     121  		 * Kernel 2.6.39 commits:  ff36fe2c845cab2102e4826c1ffa0a6ebf487c65
     122  		 *                         026eb167ae77244458fa4b4b9fc171209c079ba7
     123  		 * fix this odd behavior, so we don't have to care about it in
     124  		 * userspace.
     125  		 */
     126  		se_rem = get_linux_version() < KERNEL_VERSION(2, 6, 39);
     127  	else
     128  		/* For normal mount, contexts are translated */
     129  		se_fix = 1;
     130  
     131  	DBG(HOOK, ul_debugobj(hs, " SELinux fix options"));
     132  
     133  	/* Fix SELinux contexts */
     134  	if (se_rem || se_fix) {
     135  		static const char *const selinux_options[] = {
     136  			"context",
     137  			"fscontext",
     138  			"defcontext",
     139  			"rootcontext",
     140  			"seclabel",
     141  			NULL
     142  		};
     143  		struct libmnt_iter itr;
     144  		struct libmnt_opt *opt;
     145  
     146  		mnt_reset_iter(&itr, MNT_ITER_FORWARD);
     147  
     148  		while (mnt_optlist_next_opt(ol, &itr, &opt) == 0) {
     149  			const char *opt_name = mnt_opt_get_name(opt);
     150  
     151  			if (!is_option(opt_name, selinux_options))
     152  				continue;
     153  			if (se_rem)
     154  				rc = mnt_optlist_remove_opt(ol, opt);
     155  			else if (se_fix && mnt_opt_has_value(opt)) {
     156  				const char *val = mnt_opt_get_value(opt);
     157  				char *raw = NULL;
     158  
     159  				/* @target placeholder is replaced later when target
     160  				 * is already avalable. The mountpoint does not have to exist
     161  				 * yet (for example "-o X-mount.mkdir=" or --target-prefix).
     162  				 */
     163  				if (strcmp(opt_name, "rootcontext") == 0 &&
     164  				    strcmp(val, "@target") == 0) {
     165  					rc = mnt_context_insert_hook(cxt, "__mkdir",
     166  						       hs, MNT_STAGE_PREP_TARGET, NULL,
     167  						       hook_selinux_target);
     168  					continue;
     169  				} else {
     170  					rc = selinux_trans_to_raw_context(val, &raw);
     171  					if (rc == -1 || !raw)
     172  						rc = -EINVAL;
     173  				}
     174  				if (!rc) {
     175  					DBG(HOOK, ul_debugobj(hs, "  %s: %s to %s",
     176  								opt_name, val, raw));
     177  					rc = mnt_opt_set_quoted_value(opt, raw);
     178  				}
     179  				if (raw)
     180  					freecon(raw);
     181  
     182  				/* temporary for broken fsconfig() syscall */
     183  				cxt->has_selinux_opt = 1;
     184  			}
     185  			if (rc)
     186  				break;
     187  		}
     188  	}
     189  
     190  	return rc != 0 ? -MNT_ERR_MOUNTOPT : 0;
     191  }
     192  
     193  const struct libmnt_hookset hookset_selinux =
     194  {
     195  	.name = "__selinux",
     196  
     197  	.firststage = MNT_STAGE_PREP_OPTIONS,
     198  	.firstcall = hook_prepare_options,
     199  
     200  	.deinit = hookset_deinit
     201  };
     202  
     203  #endif /* HAVE_LIBSELINUX */