From: Paul Moore <paul@paul-moore.com>
To: "Jann Horn" <jannh@google.com>,
"James Morris" <jmorris@namei.org>,
"Serge E. Hallyn" <serge@hallyn.com>,
"Stephen Smalley" <stephen.smalley.work@gmail.com>,
"Jeff Xu" <jeffxu@google.com>,
"Thiébaud Weksteen" <tweek@google.com>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>,
Christian Brauner <brauner@kernel.org>, Jan Kara <jack@suse.cz>,
linux-fsdevel@vger.kernel.org,
linux-security-module@vger.kernel.org,
Ondrej Mosnacek <omosnace@redhat.com>,
selinux@vger.kernel.org,
Andrew Morton <akpm@linux-foundation.org>,
"Liam R. Howlett" <liam@infradead.org>,
Lorenzo Stoakes <ljs@kernel.org>,
Vlastimil Babka <vbabka@kernel.org>,
Pedro Falcato <pfalcato@suse.de>,
David Hildenbrand <david@kernel.org>,
linux-mm@kvack.org, Jann Horn <jannh@google.com>
Subject: Re: [PATCH v2 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)
Date: Thu, 27 Aug 2026 21:10:44 -0400 [thread overview]
Message-ID: <82929c994076d95f99560ecc67f6cfce@paul-moore.com> (raw)
In-Reply-To: <20260825-selinux-pokemem-v2-2-b46bc64916d8@google.com>
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
next prev parent reply other threads:[~2026-08-28 1:10 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-25 18:39 [PATCH v2 0/3] proc,security,selinux: let SELinux block FOLL_FORCE for /proc/self/mem Jann Horn
2026-08-25 18:39 ` [PATCH v2 1/3] proc: refactor /proc/$pid/mem to use struct as private_data Jann Horn
2026-08-25 18:39 ` [PATCH v2 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS) Jann Horn
2026-08-26 13:06 ` Lorenzo Stoakes (ARM)
2026-08-27 16:59 ` David Hildenbrand (Arm)
2026-08-28 1:10 ` Paul Moore [this message]
2026-08-25 18:39 ` [PATCH v2 3/3] selinux: require PROCESS__PTRACE for FOLL_FORCE introspection Jann Horn
2026-08-25 19:30 ` Stephen Smalley
2026-08-27 17:46 ` Lorenzo Stoakes (ARM)
2026-08-28 1:10 ` Paul Moore
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=82929c994076d95f99560ecc67f6cfce@paul-moore.com \
--to=paul@paul-moore.com \
--cc=akpm@linux-foundation.org \
--cc=brauner@kernel.org \
--cc=david@kernel.org \
--cc=jack@suse.cz \
--cc=jannh@google.com \
--cc=jeffxu@google.com \
--cc=jmorris@namei.org \
--cc=liam@infradead.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=linux-security-module@vger.kernel.org \
--cc=ljs@kernel.org \
--cc=omosnace@redhat.com \
--cc=pfalcato@suse.de \
--cc=selinux@vger.kernel.org \
--cc=serge@hallyn.com \
--cc=stephen.smalley.work@gmail.com \
--cc=tweek@google.com \
--cc=vbabka@kernel.org \
--cc=viro@zeniv.linux.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox