git: 2ef8baba4c0c - main - Increase protection provided by veriexec with new unlink/rename hooks.

From: Warner Losh <imp_at_FreeBSD.org>
Date: Tue, 14 Mar 2023 15:04:35 UTC
The branch main has been updated by imp:

URL: https://cgit.FreeBSD.org/src/commit/?id=2ef8baba4c0c75f39e6e67b264ae71507bbb4782

commit 2ef8baba4c0c75f39e6e67b264ae71507bbb4782
Author:     dl <dl@canigo.cat>
AuthorDate: 2023-03-14 04:26:41 +0000
Commit:     Warner Losh <imp@FreeBSD.org>
CommitDate: 2023-03-14 15:04:31 +0000

    Increase protection provided by veriexec with new unlink/rename hooks.
    
    Functions implemented :
    
    - mac_veriexec_vnode_check_unlink: Unlink on a file has been
      requested and requires validation. This function prohibits the
      deleting a protected file (or deleting one of these hard links, if
      any).
    - mac_veriexec_vnode_check_rename_from: Rename the file has been
      requested and must be validated. This function controls the renaming
      of protected file
    - mac_veriexec_vnode_check_rename_to: File overwrite rename has been
      requested and must be validated. This function prevent overwriting of
      a file protected (overwriting by mv command).
    
    The 3 fonctions together aim to control the 'removal' (via unlink) and
    the 'mv' on files protected by veriexec. The intention is to reach the
    functional level of NetBSD veriexec.
    
    Add sysctl node security.mac.veriexec.unlink to toggle control on
    syscall unlink.
    
    Add tunable kernel variable security.mac.veriexec.block_unlink to toggle
    unlink protection. Add the corresponding read-only sysctl.
    
    [ tidied up commit message, trailing whitespace, long lines, { placement ]
    
    Reviewed by: sjg, imp
    Pull Request: https://github.com/freebsd/freebsd-src/pull/613
---
 sys/security/mac_veriexec/mac_veriexec.c | 163 +++++++++++++++++++++++++++++--
 1 file changed, 155 insertions(+), 8 deletions(-)

diff --git a/sys/security/mac_veriexec/mac_veriexec.c b/sys/security/mac_veriexec/mac_veriexec.c
index 99a76abd4afb..6f06a8577212 100644
--- a/sys/security/mac_veriexec/mac_veriexec.c
+++ b/sys/security/mac_veriexec/mac_veriexec.c
@@ -73,6 +73,7 @@
 
 static int sysctl_mac_veriexec_state(SYSCTL_HANDLER_ARGS);
 static int sysctl_mac_veriexec_db(SYSCTL_HANDLER_ARGS);
+static struct mac_policy_ops mac_veriexec_ops;
 
 SYSCTL_DECL(_security_mac);
 
@@ -94,8 +95,11 @@ SYSCTL_PROC(_security_mac_veriexec, OID_AUTO, db,
     0, 0, sysctl_mac_veriexec_db,
     "A", "Verified execution fingerprint database");
 
+
 static int mac_veriexec_slot;
 
+static int mac_veriexec_block_unlink;
+
 MALLOC_DEFINE(M_VERIEXEC, "veriexec", "Verified execution data");
 
 /**
@@ -235,8 +239,8 @@ mac_veriexec_vfs_unmounted(void *arg __unused, struct mount *mp,
  *
  * @param label the label that is being initialized
  */
-static void 
-mac_veriexec_mount_init_label(struct label *label) 
+static void
+mac_veriexec_mount_init_label(struct label *label)
 {
 
 	SLOT_SET(label, 0);
@@ -252,8 +256,8 @@ mac_veriexec_mount_init_label(struct label *label)
  *
  * @param label the label that is being destroyed
  */
-static void 
-mac_veriexec_mount_destroy_label(struct label *label) 
+static void
+mac_veriexec_mount_destroy_label(struct label *label)
 {
 
 	SLOT_SET(label, 0);
@@ -296,7 +300,7 @@ mac_veriexec_vnode_destroy_label(struct label *label)
  * @brief Copy the value in the MAC per-policy slot assigned to veriexec from
  *        the @p src label to the @p dest label
  */
-static void 
+static void
 mac_veriexec_copy_label(struct label *src, struct label *dest)
 {
 
@@ -505,7 +509,7 @@ mac_veriexec_check_vp(struct ucred *cred, struct vnode *vp, accmode_t accmode)
 		/*
 		 * If file has a fingerprint then deny the write request,
 		 * otherwise invalidate the status so we don't keep checking
-		 * for the file having a fingerprint. 
+		 * for the file having a fingerprint.
 		 */
 		switch (status) {
 		case FINGERPRINT_FILE:
@@ -531,7 +535,7 @@ mac_veriexec_check_vp(struct ucred *cred, struct vnode *vp, accmode_t accmode)
 		default:
 			/*
 			 * Caller wants open to fail unless there is a valid
-			 * fingerprint registered. 
+			 * fingerprint registered.
 			 */
 			MAC_VERIEXEC_DBG(2, "fingerprint status is %d for dev "
 			    "%ju, file %ju.%ju\n", status,
@@ -571,6 +575,136 @@ mac_veriexec_vnode_check_open(struct ucred *cred, struct vnode *vp,
 	return (error);
 }
 
+/**
+ * @brief Unlink on a file has been requested and may need to be validated.
+ *
+ * @param cred		credentials to use
+ * @param dvp		parent directory for file vnode vp
+ * @param dlabel	vnode label assigned to the directory vnode
+ * @param vp		vnode of the file to unlink
+ * @param label		vnode label assigned to the vnode
+ * @param cnp		component name for vp
+ *
+ *
+ * @return 0 if opening the file should be allowed, otherwise an error code.
+ */
+static int
+mac_veriexec_vnode_check_unlink(struct ucred *cred, struct vnode *dvp __unused,
+    struct label *dvplabel __unused, struct vnode *vp,
+    struct label *label __unused, struct componentname *cnp __unused)
+{
+	int error;
+
+	/*
+	 * Look for the file on the fingerprint lists iff it has not been seen
+	 * before.
+	 */
+	if ((mac_veriexec_state & VERIEXEC_STATE_ENFORCE) == 0)
+		return (0);
+
+	/*
+	 * Check if it's a verified file
+	 */
+	error = mac_veriexec_check_vp(cred, vp, VVERIFY);
+	if (error == 0) {             /* file is verified */
+		MAC_VERIEXEC_DBG(2,
+    "(UNLINK) attempted to unlink a protected file (euid: %u)", cred->cr_uid);
+
+		return (EAUTH);
+	}
+	return (0);
+}
+
+/**
+ * @brief Rename the file has been requested and may need to be validated.
+ *
+ * @param cred		credentials to use
+ * @param dvp		parent directory for file vnode vp
+ * @param dlabel	vnode label assigned to the directory vnode
+ * @param vp		vnode of the file to rename
+ * @param label		vnode label assigned to the vnode
+ * @param cnp		component name for vp
+ *
+ *
+ * @return 0 if opening the file should be allowed, otherwise an error code.
+ */
+static int
+mac_veriexec_vnode_check_rename_from(struct ucred *cred,
+    struct vnode *dvp __unused, struct label *dvplabel __unused,
+    struct vnode *vp, struct label *label __unused,
+    struct componentname *cnp __unused)
+{
+	int error;
+
+	/*
+	 * Look for the file on the fingerprint lists iff it has not been seen
+	 * before.
+	 */
+	if ((mac_veriexec_state & VERIEXEC_STATE_ENFORCE) == 0)
+		return (0);
+
+	/*
+	 * Check if it's a verified file
+	 */
+	error = mac_veriexec_check_vp(cred, vp, VVERIFY);
+	if (error == 0) {            /* file is verified */
+		MAC_VERIEXEC_DBG(2,
+    "(RENAME_FROM) attempted to rename a protected file (euid: %u)", cred->cr_uid);
+		return (EAUTH);
+	}
+	return (0);
+}
+
+
+/**
+ * @brief Rename to file into the directory (overwrite the file name) has been
+ * requested and may need to be validated.
+ *
+ * @param cred		credentials to use
+ * @param dvp		parent directory for file vnode vp
+ * @param dlabel	vnode label assigned to the directory vnode
+ * @param vp		vnode of the overwritten file
+ * @param label		vnode label assigned to the vnode
+ * @param samedir	1 if the source and destination directories are the same
+ * @param cnp		component name for vp
+ *
+ *
+ * @return 0 if opening the file should be allowed, otherwise an error code.
+ */
+	static int
+mac_veriexec_vnode_check_rename_to(struct ucred *cred, struct vnode *dvp __unused,
+    struct label *dvplabel __unused, struct vnode *vp,
+    struct label *label __unused, int samedir __unused,
+    struct componentname *cnp __unused)
+{
+	int error;
+	/*
+	 * If there is no existing file to overwrite, vp and label will be
+	 * NULL.
+	 */
+	if (vp == NULL)
+		return (0);
+
+	/*
+	 * Look for the file on the fingerprint lists iff it has not been seen
+	 * before.
+	 */
+	if ((mac_veriexec_state & VERIEXEC_STATE_ENFORCE) == 0)
+		return (0);
+
+	/*
+	 * Check if it's a verified file
+	 */
+	error = mac_veriexec_check_vp(cred, vp, VVERIFY);
+	if (error == 0) {             /* file is verified */
+		MAC_VERIEXEC_DBG(2,
+    "(RENAME_TO) attempted to overwrite a protected file (euid: %u)", cred->cr_uid);
+		return (EAUTH);
+	}
+	return (0);
+}
+
+
 /**
  * @brief Check mode changes on file to ensure they should be allowed.
  *
@@ -626,6 +760,16 @@ mac_veriexec_init(struct mac_policy_conf *mpc __unused)
 	    EVENTHANDLER_PRI_FIRST);
 	EVENTHANDLER_REGISTER(vfs_unmounted, mac_veriexec_vfs_unmounted, NULL,
 	    EVENTHANDLER_PRI_LAST);
+
+	/* Fetch tunable value in kernel env and define a corresponding read-only sysctl */
+	mac_veriexec_block_unlink = 0;
+	TUNABLE_INT_FETCH("security.mac.veriexec.block_unlink", &mac_veriexec_block_unlink);
+	SYSCTL_INT(_security_mac_veriexec, OID_AUTO, block_unlink,
+	    CTLFLAG_RDTUN, &mac_veriexec_block_unlink, 0, "Veriexec unlink protection");
+
+	/* Check if unlink control is activated via tunable value */
+	if (!mac_veriexec_block_unlink)
+		mac_veriexec_ops.mpo_vnode_check_unlink = NULL;
 }
 
 /**
@@ -685,7 +829,7 @@ mac_veriexec_syscall(struct thread *td, int call, void *arg)
 		error = VOP_GETATTR(fp->f_vnode, &va,  td->td_ucred);
 		if (error)
 			goto check_done;
-		       
+
 		MAC_VERIEXEC_DBG(2, "mac_veriexec_fingerprint_check_image: "
 		    "va_mode=%o, check_files=%d\n", va.va_mode,
 		    ((va.va_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == 0));
@@ -729,6 +873,9 @@ static struct mac_policy_ops mac_veriexec_ops =
 	.mpo_system_check_sysctl = mac_veriexec_sysctl_check,
 	.mpo_vnode_check_exec = mac_veriexec_vnode_check_exec,
 	.mpo_vnode_check_open = mac_veriexec_vnode_check_open,
+	.mpo_vnode_check_unlink = mac_veriexec_vnode_check_unlink,
+	.mpo_vnode_check_rename_to = mac_veriexec_vnode_check_rename_to,
+	.mpo_vnode_check_rename_from = mac_veriexec_vnode_check_rename_from,
 	.mpo_vnode_check_setmode = mac_veriexec_vnode_check_setmode,
 	.mpo_vnode_copy_label = mac_veriexec_copy_label,
 	.mpo_vnode_destroy_label = mac_veriexec_vnode_destroy_label,