diff options
| author | Jann Horn <jannh@google.com> | 2026-09-07 23:00:18 +0200 |
|---|---|---|
| committer | Christian Brauner <brauner@kernel.org> | 2026-09-10 09:48:17 +0200 |
| commit | 5dac8c291d9b7cdcd433cfcd8300ebf79e4a9177 (patch) | |
| tree | 5301e571b05acb50893fd0302a6225b7540b0114 | |
| parent | 9f90f96af73d5fd2dee0a0459a89ac467f3ecf5e (diff) | |
| download | linux-next-5dac8c291d9b7cdcd433cfcd8300ebf79e4a9177.tar.gz linux-next-5dac8c291d9b7cdcd433cfcd8300ebf79e4a9177.zip | |
selinux: require PROCESS__PTRACE for FOLL_FORCE introspection
On systems configured with PROC_MEM_FORCE_ALWAYS, ensure that a process can
only create anonymous executable memory via /proc/self/mem if it has
PROCESS__PTRACE (like when using /proc/$pid/mem of another process).
This closes a hole in code integrity enforcement that Project Zero has used
in a remote Android exploit chain:
It was possible to use a memory corruption bug in a service without
EXECMEM/EXECMOD/PTRACE permission to overwrite executable code via
/proc/self/mem, which made it possible to load and run shellcode containing
a kernel exploit.
Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Acked-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
Signed-off-by: Jann Horn <jannh@google.com>
Link: https://patch.msgid.link/20260907-selinux-pokemem-v3-3-0bafbaeafe50@google.com
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
| -rw-r--r-- | security/selinux/hooks.c | 31 |
1 files changed, 31 insertions, 0 deletions
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 035aaf113d1d..2c0b62ff9fc4 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2159,6 +2159,36 @@ static int selinux_ptrace_traceme(struct task_struct *parent) SECCLASS_PROCESS, PROCESS__PTRACE, NULL); } +/** + * selinux_mem_foll_force() - Determine whether /proc/$pid/mem can use FOLL_FORCE + * @subject: credentials using which /proc/$pid/mem was opened + * @opened_by_owner: whether checks on open() were bypassed because the opener + * has the same MM as the target + * + * Decide whether it should be possible to read non-readable VMAs and write + * non-writable VMAs via /proc/self/mem. + * The @opened_by_owner case only applies to systems configured with + * PROC_MEM_FORCE_ALWAYS, and only happens on accesses that are not visible to + * selinux_ptrace_access_check() because of the introspection exceptions in + * may_access_mm() and __ptrace_may_access(). + * + * This allows a process to overwrite read-only code in its own address space. + * + * Creating an audit record on denial doesn't make sense here, since we can't + * tell whether FOLL_FORCE matters for the accessed VMAs. + */ +static int selinux_mem_foll_force(const struct cred *subject, bool opened_by_owner) +{ + struct av_decision avd; + u32 sid; + + if (!opened_by_owner) + return 0; + sid = cred_sid(subject); + + return avc_has_perm_noaudit(sid, sid, SECCLASS_PROCESS, PROCESS__PTRACE, 0, &avd); +} + static int selinux_capget(const struct task_struct *target, kernel_cap_t *effective, kernel_cap_t *inheritable, kernel_cap_t *permitted) { @@ -7565,6 +7595,7 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = { LSM_HOOK_INIT(ptrace_access_check, selinux_ptrace_access_check), LSM_HOOK_INIT(ptrace_traceme, selinux_ptrace_traceme), + LSM_HOOK_INIT(mem_foll_force, selinux_mem_foll_force), LSM_HOOK_INIT(capget, selinux_capget), LSM_HOOK_INIT(capset, selinux_capset), LSM_HOOK_INIT(capable, selinux_capable), |
