lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20250719-memfd-exec-v1-3-0ef7feba5821@gmail.com>
Date: Sat, 19 Jul 2025 05:13:13 -0600
From: Abhinav Saxena <xandfury@...il.com>
To: Mickaël Salaün <mic@...ikod.net>, 
 Günther Noack <gnoack@...gle.com>, 
 Paul Moore <paul@...l-moore.com>, James Morris <jmorris@...ei.org>, 
 "Serge E. Hallyn" <serge@...lyn.com>, Shuah Khan <shuah@...nel.org>, 
 Nathan Chancellor <nathan@...nel.org>, 
 Nick Desaulniers <nick.desaulniers+lkml@...il.com>, 
 Bill Wendling <morbo@...gle.com>, Justin Stitt <justinstitt@...gle.com>
Cc: linux-security-module@...r.kernel.org, linux-kernel@...r.kernel.org, 
 linux-kselftest@...r.kernel.org, llvm@...ts.linux.dev, 
 Abhinav Saxena <xandfury@...il.com>
Subject: [PATCH RFC 3/4] landlock: add memfd exec LSM hooks and scoping

Implement LSM hooks to enforce memfd execution restrictions:

- hook_mmap_file: Prevent executable mapping of memfd files
- hook_file_mprotect: Block mprotect() adding PROT_EXEC to memfd
  mappings
- hook_bprm_creds_for_exec: Prevent direct execution via execve()
  family
- hook_file_alloc_security: Initialize memfd files with proper access
  masks

All hooks use domain hierarchy checking to enforce scoped restrictions
with proper audit logging. This prevents multiple attack vectors:
- Direct mmap(PROT_EXEC) on memfd
- Two-stage mmap(PROT_READ) + mprotect(PROT_EXEC) bypass
- execve("/proc/self/fd/N") anonymous execution

Implement memfd execution access control in check_memfd_execute_access()
using hierarchy-aware domain checking

Signed-off-by: Abhinav Saxena <xandfury@...il.com>
---
 security/landlock/cred.c |  14 ----
 security/landlock/fs.c   | 195 ++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 194 insertions(+), 15 deletions(-)

diff --git a/security/landlock/cred.c b/security/landlock/cred.c
index 0cb3edde4d18..356dad0b7e9b 100644
--- a/security/landlock/cred.c
+++ b/security/landlock/cred.c
@@ -43,25 +43,11 @@ static void hook_cred_free(struct cred *const cred)
 		landlock_put_ruleset_deferred(dom);
 }
 
-#ifdef CONFIG_AUDIT
-
-static int hook_bprm_creds_for_exec(struct linux_binprm *const bprm)
-{
-	/* Resets for each execution. */
-	landlock_cred(bprm->cred)->domain_exec = 0;
-	return 0;
-}
-
-#endif /* CONFIG_AUDIT */
-
 static struct security_hook_list landlock_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(cred_prepare, hook_cred_prepare),
 	LSM_HOOK_INIT(cred_transfer, hook_cred_transfer),
 	LSM_HOOK_INIT(cred_free, hook_cred_free),
 
-#ifdef CONFIG_AUDIT
-	LSM_HOOK_INIT(bprm_creds_for_exec, hook_bprm_creds_for_exec),
-#endif /* CONFIG_AUDIT */
 };
 
 __init void landlock_add_cred_hooks(void)
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index d86d21034f4c..e8b58f2fd87e 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -1880,7 +1880,24 @@ static int hook_file_alloc_security(struct file *const file)
 	 * without going through the file_open hook, for example when using
 	 * memfd_create(2).
 	 */
-	landlock_file(file)->allowed_access = LANDLOCK_MASK_ACCESS_FS;
+	access_mask_t allowed_access = LANDLOCK_MASK_ACCESS_FS;
+	const struct landlock_cred_security *subject;
+	size_t layer;
+	static const struct access_masks memfd_scope = {
+		.scope = LANDLOCK_SCOPE_MEMFD_EXEC,
+	};
+
+	/* allow everything by default */
+	landlock_file(file)->allowed_access = allowed_access;
+
+	subject = landlock_get_applicable_subject(current_cred(), memfd_scope,
+						  &layer);
+	if (subject && is_memfd_file(file)) {
+		/* Creator domain restricts memfd execution */
+		allowed_access &= ~LANDLOCK_ACCESS_FS_EXECUTE;
+		landlock_file(file)->allowed_access = allowed_access;
+		/* Store creator and audit... */
+	}
 	return 0;
 }
 
@@ -2107,6 +2124,178 @@ static void hook_file_free_security(struct file *file)
 	landlock_put_ruleset_deferred(landlock_file(file)->fown_subject.domain);
 }
 
