bpf-restrict-fsaccess: enforce W^X on file mappings

Right now RestrictFileSystemAccess= checks that a file comes from a
trusted dm-verity device, but it doesn't guard against mapping that file
and making use of COW modifications. So a MAP_PRIVATE mapping of a
trusted binary can be made writable, modified, and executed, while the
vma still points to the trusted file and the pages are
attacker-controlled anonymous copies.

Enforce W^X: deny any mapping that is writable and executable at once,
and refuse to make a private file mapping executable once it has been
modified through copy-on-write.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
Christian Brauner
2026-08-31 23:33:40 +02:00
parent 73e093d16d
commit 9bb5d21c15
+22 -4
View File
@@ -16,8 +16,11 @@
* dm-verity signals signature validity
* - bdev_free_security hook: removes devices from the map on teardown
* - bprm_check_security: blocks execve() from untrusted sources
* - mmap_file: blocks PROT_EXEC mmap from untrusted sources
* - file_mprotect: blocks W->X transitions from untrusted sources
* - mmap_file: blocks PROT_EXEC mmap from untrusted sources, and
* any writable+executable (W^X-violating) mapping
* - file_mprotect: blocks writable+executable mprotect, and making
* executable a mapping from an untrusted source or
* one that was modified through copy-on-write
*
* On kernels providing the bpf_real_data_inode() kfunc (v7.2+), files on
* union filesystems (overlayfs) are resolved to the layer hosting their
@@ -39,6 +42,7 @@
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#define PROT_WRITE 0x2
#define PROT_EXEC 0x4
#define VM_EXEC 0x00000004
#define PTRACE_MODE_ATTACH 0x02
@@ -218,6 +222,10 @@ SEC("lsm.s/mmap_file")
int BPF_PROG(restrict_fsaccess_mmap_file, struct file *file, unsigned long reqprot,
unsigned long prot, unsigned long flags)
{
/* W^X: a mapping may never be writable and executable at once. */
if ((prot & PROT_EXEC) && (prot & PROT_WRITE))
return -EPERM;
/* Only enforce on executable mappings */
if (!(prot & PROT_EXEC))
return 0;
@@ -245,17 +253,27 @@ int BPF_PROG(restrict_fsaccess_file_mprotect, struct vm_area_struct *vma,
{
struct file *file;
unsigned long vm_flags;
void *anon_vma;
/* W^X: a mapping may never be writable and executable at once. */
if ((prot & PROT_EXEC) && (prot & PROT_WRITE))
return -EPERM;
/* Only enforce when adding PROT_EXEC */
if (!(prot & PROT_EXEC))
return 0;
/* If VM_EXEC is already set, the mapping is already executable — this
* mprotect isn't granting new executable capability, allow */
/* Already executable so this mprotect() grants no new capability. */
BPF_CORE_READ_INTO(&vm_flags, vma, vm_flags);
if (vm_flags & VM_EXEC)
return 0;
/* A private file mapping with an anon_vma has been written to
* (copy-on-write), so its pages may no longer match the trusted file. */
BPF_CORE_READ_INTO(&anon_vma, vma, anon_vma);
if (anon_vma)
return -EPERM;
/* Anonymous executable mapping — no file backing, deny. Direct
* dereference, see restrict_fsaccess_bprm_check(). vm_file is in the
* verifier's BTF_TYPE_SAFE_TRUSTED_OR_NULL list, so this check is live