* [PATCH v2 0/3] proc,security,selinux: let SELinux block FOLL_FORCE for /proc/self/mem
@ 2026-08-25 18:39 Jann Horn
2026-08-25 18:39 ` [PATCH v2 1/3] proc: refactor /proc/$pid/mem to use struct as private_data Jann Horn
` (2 more replies)
0 siblings, 3 replies; 8+ messages in thread
From: Jann Horn @ 2026-08-25 18:39 UTC (permalink / raw)
To: Paul Moore, James Morris, Serge E. Hallyn, Stephen Smalley,
Jeff Xu, Thiébaud Weksteen
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-fsdevel,
linux-security-module, Ondrej Mosnacek, selinux, Andrew Morton,
Liam R. Howlett, Lorenzo Stoakes, Vlastimil Babka, Pedro Falcato,
David Hildenbrand, linux-mm, Jann Horn
The goal of this series is to let SELinux prevent the use of FOLL_FORCE
when a process writes into /proc/self/mem and the system is configured
with PROC_MEM_FORCE_ALWAYS (which used to be the default behavior, and
is still used by current Android devices).
Android has SELinux policy that attempts to ensure that only trusted
code can be mapped as executable in several system processes, but this
protection can currently be bypassed by writing into /proc/self/mem.
I wrote this series after discussion with Android security folks about
the state of proc_mem_foll_force() restrictions on Android.
I'm sending this to:
- maintainers for LSM hooks
- maintainers for SELinux
- maintainers for VFS (because I think they generally own procfs?)
- some MM folks just as FYI since this touches GUP usage
- the Android folks I talked to about this
I think this should probably go through either the VFS tree or the
LSM tree.
The motivation for this series is that Project Zero managed to write a
remote exploit for Google Pixel partly because of /proc/self/mem, see
<https://projectzero.google/2026/01/pixel-0-click-part-1.html#whats-the-plan-seth-and-jann>.
Signed-off-by: Jann Horn <jannh@google.com>
---
Changes in v2:
- change SELinux hook to only check PROCESS__PTRACE (Stephen Smalley)
- rename ->introspection to ->opened_by_owner (David Hildenbrand)
- rename introspect_mem_foll_force hook to mem_foll_force_opened_by_owner
- improve comments
- add ack/review trailers
- Link to v1: https://patch.msgid.link/20260818-selinux-pokemem-v1-0-90cd2357ee05@google.com
---
Jann Horn (3):
proc: refactor /proc/$pid/mem to use struct as private_data
proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
selinux: require PROCESS__PTRACE for FOLL_FORCE introspection
fs/proc/base.c | 38 +++++++++++++++++++++++++++++++++++---
include/linux/lsm_hook_defs.h | 1 +
include/linux/security.h | 6 ++++++
security/security.c | 20 ++++++++++++++++++++
security/selinux/hooks.c | 22 ++++++++++++++++++++++
5 files changed, 84 insertions(+), 3 deletions(-)
---
base-commit: 2f1baf1fc8929e6c48370be543ad028ac7ad4131
change-id: 20260814-selinux-pokemem-44625557c4d4
Best regards,
--
Jann Horn <jannh@google.com>
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v2 1/3] proc: refactor /proc/$pid/mem to use struct as private_data
2026-08-25 18:39 [PATCH v2 0/3] proc,security,selinux: let SELinux block FOLL_FORCE for /proc/self/mem Jann Horn
@ 2026-08-25 18:39 ` Jann Horn
2026-08-25 18:39 ` [PATCH v2 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS) Jann Horn
2026-08-25 18:39 ` [PATCH v2 3/3] selinux: require PROCESS__PTRACE for FOLL_FORCE introspection Jann Horn
2 siblings, 0 replies; 8+ messages in thread
From: Jann Horn @ 2026-08-25 18:39 UTC (permalink / raw)
To: Paul Moore, James Morris, Serge E. Hallyn, Stephen Smalley,
Jeff Xu, Thiébaud Weksteen
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-fsdevel,
linux-security-module, Ondrej Mosnacek, selinux, Andrew Morton,
Liam R. Howlett, Lorenzo Stoakes, Vlastimil Babka, Pedro Falcato,
David Hildenbrand, linux-mm, Jann Horn
Refactor the handlers for proc_mem_operations to use the new struct
mem_private as ->private_data, rather than directly storing an mm_struct*
in ->private_data.
This is in preparation for adding more state in mem_private in the next
commit.
Reviewed-by: Jan Kara <jack@suse.cz>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
Signed-off-by: Jann Horn <jannh@google.com>
---
fs/proc/base.c | 29 ++++++++++++++++++++++++++---
1 file changed, 26 insertions(+), 3 deletions(-)
diff --git a/fs/proc/base.c b/fs/proc/base.c
index 780f81259052..bec6197329dc 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -848,11 +848,24 @@ static int __mem_open(struct inode *inode, struct file *file, unsigned int mode)
return 0;
}
+/* private_data for proc_mem_operations */
+struct mem_private {
+ struct mm_struct *mm;
+};
+
static int mem_open(struct inode *inode, struct file *file)
{
+ struct mem_private *priv __free(kfree) = kmalloc_obj(struct mem_private);
+
+ if (!priv)
+ return -ENOMEM;
if (WARN_ON_ONCE(!(file->f_op->fop_flags & FOP_UNSIGNED_OFFSET)))
return -EINVAL;
- return __mem_open(inode, file, PTRACE_MODE_ATTACH);
+ priv->mm = proc_mem_open(inode, PTRACE_MODE_ATTACH);
+ if (IS_ERR_OR_NULL(priv->mm))
+ return priv->mm ? PTR_ERR(priv->mm) : -ESRCH;
+ file->private_data = no_free_ptr(priv);
+ return 0;
}
static bool proc_mem_foll_force(struct file *file, struct mm_struct *mm)
@@ -880,7 +893,8 @@ static bool proc_mem_foll_force(struct file *file, struct mm_struct *mm)
static ssize_t mem_rw(struct file *file, char __user *buf,
size_t count, loff_t *ppos, int write)
{
- struct mm_struct *mm = file->private_data;
+ struct mem_private *priv = file->private_data;
+ struct mm_struct *mm = priv->mm;
unsigned long addr = *ppos;
ssize_t copied;
char *page;
@@ -970,12 +984,21 @@ static int mem_release(struct inode *inode, struct file *file)
return 0;
}
+static int mem_release_with_private(struct inode *inode, struct file *file)
+{
+ struct mem_private *priv = file->private_data;
+
+ mmdrop(priv->mm);
+ kfree(priv);
+ return 0;
+}
+
static const struct file_operations proc_mem_operations = {
.llseek = mem_lseek,
.read = mem_read,
.write = mem_write,
.open = mem_open,
- .release = mem_release,
+ .release = mem_release_with_private,
.fop_flags = FOP_UNSIGNED_OFFSET,
};
--
2.55.0.860.g4b6b3295ed-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-25 18:39 [PATCH v2 0/3] proc,security,selinux: let SELinux block FOLL_FORCE for /proc/self/mem Jann Horn
2026-08-25 18:39 ` [PATCH v2 1/3] proc: refactor /proc/$pid/mem to use struct as private_data Jann Horn
@ 2026-08-25 18:39 ` Jann Horn
2026-08-26 13:06 ` Lorenzo Stoakes (ARM)
2026-08-27 16:59 ` David Hildenbrand (Arm)
2026-08-25 18:39 ` [PATCH v2 3/3] selinux: require PROCESS__PTRACE for FOLL_FORCE introspection Jann Horn
2 siblings, 2 replies; 8+ messages in thread
From: Jann Horn @ 2026-08-25 18:39 UTC (permalink / raw)
To: Paul Moore, James Morris, Serge E. Hallyn, Stephen Smalley,
Jeff Xu, Thiébaud Weksteen
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-fsdevel,
linux-security-module, Ondrej Mosnacek, selinux, Andrew Morton,
Liam R. Howlett, Lorenzo Stoakes, Vlastimil Babka, Pedro Falcato,
David Hildenbrand, linux-mm, Jann Horn
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.
Signed-off-by: Jann Horn <jannh@google.com>
---
fs/proc/base.c | 9 +++++++++
include/linux/lsm_hook_defs.h | 1 +
include/linux/security.h | 6 ++++++
security/security.c | 20 ++++++++++++++++++++
4 files changed, 36 insertions(+)
diff --git a/fs/proc/base.c b/fs/proc/base.c
index bec6197329dc..dc6fdcb47b79 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;
@@ -886,6 +893,8 @@ static bool proc_mem_foll_force(struct file *file, struct mm_struct *mm)
}
return ptrace_active;
default:
+ if (priv->opened_by_owner)
+ return security_mem_foll_force_opened_by_owner(file->f_cred) == 0;
return true;
}
}
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 65c9609ec207..50e3f0abc676 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_opened_by_owner, const struct cred *subject)
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..74eb876054b0 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_opened_by_owner(const struct cred *subject);
int security_capget(const struct task_struct *target,
kernel_cap_t *effective,
kernel_cap_t *inheritable,
@@ -676,6 +677,11 @@ static inline int security_ptrace_traceme(struct task_struct *parent)
return cap_ptrace_traceme(parent);
}
+static inline int security_mem_foll_force_opened_by_owner(const struct cred *subject)
+{
+ 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 71aea8fdf014..fff26ff65e07 100644
--- a/security/security.c
+++ b/security/security.c
@@ -595,6 +595,26 @@ int security_ptrace_traceme(struct task_struct *parent)
return call_int_hook(ptrace_traceme, parent);
}
+/**
+ * security_mem_foll_force_opened_by_owner() - Check if introspective FOLL_FORCE is allowed
+ * @subject: credentials of the process accessing its own memory
+ *
+ * Check if FOLL_FORCE is allowed for accessing process memory through
+ * /proc/$pid/mem in the case where the opener's MM was the same as the target
+ * MM, meaning the security_ptrace_access_check() hook was bypassed on open().
+ * (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"), this hook still runs.)
+ *
+ * This is only used when the system is configured with PROC_MEM_FORCE_ALWAYS.
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_mem_foll_force_opened_by_owner(const struct cred *subject)
+{
+ return call_int_hook(mem_foll_force_opened_by_owner, subject);
+}
+
/**
* security_capget() - Get the capability sets for a process
* @target: target process
--
2.55.0.860.g4b6b3295ed-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 3/3] selinux: require PROCESS__PTRACE for FOLL_FORCE introspection
2026-08-25 18:39 [PATCH v2 0/3] proc,security,selinux: let SELinux block FOLL_FORCE for /proc/self/mem Jann Horn
2026-08-25 18:39 ` [PATCH v2 1/3] proc: refactor /proc/$pid/mem to use struct as private_data Jann Horn
2026-08-25 18:39 ` [PATCH v2 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS) Jann Horn
@ 2026-08-25 18:39 ` Jann Horn
2026-08-25 19:30 ` Stephen Smalley
2026-08-27 17:46 ` Lorenzo Stoakes (ARM)
2 siblings, 2 replies; 8+ messages in thread
From: Jann Horn @ 2026-08-25 18:39 UTC (permalink / raw)
To: Paul Moore, James Morris, Serge E. Hallyn, Stephen Smalley,
Jeff Xu, Thiébaud Weksteen
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-fsdevel,
linux-security-module, Ondrej Mosnacek, selinux, Andrew Morton,
Liam R. Howlett, Lorenzo Stoakes, Vlastimil Babka, Pedro Falcato,
David Hildenbrand, linux-mm, Jann Horn
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.
Signed-off-by: Jann Horn <jannh@google.com>
---
security/selinux/hooks.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 18dd28b2bb13..2c2c60e9e0fe 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2157,6 +2157,27 @@ static int selinux_ptrace_traceme(struct task_struct *parent)
SECCLASS_PROCESS, PROCESS__PTRACE, NULL);
}
+/*
+ * Decide whether it should be possible to read non-readable VMAs and write
+ * non-writable VMAs via /proc/self/mem.
+ * This only applies to systems configured with PROC_MEM_FORCE_ALWAYS, and only
+ * triggers 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_opened_by_owner(const struct cred *subject)
+{
+ struct av_decision avd;
+ u32 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)
{
@@ -7558,6 +7579,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_opened_by_owner, selinux_mem_foll_force_opened_by_owner),
LSM_HOOK_INIT(capget, selinux_capget),
LSM_HOOK_INIT(capset, selinux_capset),
LSM_HOOK_INIT(capable, selinux_capable),
--
2.55.0.860.g4b6b3295ed-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v2 3/3] selinux: require PROCESS__PTRACE for FOLL_FORCE introspection
2026-08-25 18:39 ` [PATCH v2 3/3] selinux: require PROCESS__PTRACE for FOLL_FORCE introspection Jann Horn
@ 2026-08-25 19:30 ` Stephen Smalley
2026-08-27 17:46 ` Lorenzo Stoakes (ARM)
1 sibling, 0 replies; 8+ messages in thread
From: Stephen Smalley @ 2026-08-25 19:30 UTC (permalink / raw)
To: Jann Horn
Cc: Paul Moore, James Morris, Serge E. Hallyn, Jeff Xu,
Thiébaud Weksteen, Alexander Viro, Christian Brauner,
Jan Kara, linux-fsdevel, linux-security-module, Ondrej Mosnacek,
selinux, Andrew Morton, Liam R. Howlett, Lorenzo Stoakes,
Vlastimil Babka, Pedro Falcato, David Hildenbrand, linux-mm
On Tue, Aug 25, 2026 at 2:39 PM Jann Horn <jannh@google.com> wrote:
>
> 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.
>
> Signed-off-by: Jann Horn <jannh@google.com>
Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-25 18:39 ` [PATCH v2 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS) Jann Horn
@ 2026-08-26 13:06 ` Lorenzo Stoakes (ARM)
2026-08-27 16:59 ` David Hildenbrand (Arm)
1 sibling, 0 replies; 8+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-26 13:06 UTC (permalink / raw)
To: Jann Horn
Cc: Paul Moore, James Morris, Serge E. Hallyn, Stephen Smalley,
Jeff Xu, Thiébaud Weksteen, Alexander Viro,
Christian Brauner, Jan Kara, linux-fsdevel, linux-security-module,
Ondrej Mosnacek, selinux, Andrew Morton, Liam R. Howlett,
Vlastimil Babka, Pedro Falcato, David Hildenbrand, linux-mm
On Tue, Aug 25, 2026 at 08:39:18PM +0200, Jann Horn wrote:
> 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.
>
> Signed-off-by: Jann Horn <jannh@google.com>
Thanks for the name change! :)
Nothing stands out so:
Acked-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
> fs/proc/base.c | 9 +++++++++
> include/linux/lsm_hook_defs.h | 1 +
> include/linux/security.h | 6 ++++++
> security/security.c | 20 ++++++++++++++++++++
> 4 files changed, 36 insertions(+)
>
> diff --git a/fs/proc/base.c b/fs/proc/base.c
> index bec6197329dc..dc6fdcb47b79 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;
>
> @@ -886,6 +893,8 @@ static bool proc_mem_foll_force(struct file *file, struct mm_struct *mm)
> }
> return ptrace_active;
> default:
> + if (priv->opened_by_owner)
> + return security_mem_foll_force_opened_by_owner(file->f_cred) == 0;
> return true;
> }
> }
> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index 65c9609ec207..50e3f0abc676 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_opened_by_owner, const struct cred *subject)
> 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..74eb876054b0 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_opened_by_owner(const struct cred *subject);
> int security_capget(const struct task_struct *target,
> kernel_cap_t *effective,
> kernel_cap_t *inheritable,
> @@ -676,6 +677,11 @@ static inline int security_ptrace_traceme(struct task_struct *parent)
> return cap_ptrace_traceme(parent);
> }
>
> +static inline int security_mem_foll_force_opened_by_owner(const struct cred *subject)
> +{
> + 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 71aea8fdf014..fff26ff65e07 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -595,6 +595,26 @@ int security_ptrace_traceme(struct task_struct *parent)
> return call_int_hook(ptrace_traceme, parent);
> }
>
> +/**
> + * security_mem_foll_force_opened_by_owner() - Check if introspective FOLL_FORCE is allowed
> + * @subject: credentials of the process accessing its own memory
> + *
> + * Check if FOLL_FORCE is allowed for accessing process memory through
> + * /proc/$pid/mem in the case where the opener's MM was the same as the target
> + * MM, meaning the security_ptrace_access_check() hook was bypassed on open().
> + * (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"), this hook still runs.)
> + *
> + * This is only used when the system is configured with PROC_MEM_FORCE_ALWAYS.
> + *
> + * Return: Returns 0 if permission is granted.
> + */
> +int security_mem_foll_force_opened_by_owner(const struct cred *subject)
> +{
> + return call_int_hook(mem_foll_force_opened_by_owner, subject);
> +}
> +
> /**
> * security_capget() - Get the capability sets for a process
> * @target: target process
>
> --
> 2.55.0.860.g4b6b3295ed-goog
>
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-25 18:39 ` [PATCH v2 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS) Jann Horn
2026-08-26 13:06 ` Lorenzo Stoakes (ARM)
@ 2026-08-27 16:59 ` David Hildenbrand (Arm)
1 sibling, 0 replies; 8+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 16:59 UTC (permalink / raw)
To: Jann Horn, Paul Moore, James Morris, Serge E. Hallyn,
Stephen Smalley, Jeff Xu, Thiébaud Weksteen
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-fsdevel,
linux-security-module, Ondrej Mosnacek, selinux, Andrew Morton,
Liam R. Howlett, Lorenzo Stoakes, Vlastimil Babka, Pedro Falcato,
linux-mm
On 8/25/26 20:39, Jann Horn wrote:
> 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.
>
> Signed-off-by: Jann Horn <jannh@google.com>
> ---
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
--
Cheers,
David
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2 3/3] selinux: require PROCESS__PTRACE for FOLL_FORCE introspection
2026-08-25 18:39 ` [PATCH v2 3/3] selinux: require PROCESS__PTRACE for FOLL_FORCE introspection Jann Horn
2026-08-25 19:30 ` Stephen Smalley
@ 2026-08-27 17:46 ` Lorenzo Stoakes (ARM)
1 sibling, 0 replies; 8+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-27 17:46 UTC (permalink / raw)
To: Jann Horn
Cc: Paul Moore, James Morris, Serge E. Hallyn, Stephen Smalley,
Jeff Xu, Thiébaud Weksteen, Alexander Viro,
Christian Brauner, Jan Kara, linux-fsdevel, linux-security-module,
Ondrej Mosnacek, selinux, Andrew Morton, Liam R. Howlett,
Vlastimil Babka, Pedro Falcato, David Hildenbrand, linux-mm
On Tue, Aug 25, 2026 at 08:39:19PM +0200, Jann Horn wrote:
> 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.
>
> Signed-off-by: Jann Horn <jannh@google.com>
Not really my area but in general looks reasonable so:
Acked-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
> security/selinux/hooks.c | 22 ++++++++++++++++++++++
> 1 file changed, 22 insertions(+)
>
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index 18dd28b2bb13..2c2c60e9e0fe 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -2157,6 +2157,27 @@ static int selinux_ptrace_traceme(struct task_struct *parent)
> SECCLASS_PROCESS, PROCESS__PTRACE, NULL);
> }
>
> +/*
> + * Decide whether it should be possible to read non-readable VMAs and write
> + * non-writable VMAs via /proc/self/mem.
> + * This only applies to systems configured with PROC_MEM_FORCE_ALWAYS, and only
> + * triggers 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_opened_by_owner(const struct cred *subject)
> +{
> + struct av_decision avd;
> + u32 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)
> {
> @@ -7558,6 +7579,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_opened_by_owner, selinux_mem_foll_force_opened_by_owner),
> LSM_HOOK_INIT(capget, selinux_capget),
> LSM_HOOK_INIT(capset, selinux_capset),
> LSM_HOOK_INIT(capable, selinux_capable),
>
> --
> 2.55.0.860.g4b6b3295ed-goog
>
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-08-27 17:46 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-25 18:39 [PATCH v2 0/3] proc,security,selinux: let SELinux block FOLL_FORCE for /proc/self/mem Jann Horn
2026-08-25 18:39 ` [PATCH v2 1/3] proc: refactor /proc/$pid/mem to use struct as private_data Jann Horn
2026-08-25 18:39 ` [PATCH v2 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS) Jann Horn
2026-08-26 13:06 ` Lorenzo Stoakes (ARM)
2026-08-27 16:59 ` David Hildenbrand (Arm)
2026-08-25 18:39 ` [PATCH v2 3/3] selinux: require PROCESS__PTRACE for FOLL_FORCE introspection Jann Horn
2026-08-25 19:30 ` Stephen Smalley
2026-08-27 17:46 ` Lorenzo Stoakes (ARM)
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).