+static bool
+check_memfd_execute_access(const struct file *file,
+			   const struct landlock_cred_security **subject,
+			   size_t *layer_plus_one)
+{
+	const struct landlock_ruleset *executor_domain, *creator_domain;
+	const struct landlock_cred_security *creator_subject;
+	static const struct access_masks memfd_scope = {
+		.scope = LANDLOCK_SCOPE_MEMFD_EXEC,
+	};
+	size_t creator_layer_plus_one = 0;
+	bool executor_scoped, creator_scoped, is_scoped;
+
+	*subject = NULL;
+	*layer_plus_one = 0;
+
+	/* Check scoping status for both executor and creator */
+	*subject = landlock_get_applicable_subject(current_cred(), memfd_scope,
+						   layer_plus_one);
+	creator_subject = landlock_get_applicable_subject(
+		file->f_cred, memfd_scope, &creator_layer_plus_one);
+
+	executor_scoped = (*subject != NULL);
+	creator_scoped = (creator_subject != NULL);
+
+	if (!creator_scoped)
+		return true; /* No scoping enabled, allow execution */
+
+	/* Get domains for comparison */
+	executor_domain = executor_scoped ? (*subject)->domain : NULL;
+	creator_domain = creator_scoped ? creator_subject->domain :
+					  landlock_cred(file->f_cred)->domain;
+
+	pr_info("MEMFD_DEBUG: executor_domain=%p, creator_domain=%p\n",
+		executor_domain, creator_domain);
+
+	/*
+	 * Same-domain: deny to prevent read-to-execute bypass
+	 * This prevents processes from bypassing execute restrictions
+	 * by creating memfd in the same domain
+	 */
+	if (executor_domain == creator_domain)
+		return false;
+
+	/*
+	 * Cross-domain: use domain hierarchy checks to see if executor is
+	 * scoped from creator domain_is_scoped() returns true when access
+	 * should be DENIED
+	 */
+	if (executor_scoped || creator_scoped) {
+		is_scoped = domain_is_scoped(executor_domain, creator_domain,
+					     LANDLOCK_SCOPE_MEMFD_EXEC);
+		pr_info("MEMFD_DEBUG: Cross-domain: is_scoped=%d, returning=%d\n",
+			is_scoped, !is_scoped);
+		/* Return true (allow) when NOT scoped, false (deny) when scoped */
+		return !is_scoped;
+	}
+
+	return true;
+}
+
+static int hook_mmap_file(struct file *file, unsigned long reqprot,
+			  unsigned long prot, unsigned long flags)
+{
+	const struct landlock_cred_security *subject;
+	size_t layer_plus_one;
+
+	/* Only check executable mappings */
+	if (!(prot & PROT_EXEC))
+		return 0;
+
+	/* Only restrict memfd files */
+	if (!is_memfd_file(file))
+		return 0;
+
+	/* Check if memfd execution is allowed */
+	if (check_memfd_execute_access(file, &subject, &layer_plus_one))
+		return 0;
+
+	/* Log denial for audit */
+	if (subject) {
+		landlock_log_denial(subject, &(struct landlock_request) {
+			.type = LANDLOCK_REQUEST_SCOPE_MEMFD_EXEC,
+			.audit = {
+				.type = LSM_AUDIT_DATA_ANONINODE,
+				.u.file = file,
+			},
+			.layer_plus_one = layer_plus_one,
+		});
+	}
+
+	return -EACCES;
+}
+
+static int hook_file_mprotect(struct vm_area_struct *vma, unsigned long reqprot,
+			      unsigned long prot)
+{
+	const struct landlock_cred_security *subject;
+	size_t layer_plus_one;
+
+	/* Only check when adding execute permission */
+	if (!(prot & PROT_EXEC))
+		return 0;
+
+	/* Must have a file backing the VMA */
+	if (!vma || !vma->vm_file)
+		return 0;
+
+	/* Only restrict memfd files */
+	if (!is_memfd_file(vma->vm_file))
+		return 0;
+
+	/* Check if memfd execution is allowed */
+	if (check_memfd_execute_access(vma->vm_file, &subject, &layer_plus_one))
+		return 0;
+
+	/* Log denial for audit */
+	if (subject) {
+		landlock_log_denial(subject, &(struct landlock_request) {
+			.type = LANDLOCK_REQUEST_SCOPE_MEMFD_EXEC,
+			.audit = {
+				.type = LSM_AUDIT_DATA_ANONINODE,
+				.u.file = vma->vm_file,
+			},
+			.layer_plus_one = layer_plus_one,
+		});
+	}
+
+	return -EACCES;
+}
+
+static int hook_bprm_creds_for_exec(struct linux_binprm *bprm)
+{
+#ifdef CONFIG_AUDIT
+	/* Resets for each execution. */
+	landlock_cred(bprm->cred)->domain_exec = 0;
+#endif /* CONFIG_AUDIT */
+
+	const struct landlock_cred_security *subject;
+	size_t layer_plus_one;
+	struct file *file;
+
+	if (!bprm)
+		return 0;
+
+	file = bprm->file;
+	if (!file)
+		return 0;
+
+	/* Only restrict memfd files */
+	if (!is_memfd_file(file))
+		return 0;
+
+	/* Check if memfd execution is allowed */
+	if (check_memfd_execute_access(file, &subject, &layer_plus_one))
+		return 0;
+
+	/* Log denial for audit */
+	if (subject) {
+		landlock_log_denial(subject, &(struct landlock_request) {
+			.type = LANDLOCK_REQUEST_SCOPE_MEMFD_EXEC,
+			.audit = {
+				.type = LSM_AUDIT_DATA_ANONINODE,
+				.u.file = file,
+			},
+			.layer_plus_one = layer_plus_one,
+		});
+	}
+
+	return -EACCES; /* maybe we should return EPERM? */
+}
+
 static struct security_hook_list landlock_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(inode_free_security_rcu, hook_inode_free_security_rcu),
 
@@ -2133,6 +2322,10 @@ static struct security_hook_list landlock_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(file_ioctl_compat, hook_file_ioctl_compat),
 	LSM_HOOK_INIT(file_set_fowner, hook_file_set_fowner),
 	LSM_HOOK_INIT(file_free_security, hook_file_free_security),
+
+	LSM_HOOK_INIT(mmap_file, hook_mmap_file),
+	LSM_HOOK_INIT(file_mprotect, hook_file_mprotect),
+	LSM_HOOK_INIT(bprm_creds_for_exec, hook_bprm_creds_for_exec),
 };
 
 __init void landlock_add_fs_hooks(void)

-- 
2.43.0


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