Thread (16 messages) 16 messages, 6 authors, 28d ago

Re: [PATCH v2 2/3] proc: query LSMs for introspective mem access (if PROC_MEM_FORCE_ALWAYS)

From: Jann Horn <jannh@google.com>
Date: 2026-08-28 13:04:05
Also in: linux-fsdevel, linux-mm, selinux

On Fri, Aug 28, 2026 at 3:10 AM Paul Moore [off-list ref] wrote:
On Aug 25, 2026 Jann Horn [off-list ref] wrote:
quoted
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.)
quoted
 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".
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help