[PATCH 2/6] landlock: Mediate CAP_SYS_RAWIO for mappings below mmap_min_addr
From: Mickaël Salaün
Date: Tue Nov 10 2026 - 15:34:06 EST
Until now, a process in a domain that denies CAP_SYS_RAWIO could still
map memory below the vm.mmap_min_addr limit if it held this capability.
cap_mmap_addr() is commoncap's mmap_addr hook, and it checks
CAP_SYS_RAWIO with cap_capable(), not security_capable(), so the check
never reaches hook_capable(). Mapping the lowest pages is what this
limit protects against, because it helps to exploit kernel NULL pointer
dereferences.
Add a Landlock mmap_addr hook that checks CAP_SYS_RAWIO against the
domain under the same condition as cap_mmap_addr(). With the same
vm.mmap_min_addr value, the hook only denies mappings for which
commoncap required the capability. security_mmap_addr() stops at
commoncap's error, so processes without the capability get the same
result as before. This also covers the x86 vm86() syscalls, which use
the same check for address 0.
Making cap_mmap_addr() call capable() would also fix this, but it would
send every low mapping through the capable hook of all LSMs (SELinux,
AppArmor, SafeSetID, BPF LSM programs), adding capability checks that
their policies never had to handle. For instance, SELinux checks
mmap_zero in its own mmap_addr hook and keeps it separate from the
capability on purpose. LSMs with their own policy for low mappings
implement it in this hook, so Landlock does the same.
Cc: Günther Noack <gnoack@xxxxxxxxxx>
Cc: Paul Moore <paul@xxxxxxxxxxxxxx>
Cc: Serge Hallyn <serge@xxxxxxxxxx>
Reported-by: Ada Smith <adasmithgt@xxxxxxxxx>
Fixes: 97c8386f1d74 ("landlock: Enforce capability restrictions")
Signed-off-by: Mickaël Salaün <mic@xxxxxxxxxxx>
---
security/landlock/cap.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/security/landlock/cap.c b/security/landlock/cap.c
index fa59118e3486..10e1ca772b9c 100644
--- a/security/landlock/cap.c
+++ b/security/landlock/cap.c
@@ -9,6 +9,7 @@
#include <linux/cred.h>
#include <linux/lsm_audit.h>
#include <linux/lsm_hooks.h>
+#include <linux/security.h>
#include <uapi/linux/landlock.h>
#include "cap.h"
@@ -90,8 +91,29 @@ static int hook_capable(const struct cred *cred, struct user_namespace *ns,
return check_cap(cred, cap, opts);
}
+/**
+ * hook_mmap_addr - Restrict mappings below dac_mmap_min_addr
+ *
+ * @addr: Address of the new mapping.
+ *
+ * cap_mmap_addr() requires CAP_SYS_RAWIO to map below dac_mmap_min_addr, but
+ * calls cap_capable() directly, which skips hook_capable(). Check the same
+ * condition against the domain. commoncap runs first, so this only sees
+ * mappings that commoncap allowed.
+ *
+ * Return: 0 if allowed, -EPERM if CAP_SYS_RAWIO use is denied.
+ */
+static int hook_mmap_addr(unsigned long addr)
+{
+ if (addr >= dac_mmap_min_addr)
+ return 0;
+
+ return check_cap(current_cred(), CAP_SYS_RAWIO, CAP_OPT_NONE);
+}
+
static struct security_hook_list landlock_hooks[] __ro_after_init = {
LSM_HOOK_INIT(capable, hook_capable),
+ LSM_HOOK_INIT(mmap_addr, hook_mmap_addr),
};
__init void landlock_add_cap_hooks(void)
--
2.56.0