* Re: [stable/linux-6.12.y 2/2] selinux: fix overlayfs mmap() and mprotect() access checks
2026-06-22 3:15 ` [stable/linux-6.12.y 2/2] selinux: fix overlayfs mmap() and mprotect() access checks Cai Xinchen
@ 2026-06-22 3:04 ` sashiko-bot
0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-06-22 3:04 UTC (permalink / raw)
To: Cai Xinchen; +Cc: paul, bpf
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] The PROCESS__EXECMEM permission is incorrectly evaluated against the mounter's credentials instead of the executing task when mapping a backing file.
- [Low] The patch references undefined struct members, functions, and LSM hooks, causing a build failure.
- [Low] Calling `backing_file_user_path()` with a `const struct file *` discards the `const` qualifier.
Pre-existing issues:
- [High] The code incorrectly uses `!file` to determine if a VMA is an anonymous stack mapping, creating a potential SELinux bypass.
--
commit ad3233d429cee42bb2ed783a8bd563912a4bf6bd
Author: Paul Moore <paul@paul-moore.com>
selinux: fix overlayfs mmap() and mprotect() access checks
This patch updates SELinux to enforce backing file access controls for
mmap() and mprotect() operations on overlayfs. It introduces checks using
the newly created security_mmap_backing_file() LSM hook and backing file
APIs.
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index 8e31d3b60fc62..1b89c8d5fa2fc 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
[ ... ]
> @@ -1724,49 +1724,72 @@ static inline int file_path_has_perm(const struct cred *cred,
> +static int __file_has_perm(const struct cred *cred, const struct file *file,
> + u32 av, bool bf_user_file)
> +
> {
> - struct file_security_struct *fsec = selinux_file(file);
> - struct inode *inode = file_inode(file);
> struct common_audit_data ad;
> - u32 sid = cred_sid(cred);
> + struct inode *inode;
> + u32 ssid = cred_sid(cred);
> + u32 tsid_fd;
> int rc;
>
> - ad.type = LSM_AUDIT_DATA_FILE;
> - ad.u.file = file;
> + if (bf_user_file) {
> + struct backing_file_security_struct *bfsec;
> + const struct path *path;
> +
> + if (WARN_ON(!(file->f_mode & FMODE_BACKING)))
> + return -EIO;
> +
> + bfsec = selinux_backing_file(file);
> + path = backing_file_user_path(file);
[Severity: Low]
Does this discard the const qualifier from the file pointer?
__file_has_perm() accepts a const struct file pointer, but
backing_file_user_path() expects a mutable pointer. This might trigger a
compiler warning.
[ ... ]
> -static int file_map_prot_check(struct file *file, unsigned long prot, int shared)
> +static int __file_map_prot_check(const struct cred *cred,
> + const struct file *file, unsigned long prot,
> + bool shared, bool bf_user_file)
> {
> - const struct cred *cred = current_cred();
> - u32 sid = cred_sid(cred);
> - int rc = 0;
> + struct inode *inode = NULL;
> + bool prot_exec = prot & PROT_EXEC;
> + bool prot_write = prot & PROT_WRITE;
> +
> + if (file) {
> + if (bf_user_file)
> + inode = d_inode(backing_file_user_path(file)->dentry);
> + else
> + inode = file_inode(file);
> + }
> +
> + if (default_noexec && prot_exec &&
> + (!file || IS_PRIVATE(inode) || (!shared && prot_write))) {
> + int rc;
> + u32 sid = cred_sid(cred);
>
> - if (default_noexec &&
> - (prot & PROT_EXEC) && (!file || IS_PRIVATE(file_inode(file)) ||
> - (!shared && (prot & PROT_WRITE)))) {
> /*
> - * We are making executable an anonymous mapping or a
> - * private file mapping that will also be writable.
> - * This has an additional check.
> + * We are making executable an anonymous mapping or a private
> + * file mapping that will also be writable.
> */
> - rc = avc_has_perm(sid, sid, SECCLASS_PROCESS,
> - PROCESS__EXECMEM, NULL);
> + rc = avc_has_perm(sid, sid, SECCLASS_PROCESS, PROCESS__EXECMEM,
> + NULL);
[Severity: High]
Does this evaluate the PROCESS__EXECMEM permission against the mounter's
credentials instead of the executing task?
When mapping a backing file, selinux_mmap_backing_file() and
selinux_file_mprotect() pass the mounter's credentials (file->f_cred) to this
function. Because PROCESS__EXECMEM is a subject-centric privilege that
governs the executing task's memory, evaluating it against the mounter's SID
could incorrectly require the mounter to possess execmem privileges, causing
legitimate executable mappings to fail.
[ ... ]
> static int selinux_file_mprotect(struct vm_area_struct *vma,
> unsigned long reqprot __always_unused,
> unsigned long prot)
> {
> + int rc;
> const struct cred *cred = current_cred();
> u32 sid = cred_sid(cred);
> + const struct file *file = vma->vm_file;
> + bool backing_file;
> + bool shared = vma->vm_flags & VM_SHARED;
> +
> + /* check if we need to trigger the "backing files are awful" mode */
> + backing_file = file && (file->f_mode & FMODE_BACKING);
>
> if (default_noexec &&
> (prot & PROT_EXEC) && !(vma->vm_flags & VM_EXEC)) {
> - int rc = 0;
> /*
> * We don't use the vma_is_initial_heap() helper as it has
> * a history of problems and is currently broken on systems
> @@ -3864,11 +3955,15 @@ static int selinux_file_mprotect(struct vm_area_struct *vma,
> vma->vm_end <= vma->vm_mm->brk) {
> rc = avc_has_perm(sid, sid, SECCLASS_PROCESS,
> PROCESS__EXECHEAP, NULL);
> - } else if (!vma->vm_file && (vma_is_initial_stack(vma) ||
> + if (rc)
> + return rc;
> + } else if (!file && (vma_is_initial_stack(vma) ||
> vma_is_stack_for_current(vma))) {
[Severity: High]
This is a pre-existing issue, but does this incorrectly use !file to determine
if a VMA is an anonymous stack mapping?
Using !file as a proxy for an anonymous VMA can be bypassed. If a stack
mapping is backed by a file (e.g., a private mapping of /dev/zero), the !file
check evaluates to false. This skips the PROCESS__EXECSTACK check and falls
through to the FILE__EXECMOD check, potentially allowing a bypass. Should this
be updated to use vma_is_anonymous(vma) instead?
[ ... ]
> @@ -6960,6 +7071,7 @@ static void selinux_bpf_token_free(struct bpf_token *token)
> struct lsm_blob_sizes selinux_blob_sizes __ro_after_init = {
> .lbs_cred = sizeof(struct task_security_struct),
> .lbs_file = sizeof(struct file_security_struct),
> + .lbs_backing_file = sizeof(struct backing_file_security_struct),
> .lbs_inode = sizeof(struct inode_security_struct),
> .lbs_ipc = sizeof(struct ipc_security_struct),
[Severity: Low]
Does this cause a build failure? It looks like lbs_backing_file (along with
the backing_file_alloc and mmap_backing_file LSM hooks registered below) does
not exist in the core LSM framework at this commit.
[ ... ]
> diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h
> index c88cae81ee4c5..dc42282a2c052 100644
> --- a/security/selinux/include/objsec.h
> +++ b/security/selinux/include/objsec.h
[ ... ]
> @@ -159,6 +163,13 @@ static inline struct file_security_struct *selinux_file(const struct file *file)
> return file->f_security + selinux_blob_sizes.lbs_file;
> }
>
> +static inline struct backing_file_security_struct *
> +selinux_backing_file(const struct file *backing_file)
> +{
> + void *blob = backing_file_security(backing_file);
> + return blob + selinux_blob_sizes.lbs_backing_file;
> +}
> +
[Severity: Low]
Is backing_file_security() defined anywhere? Similar to the LSM hook and blob
size additions, this appears to be missing from the tree and will likely
prevent compilation.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260622031509.2663919-3-caixinchen1@huawei.com?part=1
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [stable/linux-6.12.y 1/2] lsm: add backing_file LSM hooks
2026-06-22 3:15 ` [stable/linux-6.12.y 1/2] lsm: add backing_file LSM hooks Cai Xinchen
@ 2026-06-22 3:11 ` sashiko-bot
0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-06-22 3:11 UTC (permalink / raw)
To: Cai Xinchen; +Cc: bpf, paul
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Double-free and NULL pointer dereference in backing_file error path.
- [Low] Compiler warning due to pointer signedness mismatch in `lsm_blob_sizes`.
--
commit c06a83c8499b5a2cfb9d2bf689d02826faee9cb1
Author: Paul Moore <paul@paul-moore.com>
Subject: lsm: add backing_file LSM hooks
This commit adds a new LSM security blob to the backing_file struct and
introduces three new LSM hooks to manage its lifecycle and authorize mmap()
operations. These changes provide the necessary mechanisms for LSMs to
enforce access controls on mmap() and mprotect() for stacked filesystems.
> diff --git a/fs/file_table.c b/fs/file_table.c
> index 2a08bc93b0b9c..a771be30eb43d 100644
> --- a/fs/file_table.c
> +++ b/fs/file_table.c
[ ... ]
> @@ -289,7 +308,7 @@ struct file *alloc_empty_backing_file(int flags, const struct cred *cred)
>
> /* The f_mode flags must be set before fput(). */
> ff->file.f_mode |= FMODE_BACKING | FMODE_NOACCOUNT;
> - error = init_backing_file(ff);
> + error = init_backing_file(ff, user_file);
> if (unlikely(error)) {
> fput(&ff->file);
[Severity: High]
Does this fput() trigger a double-free on the error path?
FMODE_BACKING is set on the file before init_backing_file() is called.
If init_backing_file() fails (which delegates to
security_backing_file_alloc()), this fput() will eventually call
backing_file_free() and then security_backing_file_free().
However, security_backing_file_alloc() already cleans up after itself on
failure by calling security_backing_file_free() explicitly, leading to it
being called twice.
> return ERR_PTR(error);
> }
[ ... ]
> diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
> index 090d1d3e19fed..12b1576d5b027 100644
> --- a/include/linux/lsm_hooks.h
> +++ b/include/linux/lsm_hooks.h
> @@ -104,6 +104,7 @@ struct security_hook_list {
> struct lsm_blob_sizes {
> int lbs_cred;
> int lbs_file;
> + unsigned int lbs_backing_file;
[Severity: Low]
Does this cause a compiler warning due to a pointer signedness mismatch?
The other fields in struct lsm_blob_sizes are declared as int, but this
is unsigned int. When passed to lsm_set_blob_size() in security.c, it
expects an int pointer, which could break builds configured with -Werror.
> int lbs_ib;
> int lbs_inode;
[ ... ]
> diff --git a/security/security.c b/security/security.c
> index 6e4deac6ec073..aef71585d49ef 100644
> --- a/security/security.c
> +++ b/security/security.c
[ ... ]
> @@ -2893,6 +2925,57 @@ void security_file_free(struct file *file)
> }
> }
>
> +/**
> + * security_backing_file_alloc() - Allocate and setup a backing file blob
> + * @backing_file: the backing file
> + * @user_file: the associated user visible file
> + *
[ ... ]
> + */
> +int security_backing_file_alloc(struct file *backing_file,
> + const struct file *user_file)
> +{
> + int rc;
> +
> + rc = lsm_backing_file_alloc(backing_file);
> + if (rc)
> + return rc;
> + rc = call_int_hook(backing_file_alloc, backing_file, user_file);
> + if (unlikely(rc))
> + security_backing_file_free(backing_file);
[Severity: High]
This explicit cleanup on failure seems to conflict with the fput() in
alloc_empty_backing_file() mentioned above. Will this result in
security_backing_file_free() being called twice for the same file?
> +
> + return rc;
> +}
> +
> +/**
> + * security_backing_file_free() - Free a backing file blob
> + * @backing_file: the backing file
> + *
> + * Free any LSM state associate with a backing file's LSM blob, including the
> + * blob itself.
> + */
> +void security_backing_file_free(struct file *backing_file)
> +{
> + void *blob = backing_file_security(backing_file);
> +
> + call_void_hook(backing_file_free, backing_file);
[Severity: High]
Can this unconditionally trigger an invalid pointer dereference?
If security_backing_file_free() is called on an error path where the blob
allocation failed (or on the second call of a double-free), the security
blob will be NULL.
Calling the LSM free hooks before checking if the blob is NULL can cause
LSMs to dereference a NULL pointer when calculating their internal state
pointers.
> +
> + if (blob) {
> + backing_file_set_security(backing_file, NULL);
> + kmem_cache_free(lsm_backing_file_cache, blob);
> + }
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260622031509.2663919-2-caixinchen1@huawei.com?part=1
^ permalink raw reply [flat|nested] 6+ messages in thread
* [stable/linux-6.12.y 0/2] Backport Fix incorrect overlayfs mmap() and mprotect() LSM access controls
@ 2026-06-22 3:15 Cai Xinchen
2026-06-22 3:15 ` [stable/linux-6.12.y 1/2] lsm: add backing_file LSM hooks Cai Xinchen
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Cai Xinchen @ 2026-06-22 3:15 UTC (permalink / raw)
To: viro, brauner, jack, miklos, amir73il, paul, jmorris, serge,
stephen.smalley.work, omosnace, gregkh, bboscaccy
Cc: linux-fsdevel, linux-kernel, linux-unionfs, linux-security-module,
selinux, bpf, lujialin4
Backport the patch series
"Fix incorrect overlayfs mmap() and mprotect() LSM access controls" [1]
to 6.12 lts
I test selinux-testsuite[2] overlay test, it pass 135 tests.
[1] https://lore.kernel.org/all/20260403030848.731867-5-paul@paul-moore.com/
[2] https://github.com/SELinuxProject/selinux-testsuite
Paul Moore (2):
lsm: add backing_file LSM hooks
selinux: fix overlayfs mmap() and mprotect() access checks
fs/backing-file.c | 18 ++-
fs/file_table.c | 27 +++-
fs/fuse/passthrough.c | 2 +-
fs/internal.h | 3 +-
fs/overlayfs/dir.c | 2 +-
fs/overlayfs/file.c | 3 +-
include/linux/backing-file.h | 4 +-
include/linux/fs.h | 13 ++
include/linux/lsm_audit.h | 2 +-
include/linux/lsm_hook_defs.h | 5 +
include/linux/lsm_hooks.h | 1 +
include/linux/security.h | 22 +++
security/security.c | 109 ++++++++++++++
security/selinux/hooks.c | 242 ++++++++++++++++++++++--------
security/selinux/include/objsec.h | 11 ++
15 files changed, 384 insertions(+), 80 deletions(-)
--
2.18.0.huawei.25
^ permalink raw reply [flat|nested] 6+ messages in thread
* [stable/linux-6.12.y 1/2] lsm: add backing_file LSM hooks
2026-06-22 3:15 [stable/linux-6.12.y 0/2] Backport Fix incorrect overlayfs mmap() and mprotect() LSM access controls Cai Xinchen
@ 2026-06-22 3:15 ` Cai Xinchen
2026-06-22 3:11 ` sashiko-bot
2026-06-22 3:15 ` [stable/linux-6.12.y 2/2] selinux: fix overlayfs mmap() and mprotect() access checks Cai Xinchen
2026-06-25 11:31 ` [stable/linux-6.12.y 0/2] Backport Fix incorrect overlayfs mmap() and mprotect() LSM access controls Greg KH
2 siblings, 1 reply; 6+ messages in thread
From: Cai Xinchen @ 2026-06-22 3:15 UTC (permalink / raw)
To: viro, brauner, jack, miklos, amir73il, paul, jmorris, serge,
stephen.smalley.work, omosnace, gregkh, bboscaccy
Cc: linux-fsdevel, linux-kernel, linux-unionfs, linux-security-module,
selinux, bpf, lujialin4
From: Paul Moore <paul@paul-moore.com>
Mainline declares lsm_backing_file_cache in security/lsm.h. Linux 6.12.y
does not have security/lsm_init.c or security/lsm.h; the cache variable
is defined locally as static struct kmem_cache *lsm_backing_file_cache in
security/security.c.
Original commit message:
Stacked filesystems such as overlayfs do not currently provide the
necessary mechanisms for LSMs to properly enforce access controls on the
mmap() and mprotect() operations. In order to resolve this gap, a LSM
security blob is being added to the backing_file struct and the following
new LSM hooks are being created:
security_backing_file_alloc()
security_backing_file_free()
security_mmap_backing_file()
The first two hooks are to manage the lifecycle of the LSM security blob
in the backing_file struct, while the third provides a new mmap() access
control point for the underlying backing file. It is also expected that
LSMs will likely want to update their security_file_mprotect() callback
to address issues with their mprotect() controls, but that does not
require a change to the security_file_mprotect() LSM hook.
There are a three other small changes to support these new LSM hooks:
* Pass the user file associated with a backing file down to
alloc_empty_backing_file() so it can be included in the
security_backing_file_alloc() hook.
* Add getter and setter functions for the backing_file struct LSM blob
as the backing_file struct remains private to fs/file_table.c.
* Constify the file struct field in the LSM common_audit_data struct to
better support LSMs that need to pass a const file struct pointer into
the common LSM audit code.
Thanks to Arnd Bergmann for identifying the missing EXPORT_SYMBOL_GPL()
and supplying a fixup.
Cc: stable@vger.kernel.org
Cc: linux-fsdevel@vger.kernel.org
Cc: linux-unionfs@vger.kernel.org
Cc: linux-erofs@lists.ozlabs.org
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Reviewed-by: Serge Hallyn <serge@hallyn.com>
Reviewed-by: Christian Brauner <brauner@kernel.org>
Signed-off-by: Paul Moore <paul@paul-moore.com>
Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
fs/backing-file.c | 18 ++++--
fs/file_table.c | 27 +++++++--
fs/fuse/passthrough.c | 2 +-
fs/internal.h | 3 +-
fs/overlayfs/dir.c | 2 +-
fs/overlayfs/file.c | 3 +-
include/linux/backing-file.h | 4 +-
include/linux/fs.h | 13 ++++
include/linux/lsm_audit.h | 2 +-
include/linux/lsm_hook_defs.h | 5 ++
include/linux/lsm_hooks.h | 1 +
include/linux/security.h | 22 +++++++
security/security.c | 109 ++++++++++++++++++++++++++++++++++
13 files changed, 195 insertions(+), 16 deletions(-)
diff --git a/fs/backing-file.c b/fs/backing-file.c
index 09a9be945d45..389cfe3ed509 100644
--- a/fs/backing-file.c
+++ b/fs/backing-file.c
@@ -12,6 +12,7 @@
#include <linux/backing-file.h>
#include <linux/splice.h>
#include <linux/mm.h>
+#include <linux/security.h>
#include "internal.h"
@@ -29,14 +30,15 @@
* returned file into a container structure that also stores the stacked
* file's path, which can be retrieved using backing_file_user_path().
*/
-struct file *backing_file_open(const struct path *user_path, int flags,
+struct file *backing_file_open(const struct file *user_file, int flags,
const struct path *real_path,
const struct cred *cred)
{
+ const struct path *user_path = &user_file->f_path;
struct file *f;
int error;
- f = alloc_empty_backing_file(flags, cred);
+ f = alloc_empty_backing_file(flags, cred, user_file);
if (IS_ERR(f))
return f;
@@ -52,15 +54,16 @@ struct file *backing_file_open(const struct path *user_path, int flags,
}
EXPORT_SYMBOL_GPL(backing_file_open);
-struct file *backing_tmpfile_open(const struct path *user_path, int flags,
+struct file *backing_tmpfile_open(const struct file *user_file, int flags,
const struct path *real_parentpath,
umode_t mode, const struct cred *cred)
{
struct mnt_idmap *real_idmap = mnt_idmap(real_parentpath->mnt);
+ const struct path *user_path = &user_file->f_path;
struct file *f;
int error;
- f = alloc_empty_backing_file(flags, cred);
+ f = alloc_empty_backing_file(flags, cred, user_file);
if (IS_ERR(f))
return f;
@@ -326,6 +329,7 @@ EXPORT_SYMBOL_GPL(backing_file_splice_write);
int backing_file_mmap(struct file *file, struct vm_area_struct *vma,
struct backing_file_ctx *ctx)
{
+ struct file *user_file = vma->vm_file;
const struct cred *old_cred;
int ret;
@@ -339,6 +343,12 @@ int backing_file_mmap(struct file *file, struct vm_area_struct *vma,
vma_set_file(vma, file);
old_cred = override_creds(ctx->cred);
+ ret = security_mmap_backing_file(vma, file, user_file);
+ if (ret) {
+ revert_creds(old_cred);
+ return ret;
+ }
+
ret = call_mmap(vma->vm_file, vma);
revert_creds(old_cred);
diff --git a/fs/file_table.c b/fs/file_table.c
index 2a08bc93b0b9..a771be30eb43 100644
--- a/fs/file_table.c
+++ b/fs/file_table.c
@@ -46,6 +46,9 @@ static struct percpu_counter nr_files __cacheline_aligned_in_smp;
struct backing_file {
struct file file;
struct path user_path;
+#ifdef CONFIG_SECURITY
+ void *security;
+#endif
};
static inline struct backing_file *backing_file(struct file *f)
@@ -59,8 +62,21 @@ struct path *backing_file_user_path(struct file *f)
}
EXPORT_SYMBOL_GPL(backing_file_user_path);
+#ifdef CONFIG_SECURITY
+void *backing_file_security(const struct file *f)
+{
+ return backing_file(f)->security;
+}
+
+void backing_file_set_security(struct file *f, void *security)
+{
+ backing_file(f)->security = security;
+}
+#endif /* CONFIG_SECURITY */
+
static inline void backing_file_free(struct backing_file *ff)
{
+ security_backing_file_free(&ff->file);
path_put(&ff->user_path);
kfree(ff);
}
@@ -259,10 +275,12 @@ struct file *alloc_empty_file_noaccount(int flags, const struct cred *cred)
return f;
}
-static int init_backing_file(struct backing_file *ff)
+static int init_backing_file(struct backing_file *ff,
+ const struct file *user_file)
{
memset(&ff->user_path, 0, sizeof(ff->user_path));
- return 0;
+ backing_file_set_security(&ff->file, NULL);
+ return security_backing_file_alloc(&ff->file, user_file);
}
/*
@@ -272,7 +290,8 @@ static int init_backing_file(struct backing_file *ff)
* This is only for kernel internal use, and the allocate file must not be
* installed into file tables or such.
*/
-struct file *alloc_empty_backing_file(int flags, const struct cred *cred)
+struct file *alloc_empty_backing_file(int flags, const struct cred *cred,
+ const struct file *user_file)
{
struct backing_file *ff;
int error;
@@ -289,7 +308,7 @@ struct file *alloc_empty_backing_file(int flags, const struct cred *cred)
/* The f_mode flags must be set before fput(). */
ff->file.f_mode |= FMODE_BACKING | FMODE_NOACCOUNT;
- error = init_backing_file(ff);
+ error = init_backing_file(ff, user_file);
if (unlikely(error)) {
fput(&ff->file);
return ERR_PTR(error);
diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
index 6bfd09dda9e3..140e150be0de 100644
--- a/fs/fuse/passthrough.c
+++ b/fs/fuse/passthrough.c
@@ -326,7 +326,7 @@ struct fuse_backing *fuse_passthrough_open(struct file *file,
goto out;
/* Allocate backing file per fuse file to store fuse path */
- backing_file = backing_file_open(&file->f_path, file->f_flags,
+ backing_file = backing_file_open(file, file->f_flags,
&fb->file->f_path, fb->cred);
err = PTR_ERR(backing_file);
if (IS_ERR(backing_file)) {
diff --git a/fs/internal.h b/fs/internal.h
index 8c1b7acbbe8f..89a15e0f2f1b 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -99,7 +99,8 @@ extern void chroot_fs_refs(const struct path *, const struct path *);
*/
struct file *alloc_empty_file(int flags, const struct cred *cred);
struct file *alloc_empty_file_noaccount(int flags, const struct cred *cred);
-struct file *alloc_empty_backing_file(int flags, const struct cred *cred);
+struct file *alloc_empty_backing_file(int flags, const struct cred *cred,
+ const struct file *user_file);
static inline void file_put_write_access(struct file *file)
{
diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
index ab65e98a1def..1c8009bf194b 100644
--- a/fs/overlayfs/dir.c
+++ b/fs/overlayfs/dir.c
@@ -1320,7 +1320,7 @@ static int ovl_create_tmpfile(struct file *file, struct dentry *dentry,
goto out_revert_creds;
ovl_path_upper(dentry->d_parent, &realparentpath);
- realfile = backing_tmpfile_open(&file->f_path, flags, &realparentpath,
+ realfile = backing_tmpfile_open(file, flags, &realparentpath,
mode, current_cred());
err = PTR_ERR_OR_ZERO(realfile);
pr_debug("tmpfile/open(%pd2, 0%o) = %i\n", realparentpath.dentry, mode, err);
diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c
index 94095058da34..3765e1defa19 100644
--- a/fs/overlayfs/file.c
+++ b/fs/overlayfs/file.c
@@ -47,8 +47,7 @@ static struct file *ovl_open_realfile(const struct file *file,
} else {
if (!inode_owner_or_capable(real_idmap, realinode))
flags &= ~O_NOATIME;
-
- realfile = backing_file_open(file_user_path((struct file *) file),
+ realfile = backing_file_open(file,
flags, realpath, current_cred());
}
revert_creds(old_cred);
diff --git a/include/linux/backing-file.h b/include/linux/backing-file.h
index 2eed0ffb5e8f..cd18acd7ac5b 100644
--- a/include/linux/backing-file.h
+++ b/include/linux/backing-file.h
@@ -19,10 +19,10 @@ struct backing_file_ctx {
void (*end_write)(struct file *, loff_t, ssize_t);
};
-struct file *backing_file_open(const struct path *user_path, int flags,
+struct file *backing_file_open(const struct file *user_file, int flags,
const struct path *real_path,
const struct cred *cred);
-struct file *backing_tmpfile_open(const struct path *user_path, int flags,
+struct file *backing_tmpfile_open(const struct file *user_file, int flags,
const struct path *real_parentpath,
umode_t mode, const struct cred *cred);
ssize_t backing_file_read_iter(struct file *file, struct iov_iter *iter,
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 87720e1b5419..d06c2d829877 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2740,6 +2740,19 @@ struct file *dentry_create(const struct path *path, int flags, umode_t mode,
const struct cred *cred);
struct path *backing_file_user_path(struct file *f);
+#ifdef CONFIG_SECURITY
+void *backing_file_security(const struct file *f);
+void backing_file_set_security(struct file *f, void *security);
+#else
+static inline void *backing_file_security(const struct file *f)
+{
+ return NULL;
+}
+static inline void backing_file_set_security(struct file *f, void *security)
+{
+}
+#endif /* CONFIG_SECURITY */
+
/*
* When mmapping a file on a stackable filesystem (e.g., overlayfs), the file
* stored in ->vm_file is a backing file whose f_inode is on the underlying
diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h
index 97a8b21eb033..c0a2839253fa 100644
--- a/include/linux/lsm_audit.h
+++ b/include/linux/lsm_audit.h
@@ -93,7 +93,7 @@ struct common_audit_data {
#endif
char *kmod_name;
struct lsm_ioctlop_audit *op;
- struct file *file;
+ const struct file *file;
struct lsm_ibpkey_audit *ibpkey;
struct lsm_ibendport_audit *ibendport;
int reason;
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 9eca013aa5e1..addb34abffa1 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -188,6 +188,9 @@ LSM_HOOK(int, 0, file_permission, struct file *file, int mask)
LSM_HOOK(int, 0, file_alloc_security, struct file *file)
LSM_HOOK(void, LSM_RET_VOID, file_release, struct file *file)
LSM_HOOK(void, LSM_RET_VOID, file_free_security, struct file *file)
+LSM_HOOK(int, 0, backing_file_alloc, struct file *backing_file,
+ const struct file *user_file)
+LSM_HOOK(void, LSM_RET_VOID, backing_file_free, struct file *backing_file)
LSM_HOOK(int, 0, file_ioctl, struct file *file, unsigned int cmd,
unsigned long arg)
LSM_HOOK(int, 0, file_ioctl_compat, struct file *file, unsigned int cmd,
@@ -195,6 +198,8 @@ LSM_HOOK(int, 0, file_ioctl_compat, struct file *file, unsigned int cmd,
LSM_HOOK(int, 0, mmap_addr, unsigned long addr)
LSM_HOOK(int, 0, mmap_file, struct file *file, unsigned long reqprot,
unsigned long prot, unsigned long flags)
+LSM_HOOK(int, 0, mmap_backing_file, struct vm_area_struct *vma,
+ struct file *backing_file, struct file *user_file)
LSM_HOOK(int, 0, file_mprotect, struct vm_area_struct *vma,
unsigned long reqprot, unsigned long prot)
LSM_HOOK(int, 0, file_lock, struct file *file, unsigned int cmd)
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index 090d1d3e19fe..12b1576d5b02 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -104,6 +104,7 @@ struct security_hook_list {
struct lsm_blob_sizes {
int lbs_cred;
int lbs_file;
+ unsigned int lbs_backing_file;
int lbs_ib;
int lbs_inode;
int lbs_sock;
diff --git a/include/linux/security.h b/include/linux/security.h
index 2c6db949ad1a..e4300f2ff11b 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -421,11 +421,17 @@ int security_file_permission(struct file *file, int mask);
int security_file_alloc(struct file *file);
void security_file_release(struct file *file);
void security_file_free(struct file *file);
+int security_backing_file_alloc(struct file *backing_file,
+ const struct file *user_file);
+void security_backing_file_free(struct file *backing_file);
int security_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
int security_file_ioctl_compat(struct file *file, unsigned int cmd,
unsigned long arg);
int security_mmap_file(struct file *file, unsigned long prot,
unsigned long flags);
+int security_mmap_backing_file(struct vm_area_struct *vma,
+ struct file *backing_file,
+ struct file *user_file);
int security_mmap_addr(unsigned long addr);
int security_file_mprotect(struct vm_area_struct *vma, unsigned long reqprot,
unsigned long prot);
@@ -1065,6 +1071,15 @@ static inline void security_file_release(struct file *file)
static inline void security_file_free(struct file *file)
{ }
+static inline int security_backing_file_alloc(struct file *backing_file,
+ const struct file *user_file)
+{
+ return 0;
+}
+
+static inline void security_backing_file_free(struct file *backing_file)
+{ }
+
static inline int security_file_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
@@ -1084,6 +1099,13 @@ static inline int security_mmap_file(struct file *file, unsigned long prot,
return 0;
}
+static inline int security_mmap_backing_file(struct vm_area_struct *vma,
+ struct file *backing_file,
+ struct file *user_file)
+{
+ return 0;
+}
+
static inline int security_mmap_addr(unsigned long addr)
{
return cap_mmap_addr(addr);
diff --git a/security/security.c b/security/security.c
index 6e4deac6ec07..aef71585d49e 100644
--- a/security/security.c
+++ b/security/security.c
@@ -95,6 +95,7 @@ const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX + 1] = {
static BLOCKING_NOTIFIER_HEAD(blocking_lsm_notifier_chain);
static struct kmem_cache *lsm_file_cache;
+struct kmem_cache *lsm_backing_file_cache;
static struct kmem_cache *lsm_inode_cache;
char *lsm_names;
@@ -266,6 +267,7 @@ static void __init lsm_set_blob_sizes(struct lsm_blob_sizes *needed)
lsm_set_blob_size(&needed->lbs_cred, &blob_sizes.lbs_cred);
lsm_set_blob_size(&needed->lbs_file, &blob_sizes.lbs_file);
+ lsm_set_blob_size(&needed->lbs_backing_file, &blob_sizes.lbs_backing_file);
lsm_set_blob_size(&needed->lbs_ib, &blob_sizes.lbs_ib);
/*
* The inode blob gets an rcu_head in addition to
@@ -468,6 +470,7 @@ static void __init ordered_lsm_init(void)
init_debug("cred blob size = %d\n", blob_sizes.lbs_cred);
init_debug("file blob size = %d\n", blob_sizes.lbs_file);
+ init_debug("lsm_backing_file_cache = %d\n", blob_sizes.lbs_backing_file);
init_debug("ib blob size = %d\n", blob_sizes.lbs_ib);
init_debug("inode blob size = %d\n", blob_sizes.lbs_inode);
init_debug("ipc blob size = %d\n", blob_sizes.lbs_ipc);
@@ -490,6 +493,11 @@ static void __init ordered_lsm_init(void)
lsm_file_cache = kmem_cache_create("lsm_file_cache",
blob_sizes.lbs_file, 0,
SLAB_PANIC, NULL);
+ if (blob_sizes.lbs_backing_file)
+ lsm_backing_file_cache = kmem_cache_create(
+ "lsm_backing_file_cache",
+ blob_sizes.lbs_backing_file,
+ 0, SLAB_PANIC, NULL);
if (blob_sizes.lbs_inode)
lsm_inode_cache = kmem_cache_create("lsm_inode_cache",
blob_sizes.lbs_inode, 0,
@@ -666,6 +674,30 @@ int unregister_blocking_lsm_notifier(struct notifier_block *nb)
}
EXPORT_SYMBOL(unregister_blocking_lsm_notifier);
+/**
+ * lsm_backing_file_alloc - allocate a composite backing file blob
+ * @backing_file: the backing file
+ *
+ * Allocate the backing file blob for all the modules.
+ *
+ * Returns 0, or -ENOMEM if memory can't be allocated.
+ */
+static int lsm_backing_file_alloc(struct file *backing_file)
+{
+ void *blob;
+
+ if (!lsm_backing_file_cache) {
+ backing_file_set_security(backing_file, NULL);
+ return 0;
+ }
+
+ blob = kmem_cache_zalloc(lsm_backing_file_cache, GFP_KERNEL);
+ backing_file_set_security(backing_file, blob);
+ if (!blob)
+ return -ENOMEM;
+ return 0;
+}
+
/**
* lsm_blob_alloc - allocate a composite blob
* @dest: the destination for the blob
@@ -2893,6 +2925,57 @@ void security_file_free(struct file *file)
}
}
+/**
+ * security_backing_file_alloc() - Allocate and setup a backing file blob
+ * @backing_file: the backing file
+ * @user_file: the associated user visible file
+ *
+ * Allocate a backing file LSM blob and perform any necessary initialization of
+ * the LSM blob. There will be some operations where the LSM will not have
+ * access to @user_file after this point, so any important state associated
+ * with @user_file that is important to the LSM should be captured in the
+ * backing file's LSM blob.
+ *
+ * LSM's should avoid taking a reference to @user_file in this hook as it will
+ * result in problems later when the system attempts to drop/put the file
+ * references due to a circular dependency.
+ *
+ * Return: Return 0 if the hook is successful, negative values otherwise.
+ */
+int security_backing_file_alloc(struct file *backing_file,
+ const struct file *user_file)
+{
+ int rc;
+
+ rc = lsm_backing_file_alloc(backing_file);
+ if (rc)
+ return rc;
+ rc = call_int_hook(backing_file_alloc, backing_file, user_file);
+ if (unlikely(rc))
+ security_backing_file_free(backing_file);
+
+ return rc;
+}
+
+/**
+ * security_backing_file_free() - Free a backing file blob
+ * @backing_file: the backing file
+ *
+ * Free any LSM state associate with a backing file's LSM blob, including the
+ * blob itself.
+ */
+void security_backing_file_free(struct file *backing_file)
+{
+ void *blob = backing_file_security(backing_file);
+
+ call_void_hook(backing_file_free, backing_file);
+
+ if (blob) {
+ backing_file_set_security(backing_file, NULL);
+ kmem_cache_free(lsm_backing_file_cache, blob);
+ }
+}
+
/**
* security_file_ioctl() - Check if an ioctl is allowed
* @file: associated file
@@ -2981,6 +3064,32 @@ int security_mmap_file(struct file *file, unsigned long prot,
flags);
}
+/**
+ * security_mmap_backing_file - Check if mmap'ing a backing file is allowed
+ * @vma: the vm_area_struct for the mmap'd region
+ * @backing_file: the backing file being mmap'd
+ * @user_file: the user file being mmap'd
+ *
+ * Check permissions for a mmap operation on a stacked filesystem. This hook
+ * is called after the security_mmap_file() and is responsible for authorizing
+ * the mmap on @backing_file. It is important to note that the mmap operation
+ * on @user_file has already been authorized and the @vma->vm_file has been
+ * set to @backing_file.
+ *
+ * Return: Returns 0 if permission is granted.
+ */
+int security_mmap_backing_file(struct vm_area_struct *vma,
+ struct file *backing_file,
+ struct file *user_file)
+{
+ /* recommended by the stackable filesystem devs */
+ if (WARN_ON_ONCE(!(backing_file->f_mode & FMODE_BACKING)))
+ return -EIO;
+
+ return call_int_hook(mmap_backing_file, vma, backing_file, user_file);
+}
+EXPORT_SYMBOL_GPL(security_mmap_backing_file);
+
/**
* security_mmap_addr() - Check if mmap'ing an address is allowed
* @addr: address
--
2.18.0.huawei.25
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [stable/linux-6.12.y 2/2] selinux: fix overlayfs mmap() and mprotect() access checks
2026-06-22 3:15 [stable/linux-6.12.y 0/2] Backport Fix incorrect overlayfs mmap() and mprotect() LSM access controls Cai Xinchen
2026-06-22 3:15 ` [stable/linux-6.12.y 1/2] lsm: add backing_file LSM hooks Cai Xinchen
@ 2026-06-22 3:15 ` Cai Xinchen
2026-06-22 3:04 ` sashiko-bot
2026-06-25 11:31 ` [stable/linux-6.12.y 0/2] Backport Fix incorrect overlayfs mmap() and mprotect() LSM access controls Greg KH
2 siblings, 1 reply; 6+ messages in thread
From: Cai Xinchen @ 2026-06-22 3:15 UTC (permalink / raw)
To: viro, brauner, jack, miklos, amir73il, paul, jmorris, serge,
stephen.smalley.work, omosnace, gregkh, bboscaccy
Cc: linux-fsdevel, linux-kernel, linux-unionfs, linux-security-module,
selinux, bpf, lujialin4
From: Paul Moore <paul@paul-moore.com>
The existing SELinux security model for overlayfs is to allow access if
the current task is able to access the top level file (the "user" file)
and the mounter's credentials are sufficient to access the lower
level file (the "backing" file). Unfortunately, the current code does
not properly enforce these access controls for both mmap() and mprotect()
operations on overlayfs filesystems.
This patch makes use of the newly created security_mmap_backing_file()
LSM hook to provide the missing backing file enforcement for mmap()
operations, and leverages the backing file API and new LSM blob to
provide the necessary information to properly enforce the mprotect()
access controls.
Cc: stable@vger.kernel.org
Acked-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
security/selinux/hooks.c | 242 ++++++++++++++++++++++--------
security/selinux/include/objsec.h | 11 ++
2 files changed, 189 insertions(+), 64 deletions(-)
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 8e31d3b60fc6..1b89c8d5fa2f 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -1724,49 +1724,72 @@ static inline int file_path_has_perm(const struct cred *cred,
static int bpf_fd_pass(const struct file *file, u32 sid);
#endif
-/* Check whether a task can use an open file descriptor to
- access an inode in a given way. Check access to the
- descriptor itself, and then use dentry_has_perm to
- check a particular permission to the file.
- Access to the descriptor is implicitly granted if it
- has the same SID as the process. If av is zero, then
- access to the file is not checked, e.g. for cases
- where only the descriptor is affected like seek. */
-static int file_has_perm(const struct cred *cred,
- struct file *file,
- u32 av)
+static int __file_has_perm(const struct cred *cred, const struct file *file,
+ u32 av, bool bf_user_file)
+
{
- struct file_security_struct *fsec = selinux_file(file);
- struct inode *inode = file_inode(file);
struct common_audit_data ad;
- u32 sid = cred_sid(cred);
+ struct inode *inode;
+ u32 ssid = cred_sid(cred);
+ u32 tsid_fd;
int rc;
- ad.type = LSM_AUDIT_DATA_FILE;
- ad.u.file = file;
+ if (bf_user_file) {
+ struct backing_file_security_struct *bfsec;
+ const struct path *path;
- if (sid != fsec->sid) {
- rc = avc_has_perm(sid, fsec->sid,
- SECCLASS_FD,
- FD__USE,
- &ad);
+ if (WARN_ON(!(file->f_mode & FMODE_BACKING)))
+ return -EIO;
+
+ bfsec = selinux_backing_file(file);
+ path = backing_file_user_path(file);
+ tsid_fd = bfsec->uf_sid;
+ inode = d_inode(path->dentry);
+
+ ad.type = LSM_AUDIT_DATA_PATH;
+ ad.u.path = *path;
+ } else {
+ struct file_security_struct *fsec = selinux_file(file);
+
+ tsid_fd = fsec->sid;
+ inode = file_inode(file);
+
+ ad.type = LSM_AUDIT_DATA_FILE;
+ ad.u.file = file;
+ }
+
+ if (ssid != tsid_fd) {
+ rc = avc_has_perm(ssid, tsid_fd, SECCLASS_FD, FD__USE, &ad);
if (rc)
- goto out;
+ return rc;
}
#ifdef CONFIG_BPF_SYSCALL
- rc = bpf_fd_pass(file, cred_sid(cred));
+ /* regardless of backing vs user file, use the underlying file here */
+ rc = bpf_fd_pass(file, ssid);
if (rc)
return rc;
#endif
/* av is zero if only checking access to the descriptor. */
- rc = 0;
if (av)
- rc = inode_has_perm(cred, inode, av, &ad);
+ return inode_has_perm(cred, inode, av, &ad);
-out:
- return rc;
+ return 0;
+}
+
+/* Check whether a task can use an open file descriptor to
+ access an inode in a given way. Check access to the
+ descriptor itself, and then use dentry_has_perm to
+ check a particular permission to the file.
+ Access to the descriptor is implicitly granted if it
+ has the same SID as the process. If av is zero, then
+ access to the file is not checked, e.g. for cases
+ where only the descriptor is affected like seek. */
+static inline int file_has_perm(const struct cred *cred,
+ const struct file *file, u32 av)
+{
+ return __file_has_perm(cred, file, av, false);
}
/*
@@ -3653,6 +3676,17 @@ static int selinux_file_alloc_security(struct file *file)
return 0;
}
+static int selinux_backing_file_alloc(struct file *backing_file,
+ const struct file *user_file)
+{
+ struct backing_file_security_struct *bfsec;
+
+ bfsec = selinux_backing_file(backing_file);
+ bfsec->uf_sid = selinux_file(user_file)->sid;
+
+ return 0;
+}
+
/*
* Check whether a task has the ioctl permission and cmd
* operation to an inode.
@@ -3770,42 +3804,55 @@ static int selinux_file_ioctl_compat(struct file *file, unsigned int cmd,
static int default_noexec __ro_after_init;
-static int file_map_prot_check(struct file *file, unsigned long prot, int shared)
+static int __file_map_prot_check(const struct cred *cred,
+ const struct file *file, unsigned long prot,
+ bool shared, bool bf_user_file)
{
- const struct cred *cred = current_cred();
- u32 sid = cred_sid(cred);
- int rc = 0;
+ struct inode *inode = NULL;
+ bool prot_exec = prot & PROT_EXEC;
+ bool prot_write = prot & PROT_WRITE;
+
+ if (file) {
+ if (bf_user_file)
+ inode = d_inode(backing_file_user_path(file)->dentry);
+ else
+ inode = file_inode(file);
+ }
+
+ if (default_noexec && prot_exec &&
+ (!file || IS_PRIVATE(inode) || (!shared && prot_write))) {
+ int rc;
+ u32 sid = cred_sid(cred);
- if (default_noexec &&
- (prot & PROT_EXEC) && (!file || IS_PRIVATE(file_inode(file)) ||
- (!shared && (prot & PROT_WRITE)))) {
/*
- * We are making executable an anonymous mapping or a
- * private file mapping that will also be writable.
- * This has an additional check.
+ * We are making executable an anonymous mapping or a private
+ * file mapping that will also be writable.
*/
- rc = avc_has_perm(sid, sid, SECCLASS_PROCESS,
- PROCESS__EXECMEM, NULL);
+ rc = avc_has_perm(sid, sid, SECCLASS_PROCESS, PROCESS__EXECMEM,
+ NULL);
if (rc)
- goto error;
+ return rc;
}
if (file) {
- /* read access is always possible with a mapping */
+ /* "read" always possible, "write" only if shared */
u32 av = FILE__READ;
-
- /* write access only matters if the mapping is shared */
- if (shared && (prot & PROT_WRITE))
+ if (shared && prot_write)
av |= FILE__WRITE;
-
- if (prot & PROT_EXEC)
+ if (prot_exec)
av |= FILE__EXECUTE;
- return file_has_perm(cred, file, av);
+ return __file_has_perm(cred, file, av, bf_user_file);
}
-error:
- return rc;
+ return 0;
+}
+
+static inline int file_map_prot_check(const struct cred *cred,
+ const struct file *file,
+ unsigned long prot, bool shared)
+{
+ return __file_map_prot_check(cred, file, prot, shared, false);
}
static int selinux_mmap_addr(unsigned long addr)
@@ -3821,36 +3868,80 @@ static int selinux_mmap_addr(unsigned long addr)
return rc;
}
-static int selinux_mmap_file(struct file *file,
- unsigned long reqprot __always_unused,
- unsigned long prot, unsigned long flags)
+static int selinux_mmap_file_common(const struct cred *cred, struct file *file,
+ unsigned long prot, bool shared)
{
- struct common_audit_data ad;
- int rc;
-
if (file) {
+ int rc;
+ struct common_audit_data ad;
+
ad.type = LSM_AUDIT_DATA_FILE;
ad.u.file = file;
- rc = inode_has_perm(current_cred(), file_inode(file),
- FILE__MAP, &ad);
+ rc = inode_has_perm(cred, file_inode(file), FILE__MAP, &ad);
if (rc)
return rc;
}
- return file_map_prot_check(file, prot,
- (flags & MAP_TYPE) == MAP_SHARED);
+ return file_map_prot_check(cred, file, prot, shared);
+}
+
+static int selinux_mmap_file(struct file *file,
+ unsigned long reqprot __always_unused,
+ unsigned long prot, unsigned long flags)
+{
+ return selinux_mmap_file_common(current_cred(), file, prot,
+ (flags & MAP_TYPE) == MAP_SHARED);
+}
+
+/**
+ * selinux_mmap_backing_file - Check mmap permissions on a backing file
+ * @vma: memory region
+ * @backing_file: stacked filesystem backing file
+ * @user_file: user visible file
+ *
+ * This is called after selinux_mmap_file() on stacked filesystems, and it
+ * is this function's responsibility to verify access to @backing_file and
+ * setup the SELinux state for possible later use in the mprotect() code path.
+ *
+ * By the time this function is called, mmap() access to @user_file has already
+ * been authorized and @vma->vm_file has been set to point to @backing_file.
+ *
+ * Return zero on success, negative values otherwise.
+ */
+static int selinux_mmap_backing_file(struct vm_area_struct *vma,
+ struct file *backing_file,
+ struct file *user_file __always_unused)
+{
+ unsigned long prot = 0;
+
+ /* translate vma->vm_flags perms into PROT perms */
+ if (vma->vm_flags & VM_READ)
+ prot |= PROT_READ;
+ if (vma->vm_flags & VM_WRITE)
+ prot |= PROT_WRITE;
+ if (vma->vm_flags & VM_EXEC)
+ prot |= PROT_EXEC;
+
+ return selinux_mmap_file_common(backing_file->f_cred, backing_file,
+ prot, vma->vm_flags & VM_SHARED);
}
static int selinux_file_mprotect(struct vm_area_struct *vma,
unsigned long reqprot __always_unused,
unsigned long prot)
{
+ int rc;
const struct cred *cred = current_cred();
u32 sid = cred_sid(cred);
+ const struct file *file = vma->vm_file;
+ bool backing_file;
+ bool shared = vma->vm_flags & VM_SHARED;
+
+ /* check if we need to trigger the "backing files are awful" mode */
+ backing_file = file && (file->f_mode & FMODE_BACKING);
if (default_noexec &&
(prot & PROT_EXEC) && !(vma->vm_flags & VM_EXEC)) {
- int rc = 0;
/*
* We don't use the vma_is_initial_heap() helper as it has
* a history of problems and is currently broken on systems
@@ -3864,11 +3955,15 @@ static int selinux_file_mprotect(struct vm_area_struct *vma,
vma->vm_end <= vma->vm_mm->brk) {
rc = avc_has_perm(sid, sid, SECCLASS_PROCESS,
PROCESS__EXECHEAP, NULL);
- } else if (!vma->vm_file && (vma_is_initial_stack(vma) ||
+ if (rc)
+ return rc;
+ } else if (!file && (vma_is_initial_stack(vma) ||
vma_is_stack_for_current(vma))) {
rc = avc_has_perm(sid, sid, SECCLASS_PROCESS,
PROCESS__EXECSTACK, NULL);
- } else if (vma->vm_file && vma->anon_vma) {
+ if (rc)
+ return rc;
+ } else if (file && vma->anon_vma) {
/*
* We are making executable a file mapping that has
* had some COW done. Since pages might have been
@@ -3876,13 +3971,29 @@ static int selinux_file_mprotect(struct vm_area_struct *vma,
* modified content. This typically should only
* occur for text relocations.
*/
- rc = file_has_perm(cred, vma->vm_file, FILE__EXECMOD);
+ rc = __file_has_perm(cred, file, FILE__EXECMOD,
+ backing_file);
+ if (rc)
+ return rc;
+ if (backing_file) {
+ rc = file_has_perm(file->f_cred, file,
+ FILE__EXECMOD);
+ if (rc)
+ return rc;
+ }
}
+ }
+
+ rc = __file_map_prot_check(cred, file, prot, shared, backing_file);
+ if (rc)
+ return rc;
+ if (backing_file) {
+ rc = file_map_prot_check(file->f_cred, file, prot, shared);
if (rc)
return rc;
}
- return file_map_prot_check(vma->vm_file, prot, vma->vm_flags&VM_SHARED);
+ return 0;
}
static int selinux_file_lock(struct file *file, unsigned int cmd)
@@ -6960,6 +7071,7 @@ static void selinux_bpf_token_free(struct bpf_token *token)
struct lsm_blob_sizes selinux_blob_sizes __ro_after_init = {
.lbs_cred = sizeof(struct task_security_struct),
.lbs_file = sizeof(struct file_security_struct),
+ .lbs_backing_file = sizeof(struct backing_file_security_struct),
.lbs_inode = sizeof(struct inode_security_struct),
.lbs_ipc = sizeof(struct ipc_security_struct),
.lbs_key = sizeof(struct key_security_struct),
@@ -7165,9 +7277,11 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
LSM_HOOK_INIT(file_permission, selinux_file_permission),
LSM_HOOK_INIT(file_alloc_security, selinux_file_alloc_security),
+ LSM_HOOK_INIT(backing_file_alloc, selinux_backing_file_alloc),
LSM_HOOK_INIT(file_ioctl, selinux_file_ioctl),
LSM_HOOK_INIT(file_ioctl_compat, selinux_file_ioctl_compat),
LSM_HOOK_INIT(mmap_file, selinux_mmap_file),
+ LSM_HOOK_INIT(mmap_backing_file, selinux_mmap_backing_file),
LSM_HOOK_INIT(mmap_addr, selinux_mmap_addr),
LSM_HOOK_INIT(file_mprotect, selinux_file_mprotect),
LSM_HOOK_INIT(file_lock, selinux_file_lock),
diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h
index c88cae81ee4c..dc42282a2c05 100644
--- a/security/selinux/include/objsec.h
+++ b/security/selinux/include/objsec.h
@@ -61,6 +61,10 @@ struct file_security_struct {
u32 pseqno; /* Policy seqno at the time of file open */
};
+struct backing_file_security_struct {
+ u32 uf_sid; /* associated user file fsec->sid */
+};
+
struct superblock_security_struct {
u32 sid; /* SID of file system superblock */
u32 def_sid; /* default SID for labeling */
@@ -159,6 +163,13 @@ static inline struct file_security_struct *selinux_file(const struct file *file)
return file->f_security + selinux_blob_sizes.lbs_file;
}
+static inline struct backing_file_security_struct *
+selinux_backing_file(const struct file *backing_file)
+{
+ void *blob = backing_file_security(backing_file);
+ return blob + selinux_blob_sizes.lbs_backing_file;
+}
+
static inline struct inode_security_struct *
selinux_inode(const struct inode *inode)
{
--
2.18.0.huawei.25
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [stable/linux-6.12.y 0/2] Backport Fix incorrect overlayfs mmap() and mprotect() LSM access controls
2026-06-22 3:15 [stable/linux-6.12.y 0/2] Backport Fix incorrect overlayfs mmap() and mprotect() LSM access controls Cai Xinchen
2026-06-22 3:15 ` [stable/linux-6.12.y 1/2] lsm: add backing_file LSM hooks Cai Xinchen
2026-06-22 3:15 ` [stable/linux-6.12.y 2/2] selinux: fix overlayfs mmap() and mprotect() access checks Cai Xinchen
@ 2026-06-25 11:31 ` Greg KH
2 siblings, 0 replies; 6+ messages in thread
From: Greg KH @ 2026-06-25 11:31 UTC (permalink / raw)
To: Cai Xinchen
Cc: viro, brauner, jack, miklos, amir73il, paul, jmorris, serge,
stephen.smalley.work, omosnace, bboscaccy, linux-fsdevel,
linux-kernel, linux-unionfs, linux-security-module, selinux, bpf,
lujialin4
On Mon, Jun 22, 2026 at 11:15:07AM +0800, Cai Xinchen wrote:
> Backport the patch series
> "Fix incorrect overlayfs mmap() and mprotect() LSM access controls" [1]
> to 6.12 lts
>
> I test selinux-testsuite[2] overlay test, it pass 135 tests.
>
> [1] https://lore.kernel.org/all/20260403030848.731867-5-paul@paul-moore.com/
> [2] https://github.com/SELinuxProject/selinux-testsuite
Again, upstream git ids are needed. Please redo all of these and
resend.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-06-25 11:35 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-22 3:15 [stable/linux-6.12.y 0/2] Backport Fix incorrect overlayfs mmap() and mprotect() LSM access controls Cai Xinchen
2026-06-22 3:15 ` [stable/linux-6.12.y 1/2] lsm: add backing_file LSM hooks Cai Xinchen
2026-06-22 3:11 ` sashiko-bot
2026-06-22 3:15 ` [stable/linux-6.12.y 2/2] selinux: fix overlayfs mmap() and mprotect() access checks Cai Xinchen
2026-06-22 3:04 ` sashiko-bot
2026-06-25 11:31 ` [stable/linux-6.12.y 0/2] Backport Fix incorrect overlayfs mmap() and mprotect() LSM access controls Greg KH
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.