diff options
| author | Jann Horn <jannh@google.com> | 2026-09-07 23:00:17 +0200 |
|---|---|---|
| committer | Christian Brauner <brauner@kernel.org> | 2026-09-10 09:48:17 +0200 |
| commit | 9f90f96af73d5fd2dee0a0459a89ac467f3ecf5e (patch) | |
| tree | e0a387569537df91a0ea964d2dc27e52957e12ae | |
| parent | d5662602d178c6090b2e3887029f91a7e1be3a0f (diff) | |
| download | linux-next-9f90f96af73d5fd2dee0a0459a89ac467f3ecf5e.tar.gz linux-next-9f90f96af73d5fd2dee0a0459a89ac467f3ecf5e.zip | |
proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
If the system is running with PROC_MEM_FORCE_ALWAYS, LSMs currently have no
good opportunity to block a process from overwriting read-only code in its
own address space through FOLL_FORCE writes via /proc/self/mem.
The security_ptrace_access_check() LSM hook is bypassed when a process
opens /proc/self/mem because this is considered "introspection".
This causes a hole in SELinux EXECMEM enforcement, which tries to ensure
that a process cannot create executable anonymous pages.
PROC_MEM_FORCE_PTRACE prevents that and ensures that such FOLL_FORCE
accesses are only possible when the LSM allows ptrace() attachment; but it
is unclear how quickly PROC_MEM_FORCE_PTRACE can be deployed in
environments running lots of third-party code, such as Android.
So, introduce a new LSM hook that can forbid FOLL_FORCE specifically for
such "introspective" accesses.
Acked-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Signed-off-by: Jann Horn <jannh@google.com>
Link: https://patch.msgid.link/20260907-selinux-pokemem-v3-2-0bafbaeafe50@google.com
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
| -rw-r--r-- | fs/proc/base.c | 14 | ||||
| -rw-r--r-- | include/linux/lsm_hook_defs.h | 1 | ||||
| -rw-r--r-- | include/linux/security.h | 7 | ||||
| -rw-r--r-- | security/security.c | 25 |
4 files changed, 45 insertions, 2 deletions
diff --git a/fs/proc/base.c b/fs/proc/base.c index 3add7c7a97e2..6d369ba2edd0 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -851,6 +851,11 @@ static int __mem_open(struct inode *inode, struct file *file, unsigned int mode) /* private_data for proc_mem_operations */ struct mem_private { struct mm_struct *mm; + /* + * Was the ptrace access check on open bypassed because the opener used + * the same MM (introspection)? + */ + bool opened_by_owner; }; static int mem_open(struct inode *inode, struct file *file) @@ -864,12 +869,14 @@ static int mem_open(struct inode *inode, struct file *file) priv->mm = proc_mem_open(inode, PTRACE_MODE_ATTACH); if (IS_ERR_OR_NULL(priv->mm)) return priv->mm ? PTR_ERR(priv->mm) : -ESRCH; + priv->opened_by_owner = priv->mm == current->mm; file->private_data = no_free_ptr(priv); return 0; } static bool proc_mem_foll_force(struct file *file, struct mm_struct *mm) { + struct mem_private *priv = file->private_data; struct task_struct *task; bool ptrace_active = false; @@ -884,10 +891,13 @@ static bool proc_mem_foll_force(struct file *file, struct mm_struct *mm) READ_ONCE(task->parent) == current; put_task_struct(task); } - return ptrace_active; + if (!ptrace_active) + return false; + break; default: - return true; + break; } + return security_mem_foll_force(file->f_cred, priv->opened_by_owner) == 0; } static ssize_t mem_rw(struct file *file, char __user *buf, diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index 65c9609ec207..12f84a1e6fab 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -36,6 +36,7 @@ LSM_HOOK(int, 0, binder_transfer_file, const struct cred *from, LSM_HOOK(int, 0, ptrace_access_check, struct task_struct *child, unsigned int mode) LSM_HOOK(int, 0, ptrace_traceme, struct task_struct *parent) +LSM_HOOK(int, 0, mem_foll_force, const struct cred *subject, bool opened_by_owner) LSM_HOOK(int, 0, capget, const struct task_struct *target, kernel_cap_t *effective, kernel_cap_t *inheritable, kernel_cap_t *permitted) LSM_HOOK(int, 0, capset, struct cred *new, const struct cred *old, diff --git a/include/linux/security.h b/include/linux/security.h index 153e9043058f..e8bc2e644241 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -338,6 +338,7 @@ int security_binder_transfer_file(const struct cred *from, const struct cred *to, const struct file *file); int security_ptrace_access_check(struct task_struct *child, unsigned int mode); int security_ptrace_traceme(struct task_struct *parent); +int security_mem_foll_force(const struct cred *subject, bool opened_by_owner); int security_capget(const struct task_struct *target, kernel_cap_t *effective, kernel_cap_t *inheritable, @@ -676,6 +677,12 @@ static inline int security_ptrace_traceme(struct task_struct *parent) return cap_ptrace_traceme(parent); } +static inline int security_mem_foll_force(const struct cred *subject, + bool opened_by_owner) +{ + return 0; +} + static inline int security_capget(const struct task_struct *target, kernel_cap_t *effective, kernel_cap_t *inheritable, diff --git a/security/security.c b/security/security.c index 2ee276ab15c5..4892153842d4 100644 --- a/security/security.c +++ b/security/security.c @@ -596,6 +596,31 @@ int security_ptrace_traceme(struct task_struct *parent) } /** + * security_mem_foll_force() - Check if FOLL_FORCE is allowed + * @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 + * + * Check if FOLL_FORCE is allowed for accessing process memory through + * /proc/$pid/mem. opened_by_owner signals whether the opener's MM was the same + * as the target MM, meaning the security_ptrace_access_check() hook was + * bypassed on open(). + * (Current current->mm does not matter for this; for example, if write() is + * called on an FD that was received from another process which obtained it with + * open("/proc/self/mem"), @opened_by_owner is still true.) + * + * Note that this hook is only designed to be useful in the opened_by_owner + * case, where the subject credentials effectively also describe the object. + * + * Return: Returns 0 if permission is granted. + */ +int security_mem_foll_force(const struct cred *subject, + bool opened_by_owner) +{ + return call_int_hook(mem_foll_force, subject, opened_by_owner); +} + +/** * security_capget() - Get the capability sets for a process * @target: target process * @effective: effective capability set |
