* [PATCH 0/3] proc,security,selinux: let SELinux block FOLL_FORCE for /proc/self/mem
@ 2026-08-18 19:51 Jann Horn
2026-08-18 19:51 ` [PATCH 1/3] proc: refactor /proc/$pid/mem to use struct as private_data Jann Horn
` (2 more replies)
0 siblings, 3 replies; 32+ messages in thread
From: Jann Horn @ 2026-08-18 19:51 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>
---
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 EXECMEM or PTRACE for FOLL_FORCE introspection
fs/proc/base.c | 35 ++++++++++++++++++++++++++++++++---
include/linux/lsm_hook_defs.h | 1 +
include/linux/security.h | 6 ++++++
security/security.c | 15 +++++++++++++++
security/selinux/hooks.c | 27 +++++++++++++++++++++++++++
5 files changed, 81 insertions(+), 3 deletions(-)
---
base-commit: 2f1baf1fc8929e6c48370be543ad028ac7ad4131
change-id: 20260814-selinux-pokemem-44625557c4d4
Best regards,
--
Jann Horn <jannh@google.com>
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH 1/3] proc: refactor /proc/$pid/mem to use struct as private_data
2026-08-18 19:51 [PATCH 0/3] proc,security,selinux: let SELinux block FOLL_FORCE for /proc/self/mem Jann Horn
@ 2026-08-18 19:51 ` Jann Horn
2026-08-20 11:20 ` Jan Kara
` (2 more replies)
2026-08-18 19:51 ` [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS) Jann Horn
2026-08-18 19:51 ` [PATCH 3/3] selinux: require EXECMEM or PTRACE for FOLL_FORCE introspection Jann Horn
2 siblings, 3 replies; 32+ messages in thread
From: Jann Horn @ 2026-08-18 19:51 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.
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.737.g08866a6d13-goog
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-18 19:51 [PATCH 0/3] proc,security,selinux: let SELinux block FOLL_FORCE for /proc/self/mem Jann Horn
2026-08-18 19:51 ` [PATCH 1/3] proc: refactor /proc/$pid/mem to use struct as private_data Jann Horn
@ 2026-08-18 19:51 ` Jann Horn
2026-08-20 17:22 ` David Hildenbrand (Arm)
` (3 more replies)
2026-08-18 19:51 ` [PATCH 3/3] selinux: require EXECMEM or PTRACE for FOLL_FORCE introspection Jann Horn
2 siblings, 4 replies; 32+ messages in thread
From: Jann Horn @ 2026-08-18 19:51 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 | 6 ++++++
include/linux/lsm_hook_defs.h | 1 +
include/linux/security.h | 6 ++++++
security/security.c | 15 +++++++++++++++
4 files changed, 28 insertions(+)
diff --git a/fs/proc/base.c b/fs/proc/base.c
index bec6197329dc..3dfaef49bb70 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -851,6 +851,8 @@ 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 bypassed due to introspection? */
+ bool introspection;
};
static int mem_open(struct inode *inode, struct file *file)
@@ -864,12 +866,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->introspection = 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 +890,8 @@ static bool proc_mem_foll_force(struct file *file, struct mm_struct *mm)
}
return ptrace_active;
default:
+ if (priv->introspection)
+ return security_introspect_mem_foll_force(file->f_cred) == 0;
return true;
}
}
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 65c9609ec207..67452f71bedf 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, introspect_mem_foll_force, 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..f8483be58bc8 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_introspect_mem_foll_force(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_introspect_mem_foll_force(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..d0f790a534eb 100644
--- a/security/security.c
+++ b/security/security.c
@@ -595,6 +595,21 @@ int security_ptrace_traceme(struct task_struct *parent)
return call_int_hook(ptrace_traceme, parent);
}
+/**
+ * security_introspect_mem_foll_force() - Check if introspective FOLL_FORCE is allowed
+ * @subject: credentials of the process accessing its own memory
+ *
+ * Check if FOLL_FORCE is allowed for a process accessing its own memory, which
+ * bypasses the security_ptrace_access_check() hook.
+ * This is only used when the system is configured with PROC_MEM_FORCE_ALWAYS.
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_introspect_mem_foll_force(const struct cred *subject)
+{
+ return call_int_hook(introspect_mem_foll_force, subject);
+}
+
/**
* security_capget() - Get the capability sets for a process
* @target: target process
--
2.55.0.737.g08866a6d13-goog
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 3/3] selinux: require EXECMEM or PTRACE for FOLL_FORCE introspection
2026-08-18 19:51 [PATCH 0/3] proc,security,selinux: let SELinux block FOLL_FORCE for /proc/self/mem Jann Horn
2026-08-18 19:51 ` [PATCH 1/3] proc: refactor /proc/$pid/mem to use struct as private_data Jann Horn
2026-08-18 19:51 ` [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS) Jann Horn
@ 2026-08-18 19:51 ` Jann Horn
2026-08-19 14:54 ` Stephen Smalley
2026-08-21 18:56 ` Lorenzo Stoakes (ARM)
2 siblings, 2 replies; 32+ messages in thread
From: Jann Horn @ 2026-08-18 19:51 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 one
of:
- EXECMEM (like for other methods of creating anonymous executable pages)
- PTRACE (like when using /proc/$pid/mem of another process)
This closes a hole in EXECMEM 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
permission to overwrite executable code via /proc/self/mem, which made it
possible to load and run shellcode with a kernel exploit.
Signed-off-by: Jann Horn <jannh@google.com>
---
security/selinux/hooks.c | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 18dd28b2bb13..905137c47321 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2157,6 +2157,32 @@ 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().
+ *
+ * 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_introspect_mem_foll_force(const struct cred *subject)
+{
+ struct av_decision avd;
+ int rc;
+ u32 sid = cred_sid(subject);
+
+ /* Allow if the process is generally allowed to have executable anonymous memory. */
+ rc = avc_has_perm_noaudit(sid, sid, SECCLASS_PROCESS, PROCESS__EXECMEM, 0, &avd);
+
+ /* Also allow if selinux_ptrace_access_check() would allow it. */
+ if (rc)
+ rc = avc_has_perm_noaudit(sid, sid, SECCLASS_PROCESS, PROCESS__PTRACE, 0, &avd);
+ return rc;
+}
+
static int selinux_capget(const struct task_struct *target, kernel_cap_t *effective,
kernel_cap_t *inheritable, kernel_cap_t *permitted)
{
@@ -7558,6 +7584,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(introspect_mem_foll_force, selinux_introspect_mem_foll_force),
LSM_HOOK_INIT(capget, selinux_capget),
LSM_HOOK_INIT(capset, selinux_capset),
LSM_HOOK_INIT(capable, selinux_capable),
--
2.55.0.737.g08866a6d13-goog
^ permalink raw reply related [flat|nested] 32+ messages in thread
* Re: [PATCH 3/3] selinux: require EXECMEM or PTRACE for FOLL_FORCE introspection
2026-08-18 19:51 ` [PATCH 3/3] selinux: require EXECMEM or PTRACE for FOLL_FORCE introspection Jann Horn
@ 2026-08-19 14:54 ` Stephen Smalley
2026-08-20 15:23 ` Jann Horn
2026-08-21 18:56 ` Lorenzo Stoakes (ARM)
1 sibling, 1 reply; 32+ messages in thread
From: Stephen Smalley @ 2026-08-19 14:54 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 18, 2026 at 3:51 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 one
> of:
>
> - EXECMEM (like for other methods of creating anonymous executable pages)
> - PTRACE (like when using /proc/$pid/mem of another process)
Allowing it if _either_ permission is allowed is unusual in SELinux
and seems prone to errors.
Is there a reason to not just require PTRACE always?
>
> This closes a hole in EXECMEM 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
> permission to overwrite executable code via /proc/self/mem, which made it
> possible to load and run shellcode with a kernel exploit.
>
> Signed-off-by: Jann Horn <jannh@google.com>
> ---
> security/selinux/hooks.c | 27 +++++++++++++++++++++++++++
> 1 file changed, 27 insertions(+)
>
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index 18dd28b2bb13..905137c47321 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -2157,6 +2157,32 @@ 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().
> + *
> + * 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_introspect_mem_foll_force(const struct cred *subject)
> +{
> + struct av_decision avd;
> + int rc;
> + u32 sid = cred_sid(subject);
> +
> + /* Allow if the process is generally allowed to have executable anonymous memory. */
> + rc = avc_has_perm_noaudit(sid, sid, SECCLASS_PROCESS, PROCESS__EXECMEM, 0, &avd);
> +
> + /* Also allow if selinux_ptrace_access_check() would allow it. */
> + if (rc)
> + rc = avc_has_perm_noaudit(sid, sid, SECCLASS_PROCESS, PROCESS__PTRACE, 0, &avd);
> + return rc;
> +}
> +
> static int selinux_capget(const struct task_struct *target, kernel_cap_t *effective,
> kernel_cap_t *inheritable, kernel_cap_t *permitted)
> {
> @@ -7558,6 +7584,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(introspect_mem_foll_force, selinux_introspect_mem_foll_force),
> LSM_HOOK_INIT(capget, selinux_capget),
> LSM_HOOK_INIT(capset, selinux_capset),
> LSM_HOOK_INIT(capable, selinux_capable),
>
> --
> 2.55.0.737.g08866a6d13-goog
>
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 1/3] proc: refactor /proc/$pid/mem to use struct as private_data
2026-08-18 19:51 ` [PATCH 1/3] proc: refactor /proc/$pid/mem to use struct as private_data Jann Horn
@ 2026-08-20 11:20 ` Jan Kara
2026-08-20 17:18 ` David Hildenbrand (Arm)
2026-08-21 18:34 ` Lorenzo Stoakes (ARM)
2 siblings, 0 replies; 32+ messages in thread
From: Jan Kara @ 2026-08-20 11:20 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,
Lorenzo Stoakes, Vlastimil Babka, Pedro Falcato,
David Hildenbrand, linux-mm
On Tue 18-08-26 21:51:05, Jann Horn wrote:
> 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.
>
> Signed-off-by: Jann Horn <jannh@google.com>
Looks good to me. Feel free to add:
Reviewed-by: Jan Kara <jack@suse.cz>
Honza
> ---
> 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.737.g08866a6d13-goog
>
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 3/3] selinux: require EXECMEM or PTRACE for FOLL_FORCE introspection
2026-08-19 14:54 ` Stephen Smalley
@ 2026-08-20 15:23 ` Jann Horn
2026-08-21 13:52 ` Stephen Smalley
0 siblings, 1 reply; 32+ messages in thread
From: Jann Horn @ 2026-08-20 15:23 UTC (permalink / raw)
To: Stephen Smalley
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 Wed, Aug 19, 2026 at 4:54 PM Stephen Smalley
<stephen.smalley.work@gmail.com> wrote:
> On Tue, Aug 18, 2026 at 3:51 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 one
> > of:
> >
> > - EXECMEM (like for other methods of creating anonymous executable pages)
> > - PTRACE (like when using /proc/$pid/mem of another process)
>
> Allowing it if _either_ permission is allowed is unusual in SELinux
> and seems prone to errors.
> Is there a reason to not just require PTRACE always?
Hmm, that might work.
My two concerns about that approach are:
1. This is an operation where a process acts on itself, while PTRACE
is normally for acting on another process. It feels to me like those
are different types of privilege.
2. I feel like blocking this operation even for a process with EXECMEM
privilege increases the risk of breaking existing configurations.
I think if we want to gate it on PTRACE, we might need to put this
behind a policy capability and let policy authors figure this out?
But I'm no expert on how new security hooks are normally added, or
what SELinux policies outside Android look like, so if you think it
would be fine to just check for PTRACE, I'd be happy to go with that.
(I've caused stable regressions several times at this point and am
trying to avoid causing more...)
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 1/3] proc: refactor /proc/$pid/mem to use struct as private_data
2026-08-18 19:51 ` [PATCH 1/3] proc: refactor /proc/$pid/mem to use struct as private_data Jann Horn
2026-08-20 11:20 ` Jan Kara
@ 2026-08-20 17:18 ` David Hildenbrand (Arm)
2026-08-21 18:34 ` Lorenzo Stoakes (ARM)
2 siblings, 0 replies; 32+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-20 17:18 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/18/26 21:51, Jann Horn wrote:
> 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.
>
> Signed-off-by: Jann Horn <jannh@google.com>
> ---
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
--
Cheers,
David
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-18 19:51 ` [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS) Jann Horn
@ 2026-08-20 17:22 ` David Hildenbrand (Arm)
2026-08-20 18:44 ` Jann Horn
2026-08-21 19:00 ` Lorenzo Stoakes (ARM)
` (2 subsequent siblings)
3 siblings, 1 reply; 32+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-20 17:22 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/18/26 21:51, 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>
> ---
> fs/proc/base.c | 6 ++++++
> include/linux/lsm_hook_defs.h | 1 +
> include/linux/security.h | 6 ++++++
> security/security.c | 15 +++++++++++++++
> 4 files changed, 28 insertions(+)
>
> diff --git a/fs/proc/base.c b/fs/proc/base.c
> index bec6197329dc..3dfaef49bb70 100644
> --- a/fs/proc/base.c
> +++ b/fs/proc/base.c
> @@ -851,6 +851,8 @@ 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 bypassed due to introspection? */
> + bool introspection;
> };
>
> static int mem_open(struct inode *inode, struct file *file)
> @@ -864,12 +866,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->introspection = priv->mm == current->mm;
Is the feat that the fd could be passed to someone else that would then not be
detected as introspection?
I'm just wondering why we cannot perform this check in proc_mem_foll_force() and
avoid rememebring "introspection".
--
Cheers,
David
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-20 17:22 ` David Hildenbrand (Arm)
@ 2026-08-20 18:44 ` Jann Horn
2026-08-21 14:18 ` David Hildenbrand (Arm)
0 siblings, 1 reply; 32+ messages in thread
From: Jann Horn @ 2026-08-20 18:44 UTC (permalink / raw)
To: David Hildenbrand (Arm)
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,
Lorenzo Stoakes, Vlastimil Babka, Pedro Falcato, linux-mm
On Thu, Aug 20, 2026 at 7:22 PM David Hildenbrand (Arm)
<david@kernel.org> wrote:
> On 8/18/26 21:51, 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>
> > ---
> > fs/proc/base.c | 6 ++++++
> > include/linux/lsm_hook_defs.h | 1 +
> > include/linux/security.h | 6 ++++++
> > security/security.c | 15 +++++++++++++++
> > 4 files changed, 28 insertions(+)
> >
> > diff --git a/fs/proc/base.c b/fs/proc/base.c
> > index bec6197329dc..3dfaef49bb70 100644
> > --- a/fs/proc/base.c
> > +++ b/fs/proc/base.c
> > @@ -851,6 +851,8 @@ 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 bypassed due to introspection? */
> > + bool introspection;
> > };
> >
> > static int mem_open(struct inode *inode, struct file *file)
> > @@ -864,12 +866,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->introspection = priv->mm == current->mm;
>
> Is the feat that the fd could be passed to someone else that would then not be
> detected as introspection?
Yes, exactly, that's the primary reason why I did it this way.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 3/3] selinux: require EXECMEM or PTRACE for FOLL_FORCE introspection
2026-08-20 15:23 ` Jann Horn
@ 2026-08-21 13:52 ` Stephen Smalley
2026-08-21 15:07 ` Jann Horn
0 siblings, 1 reply; 32+ messages in thread
From: Stephen Smalley @ 2026-08-21 13:52 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 Thu, Aug 20, 2026 at 11:23 AM Jann Horn <jannh@google.com> wrote:
>
> On Wed, Aug 19, 2026 at 4:54 PM Stephen Smalley
> <stephen.smalley.work@gmail.com> wrote:
> > On Tue, Aug 18, 2026 at 3:51 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 one
> > > of:
> > >
> > > - EXECMEM (like for other methods of creating anonymous executable pages)
> > > - PTRACE (like when using /proc/$pid/mem of another process)
> >
> > Allowing it if _either_ permission is allowed is unusual in SELinux
> > and seems prone to errors.
> > Is there a reason to not just require PTRACE always?
>
> Hmm, that might work.
>
> My two concerns about that approach are:
>
> 1. This is an operation where a process acts on itself, while PTRACE
> is normally for acting on another process. It feels to me like those
> are different types of privilege.
> 2. I feel like blocking this operation even for a process with EXECMEM
> privilege increases the risk of breaking existing configurations.
>
> I think if we want to gate it on PTRACE, we might need to put this
> behind a policy capability and let policy authors figure this out?
> But I'm no expert on how new security hooks are normally added, or
> what SELinux policies outside Android look like, so if you think it
> would be fine to just check for PTRACE, I'd be happy to go with that.
I don't think a new policy capability is necessary here. Is there any legitimate
user of this capability that wouldn't already have ptrace permission?
The other alternative is to introduce a new permission for this, but that will
deny-by-default on existing Android and allow-by-default on Fedora based
on handle-unknown until it is defined in the policy. In the case of Android, my
understanding was that kernel updates can always be coordinated with policy
updates so there is no need to worry about a new kernel breaking userspace
with old policies i.e. if and when Android ships a kernel that
includes these patches
they can also ship an updated policy that allows the new permission
where necessary
but I could be wrong.
> (I've caused stable regressions several times at this point and am
> trying to avoid causing more...)
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-20 18:44 ` Jann Horn
@ 2026-08-21 14:18 ` David Hildenbrand (Arm)
2026-08-21 14:48 ` Jann Horn
0 siblings, 1 reply; 32+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-21 14:18 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,
Lorenzo Stoakes, Vlastimil Babka, Pedro Falcato, linux-mm
On 8/20/26 20:44, Jann Horn wrote:
> On Thu, Aug 20, 2026 at 7:22 PM David Hildenbrand (Arm)
> <david@kernel.org> wrote:
>> On 8/18/26 21:51, 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>
>>> ---
>>> fs/proc/base.c | 6 ++++++
>>> include/linux/lsm_hook_defs.h | 1 +
>>> include/linux/security.h | 6 ++++++
>>> security/security.c | 15 +++++++++++++++
>>> 4 files changed, 28 insertions(+)
>>>
>>> diff --git a/fs/proc/base.c b/fs/proc/base.c
>>> index bec6197329dc..3dfaef49bb70 100644
>>> --- a/fs/proc/base.c
>>> +++ b/fs/proc/base.c
>>> @@ -851,6 +851,8 @@ 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 bypassed due to introspection? */
>>> + bool introspection;
>>> };
>>>
>>> static int mem_open(struct inode *inode, struct file *file)
>>> @@ -864,12 +866,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->introspection = priv->mm == current->mm;
>>
>> Is the feat that the fd could be passed to someone else that would then not be
>> detected as introspection?
>
> Yes, exactly, that's the primary reason why I did it this way.
Okay, would "opened_by_owner" or something like that be clearer? At least
"introspection" is less intuitive for me.
--
Cheers,
David
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-21 14:18 ` David Hildenbrand (Arm)
@ 2026-08-21 14:48 ` Jann Horn
2026-08-21 18:52 ` Lorenzo Stoakes (ARM)
0 siblings, 1 reply; 32+ messages in thread
From: Jann Horn @ 2026-08-21 14:48 UTC (permalink / raw)
To: David Hildenbrand (Arm)
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,
Lorenzo Stoakes, Vlastimil Babka, Pedro Falcato, linux-mm
On Fri, Aug 21, 2026 at 4:19 PM David Hildenbrand (Arm)
<david@kernel.org> wrote:
> On 8/20/26 20:44, Jann Horn wrote:
> > On Thu, Aug 20, 2026 at 7:22 PM David Hildenbrand (Arm)
> > <david@kernel.org> wrote:
> >> On 8/18/26 21:51, 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>
> >>> ---
> >>> fs/proc/base.c | 6 ++++++
> >>> include/linux/lsm_hook_defs.h | 1 +
> >>> include/linux/security.h | 6 ++++++
> >>> security/security.c | 15 +++++++++++++++
> >>> 4 files changed, 28 insertions(+)
> >>>
> >>> diff --git a/fs/proc/base.c b/fs/proc/base.c
> >>> index bec6197329dc..3dfaef49bb70 100644
> >>> --- a/fs/proc/base.c
> >>> +++ b/fs/proc/base.c
> >>> @@ -851,6 +851,8 @@ 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 bypassed due to introspection? */
> >>> + bool introspection;
> >>> };
> >>>
> >>> static int mem_open(struct inode *inode, struct file *file)
> >>> @@ -864,12 +866,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->introspection = priv->mm == current->mm;
> >>
> >> Is the feat that the fd could be passed to someone else that would then not be
> >> detected as introspection?
> >
> > Yes, exactly, that's the primary reason why I did it this way.
>
> Okay, would "opened_by_owner" or something like that be clearer? At least
> "introspection" is less intuitive for me.
Ack, I'll rename it to something like that for the next version.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 3/3] selinux: require EXECMEM or PTRACE for FOLL_FORCE introspection
2026-08-21 13:52 ` Stephen Smalley
@ 2026-08-21 15:07 ` Jann Horn
0 siblings, 0 replies; 32+ messages in thread
From: Jann Horn @ 2026-08-21 15:07 UTC (permalink / raw)
To: Stephen Smalley
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 Fri, Aug 21, 2026 at 3:52 PM Stephen Smalley
<stephen.smalley.work@gmail.com> wrote:
> On Thu, Aug 20, 2026 at 11:23 AM Jann Horn <jannh@google.com> wrote:
> > On Wed, Aug 19, 2026 at 4:54 PM Stephen Smalley
> > <stephen.smalley.work@gmail.com> wrote:
> > > On Tue, Aug 18, 2026 at 3:51 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 one
> > > > of:
> > > >
> > > > - EXECMEM (like for other methods of creating anonymous executable pages)
> > > > - PTRACE (like when using /proc/$pid/mem of another process)
> > >
> > > Allowing it if _either_ permission is allowed is unusual in SELinux
> > > and seems prone to errors.
> > > Is there a reason to not just require PTRACE always?
> >
> > Hmm, that might work.
> >
> > My two concerns about that approach are:
> >
> > 1. This is an operation where a process acts on itself, while PTRACE
> > is normally for acting on another process. It feels to me like those
> > are different types of privilege.
> > 2. I feel like blocking this operation even for a process with EXECMEM
> > privilege increases the risk of breaking existing configurations.
> >
> > I think if we want to gate it on PTRACE, we might need to put this
> > behind a policy capability and let policy authors figure this out?
> > But I'm no expert on how new security hooks are normally added, or
> > what SELinux policies outside Android look like, so if you think it
> > would be fine to just check for PTRACE, I'd be happy to go with that.
>
> I don't think a new policy capability is necessary here. Is there any legitimate
> user of this capability that wouldn't already have ptrace permission?
I do not know of one.
I was thinking that it is theoretically possible that some program out
there uses /proc/self/mem to patch its own code (similarly to how the
Linux kernel has Static Keys) or to inspect its own execute-only,
non-readable code (which I think would also require FOLL_FORCE); but
I'm not aware of any concrete cases that do that.
So I guess if nobody else has an opinion on this, I'll change this to
only check for PTRACE as you suggested.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 1/3] proc: refactor /proc/$pid/mem to use struct as private_data
2026-08-18 19:51 ` [PATCH 1/3] proc: refactor /proc/$pid/mem to use struct as private_data Jann Horn
2026-08-20 11:20 ` Jan Kara
2026-08-20 17:18 ` David Hildenbrand (Arm)
@ 2026-08-21 18:34 ` Lorenzo Stoakes (ARM)
2 siblings, 0 replies; 32+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-21 18:34 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 18, 2026 at 09:51:05PM +0200, Jann Horn wrote:
> 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.
>
> Signed-off-by: Jann Horn <jannh@google.com>
You had me at helper struct Jann, you had me at helper struct :)
Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
> 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;
> +}
OK I see that we mm_grab() in proc_mem_open().
> +
> 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.737.g08866a6d13-goog
>
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-21 14:48 ` Jann Horn
@ 2026-08-21 18:52 ` Lorenzo Stoakes (ARM)
2026-08-24 17:06 ` Jann Horn
0 siblings, 1 reply; 32+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-21 18:52 UTC (permalink / raw)
To: Jann Horn
Cc: David Hildenbrand (Arm), 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, linux-mm
On Fri, Aug 21, 2026 at 04:48:32PM +0200, Jann Horn wrote:
> On Fri, Aug 21, 2026 at 4:19 PM David Hildenbrand (Arm)
> <david@kernel.org> wrote:
> > On 8/20/26 20:44, Jann Horn wrote:
> > > On Thu, Aug 20, 2026 at 7:22 PM David Hildenbrand (Arm)
> > > <david@kernel.org> wrote:
> > >> On 8/18/26 21:51, 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>
> > >>> ---
> > >>> fs/proc/base.c | 6 ++++++
> > >>> include/linux/lsm_hook_defs.h | 1 +
> > >>> include/linux/security.h | 6 ++++++
> > >>> security/security.c | 15 +++++++++++++++
> > >>> 4 files changed, 28 insertions(+)
> > >>>
> > >>> diff --git a/fs/proc/base.c b/fs/proc/base.c
> > >>> index bec6197329dc..3dfaef49bb70 100644
> > >>> --- a/fs/proc/base.c
> > >>> +++ b/fs/proc/base.c
> > >>> @@ -851,6 +851,8 @@ 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 bypassed due to introspection? */
> > >>> + bool introspection;
> > >>> };
> > >>>
> > >>> static int mem_open(struct inode *inode, struct file *file)
> > >>> @@ -864,12 +866,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->introspection = priv->mm == current->mm;
> > >>
> > >> Is the feat that the fd could be passed to someone else that would then not be
> > >> detected as introspection?
> > >
> > > Yes, exactly, that's the primary reason why I did it this way.
> >
> > Okay, would "opened_by_owner" or something like that be clearer? At least
> > "introspection" is less intuitive for me.
>
> Ack, I'll rename it to something like that for the next version.
I agree the naming is confusing.
OK so the whole thing is:
mem_open()
-> __mem_open()
-> proc_mem_open()
-> mm_access()
-> may_access_mm()
And:
static bool may_access_mm(struct mm_struct *mm, struct task_struct *task, unsigned int mode)
{
if (mm == current->mm)
return true;
...
}
And what this flag is carrying is 'hey the reason we allowed the _open_ is
because it's looking at its own address space'.
I did wonder if what you're protecting against is even a process updating
execmem _it_ owns, no fd shared anywhere, as something LSM might want to
prevent even so?
The sharing a /proc/mem fd seems like that's a pretty dumb thing to do in
general :) but I guess you have to protect against that.
But TL;DR I agree with David on the naming, opened_by_owner is probably the
least-worst way of saying it very plainly and covers both cases.
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 3/3] selinux: require EXECMEM or PTRACE for FOLL_FORCE introspection
2026-08-18 19:51 ` [PATCH 3/3] selinux: require EXECMEM or PTRACE for FOLL_FORCE introspection Jann Horn
2026-08-19 14:54 ` Stephen Smalley
@ 2026-08-21 18:56 ` Lorenzo Stoakes (ARM)
2026-08-24 17:17 ` Jann Horn
1 sibling, 1 reply; 32+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-21 18:56 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 18, 2026 at 09:51:07PM +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 one
> of:
>
> - EXECMEM (like for other methods of creating anonymous executable pages)
> - PTRACE (like when using /proc/$pid/mem of another process)
>
> This closes a hole in EXECMEM 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
> permission to overwrite executable code via /proc/self/mem, which made it
> possible to load and run shellcode with a kernel exploit.
>
> Signed-off-by: Jann Horn <jannh@google.com>
> ---
> security/selinux/hooks.c | 27 +++++++++++++++++++++++++++
> 1 file changed, 27 insertions(+)
>
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index 18dd28b2bb13..905137c47321 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -2157,6 +2157,32 @@ 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().
It might be worth mentioning may_access_mm() here, and obviously propagate the
suggested name change introspection -> opened_by_owner or fd_from_order maybe
even?
> + *
> + * 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_introspect_mem_foll_force(const struct cred *subject)
> +{
> + struct av_decision avd;
> + int rc;
> + u32 sid = cred_sid(subject);
> +
> + /* Allow if the process is generally allowed to have executable anonymous memory. */
> + rc = avc_has_perm_noaudit(sid, sid, SECCLASS_PROCESS, PROCESS__EXECMEM, 0, &avd);
But does it make sense for the shared-by-fd case? In that case you're now
updating execmem for another process's memory right?
> +
> + /* Also allow if selinux_ptrace_access_check() would allow it. */
> + if (rc)
> + rc = avc_has_perm_noaudit(sid, sid, SECCLASS_PROCESS, PROCESS__PTRACE, 0, &avd);
> + return rc;
> +}
> +
> static int selinux_capget(const struct task_struct *target, kernel_cap_t *effective,
> kernel_cap_t *inheritable, kernel_cap_t *permitted)
> {
> @@ -7558,6 +7584,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(introspect_mem_foll_force, selinux_introspect_mem_foll_force),
> LSM_HOOK_INIT(capget, selinux_capget),
> LSM_HOOK_INIT(capset, selinux_capset),
> LSM_HOOK_INIT(capable, selinux_capable),
>
> --
> 2.55.0.737.g08866a6d13-goog
>
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-18 19:51 ` [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS) Jann Horn
2026-08-20 17:22 ` David Hildenbrand (Arm)
@ 2026-08-21 19:00 ` Lorenzo Stoakes (ARM)
2026-08-24 17:28 ` Jann Horn
2026-08-25 13:13 ` Christian Brauner
2026-08-25 13:19 ` Christian Brauner
3 siblings, 1 reply; 32+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-21 19:00 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 18, 2026 at 09:51:06PM +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>
> ---
> fs/proc/base.c | 6 ++++++
> include/linux/lsm_hook_defs.h | 1 +
> include/linux/security.h | 6 ++++++
> security/security.c | 15 +++++++++++++++
> 4 files changed, 28 insertions(+)
>
> diff --git a/fs/proc/base.c b/fs/proc/base.c
> index bec6197329dc..3dfaef49bb70 100644
> --- a/fs/proc/base.c
> +++ b/fs/proc/base.c
> @@ -851,6 +851,8 @@ 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 bypassed due to introspection? */
> + bool introspection;
> };
>
> static int mem_open(struct inode *inode, struct file *file)
> @@ -864,12 +866,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->introspection = 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 +890,8 @@ static bool proc_mem_foll_force(struct file *file, struct mm_struct *mm)
> }
> return ptrace_active;
> default:
> + if (priv->introspection)
> + return security_introspect_mem_foll_force(file->f_cred) == 0;
As per 3/3 I wonder if you need an additional parameter to cover the fd -> some
other process case?
Like:
if (priv->owned_by_owner) {
const bool is_remote = current->mm != priv->mm;
return !security_fd_from_owner_mem_foll_force(file->f_cred,
is_remote);
}
(I'm not sure how LSM hooks are supposed to look :)
> return true;
> }
> }
> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index 65c9609ec207..67452f71bedf 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, introspect_mem_foll_force, 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..f8483be58bc8 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_introspect_mem_foll_force(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_introspect_mem_foll_force(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..d0f790a534eb 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -595,6 +595,21 @@ int security_ptrace_traceme(struct task_struct *parent)
> return call_int_hook(ptrace_traceme, parent);
> }
>
> +/**
> + * security_introspect_mem_foll_force() - Check if introspective FOLL_FORCE is allowed
> + * @subject: credentials of the process accessing its own memory
> + *
> + * Check if FOLL_FORCE is allowed for a process accessing its own memory, which
> + * bypasses the security_ptrace_access_check() hook.
This should be updated to also explicitly mention the fd case. As surely in that
case this is not true? Unless I'm missing something.
> + * This is only used when the system is configured with PROC_MEM_FORCE_ALWAYS.
> + *
> + * Return: Returns 0 if permission is granted.
> + */
> +int security_introspect_mem_foll_force(const struct cred *subject)
> +{
> + return call_int_hook(introspect_mem_foll_force, subject);
> +}
> +
> /**
> * security_capget() - Get the capability sets for a process
> * @target: target process
>
> --
> 2.55.0.737.g08866a6d13-goog
>
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-21 18:52 ` Lorenzo Stoakes (ARM)
@ 2026-08-24 17:06 ` Jann Horn
2026-08-24 17:32 ` Lorenzo Stoakes (ARM)
0 siblings, 1 reply; 32+ messages in thread
From: Jann Horn @ 2026-08-24 17:06 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
Cc: David Hildenbrand (Arm), 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, linux-mm
On Fri, Aug 21, 2026 at 8:52 PM Lorenzo Stoakes (ARM) <ljs@kernel.org> wrote:
> OK so the whole thing is:
>
> mem_open()
> -> __mem_open()
> -> proc_mem_open()
> -> mm_access()
> -> may_access_mm()
>
> And:
>
> static bool may_access_mm(struct mm_struct *mm, struct task_struct *task, unsigned int mode)
> {
> if (mm == current->mm)
> return true;
> ...
> }
>
> And what this flag is carrying is 'hey the reason we allowed the _open_ is
> because it's looking at its own address space'.
Yes.
> I did wonder if what you're protecting against is even a process updating
> execmem _it_ owns, no fd shared anywhere, as something LSM might want to
> prevent even so?
Sorry, can you rephrase that? My goal with this series is to let LSMs
block a process that tries to modify its own non-writable executable
memory using /proc/self/mem; I'm not sure if that answers your
question.
For context: In this series, I'm using "execmem" to refer to the
SELinux permission PROCESS__EXECMEM, which essentially controls
whether a process is allowed to create writable+executable mappings
that can contain anonymous pages. Additionally, it blocks creating
executable mappings of S_PRIVATE inodes. There are other SELinux
permissions for things like making a VMA containing anonymous pages
executable (FILE__EXECMOD and others) or mapping files as executable
(FILE__EXECUTE). FILE__EXECUTE is granular, it can be granted based on
the security labels of the process and the file that is mapped.
> The sharing a /proc/mem fd seems like that's a pretty dumb thing to do in
> general :) but I guess you have to protect against that.
Yeah, it's a kinda weird thing to do...
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 3/3] selinux: require EXECMEM or PTRACE for FOLL_FORCE introspection
2026-08-21 18:56 ` Lorenzo Stoakes (ARM)
@ 2026-08-24 17:17 ` Jann Horn
0 siblings, 0 replies; 32+ messages in thread
From: Jann Horn @ 2026-08-24 17:17 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
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 Fri, Aug 21, 2026 at 8:56 PM Lorenzo Stoakes (ARM) <ljs@kernel.org> wrote:
> On Tue, Aug 18, 2026 at 09:51:07PM +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 one
> > of:
> >
> > - EXECMEM (like for other methods of creating anonymous executable pages)
> > - PTRACE (like when using /proc/$pid/mem of another process)
> >
> > This closes a hole in EXECMEM 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
> > permission to overwrite executable code via /proc/self/mem, which made it
> > possible to load and run shellcode with a kernel exploit.
> >
> > Signed-off-by: Jann Horn <jannh@google.com>
> > ---
> > security/selinux/hooks.c | 27 +++++++++++++++++++++++++++
> > 1 file changed, 27 insertions(+)
> >
> > diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> > index 18dd28b2bb13..905137c47321 100644
> > --- a/security/selinux/hooks.c
> > +++ b/security/selinux/hooks.c
> > @@ -2157,6 +2157,32 @@ 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().
>
> It might be worth mentioning may_access_mm() here, and obviously propagate the
> suggested name change introspection -> opened_by_owner or fd_from_order maybe
> even?
Ack, makes sense to mention may_access_mm() here.
Ack, I'll rename this to selinux_mem_foll_force_opened_by_owner().
> > + *
> > + * 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_introspect_mem_foll_force(const struct cred *subject)
> > +{
> > + struct av_decision avd;
> > + int rc;
> > + u32 sid = cred_sid(subject);
> > +
> > + /* Allow if the process is generally allowed to have executable anonymous memory. */
> > + rc = avc_has_perm_noaudit(sid, sid, SECCLASS_PROCESS, PROCESS__EXECMEM, 0, &avd);
>
> But does it make sense for the shared-by-fd case? In that case you're now
> updating execmem for another process's memory right?
Since the existing check for PROCESS__PTRACE happens on open(), I
figured it would make sense to also check against the open()-time
credentials here.
(I will be changing this to check just for PROCESS__PTRACE based on
Stephen's feedback.)
> > +
> > + /* Also allow if selinux_ptrace_access_check() would allow it. */
> > + if (rc)
> > + rc = avc_has_perm_noaudit(sid, sid, SECCLASS_PROCESS, PROCESS__PTRACE, 0, &avd);
> > + return rc;
> > +}
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-21 19:00 ` Lorenzo Stoakes (ARM)
@ 2026-08-24 17:28 ` Jann Horn
2026-08-25 14:02 ` Lorenzo Stoakes (ARM)
0 siblings, 1 reply; 32+ messages in thread
From: Jann Horn @ 2026-08-24 17:28 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
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 Fri, Aug 21, 2026 at 9:00 PM Lorenzo Stoakes (ARM) <ljs@kernel.org> wrote:
> On Tue, Aug 18, 2026 at 09:51:06PM +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>
> > @@ -886,6 +890,8 @@ static bool proc_mem_foll_force(struct file *file, struct mm_struct *mm)
> > }
> > return ptrace_active;
> > default:
> > + if (priv->introspection)
> > + return security_introspect_mem_foll_force(file->f_cred) == 0;
>
> As per 3/3 I wonder if you need an additional parameter to cover the fd -> some
> other process case?
>
> Like:
> if (priv->owned_by_owner) {
> const bool is_remote = current->mm != priv->mm;
>
> return !security_fd_from_owner_mem_foll_force(file->f_cred,
> is_remote);
> }
>
> (I'm not sure how LSM hooks are supposed to look :)
We could do that if we wanted to treat cases differently based on the
identity of the writer, but I think in general that's not a good idea.
In general, if you send an FD to some daemon, and the daemon writes
into the FD, this should not cause access control decisions based on
the identity of the daemon, because it can cause "confused deputy"
bugs - the daemon might think it is just writing log output into a
normal file, or something like that.
> > diff --git a/security/security.c b/security/security.c
> > index 71aea8fdf014..d0f790a534eb 100644
> > --- a/security/security.c
> > +++ b/security/security.c
> > @@ -595,6 +595,21 @@ int security_ptrace_traceme(struct task_struct *parent)
> > return call_int_hook(ptrace_traceme, parent);
> > }
> >
> > +/**
> > + * security_introspect_mem_foll_force() - Check if introspective FOLL_FORCE is allowed
> > + * @subject: credentials of the process accessing its own memory
> > + *
> > + * Check if FOLL_FORCE is allowed for a process accessing its own memory, which
> > + * bypasses the security_ptrace_access_check() hook.
>
> This should be updated to also explicitly mention the fd case. As surely in that
> case this is not true? Unless I'm missing something.
Yeah, I'll clarify this comment.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-24 17:06 ` Jann Horn
@ 2026-08-24 17:32 ` Lorenzo Stoakes (ARM)
2026-08-24 17:43 ` Jann Horn
0 siblings, 1 reply; 32+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-24 17:32 UTC (permalink / raw)
To: Jann Horn
Cc: David Hildenbrand (Arm), 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, linux-mm
On Mon, Aug 24, 2026 at 07:06:04PM +0200, Jann Horn wrote:
> On Fri, Aug 21, 2026 at 8:52 PM Lorenzo Stoakes (ARM) <ljs@kernel.org> wrote:
> > OK so the whole thing is:
> >
> > mem_open()
> > -> __mem_open()
> > -> proc_mem_open()
> > -> mm_access()
> > -> may_access_mm()
> >
> > And:
> >
> > static bool may_access_mm(struct mm_struct *mm, struct task_struct *task, unsigned int mode)
> > {
> > if (mm == current->mm)
> > return true;
> > ...
> > }
> >
> > And what this flag is carrying is 'hey the reason we allowed the _open_ is
> > because it's looking at its own address space'.
>
> Yes.
OK cool. Obviously do agree with David that calling out the ownership aspect in
the name would be helpful!
>
> > I did wonder if what you're protecting against is even a process updating
> > execmem _it_ owns, no fd shared anywhere, as something LSM might want to
> > prevent even so?
>
> Sorry, can you rephrase that? My goal with this series is to let LSMs
> block a process that tries to modify its own non-writable executable
> memory using /proc/self/mem; I'm not sure if that answers your
> question.
Right, I guess my confusion comes from David's clarification about passing an
fd, perhaps I misunderstood that being somehow the _primary_ thing you were
protecting against.
>
> For context: In this series, I'm using "execmem" to refer to the
> SELinux permission PROCESS__EXECMEM, which essentially controls
> whether a process is allowed to create writable+executable mappings
> that can contain anonymous pages. Additionally, it blocks creating
> executable mappings of S_PRIVATE inodes. There are other SELinux
> permissions for things like making a VMA containing anonymous pages
> executable (FILE__EXECMOD and others) or mapping files as executable
> (FILE__EXECUTE). FILE__EXECUTE is granular, it can be granted based on
> the security labels of the process and the file that is mapped.
Ack thanks for the clarification.
>
> > The sharing a /proc/mem fd seems like that's a pretty dumb thing to do in
> > general :) but I guess you have to protect against that.
>
> Yeah, it's a kinda weird thing to do...
Yup :)) but I guess we have to account for people doing weird stuff...
In this case (I do mention it in a reply elsewhere I think) it does seem
like perhaps you should separately check for current->mm != mm of (what was
originally /proc/self/mm)?
Or at least it seems like a crazy thing to be able to get full access to
another process's memory (that it... gave you though).
Anyway perhaps overthinking it :)
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-24 17:32 ` Lorenzo Stoakes (ARM)
@ 2026-08-24 17:43 ` Jann Horn
2026-08-25 13:42 ` Lorenzo Stoakes (ARM)
0 siblings, 1 reply; 32+ messages in thread
From: Jann Horn @ 2026-08-24 17:43 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
Cc: David Hildenbrand (Arm), 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, linux-mm
On Mon, Aug 24, 2026 at 7:33 PM Lorenzo Stoakes (ARM) <ljs@kernel.org> wrote:
> On Mon, Aug 24, 2026 at 07:06:04PM +0200, Jann Horn wrote:
> > On Fri, Aug 21, 2026 at 8:52 PM Lorenzo Stoakes (ARM) <ljs@kernel.org> wrote:
> > > The sharing a /proc/mem fd seems like that's a pretty dumb thing to do in
> > > general :) but I guess you have to protect against that.
> >
> > Yeah, it's a kinda weird thing to do...
>
> Yup :)) but I guess we have to account for people doing weird stuff...
>
> In this case (I do mention it in a reply elsewhere I think) it does seem
> like perhaps you should separately check for current->mm != mm of (what was
> originally /proc/self/mm)?
I wouldn't want to do it for this access check, since that could lead
to "confused deputy" problems.
> Or at least it seems like a crazy thing to be able to get full access to
> another process's memory (that it... gave you though).
>
> Anyway perhaps overthinking it :)
Hm, yes, though I guess that is kind of orthogonal.
For what it's worth, SELinux can prevent such things happening across
domain boundaries - it enforces that, in the SELinux ruleset, the
process calling read()/write() on an FD is granted FILE__READ /
FILE__WRITE permission to the file's inode.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-18 19:51 ` [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS) Jann Horn
2026-08-20 17:22 ` David Hildenbrand (Arm)
2026-08-21 19:00 ` Lorenzo Stoakes (ARM)
@ 2026-08-25 13:13 ` Christian Brauner
2026-08-25 13:46 ` Jann Horn
2026-08-25 13:19 ` Christian Brauner
3 siblings, 1 reply; 32+ messages in thread
From: Christian Brauner @ 2026-08-25 13:13 UTC (permalink / raw)
To: Jann Horn
Cc: Paul Moore, James Morris, Serge E. Hallyn, Stephen Smalley,
Jeff Xu, Thiébaud Weksteen, Alexander Viro, 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 18, 2026 at 09:51:06PM +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".
Useful. I ran into this issue just yesterday in:
https://github.com/systemd/systemd/pull/43511
Btw, it's kinda annoying that PTRACE_POKE{TEXT,DATA} still allows foll
force writes even if PROC_MEM_FORCE_NEVER is enabled. So ideally there'd
also be a hook for that option.
The other thing - though not directly FOLL_FORCE related - is that
process_vm_writev() isn't subject to any security restrictions other
than seccomp filtering either.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-18 19:51 ` [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS) Jann Horn
` (2 preceding siblings ...)
2026-08-25 13:13 ` Christian Brauner
@ 2026-08-25 13:19 ` Christian Brauner
2026-08-25 14:00 ` Jann Horn
3 siblings, 1 reply; 32+ messages in thread
From: Christian Brauner @ 2026-08-25 13:19 UTC (permalink / raw)
To: Jann Horn
Cc: Paul Moore, James Morris, Serge E. Hallyn, Stephen Smalley,
Jeff Xu, Thiébaud Weksteen, Alexander Viro, 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 18, 2026 at 09:51:06PM +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>
> ---
> fs/proc/base.c | 6 ++++++
> include/linux/lsm_hook_defs.h | 1 +
> include/linux/security.h | 6 ++++++
> security/security.c | 15 +++++++++++++++
> 4 files changed, 28 insertions(+)
>
> diff --git a/fs/proc/base.c b/fs/proc/base.c
> index bec6197329dc..3dfaef49bb70 100644
> --- a/fs/proc/base.c
> +++ b/fs/proc/base.c
> @@ -851,6 +851,8 @@ 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 bypassed due to introspection? */
> + bool introspection;
> };
>
> static int mem_open(struct inode *inode, struct file *file)
> @@ -864,12 +866,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->introspection = 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 +890,8 @@ static bool proc_mem_foll_force(struct file *file, struct mm_struct *mm)
> }
> return ptrace_active;
> default:
> + if (priv->introspection)
> + return security_introspect_mem_foll_force(file->f_cred) == 0;
Hm. Why not pass the reason to security_introspect_mem_foll_force() and
call it unconditionally? Similarly it could also be called for the
active ptracer case. Then you'd just need to pass a flag to the security
hook and the LSM can decide based on that.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-24 17:43 ` Jann Horn
@ 2026-08-25 13:42 ` Lorenzo Stoakes (ARM)
2026-08-25 14:08 ` Jann Horn
0 siblings, 1 reply; 32+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-25 13:42 UTC (permalink / raw)
To: Jann Horn
Cc: David Hildenbrand (Arm), 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, linux-mm
On Mon, Aug 24, 2026 at 07:43:02PM +0200, Jann Horn wrote:
> On Mon, Aug 24, 2026 at 7:33 PM Lorenzo Stoakes (ARM) <ljs@kernel.org> wrote:
> > On Mon, Aug 24, 2026 at 07:06:04PM +0200, Jann Horn wrote:
> > > On Fri, Aug 21, 2026 at 8:52 PM Lorenzo Stoakes (ARM) <ljs@kernel.org> wrote:
> > > > The sharing a /proc/mem fd seems like that's a pretty dumb thing to do in
> > > > general :) but I guess you have to protect against that.
> > >
> > > Yeah, it's a kinda weird thing to do...
> >
> > Yup :)) but I guess we have to account for people doing weird stuff...
> >
> > In this case (I do mention it in a reply elsewhere I think) it does seem
> > like perhaps you should separately check for current->mm != mm of (what was
> > originally /proc/self/mm)?
>
> I wouldn't want to do it for this access check, since that could lead
> to "confused deputy" problems.
I guess if it got the decision wrong somehow that'd be a problem? Or wrongly
OK'd it on one level but then that led to the fd being passed on assumption it
was OK to do it or something?
>
> > Or at least it seems like a crazy thing to be able to get full access to
> > another process's memory (that it... gave you though).
> >
> > Anyway perhaps overthinking it :)
>
> Hm, yes, though I guess that is kind of orthogonal.
>
> For what it's worth, SELinux can prevent such things happening across
> domain boundaries - it enforces that, in the SELinux ruleset, the
> process calling read()/write() on an FD is granted FILE__READ /
> FILE__WRITE permission to the file's inode.
Ah OK that's good that it's at least possible to check for this class of problem
so the rest is moot then :)
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-25 13:13 ` Christian Brauner
@ 2026-08-25 13:46 ` Jann Horn
0 siblings, 0 replies; 32+ messages in thread
From: Jann Horn @ 2026-08-25 13:46 UTC (permalink / raw)
To: Christian Brauner
Cc: Paul Moore, James Morris, Serge E. Hallyn, Stephen Smalley,
Jeff Xu, Thiébaud Weksteen, Alexander Viro, 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 3:13 PM Christian Brauner <brauner@kernel.org> wrote:
> On Tue, Aug 18, 2026 at 09:51:06PM +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".
>
> Useful. I ran into this issue just yesterday in:
>
> https://github.com/systemd/systemd/pull/43511
>
> Btw, it's kinda annoying that PTRACE_POKE{TEXT,DATA} still allows foll
> force writes even if PROC_MEM_FORCE_NEVER is enabled. So ideally there'd
> also be a hook for that option.
You need either /proc/$pid/mem or PTRACE_POKETEXT for installing
software breakpoints. I think if you want to completely block
FOLL_FORCE through ptrace, you're probably in a scenario where ptrace
should either be blocked completely, or at least restricted to
mostly-read-only operations?
> The other thing - though not directly FOLL_FORCE related - is that
> process_vm_writev() isn't subject to any security restrictions other
> than seccomp filtering either.
It does go through mm_access() and ptrace_may_access() unless the
access is same-MM, so it can be blocked by LSMs like Yama.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-25 13:19 ` Christian Brauner
@ 2026-08-25 14:00 ` Jann Horn
0 siblings, 0 replies; 32+ messages in thread
From: Jann Horn @ 2026-08-25 14:00 UTC (permalink / raw)
To: Christian Brauner
Cc: Paul Moore, James Morris, Serge E. Hallyn, Stephen Smalley,
Jeff Xu, Thiébaud Weksteen, Alexander Viro, 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 3:19 PM Christian Brauner <brauner@kernel.org> wrote:
> On Tue, Aug 18, 2026 at 09:51:06PM +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>
> > ---
> > fs/proc/base.c | 6 ++++++
> > include/linux/lsm_hook_defs.h | 1 +
> > include/linux/security.h | 6 ++++++
> > security/security.c | 15 +++++++++++++++
> > 4 files changed, 28 insertions(+)
> >
> > diff --git a/fs/proc/base.c b/fs/proc/base.c
> > index bec6197329dc..3dfaef49bb70 100644
> > --- a/fs/proc/base.c
> > +++ b/fs/proc/base.c
> > @@ -851,6 +851,8 @@ 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 bypassed due to introspection? */
> > + bool introspection;
> > };
> >
> > static int mem_open(struct inode *inode, struct file *file)
> > @@ -864,12 +866,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->introspection = 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 +890,8 @@ static bool proc_mem_foll_force(struct file *file, struct mm_struct *mm)
> > }
> > return ptrace_active;
> > default:
> > + if (priv->introspection)
> > + return security_introspect_mem_foll_force(file->f_cred) == 0;
>
> Hm. Why not pass the reason to security_introspect_mem_foll_force() and
> call it unconditionally? Similarly it could also be called for the
> active ptracer case. Then you'd just need to pass a flag to the security
> hook and the LSM can decide based on that.
A process which is attached as a ptracer can modify memory with (for
example) PTRACE_POKETEXT and registers with (for example)
PTRACE_SETREGS. LSMs that want to prevent such debugging operations
are supposed to prevent ptrace attachment with the ptrace_access_check
hook.
I am just trying to plug the enforcement hole where ptrace-style
modification of process state is possible without going through
ptrace_access_check - which means just looking at these
"introspection" cases.
If I wanted to provide LSMs with a more granular ability to do some
PTRACE_MODE_ATTACH operations (like attaching via ptrace) while
blocking other operations (like PTRACE_POKETEXT or changing register
values), that would require more plumbing, so I'm not trying to do
that here.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-24 17:28 ` Jann Horn
@ 2026-08-25 14:02 ` Lorenzo Stoakes (ARM)
0 siblings, 0 replies; 32+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-25 14:02 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 Mon, Aug 24, 2026 at 07:28:24PM +0200, Jann Horn wrote:
> On Fri, Aug 21, 2026 at 9:00 PM Lorenzo Stoakes (ARM) <ljs@kernel.org> wrote:
> > On Tue, Aug 18, 2026 at 09:51:06PM +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>
>
> > > @@ -886,6 +890,8 @@ static bool proc_mem_foll_force(struct file *file, struct mm_struct *mm)
> > > }
> > > return ptrace_active;
> > > default:
> > > + if (priv->introspection)
> > > + return security_introspect_mem_foll_force(file->f_cred) == 0;
> >
> > As per 3/3 I wonder if you need an additional parameter to cover the fd -> some
> > other process case?
> >
> > Like:
> > if (priv->owned_by_owner) {
> > const bool is_remote = current->mm != priv->mm;
> >
> > return !security_fd_from_owner_mem_foll_force(file->f_cred,
> > is_remote);
> > }
> >
> > (I'm not sure how LSM hooks are supposed to look :)
>
> We could do that if we wanted to treat cases differently based on the
> identity of the writer, but I think in general that's not a good idea.
>
> In general, if you send an FD to some daemon, and the daemon writes
> into the FD, this should not cause access control decisions based on
> the identity of the daemon, because it can cause "confused deputy"
> bugs - the daemon might think it is just writing log output into a
> normal file, or something like that.
Ack yup, variations of a theme of this, I think I was overly confused by
the fd-passing stuff vs. the key reason for the series.
In general the thing LGTM other than the naming so a respin should be good!
>
> > > diff --git a/security/security.c b/security/security.c
> > > index 71aea8fdf014..d0f790a534eb 100644
> > > --- a/security/security.c
> > > +++ b/security/security.c
> > > @@ -595,6 +595,21 @@ int security_ptrace_traceme(struct task_struct *parent)
> > > return call_int_hook(ptrace_traceme, parent);
> > > }
> > >
> > > +/**
> > > + * security_introspect_mem_foll_force() - Check if introspective FOLL_FORCE is allowed
> > > + * @subject: credentials of the process accessing its own memory
> > > + *
> > > + * Check if FOLL_FORCE is allowed for a process accessing its own memory, which
> > > + * bypasses the security_ptrace_access_check() hook.
> >
> > This should be updated to also explicitly mention the fd case. As surely in that
> > case this is not true? Unless I'm missing something.
>
> Yeah, I'll clarify this comment.
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-25 13:42 ` Lorenzo Stoakes (ARM)
@ 2026-08-25 14:08 ` Jann Horn
2026-08-25 14:24 ` Lorenzo Stoakes (ARM)
0 siblings, 1 reply; 32+ messages in thread
From: Jann Horn @ 2026-08-25 14:08 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
Cc: David Hildenbrand (Arm), 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, linux-mm
On Tue, Aug 25, 2026 at 3:42 PM Lorenzo Stoakes (ARM) <ljs@kernel.org> wrote:
> On Mon, Aug 24, 2026 at 07:43:02PM +0200, Jann Horn wrote:
> > On Mon, Aug 24, 2026 at 7:33 PM Lorenzo Stoakes (ARM) <ljs@kernel.org> wrote:
> > > On Mon, Aug 24, 2026 at 07:06:04PM +0200, Jann Horn wrote:
> > > > On Fri, Aug 21, 2026 at 8:52 PM Lorenzo Stoakes (ARM) <ljs@kernel.org> wrote:
> > > > > The sharing a /proc/mem fd seems like that's a pretty dumb thing to do in
> > > > > general :) but I guess you have to protect against that.
> > > >
> > > > Yeah, it's a kinda weird thing to do...
> > >
> > > Yup :)) but I guess we have to account for people doing weird stuff...
> > >
> > > In this case (I do mention it in a reply elsewhere I think) it does seem
> > > like perhaps you should separately check for current->mm != mm of (what was
> > > originally /proc/self/mm)?
> >
> > I wouldn't want to do it for this access check, since that could lead
> > to "confused deputy" problems.
>
> I guess if it got the decision wrong somehow that'd be a problem? Or wrongly
> OK'd it on one level but then that led to the fd being passed on assumption it
> was OK to do it or something?
The problematic scenario would be something like:
1. process A opens fd1=open("/proc/self/mem",O_RDWR)
2. process A does lseek(fd1, <address of libc>, SEEK_SET)
3. A sends fd1 to privileged daemon B as a "log output" FD
4. privileged daemon B write()s into fd1
In this scenario, daemon B is just trying to write log output into a
file descriptor. If we checked the current credentials on write(), we
might enable FOLL_FORCE just because daemon B is generally permitted
to use ptrace.
This illustrates why, in general, the "ambient privilege" that a
process has must not influence write() access decisions.
For a similar historical example, see
https://project-zero.issues.chromium.org/issues/42450869 where we used
to do capability checks in the old expand_downwards() logic, which
could be reached by writing into /proc/$pid/mem. That made it possible
for an unprivileged process to map virtual address 0 by providing
/proc/$pid/mem as stderr to a setuid root binary.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-25 14:08 ` Jann Horn
@ 2026-08-25 14:24 ` Lorenzo Stoakes (ARM)
2026-08-25 15:00 ` Jann Horn
0 siblings, 1 reply; 32+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-25 14:24 UTC (permalink / raw)
To: Jann Horn
Cc: David Hildenbrand (Arm), 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, linux-mm
On Tue, Aug 25, 2026 at 04:08:42PM +0200, Jann Horn wrote:
> On Tue, Aug 25, 2026 at 3:42 PM Lorenzo Stoakes (ARM) <ljs@kernel.org> wrote:
> > On Mon, Aug 24, 2026 at 07:43:02PM +0200, Jann Horn wrote:
> > > On Mon, Aug 24, 2026 at 7:33 PM Lorenzo Stoakes (ARM) <ljs@kernel.org> wrote:
> > > > On Mon, Aug 24, 2026 at 07:06:04PM +0200, Jann Horn wrote:
> > > > > On Fri, Aug 21, 2026 at 8:52 PM Lorenzo Stoakes (ARM) <ljs@kernel.org> wrote:
> > > > > > The sharing a /proc/mem fd seems like that's a pretty dumb thing to do in
> > > > > > general :) but I guess you have to protect against that.
> > > > >
> > > > > Yeah, it's a kinda weird thing to do...
> > > >
> > > > Yup :)) but I guess we have to account for people doing weird stuff...
> > > >
> > > > In this case (I do mention it in a reply elsewhere I think) it does seem
> > > > like perhaps you should separately check for current->mm != mm of (what was
> > > > originally /proc/self/mm)?
> > >
> > > I wouldn't want to do it for this access check, since that could lead
> > > to "confused deputy" problems.
> >
> > I guess if it got the decision wrong somehow that'd be a problem? Or wrongly
> > OK'd it on one level but then that led to the fd being passed on assumption it
> > was OK to do it or something?
>
> The problematic scenario would be something like:
>
> 1. process A opens fd1=open("/proc/self/mem",O_RDWR)
> 2. process A does lseek(fd1, <address of libc>, SEEK_SET)
> 3. A sends fd1 to privileged daemon B as a "log output" FD
> 4. privileged daemon B write()s into fd1
>
> In this scenario, daemon B is just trying to write log output into a
> file descriptor. If we checked the current credentials on write(), we
> might enable FOLL_FORCE just because daemon B is generally permitted
> to use ptrace.
>
> This illustrates why, in general, the "ambient privilege" that a
> process has must not influence write() access decisions.
Ahh. That makes sense.
But I mean in this case the check would be that the mm is the one belonging to
the process in question so wouldn't you need in the first place to have obtained
a privileged mm anyway?
If the check is literally mm of /proc/$pid/mem == current->mm?
And wouldn't prilileged process -> fd to /proc/$pid/mem -> less privileged
process be a fail in itself?
>
> For a similar historical example, see
> https://project-zero.issues.chromium.org/issues/42450869 where we used
> to do capability checks in the old expand_downwards() logic, which
> could be reached by writing into /proc/$pid/mem. That made it possible
> for an unprivileged process to map virtual address 0 by providing
> /proc/$pid/mem as stderr to a setuid root binary.
This does raise questions about /proc/$pid/mm as a whole but I guess that ship
sailed long ago...
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-25 14:24 ` Lorenzo Stoakes (ARM)
@ 2026-08-25 15:00 ` Jann Horn
0 siblings, 0 replies; 32+ messages in thread
From: Jann Horn @ 2026-08-25 15:00 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
Cc: David Hildenbrand (Arm), 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, linux-mm
On Tue, Aug 25, 2026 at 4:24 PM Lorenzo Stoakes (ARM) <ljs@kernel.org> wrote:
> On Tue, Aug 25, 2026 at 04:08:42PM +0200, Jann Horn wrote:
> > On Tue, Aug 25, 2026 at 3:42 PM Lorenzo Stoakes (ARM) <ljs@kernel.org> wrote:
> > > On Mon, Aug 24, 2026 at 07:43:02PM +0200, Jann Horn wrote:
> > > > On Mon, Aug 24, 2026 at 7:33 PM Lorenzo Stoakes (ARM) <ljs@kernel.org> wrote:
> > > > > On Mon, Aug 24, 2026 at 07:06:04PM +0200, Jann Horn wrote:
> > > > > > On Fri, Aug 21, 2026 at 8:52 PM Lorenzo Stoakes (ARM) <ljs@kernel.org> wrote:
> > > > > > > The sharing a /proc/mem fd seems like that's a pretty dumb thing to do in
> > > > > > > general :) but I guess you have to protect against that.
> > > > > >
> > > > > > Yeah, it's a kinda weird thing to do...
> > > > >
> > > > > Yup :)) but I guess we have to account for people doing weird stuff...
> > > > >
> > > > > In this case (I do mention it in a reply elsewhere I think) it does seem
> > > > > like perhaps you should separately check for current->mm != mm of (what was
> > > > > originally /proc/self/mm)?
> > > >
> > > > I wouldn't want to do it for this access check, since that could lead
> > > > to "confused deputy" problems.
> > >
> > > I guess if it got the decision wrong somehow that'd be a problem? Or wrongly
> > > OK'd it on one level but then that led to the fd being passed on assumption it
> > > was OK to do it or something?
> >
> > The problematic scenario would be something like:
> >
> > 1. process A opens fd1=open("/proc/self/mem",O_RDWR)
> > 2. process A does lseek(fd1, <address of libc>, SEEK_SET)
> > 3. A sends fd1 to privileged daemon B as a "log output" FD
> > 4. privileged daemon B write()s into fd1
> >
> > In this scenario, daemon B is just trying to write log output into a
> > file descriptor. If we checked the current credentials on write(), we
> > might enable FOLL_FORCE just because daemon B is generally permitted
> > to use ptrace.
> >
> > This illustrates why, in general, the "ambient privilege" that a
> > process has must not influence write() access decisions.
>
> Ahh. That makes sense.
>
> But I mean in this case the check would be that the mm is the one belonging to
> the process in question so wouldn't you need in the first place to have obtained
> a privileged mm anyway?
>
> If the check is literally mm of /proc/$pid/mem == current->mm?
Ah, right, true.
Still, I think I want to generally avoid looking at the current
process in write(). If I did add such a check, and the check failed,
I'm also not sure what I'd do with this information.
> And wouldn't prilileged process -> fd to /proc/$pid/mem -> less privileged
> process be a fail in itself?
Yes, true.
^ permalink raw reply [flat|nested] 32+ messages in thread
end of thread, other threads:[~2026-08-25 15:01 UTC | newest]
Thread overview: 32+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-18 19:51 [PATCH 0/3] proc,security,selinux: let SELinux block FOLL_FORCE for /proc/self/mem Jann Horn
2026-08-18 19:51 ` [PATCH 1/3] proc: refactor /proc/$pid/mem to use struct as private_data Jann Horn
2026-08-20 11:20 ` Jan Kara
2026-08-20 17:18 ` David Hildenbrand (Arm)
2026-08-21 18:34 ` Lorenzo Stoakes (ARM)
2026-08-18 19:51 ` [PATCH 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS) Jann Horn
2026-08-20 17:22 ` David Hildenbrand (Arm)
2026-08-20 18:44 ` Jann Horn
2026-08-21 14:18 ` David Hildenbrand (Arm)
2026-08-21 14:48 ` Jann Horn
2026-08-21 18:52 ` Lorenzo Stoakes (ARM)
2026-08-24 17:06 ` Jann Horn
2026-08-24 17:32 ` Lorenzo Stoakes (ARM)
2026-08-24 17:43 ` Jann Horn
2026-08-25 13:42 ` Lorenzo Stoakes (ARM)
2026-08-25 14:08 ` Jann Horn
2026-08-25 14:24 ` Lorenzo Stoakes (ARM)
2026-08-25 15:00 ` Jann Horn
2026-08-21 19:00 ` Lorenzo Stoakes (ARM)
2026-08-24 17:28 ` Jann Horn
2026-08-25 14:02 ` Lorenzo Stoakes (ARM)
2026-08-25 13:13 ` Christian Brauner
2026-08-25 13:46 ` Jann Horn
2026-08-25 13:19 ` Christian Brauner
2026-08-25 14:00 ` Jann Horn
2026-08-18 19:51 ` [PATCH 3/3] selinux: require EXECMEM or PTRACE for FOLL_FORCE introspection Jann Horn
2026-08-19 14:54 ` Stephen Smalley
2026-08-20 15:23 ` Jann Horn
2026-08-21 13:52 ` Stephen Smalley
2026-08-21 15:07 ` Jann Horn
2026-08-21 18:56 ` Lorenzo Stoakes (ARM)
2026-08-24 17:17 ` Jann Horn
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox