* Re: [PATCH v2 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-25 18:39 ` [PATCH v2 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS) Jann Horn
@ 2026-08-26 13:06 ` Lorenzo Stoakes (ARM)
2026-08-27 16:59 ` David Hildenbrand (Arm)
2026-08-28 1:10 ` Paul Moore
2 siblings, 0 replies; 15+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-26 13:06 UTC (permalink / raw)
To: Jann Horn
Cc: Paul Moore, James Morris, Serge E. Hallyn, Stephen Smalley,
Jeff Xu, Thiébaud Weksteen, Alexander Viro,
Christian Brauner, Jan Kara, linux-fsdevel, linux-security-module,
Ondrej Mosnacek, selinux, Andrew Morton, Liam R. Howlett,
Vlastimil Babka, Pedro Falcato, David Hildenbrand, linux-mm
On Tue, Aug 25, 2026 at 08:39:18PM +0200, Jann Horn wrote:
> If the system is running with PROC_MEM_FORCE_ALWAYS, LSMs currently have no
> good opportunity to block a process from overwriting read-only code in its
> own address space through FOLL_FORCE writes via /proc/self/mem.
> The security_ptrace_access_check() LSM hook is bypassed when a process
> opens /proc/self/mem because this is considered "introspection".
>
> This causes a hole in SELinux EXECMEM enforcement, which tries to ensure
> that a process cannot create executable anonymous pages.
>
> PROC_MEM_FORCE_PTRACE prevents that and ensures that such FOLL_FORCE
> accesses are only possible when the LSM allows ptrace() attachment; but it
> is unclear how quickly PROC_MEM_FORCE_PTRACE can be deployed in
> environments running lots of third-party code, such as Android.
>
> So, introduce a new LSM hook that can forbid FOLL_FORCE specifically for
> such "introspective" accesses.
>
> Signed-off-by: Jann Horn <jannh@google.com>
Thanks for the name change! :)
Nothing stands out so:
Acked-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
> fs/proc/base.c | 9 +++++++++
> include/linux/lsm_hook_defs.h | 1 +
> include/linux/security.h | 6 ++++++
> security/security.c | 20 ++++++++++++++++++++
> 4 files changed, 36 insertions(+)
>
> diff --git a/fs/proc/base.c b/fs/proc/base.c
> index bec6197329dc..dc6fdcb47b79 100644
> --- a/fs/proc/base.c
> +++ b/fs/proc/base.c
> @@ -851,6 +851,11 @@ static int __mem_open(struct inode *inode, struct file *file, unsigned int mode)
> /* private_data for proc_mem_operations */
> struct mem_private {
> struct mm_struct *mm;
> + /*
> + * Was the ptrace access check on open bypassed because the opener used
> + * the same MM (introspection)?
> + */
> + bool opened_by_owner;
> };
>
> static int mem_open(struct inode *inode, struct file *file)
> @@ -864,12 +869,14 @@ static int mem_open(struct inode *inode, struct file *file)
> priv->mm = proc_mem_open(inode, PTRACE_MODE_ATTACH);
> if (IS_ERR_OR_NULL(priv->mm))
> return priv->mm ? PTR_ERR(priv->mm) : -ESRCH;
> + priv->opened_by_owner = priv->mm == current->mm;
> file->private_data = no_free_ptr(priv);
> return 0;
> }
>
> static bool proc_mem_foll_force(struct file *file, struct mm_struct *mm)
> {
> + struct mem_private *priv = file->private_data;
> struct task_struct *task;
> bool ptrace_active = false;
>
> @@ -886,6 +893,8 @@ static bool proc_mem_foll_force(struct file *file, struct mm_struct *mm)
> }
> return ptrace_active;
> default:
> + if (priv->opened_by_owner)
> + return security_mem_foll_force_opened_by_owner(file->f_cred) == 0;
> return true;
> }
> }
> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index 65c9609ec207..50e3f0abc676 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -36,6 +36,7 @@ LSM_HOOK(int, 0, binder_transfer_file, const struct cred *from,
> LSM_HOOK(int, 0, ptrace_access_check, struct task_struct *child,
> unsigned int mode)
> LSM_HOOK(int, 0, ptrace_traceme, struct task_struct *parent)
> +LSM_HOOK(int, 0, mem_foll_force_opened_by_owner, const struct cred *subject)
> LSM_HOOK(int, 0, capget, const struct task_struct *target, kernel_cap_t *effective,
> kernel_cap_t *inheritable, kernel_cap_t *permitted)
> LSM_HOOK(int, 0, capset, struct cred *new, const struct cred *old,
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 153e9043058f..74eb876054b0 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -338,6 +338,7 @@ int security_binder_transfer_file(const struct cred *from,
> const struct cred *to, const struct file *file);
> int security_ptrace_access_check(struct task_struct *child, unsigned int mode);
> int security_ptrace_traceme(struct task_struct *parent);
> +int security_mem_foll_force_opened_by_owner(const struct cred *subject);
> int security_capget(const struct task_struct *target,
> kernel_cap_t *effective,
> kernel_cap_t *inheritable,
> @@ -676,6 +677,11 @@ static inline int security_ptrace_traceme(struct task_struct *parent)
> return cap_ptrace_traceme(parent);
> }
>
> +static inline int security_mem_foll_force_opened_by_owner(const struct cred *subject)
> +{
> + return 0;
> +}
> +
> static inline int security_capget(const struct task_struct *target,
> kernel_cap_t *effective,
> kernel_cap_t *inheritable,
> diff --git a/security/security.c b/security/security.c
> index 71aea8fdf014..fff26ff65e07 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -595,6 +595,26 @@ int security_ptrace_traceme(struct task_struct *parent)
> return call_int_hook(ptrace_traceme, parent);
> }
>
> +/**
> + * security_mem_foll_force_opened_by_owner() - Check if introspective FOLL_FORCE is allowed
> + * @subject: credentials of the process accessing its own memory
> + *
> + * Check if FOLL_FORCE is allowed for accessing process memory through
> + * /proc/$pid/mem in the case where the opener's MM was the same as the target
> + * MM, meaning the security_ptrace_access_check() hook was bypassed on open().
> + * (current->mm does not matter for this; for example, if write() is called on
> + * an FD that was received from another process which obtained it with
> + * open("/proc/self/mem"), this hook still runs.)
> + *
> + * This is only used when the system is configured with PROC_MEM_FORCE_ALWAYS.
> + *
> + * Return: Returns 0 if permission is granted.
> + */
> +int security_mem_foll_force_opened_by_owner(const struct cred *subject)
> +{
> + return call_int_hook(mem_foll_force_opened_by_owner, subject);
> +}
> +
> /**
> * security_capget() - Get the capability sets for a process
> * @target: target process
>
> --
> 2.55.0.860.g4b6b3295ed-goog
>
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v2 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-25 18:39 ` [PATCH v2 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS) Jann Horn
2026-08-26 13:06 ` Lorenzo Stoakes (ARM)
@ 2026-08-27 16:59 ` David Hildenbrand (Arm)
2026-08-28 1:10 ` Paul Moore
2 siblings, 0 replies; 15+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 16:59 UTC (permalink / raw)
To: Jann Horn, Paul Moore, James Morris, Serge E. Hallyn,
Stephen Smalley, Jeff Xu, Thiébaud Weksteen
Cc: Alexander Viro, Christian Brauner, Jan Kara, linux-fsdevel,
linux-security-module, Ondrej Mosnacek, selinux, Andrew Morton,
Liam R. Howlett, Lorenzo Stoakes, Vlastimil Babka, Pedro Falcato,
linux-mm
On 8/25/26 20:39, Jann Horn wrote:
> If the system is running with PROC_MEM_FORCE_ALWAYS, LSMs currently have no
> good opportunity to block a process from overwriting read-only code in its
> own address space through FOLL_FORCE writes via /proc/self/mem.
> The security_ptrace_access_check() LSM hook is bypassed when a process
> opens /proc/self/mem because this is considered "introspection".
>
> This causes a hole in SELinux EXECMEM enforcement, which tries to ensure
> that a process cannot create executable anonymous pages.
>
> PROC_MEM_FORCE_PTRACE prevents that and ensures that such FOLL_FORCE
> accesses are only possible when the LSM allows ptrace() attachment; but it
> is unclear how quickly PROC_MEM_FORCE_PTRACE can be deployed in
> environments running lots of third-party code, such as Android.
>
> So, introduce a new LSM hook that can forbid FOLL_FORCE specifically for
> such "introspective" accesses.
>
> Signed-off-by: Jann Horn <jannh@google.com>
> ---
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
--
Cheers,
David
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-25 18:39 ` [PATCH v2 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS) Jann Horn
2026-08-26 13:06 ` Lorenzo Stoakes (ARM)
2026-08-27 16:59 ` David Hildenbrand (Arm)
@ 2026-08-28 1:10 ` Paul Moore
2026-08-28 13:03 ` Jann Horn
2 siblings, 1 reply; 15+ messages in thread
From: Paul Moore @ 2026-08-28 1:10 UTC (permalink / raw)
To: Jann Horn, 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 Aug 25, 2026 Jann Horn <jannh@google.com> wrote:
>
> If the system is running with PROC_MEM_FORCE_ALWAYS, LSMs currently have no
> good opportunity to block a process from overwriting read-only code in its
> own address space through FOLL_FORCE writes via /proc/self/mem.
> The security_ptrace_access_check() LSM hook is bypassed when a process
> opens /proc/self/mem because this is considered "introspection".
>
> This causes a hole in SELinux EXECMEM enforcement, which tries to ensure
> that a process cannot create executable anonymous pages.
>
> PROC_MEM_FORCE_PTRACE prevents that and ensures that such FOLL_FORCE
> accesses are only possible when the LSM allows ptrace() attachment; but it
> is unclear how quickly PROC_MEM_FORCE_PTRACE can be deployed in
> environments running lots of third-party code, such as Android.
>
> So, introduce a new LSM hook that can forbid FOLL_FORCE specifically for
> such "introspective" accesses.
>
> Signed-off-by: Jann Horn <jannh@google.com>
> Acked-by: David Hildenbrand (Arm) <david@kernel.org>
> Acked-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
> fs/proc/base.c | 9 +++++++++
> include/linux/lsm_hook_defs.h | 1 +
> include/linux/security.h | 6 ++++++
> security/security.c | 20 ++++++++++++++++++++
> 4 files changed, 36 insertions(+)
>
> diff --git a/fs/proc/base.c b/fs/proc/base.c
> index bec6197329dc..dc6fdcb47b79 100644
> --- a/fs/proc/base.c
> +++ b/fs/proc/base.c
> @@ -851,6 +851,11 @@ static int __mem_open(struct inode *inode, struct file *file, unsigned int mode)
> /* private_data for proc_mem_operations */
> struct mem_private {
> struct mm_struct *mm;
> + /*
> + * Was the ptrace access check on open bypassed because the opener used
> + * the same MM (introspection)?
> + */
> + bool opened_by_owner;
> };
>
> static int mem_open(struct inode *inode, struct file *file)
> @@ -864,12 +869,14 @@ static int mem_open(struct inode *inode, struct file *file)
> priv->mm = proc_mem_open(inode, PTRACE_MODE_ATTACH);
> if (IS_ERR_OR_NULL(priv->mm))
> return priv->mm ? PTR_ERR(priv->mm) : -ESRCH;
> + priv->opened_by_owner = priv->mm == current->mm;
> file->private_data = no_free_ptr(priv);
> return 0;
> }
>
> static bool proc_mem_foll_force(struct file *file, struct mm_struct *mm)
> {
> + struct mem_private *priv = file->private_data;
> struct task_struct *task;
> bool ptrace_active = false;
>
> @@ -886,6 +893,8 @@ static bool proc_mem_foll_force(struct file *file, struct mm_struct *mm)
> }
> return ptrace_active;
> default:
> + if (priv->opened_by_owner)
> + return security_mem_foll_force_opened_by_owner(file->f_cred) == 0;
> return true;
> }
> }
First things first, we've got to shorten that hook name :) What do you
think of security_proc_mem_foll_force()?
Beyond that, we really try to avoid making LSM hook calls conditional. It
can limit what an LSM can enforce, it tends to be a bit more fragile, and
it adds some unnecessary work in the case where CONFIG_SECURITY is
disabled. I would suggest passing 'opened_by_owner' flag as a second
parameter to the LSM hook and calling the hook unconditionally in the
default switch case as a replacement for the 'return true;' statement. I
understand it may seem a bit odd, but we try to make the LSM interface as
generic as possible with respect to different models and this is one way
we do that. It also ensures we don't have to process the 'opened_by_owner'
check in that case where the LSM is disabled.
> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index 65c9609ec207..50e3f0abc676 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -36,6 +36,7 @@ LSM_HOOK(int, 0, binder_transfer_file, const struct cred *from,
> LSM_HOOK(int, 0, ptrace_access_check, struct task_struct *child,
> unsigned int mode)
> LSM_HOOK(int, 0, ptrace_traceme, struct task_struct *parent)
> +LSM_HOOK(int, 0, mem_foll_force_opened_by_owner, const struct cred *subject)
See the default/disabled return value discussion below.
> LSM_HOOK(int, 0, capget, const struct task_struct *target, kernel_cap_t *effective,
> kernel_cap_t *inheritable, kernel_cap_t *permitted)
> LSM_HOOK(int, 0, capset, struct cred *new, const struct cred *old,
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 153e9043058f..74eb876054b0 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -338,6 +338,7 @@ int security_binder_transfer_file(const struct cred *from,
> const struct cred *to, const struct file *file);
> int security_ptrace_access_check(struct task_struct *child, unsigned int mode);
> int security_ptrace_traceme(struct task_struct *parent);
> +int security_mem_foll_force_opened_by_owner(const struct cred *subject);
> int security_capget(const struct task_struct *target,
> kernel_cap_t *effective,
> kernel_cap_t *inheritable,
> @@ -676,6 +677,11 @@ static inline int security_ptrace_traceme(struct task_struct *parent)
> return cap_ptrace_traceme(parent);
> }
>
> +static inline int security_mem_foll_force_opened_by_owner(const struct cred *subject)
> +{
> + return 0;
> +}
With proc_mem_foll_force() currently returning true/1 in this case,
shouldn't the LSM hook return true/1 when disabled?
> static inline int security_capget(const struct task_struct *target,
> kernel_cap_t *effective,
> kernel_cap_t *inheritable,
> diff --git a/security/security.c b/security/security.c
> index 71aea8fdf014..fff26ff65e07 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -595,6 +595,26 @@ int security_ptrace_traceme(struct task_struct *parent)
> return call_int_hook(ptrace_traceme, parent);
> }
>
> +/**
> + * security_mem_foll_force_opened_by_owner() - Check if introspective FOLL_FORCE is allowed
> + * @subject: credentials of the process accessing its own memory
> + *
> + * Check if FOLL_FORCE is allowed for accessing process memory through
> + * /proc/$pid/mem in the case where the opener's MM was the same as the target
> + * MM, meaning the security_ptrace_access_check() hook was bypassed on open().
> + * (current->mm does not matter for this; for example, if write() is called on
> + * an FD that was received from another process which obtained it with
> + * open("/proc/self/mem"), this hook still runs.)
> + *
> + * This is only used when the system is configured with PROC_MEM_FORCE_ALWAYS.
> + *
> + * Return: Returns 0 if permission is granted.
> + */
> +int security_mem_foll_force_opened_by_owner(const struct cred *subject)
> +{
> + return call_int_hook(mem_foll_force_opened_by_owner, subject);
> +}
Please don't forget to change the LSM callback name when you are changing
the LSM hook name.
--
paul-moore.com
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v2 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-28 1:10 ` Paul Moore
@ 2026-08-28 13:03 ` Jann Horn
2026-08-28 13:20 ` Jann Horn
2026-08-28 21:04 ` Paul Moore
0 siblings, 2 replies; 15+ messages in thread
From: Jann Horn @ 2026-08-28 13:03 UTC (permalink / raw)
To: Paul Moore
Cc: 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 Fri, Aug 28, 2026 at 3:10 AM Paul Moore <paul@paul-moore.com> wrote:
> On Aug 25, 2026 Jann Horn <jannh@google.com> wrote:
> > If the system is running with PROC_MEM_FORCE_ALWAYS, LSMs currently have no
> > good opportunity to block a process from overwriting read-only code in its
> > own address space through FOLL_FORCE writes via /proc/self/mem.
> > The security_ptrace_access_check() LSM hook is bypassed when a process
> > opens /proc/self/mem because this is considered "introspection".
> >
> > This causes a hole in SELinux EXECMEM enforcement, which tries to ensure
> > that a process cannot create executable anonymous pages.
> >
> > PROC_MEM_FORCE_PTRACE prevents that and ensures that such FOLL_FORCE
> > accesses are only possible when the LSM allows ptrace() attachment; but it
> > is unclear how quickly PROC_MEM_FORCE_PTRACE can be deployed in
> > environments running lots of third-party code, such as Android.
> >
> > So, introduce a new LSM hook that can forbid FOLL_FORCE specifically for
> > such "introspective" accesses.
> >
> > Signed-off-by: Jann Horn <jannh@google.com>
> > Acked-by: David Hildenbrand (Arm) <david@kernel.org>
> > Acked-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> > ---
> > fs/proc/base.c | 9 +++++++++
> > include/linux/lsm_hook_defs.h | 1 +
> > include/linux/security.h | 6 ++++++
> > security/security.c | 20 ++++++++++++++++++++
> > 4 files changed, 36 insertions(+)
> >
> > diff --git a/fs/proc/base.c b/fs/proc/base.c
> > index bec6197329dc..dc6fdcb47b79 100644
> > --- a/fs/proc/base.c
> > +++ b/fs/proc/base.c
> > @@ -851,6 +851,11 @@ static int __mem_open(struct inode *inode, struct file *file, unsigned int mode)
> > /* private_data for proc_mem_operations */
> > struct mem_private {
> > struct mm_struct *mm;
> > + /*
> > + * Was the ptrace access check on open bypassed because the opener used
> > + * the same MM (introspection)?
> > + */
> > + bool opened_by_owner;
> > };
> >
> > static int mem_open(struct inode *inode, struct file *file)
> > @@ -864,12 +869,14 @@ static int mem_open(struct inode *inode, struct file *file)
> > priv->mm = proc_mem_open(inode, PTRACE_MODE_ATTACH);
> > if (IS_ERR_OR_NULL(priv->mm))
> > return priv->mm ? PTR_ERR(priv->mm) : -ESRCH;
> > + priv->opened_by_owner = priv->mm == current->mm;
> > file->private_data = no_free_ptr(priv);
> > return 0;
> > }
> >
> > static bool proc_mem_foll_force(struct file *file, struct mm_struct *mm)
> > {
> > + struct mem_private *priv = file->private_data;
> > struct task_struct *task;
> > bool ptrace_active = false;
> >
> > @@ -886,6 +893,8 @@ static bool proc_mem_foll_force(struct file *file, struct mm_struct *mm)
> > }
> > return ptrace_active;
> > default:
> > + if (priv->opened_by_owner)
> > + return security_mem_foll_force_opened_by_owner(file->f_cred) == 0;
> > return true;
> > }
> > }
>
> First things first, we've got to shorten that hook name :) What do you
> think of security_proc_mem_foll_force()?
If we move the "opened_by_owner" part into a flag then I guess that
works... will do.
> Beyond that, we really try to avoid making LSM hook calls conditional. It
> can limit what an LSM can enforce, it tends to be a bit more fragile, and
> it adds some unnecessary work in the case where CONFIG_SECURITY is
> disabled. I would suggest passing 'opened_by_owner' flag as a second
> parameter to the LSM hook and calling the hook unconditionally in the
> default switch case as a replacement for the 'return true;' statement. I
> understand it may seem a bit odd, but we try to make the LSM interface as
> generic as possible with respect to different models and this is one way
I guess I can do that, but then the question becomes, what other modes
of using the LSM hook that don't currently exist in the kernel should
I be supporting with this? I can make this a parameter, but any LSM
policy that actually uses the parameter in a different way would
probably be buggy/inconsistent, unless other new LSM hooks are added.
If your intent is to make the hook work for any /proc/$pid/mem access,
including when the caller is ptrace-attached, then I guess I have to
move around the security hook call in proc_mem_foll_force() a little
bit. I guess I'll do that in v3, though I really don't like trying to
come up with a reasonable in-kernel API contract for a scenario that
currently has zero users.
> we do that. It also ensures we don't have to process the 'opened_by_owner'
> check in that case where the LSM is disabled.
(I don't think that's an improvement - we have to do the comparison
either way, but in the current version, we then use it to
conditionally branch to a security hook, while the v3 patch will
instead unconditionally call into the security hook.)
> > LSM_HOOK(int, 0, capget, const struct task_struct *target, kernel_cap_t *effective,
> > kernel_cap_t *inheritable, kernel_cap_t *permitted)
> > LSM_HOOK(int, 0, capset, struct cred *new, const struct cred *old,
> > diff --git a/include/linux/security.h b/include/linux/security.h
> > index 153e9043058f..74eb876054b0 100644
> > --- a/include/linux/security.h
> > +++ b/include/linux/security.h
> > @@ -338,6 +338,7 @@ int security_binder_transfer_file(const struct cred *from,
> > const struct cred *to, const struct file *file);
> > int security_ptrace_access_check(struct task_struct *child, unsigned int mode);
> > int security_ptrace_traceme(struct task_struct *parent);
> > +int security_mem_foll_force_opened_by_owner(const struct cred *subject);
> > int security_capget(const struct task_struct *target,
> > kernel_cap_t *effective,
> > kernel_cap_t *inheritable,
> > @@ -676,6 +677,11 @@ static inline int security_ptrace_traceme(struct task_struct *parent)
> > return cap_ptrace_traceme(parent);
> > }
> >
> > +static inline int security_mem_foll_force_opened_by_owner(const struct cred *subject)
> > +{
> > + return 0;
> > +}
>
> With proc_mem_foll_force() currently returning true/1 in this case,
> shouldn't the LSM hook return true/1 when disabled?
No, I have proc_mem_foll_force() doing:
"return security_mem_foll_force_opened_by_owner(file->f_cred) == 0;"
So returning 0 means "allowed", returning an error code means "denied".
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v2 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-28 13:03 ` Jann Horn
@ 2026-08-28 13:20 ` Jann Horn
2026-08-28 21:10 ` Paul Moore
2026-08-28 21:04 ` Paul Moore
1 sibling, 1 reply; 15+ messages in thread
From: Jann Horn @ 2026-08-28 13:20 UTC (permalink / raw)
To: Paul Moore
Cc: 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 Fri, Aug 28, 2026 at 3:03 PM Jann Horn <jannh@google.com> wrote:
> On Fri, Aug 28, 2026 at 3:10 AM Paul Moore <paul@paul-moore.com> wrote:
> > Beyond that, we really try to avoid making LSM hook calls conditional. It
> > can limit what an LSM can enforce, it tends to be a bit more fragile, and
> > it adds some unnecessary work in the case where CONFIG_SECURITY is
> > disabled. I would suggest passing 'opened_by_owner' flag as a second
> > parameter to the LSM hook and calling the hook unconditionally in the
> > default switch case as a replacement for the 'return true;' statement. I
> > understand it may seem a bit odd, but we try to make the LSM interface as
> > generic as possible with respect to different models and this is one way
>
> I guess I can do that, but then the question becomes, what other modes
> of using the LSM hook that don't currently exist in the kernel should
> I be supporting with this? I can make this a parameter, but any LSM
> policy that actually uses the parameter in a different way would
> probably be buggy/inconsistent, unless other new LSM hooks are added.
>
> If your intent is to make the hook work for any /proc/$pid/mem access,
> including when the caller is ptrace-attached, then I guess I have to
> move around the security hook call in proc_mem_foll_force() a little
> bit. I guess I'll do that in v3, though I really don't like trying to
> come up with a reasonable in-kernel API contract for a scenario that
> currently has zero users.
I started thinking about how to implement this, and an annoying aspect
about this is:
Currently, I only have to look at the credentials of the opener of the
file - that is both the subject and object of the access. But if I
want to provide a hook that works for any /proc/$pid/mem access, and
want the hook to know what the object of the access is, and do it
race-free, then I would need to grab the creds of the target task in
mm_access(), bubble them up into proc_mem_open() and mem_open() (which
will require touching all the callers of mm_access() and
proc_mem_open()), store them into mem_private there, then let
proc_mem_foll_force() extract the creds from there. All that would be
unused code.
Do you think I should just not provide credentials for the target, and
document "if opened_by_owner is true, the subject credentials are also
the object credentials; if opened_by_owner is false, you don't get to
know what the object is"?
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-28 13:20 ` Jann Horn
@ 2026-08-28 21:10 ` Paul Moore
0 siblings, 0 replies; 15+ messages in thread
From: Paul Moore @ 2026-08-28 21:10 UTC (permalink / raw)
To: Jann Horn
Cc: 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 Fri, Aug 28, 2026 at 9:21 AM Jann Horn <jannh@google.com> wrote:
> On Fri, Aug 28, 2026 at 3:03 PM Jann Horn <jannh@google.com> wrote:
> > On Fri, Aug 28, 2026 at 3:10 AM Paul Moore <paul@paul-moore.com> wrote:
> > > Beyond that, we really try to avoid making LSM hook calls conditional. It
> > > can limit what an LSM can enforce, it tends to be a bit more fragile, and
> > > it adds some unnecessary work in the case where CONFIG_SECURITY is
> > > disabled. I would suggest passing 'opened_by_owner' flag as a second
> > > parameter to the LSM hook and calling the hook unconditionally in the
> > > default switch case as a replacement for the 'return true;' statement. I
> > > understand it may seem a bit odd, but we try to make the LSM interface as
> > > generic as possible with respect to different models and this is one way
> >
> > I guess I can do that, but then the question becomes, what other modes
> > of using the LSM hook that don't currently exist in the kernel should
> > I be supporting with this? I can make this a parameter, but any LSM
> > policy that actually uses the parameter in a different way would
> > probably be buggy/inconsistent, unless other new LSM hooks are added.
> >
> > If your intent is to make the hook work for any /proc/$pid/mem access,
> > including when the caller is ptrace-attached, then I guess I have to
> > move around the security hook call in proc_mem_foll_force() a little
> > bit. I guess I'll do that in v3, though I really don't like trying to
> > come up with a reasonable in-kernel API contract for a scenario that
> > currently has zero users.
>
> I started thinking about how to implement this, and an annoying aspect
> about this is:
>
> Currently, I only have to look at the credentials of the opener of the
> file - that is both the subject and object of the access. But if I
> want to provide a hook that works for any /proc/$pid/mem access, and
> want the hook to know what the object of the access is, and do it
> race-free, then I would need to grab the creds of the target task in
> mm_access(), bubble them up into proc_mem_open() and mem_open() (which
> will require touching all the callers of mm_access() and
> proc_mem_open()), store them into mem_private there, then let
> proc_mem_foll_force() extract the creds from there. All that would be
> unused code.
>
> Do you think I should just not provide credentials for the target, and
> document "if opened_by_owner is true, the subject credentials are also
> the object credentials; if opened_by_owner is false, you don't get to
> know what the object is"?
From my perspective there are good reasons for not making the LSM hook
conditional (see previous emails), but I will happily admit that we
don't have any (?) good reasons for attempting to generalize this hook
beyond what it is at this point in time. I would suggest documenting
the limitations when opened_by_owner is false and leave it at that; if
someone wants to implement something that requires more
hooks/parameters we can always revisit this.
--
paul-moore.com
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
2026-08-28 13:03 ` Jann Horn
2026-08-28 13:20 ` Jann Horn
@ 2026-08-28 21:04 ` Paul Moore
1 sibling, 0 replies; 15+ messages in thread
From: Paul Moore @ 2026-08-28 21:04 UTC (permalink / raw)
To: Jann Horn
Cc: 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 Fri, Aug 28, 2026 at 9:04 AM Jann Horn <jannh@google.com> wrote:
> On Fri, Aug 28, 2026 at 3:10 AM Paul Moore <paul@paul-moore.com> wrote:
> > On Aug 25, 2026 Jann Horn <jannh@google.com> wrote:
> > > If the system is running with PROC_MEM_FORCE_ALWAYS, LSMs currently have no
> > > good opportunity to block a process from overwriting read-only code in its
> > > own address space through FOLL_FORCE writes via /proc/self/mem.
> > > The security_ptrace_access_check() LSM hook is bypassed when a process
> > > opens /proc/self/mem because this is considered "introspection".
> > >
> > > This causes a hole in SELinux EXECMEM enforcement, which tries to ensure
> > > that a process cannot create executable anonymous pages.
> > >
> > > PROC_MEM_FORCE_PTRACE prevents that and ensures that such FOLL_FORCE
> > > accesses are only possible when the LSM allows ptrace() attachment; but it
> > > is unclear how quickly PROC_MEM_FORCE_PTRACE can be deployed in
> > > environments running lots of third-party code, such as Android.
> > >
> > > So, introduce a new LSM hook that can forbid FOLL_FORCE specifically for
> > > such "introspective" accesses.
> > >
> > > Signed-off-by: Jann Horn <jannh@google.com>
> > > Acked-by: David Hildenbrand (Arm) <david@kernel.org>
> > > Acked-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> > > ---
> > > fs/proc/base.c | 9 +++++++++
> > > include/linux/lsm_hook_defs.h | 1 +
> > > include/linux/security.h | 6 ++++++
> > > security/security.c | 20 ++++++++++++++++++++
> > > 4 files changed, 36 insertions(+)
> > >
> > > diff --git a/fs/proc/base.c b/fs/proc/base.c
> > > index bec6197329dc..dc6fdcb47b79 100644
> > > --- a/fs/proc/base.c
> > > +++ b/fs/proc/base.c
> > > @@ -851,6 +851,11 @@ static int __mem_open(struct inode *inode, struct file *file, unsigned int mode)
> > > /* private_data for proc_mem_operations */
> > > struct mem_private {
> > > struct mm_struct *mm;
> > > + /*
> > > + * Was the ptrace access check on open bypassed because the opener used
> > > + * the same MM (introspection)?
> > > + */
> > > + bool opened_by_owner;
> > > };
> > >
> > > static int mem_open(struct inode *inode, struct file *file)
> > > @@ -864,12 +869,14 @@ static int mem_open(struct inode *inode, struct file *file)
> > > priv->mm = proc_mem_open(inode, PTRACE_MODE_ATTACH);
> > > if (IS_ERR_OR_NULL(priv->mm))
> > > return priv->mm ? PTR_ERR(priv->mm) : -ESRCH;
> > > + priv->opened_by_owner = priv->mm == current->mm;
> > > file->private_data = no_free_ptr(priv);
> > > return 0;
> > > }
> > >
> > > static bool proc_mem_foll_force(struct file *file, struct mm_struct *mm)
> > > {
> > > + struct mem_private *priv = file->private_data;
> > > struct task_struct *task;
> > > bool ptrace_active = false;
> > >
> > > @@ -886,6 +893,8 @@ static bool proc_mem_foll_force(struct file *file, struct mm_struct *mm)
> > > }
> > > return ptrace_active;
> > > default:
> > > + if (priv->opened_by_owner)
> > > + return security_mem_foll_force_opened_by_owner(file->f_cred) == 0;
> > > return true;
> > > }
> > > }
> >
> > First things first, we've got to shorten that hook name :) What do you
> > think of security_proc_mem_foll_force()?
>
> If we move the "opened_by_owner" part into a flag then I guess that
> works... will do.
>
> > Beyond that, we really try to avoid making LSM hook calls conditional. It
> > can limit what an LSM can enforce, it tends to be a bit more fragile, and
> > it adds some unnecessary work in the case where CONFIG_SECURITY is
> > disabled. I would suggest passing 'opened_by_owner' flag as a second
> > parameter to the LSM hook and calling the hook unconditionally in the
> > default switch case as a replacement for the 'return true;' statement. I
> > understand it may seem a bit odd, but we try to make the LSM interface as
> > generic as possible with respect to different models and this is one way
>
> I guess I can do that, but then the question becomes, what other modes
> of using the LSM hook that don't currently exist in the kernel should
> I be supporting with this? I can make this a parameter, but any LSM
> policy that actually uses the parameter in a different way would
> probably be buggy/inconsistent, unless other new LSM hooks are added.
Possibly. People do all sorts of things with the LSM hooks, not all
of them relate to access control.
> If your intent is to make the hook work for any /proc/$pid/mem access,
> including when the caller is ptrace-attached, then I guess I have to
> move around the security hook call in proc_mem_foll_force() a little
> bit. I guess I'll do that in v3, though I really don't like trying to
> come up with a reasonable in-kernel API contract for a scenario that
> currently has zero users.
As I said, I don't really have a specific model/intent in mind other
than avoiding a conditional LSM hook call.
> > we do that. It also ensures we don't have to process the 'opened_by_owner'
> > check in that case where the LSM is disabled.
>
> (I don't think that's an improvement - we have to do the comparison
> either way, but in the current version, we then use it to
> conditionally branch to a security hook, while the v3 patch will
> instead unconditionally call into the security hook.)
If we move the opened_by_owner check into the LSMs, in the
!CONFIG_SECURITY case we avoid the check entirely. We try to do that
when we can to minimize impact of the LSM when it is disabled at build
time.
> > > +static inline int security_mem_foll_force_opened_by_owner(const struct cred *subject)
> > > +{
> > > + return 0;
> > > +}
> >
> > With proc_mem_foll_force() currently returning true/1 in this case,
> > shouldn't the LSM hook return true/1 when disabled?
>
> No, I have proc_mem_foll_force() doing:
> "return security_mem_foll_force_opened_by_owner(file->f_cred) == 0;"
Ah, forgive me, my mind stopped reading once it hit the closing
parenthesis! That's not a common pattern for calling LSM hooks so my
brain wasn't expecting that :)
--
paul-moore.com
^ permalink raw reply [flat|nested] 15+ messages in thread