* [PATCH v4 0/3] Fix incorrect overlayfs mmap() and mprotect() LSM access controls
@ 2026-04-03 3:08 Paul Moore
2026-04-03 3:08 ` [PATCH v4 1/3] fs: prepare for adding LSM blob to backing_file Paul Moore
` (2 more replies)
0 siblings, 3 replies; 11+ messages in thread
From: Paul Moore @ 2026-04-03 3:08 UTC (permalink / raw)
To: linux-security-module, selinux, linux-fsdevel, linux-unionfs,
linux-erofs
Cc: Amir Goldstein, Gao Xiang, Christian Brauner
Another week, another revision to this patchset. The v3 revision can be
found at the lore[1] link below.
The revision still takes the same basic approach introduced in v2, with
the most significant change in v4 being the change to make the backing
file LSM blob conditional on CONFIG_SECURITY. This requires a number of
other changes to ensure that all accesses of the LSM blob go through a
set of accessor functions which can be converted into dummy functions
when !CONFIG_SECURITY.
While the changes between v3 and v4 were fairly straight forward, there
were enough of them that it felt wrong to preserve the ACKs from previous
revisions. It would be appreciated if those of you who had previously
ACK'd a patch could take a second look and renew your ACK (or comment on
the problem preventing you from ACK'ing).
Thanks all.
[1] https://lore.kernel.org/linux-security-module/20260327220446.353103-4-paul@paul-moore.com/
--
CHANGELOG:
v4:
- added fs prep patch (Amir)
- added CONFIG_SECURITY conditional code (Amir)
v3:
- fix the LSM hook stubs (kernel robot, Ryan Lee)
- fix the lsm_backing_file_cache allocation size (Ryan Lee)
- minor style, simplicity tweaks to the SELinux patch
v2:
- remove the user O_PATH file patch from Amir
- add the backing_file LSM blob and lifecycle hooks
- update the SELinux code to reflect the other changes
v1:
- initial version
--
Amir Goldstein (1):
fs: prepare for adding LSM blob to backing_file
Paul Moore (2):
lsm: add backing_file LSM hooks
selinux: fix overlayfs mmap() and mprotect() access checks
fs/backing-file.c | 18 +-
fs/erofs/ishare.c | 10 +
fs/file_table.c | 43 ++++-
fs/fuse/passthrough.c | 2
fs/internal.h | 3
fs/overlayfs/dir.c | 2
fs/overlayfs/file.c | 2
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/lsm.h | 1
security/lsm_init.c | 9 +
security/security.c | 102 +++++++++++
security/selinux/hooks.c | 256 +++++++++++++++++++++---------
security/selinux/include/objsec.h | 11 +
18 files changed, 419 insertions(+), 87 deletions(-)
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v4 1/3] fs: prepare for adding LSM blob to backing_file
2026-04-03 3:08 [PATCH v4 0/3] Fix incorrect overlayfs mmap() and mprotect() LSM access controls Paul Moore
@ 2026-04-03 3:08 ` Paul Moore
2026-04-05 0:14 ` Serge E. Hallyn
2026-04-03 3:08 ` [PATCH v4 2/3] lsm: add backing_file LSM hooks Paul Moore
2026-04-03 3:08 ` [PATCH v4 3/3] selinux: fix overlayfs mmap() and mprotect() access checks Paul Moore
2 siblings, 1 reply; 11+ messages in thread
From: Paul Moore @ 2026-04-03 3:08 UTC (permalink / raw)
To: linux-security-module, selinux, linux-fsdevel, linux-unionfs,
linux-erofs
Cc: Amir Goldstein, Gao Xiang, Christian Brauner
From: Amir Goldstein <amir73il@gmail.com>
In preparation to adding LSM blob to backing_file struct, factor out
helpers init_backing_file() and backing_file_free().
Cc: stable@vger.kernel.org
Cc: linux-fsdevel@vger.kernel.org
Cc: linux-unionfs@vger.kernel.org
Cc: linux-erofs@lists.ozlabs.org
Signed-off-by: Amir Goldstein <amir73il@gmail.com>
[PM: use the term "LSM blob", fix comment style to match file]
Signed-off-by: Paul Moore <paul@paul-moore.com>
---
fs/file_table.c | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/fs/file_table.c b/fs/file_table.c
index aaa5faaace1e..3b3792903185 100644
--- a/fs/file_table.c
+++ b/fs/file_table.c
@@ -66,6 +66,12 @@ void backing_file_set_user_path(struct file *f, const struct path *path)
}
EXPORT_SYMBOL_GPL(backing_file_set_user_path);
+static inline void backing_file_free(struct backing_file *ff)
+{
+ path_put(&ff->user_path);
+ kmem_cache_free(bfilp_cachep, ff);
+}
+
static inline void file_free(struct file *f)
{
security_file_free(f);
@@ -73,8 +79,7 @@ static inline void file_free(struct file *f)
percpu_counter_dec(&nr_files);
put_cred(f->f_cred);
if (unlikely(f->f_mode & FMODE_BACKING)) {
- path_put(backing_file_user_path(f));
- kmem_cache_free(bfilp_cachep, backing_file(f));
+ backing_file_free(backing_file(f));
} else {
kmem_cache_free(filp_cachep, f);
}
@@ -283,6 +288,12 @@ struct file *alloc_empty_file_noaccount(int flags, const struct cred *cred)
return f;
}
+static int init_backing_file(struct backing_file *ff)
+{
+ memset(&ff->user_path, 0, sizeof(ff->user_path));
+ return 0;
+}
+
/*
* Variant of alloc_empty_file() that allocates a backing_file container
* and doesn't check and modify nr_files.
@@ -305,7 +316,14 @@ struct file *alloc_empty_backing_file(int flags, const struct cred *cred)
return ERR_PTR(error);
}
+ /* The f_mode flags must be set before fput(). */
ff->file.f_mode |= FMODE_BACKING | FMODE_NOACCOUNT;
+ error = init_backing_file(ff);
+ if (unlikely(error)) {
+ fput(&ff->file);
+ return ERR_PTR(error);
+ }
+
return &ff->file;
}
EXPORT_SYMBOL_GPL(alloc_empty_backing_file);
--
2.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v4 2/3] lsm: add backing_file LSM hooks
2026-04-03 3:08 [PATCH v4 0/3] Fix incorrect overlayfs mmap() and mprotect() LSM access controls Paul Moore
2026-04-03 3:08 ` [PATCH v4 1/3] fs: prepare for adding LSM blob to backing_file Paul Moore
@ 2026-04-03 3:08 ` Paul Moore
2026-04-03 6:12 ` Amir Goldstein
2026-04-05 3:12 ` Serge E. Hallyn
2026-04-03 3:08 ` [PATCH v4 3/3] selinux: fix overlayfs mmap() and mprotect() access checks Paul Moore
2 siblings, 2 replies; 11+ messages in thread
From: Paul Moore @ 2026-04-03 3:08 UTC (permalink / raw)
To: linux-security-module, selinux, linux-fsdevel, linux-unionfs,
linux-erofs
Cc: Amir Goldstein, Gao Xiang, Christian Brauner
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
Signed-off-by: Paul Moore <paul@paul-moore.com>
---
fs/backing-file.c | 18 ++++--
fs/erofs/ishare.c | 10 +++-
fs/file_table.c | 27 +++++++--
fs/fuse/passthrough.c | 2 +-
fs/internal.h | 3 +-
fs/overlayfs/dir.c | 2 +-
fs/overlayfs/file.c | 2 +-
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/lsm.h | 1 +
security/lsm_init.c | 9 +++
security/security.c | 102 ++++++++++++++++++++++++++++++++++
16 files changed, 206 insertions(+), 17 deletions(-)
diff --git a/fs/backing-file.c b/fs/backing-file.c
index 45da8600d564..1f3bbfc75882 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;
@@ -336,8 +339,13 @@ int backing_file_mmap(struct file *file, struct vm_area_struct *vma,
vma_set_file(vma, file);
- scoped_with_creds(ctx->cred)
+ scoped_with_creds(ctx->cred) {
+ ret = security_mmap_backing_file(vma, file, user_file);
+ if (ret)
+ return ret;
+
ret = vfs_mmap(vma->vm_file, vma);
+ }
if (ctx->accessed)
ctx->accessed(user_file);
diff --git a/fs/erofs/ishare.c b/fs/erofs/ishare.c
index ec433bacc592..6ed66b17359b 100644
--- a/fs/erofs/ishare.c
+++ b/fs/erofs/ishare.c
@@ -4,6 +4,7 @@
*/
#include <linux/xxhash.h>
#include <linux/mount.h>
+#include <linux/security.h>
#include "internal.h"
#include "xattr.h"
@@ -106,7 +107,8 @@ static int erofs_ishare_file_open(struct inode *inode, struct file *file)
if (file->f_flags & O_DIRECT)
return -EINVAL;
- realfile = alloc_empty_backing_file(O_RDONLY|O_NOATIME, current_cred());
+ realfile = alloc_empty_backing_file(O_RDONLY|O_NOATIME, current_cred(),
+ file);
if (IS_ERR(realfile))
return PTR_ERR(realfile);
ihold(sharedinode);
@@ -150,8 +152,14 @@ static ssize_t erofs_ishare_file_read_iter(struct kiocb *iocb,
static int erofs_ishare_mmap(struct file *file, struct vm_area_struct *vma)
{
struct file *realfile = file->private_data;
+ int err;
vma_set_file(vma, realfile);
+
+ err = security_mmap_backing_file(vma, realfile, file);
+ if (err)
+ return err;
+
return generic_file_readonly_mmap(file, vma);
}
diff --git a/fs/file_table.c b/fs/file_table.c
index 3b3792903185..d19d879b6efc 100644
--- a/fs/file_table.c
+++ b/fs/file_table.c
@@ -50,6 +50,9 @@ struct backing_file {
struct path user_path;
freeptr_t bf_freeptr;
};
+#ifdef CONFIG_SECURITY
+ void *security;
+#endif
};
#define backing_file(f) container_of(f, struct backing_file, file)
@@ -66,8 +69,21 @@ void backing_file_set_user_path(struct file *f, const struct path *path)
}
EXPORT_SYMBOL_GPL(backing_file_set_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);
kmem_cache_free(bfilp_cachep, ff);
}
@@ -288,10 +304,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);
}
/*
@@ -301,7 +319,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;
@@ -318,7 +337,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 72de97c03d0e..f2d08ac2459b 100644
--- a/fs/fuse/passthrough.c
+++ b/fs/fuse/passthrough.c
@@ -167,7 +167,7 @@ struct fuse_backing *fuse_passthrough_open(struct file *file, int backing_id)
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 cbc384a1aa09..77e90e4124e0 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -106,7 +106,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);
void backing_file_set_user_path(struct file *f, const struct path *path);
static inline void file_put_write_access(struct file *file)
diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
index ff3dbd1ca61f..f2f20a611af3 100644
--- a/fs/overlayfs/dir.c
+++ b/fs/overlayfs/dir.c
@@ -1374,7 +1374,7 @@ static int ovl_create_tmpfile(struct file *file, struct dentry *dentry,
return PTR_ERR(cred);
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 97bed2286030..27cc07738f33 100644
--- a/fs/overlayfs/file.c
+++ b/fs/overlayfs/file.c
@@ -48,7 +48,7 @@ static struct file *ovl_open_realfile(const struct file *file,
if (!inode_owner_or_capable(real_idmap, realinode))
flags &= ~O_NOATIME;
- realfile = backing_file_open(file_user_path(file),
+ realfile = backing_file_open(file,
flags, realpath, current_cred());
}
}
diff --git a/include/linux/backing-file.h b/include/linux/backing-file.h
index 1476a6ed1bfd..c939cd222730 100644
--- a/include/linux/backing-file.h
+++ b/include/linux/backing-file.h
@@ -18,10 +18,10 @@ struct backing_file_ctx {
void (*end_write)(struct kiocb *iocb, 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 8b3dd145b25e..d0d0e8f55589 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2475,6 +2475,19 @@ struct file *dentry_create(struct path *path, int flags, umode_t mode,
const struct cred *cred);
const struct path *backing_file_user_path(const 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 382c56a97bba..584db296e43b 100644
--- a/include/linux/lsm_audit.h
+++ b/include/linux/lsm_audit.h
@@ -94,7 +94,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 8c42b4bde09c..b4958167e381 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -191,6 +191,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,
@@ -198,6 +201,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 d48bf0ad26f4..b4f8cad53ddb 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 {
unsigned int lbs_cred;
unsigned int lbs_file;
+ unsigned int lbs_backing_file;
unsigned int lbs_ib;
unsigned int lbs_inode;
unsigned int lbs_sock;
diff --git a/include/linux/security.h b/include/linux/security.h
index ee88dd2d2d1f..8d2d4856934e 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -472,11 +472,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);
@@ -1141,6 +1147,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)
{
@@ -1160,6 +1175,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/lsm.h b/security/lsm.h
index db77cc83e158..32f808ad4335 100644
--- a/security/lsm.h
+++ b/security/lsm.h
@@ -29,6 +29,7 @@ extern struct lsm_blob_sizes blob_sizes;
/* LSM blob caches */
extern struct kmem_cache *lsm_file_cache;
+extern struct kmem_cache *lsm_backing_file_cache;
extern struct kmem_cache *lsm_inode_cache;
/* LSM blob allocators */
diff --git a/security/lsm_init.c b/security/lsm_init.c
index 573e2a7250c4..7c0fd17f1601 100644
--- a/security/lsm_init.c
+++ b/security/lsm_init.c
@@ -293,6 +293,8 @@ static void __init lsm_prepare(struct lsm_info *lsm)
blobs = lsm->blobs;
lsm_blob_size_update(&blobs->lbs_cred, &blob_sizes.lbs_cred);
lsm_blob_size_update(&blobs->lbs_file, &blob_sizes.lbs_file);
+ lsm_blob_size_update(&blobs->lbs_backing_file,
+ &blob_sizes.lbs_backing_file);
lsm_blob_size_update(&blobs->lbs_ib, &blob_sizes.lbs_ib);
/* inode blob gets an rcu_head in addition to LSM blobs. */
if (blobs->lbs_inode && blob_sizes.lbs_inode == 0)
@@ -441,6 +443,8 @@ int __init security_init(void)
if (lsm_debug) {
lsm_pr("blob(cred) size %d\n", blob_sizes.lbs_cred);
lsm_pr("blob(file) size %d\n", blob_sizes.lbs_file);
+ lsm_pr("blob(backing_file) size %d\n",
+ blob_sizes.lbs_backing_file);
lsm_pr("blob(ib) size %d\n", blob_sizes.lbs_ib);
lsm_pr("blob(inode) size %d\n", blob_sizes.lbs_inode);
lsm_pr("blob(ipc) size %d\n", blob_sizes.lbs_ipc);
@@ -462,6 +466,11 @@ int __init security_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,
diff --git a/security/security.c b/security/security.c
index a26c1474e2e4..048560ef6a1a 100644
--- a/security/security.c
+++ b/security/security.c
@@ -82,6 +82,7 @@ const struct lsm_id *lsm_idlist[MAX_LSM_COUNT];
struct lsm_blob_sizes blob_sizes;
struct kmem_cache *lsm_file_cache;
+struct kmem_cache *lsm_backing_file_cache;
struct kmem_cache *lsm_inode_cache;
#define SECURITY_HOOK_ACTIVE_KEY(HOOK, IDX) security_hook_active_##HOOK##_##IDX
@@ -173,6 +174,30 @@ static int lsm_file_alloc(struct file *file)
return 0;
}
+/**
+ * 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
@@ -2418,6 +2443,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
@@ -2506,6 +2582,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.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v4 3/3] selinux: fix overlayfs mmap() and mprotect() access checks
2026-04-03 3:08 [PATCH v4 0/3] Fix incorrect overlayfs mmap() and mprotect() LSM access controls Paul Moore
2026-04-03 3:08 ` [PATCH v4 1/3] fs: prepare for adding LSM blob to backing_file Paul Moore
2026-04-03 3:08 ` [PATCH v4 2/3] lsm: add backing_file LSM hooks Paul Moore
@ 2026-04-03 3:08 ` Paul Moore
2026-04-03 6:17 ` Amir Goldstein
2026-04-07 12:14 ` Stephen Smalley
2 siblings, 2 replies; 11+ messages in thread
From: Paul Moore @ 2026-04-03 3:08 UTC (permalink / raw)
To: linux-security-module, selinux, linux-fsdevel, linux-unionfs,
linux-erofs
Cc: Amir Goldstein, Gao Xiang, Christian Brauner
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
Signed-off-by: Paul Moore <paul@paul-moore.com>
---
security/selinux/hooks.c | 256 +++++++++++++++++++++---------
security/selinux/include/objsec.h | 11 ++
2 files changed, 196 insertions(+), 71 deletions(-)
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index d8224ea113d1..76e0fb7dcb36 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -1745,6 +1745,60 @@ static inline int file_path_has_perm(const struct cred *cred,
static int bpf_fd_pass(const struct file *file, u32 sid);
#endif
+static int __file_has_perm(const struct cred *cred, const struct file *file,
+ u32 av, bool bf_user_file)
+
+{
+ struct common_audit_data ad;
+ struct inode *inode;
+ u32 ssid = cred_sid(cred);
+ u32 tsid_fd;
+ int rc;
+
+ 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);
+ 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)
+ return rc;
+ }
+
+#ifdef CONFIG_BPF_SYSCALL
+ /* 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. */
+ if (av)
+ return inode_has_perm(cred, inode, av, &ad);
+
+ 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
@@ -1753,41 +1807,10 @@ static int bpf_fd_pass(const struct file *file, u32 sid);
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 inline int file_has_perm(const struct cred *cred,
+ const struct file *file, u32 av)
{
- struct file_security_struct *fsec = selinux_file(file);
- struct inode *inode = file_inode(file);
- struct common_audit_data ad;
- u32 sid = cred_sid(cred);
- int rc;
-
- ad.type = LSM_AUDIT_DATA_FILE;
- ad.u.file = file;
-
- if (sid != fsec->sid) {
- rc = avc_has_perm(sid, fsec->sid,
- SECCLASS_FD,
- FD__USE,
- &ad);
- if (rc)
- goto out;
- }
-
-#ifdef CONFIG_BPF_SYSCALL
- rc = bpf_fd_pass(file, cred_sid(cred));
- 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);
-
-out:
- return rc;
+ return __file_has_perm(cred, file, av, false);
}
/*
@@ -3825,6 +3848,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.
@@ -3942,42 +3976,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)
@@ -3993,36 +4040,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
@@ -4036,11 +4127,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
@@ -4048,13 +4143,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)
@@ -7393,6 +7504,7 @@ struct lsm_blob_sizes selinux_blob_sizes __ro_after_init = {
.lbs_cred = sizeof(struct cred_security_struct),
.lbs_task = 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),
@@ -7498,9 +7610,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 5bddd28ea5cb..b19e5d978e82 100644
--- a/security/selinux/include/objsec.h
+++ b/security/selinux/include/objsec.h
@@ -88,6 +88,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 */
@@ -195,6 +199,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.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v4 2/3] lsm: add backing_file LSM hooks
2026-04-03 3:08 ` [PATCH v4 2/3] lsm: add backing_file LSM hooks Paul Moore
@ 2026-04-03 6:12 ` Amir Goldstein
2026-04-03 21:14 ` Paul Moore
2026-04-05 3:12 ` Serge E. Hallyn
1 sibling, 1 reply; 11+ messages in thread
From: Amir Goldstein @ 2026-04-03 6:12 UTC (permalink / raw)
To: Paul Moore
Cc: linux-security-module, selinux, linux-fsdevel, linux-unionfs,
linux-erofs, Gao Xiang, Christian Brauner
On Fri, Apr 3, 2026 at 5:09 AM Paul Moore <paul@paul-moore.com> wrote:
>
> 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
> Signed-off-by: Paul Moore <paul@paul-moore.com>
That looks nicer.
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Thanks,
Amir.
> ---
> fs/backing-file.c | 18 ++++--
> fs/erofs/ishare.c | 10 +++-
> fs/file_table.c | 27 +++++++--
> fs/fuse/passthrough.c | 2 +-
> fs/internal.h | 3 +-
> fs/overlayfs/dir.c | 2 +-
> fs/overlayfs/file.c | 2 +-
> 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/lsm.h | 1 +
> security/lsm_init.c | 9 +++
> security/security.c | 102 ++++++++++++++++++++++++++++++++++
> 16 files changed, 206 insertions(+), 17 deletions(-)
>
> diff --git a/fs/backing-file.c b/fs/backing-file.c
> index 45da8600d564..1f3bbfc75882 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;
>
> @@ -336,8 +339,13 @@ int backing_file_mmap(struct file *file, struct vm_area_struct *vma,
>
> vma_set_file(vma, file);
>
> - scoped_with_creds(ctx->cred)
> + scoped_with_creds(ctx->cred) {
> + ret = security_mmap_backing_file(vma, file, user_file);
> + if (ret)
> + return ret;
> +
> ret = vfs_mmap(vma->vm_file, vma);
> + }
>
> if (ctx->accessed)
> ctx->accessed(user_file);
> diff --git a/fs/erofs/ishare.c b/fs/erofs/ishare.c
> index ec433bacc592..6ed66b17359b 100644
> --- a/fs/erofs/ishare.c
> +++ b/fs/erofs/ishare.c
> @@ -4,6 +4,7 @@
> */
> #include <linux/xxhash.h>
> #include <linux/mount.h>
> +#include <linux/security.h>
> #include "internal.h"
> #include "xattr.h"
>
> @@ -106,7 +107,8 @@ static int erofs_ishare_file_open(struct inode *inode, struct file *file)
>
> if (file->f_flags & O_DIRECT)
> return -EINVAL;
> - realfile = alloc_empty_backing_file(O_RDONLY|O_NOATIME, current_cred());
> + realfile = alloc_empty_backing_file(O_RDONLY|O_NOATIME, current_cred(),
> + file);
> if (IS_ERR(realfile))
> return PTR_ERR(realfile);
> ihold(sharedinode);
> @@ -150,8 +152,14 @@ static ssize_t erofs_ishare_file_read_iter(struct kiocb *iocb,
> static int erofs_ishare_mmap(struct file *file, struct vm_area_struct *vma)
> {
> struct file *realfile = file->private_data;
> + int err;
>
> vma_set_file(vma, realfile);
> +
> + err = security_mmap_backing_file(vma, realfile, file);
> + if (err)
> + return err;
> +
> return generic_file_readonly_mmap(file, vma);
> }
>
> diff --git a/fs/file_table.c b/fs/file_table.c
> index 3b3792903185..d19d879b6efc 100644
> --- a/fs/file_table.c
> +++ b/fs/file_table.c
> @@ -50,6 +50,9 @@ struct backing_file {
> struct path user_path;
> freeptr_t bf_freeptr;
> };
> +#ifdef CONFIG_SECURITY
> + void *security;
> +#endif
> };
>
> #define backing_file(f) container_of(f, struct backing_file, file)
> @@ -66,8 +69,21 @@ void backing_file_set_user_path(struct file *f, const struct path *path)
> }
> EXPORT_SYMBOL_GPL(backing_file_set_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);
> kmem_cache_free(bfilp_cachep, ff);
> }
> @@ -288,10 +304,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);
> }
>
> /*
> @@ -301,7 +319,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;
> @@ -318,7 +337,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 72de97c03d0e..f2d08ac2459b 100644
> --- a/fs/fuse/passthrough.c
> +++ b/fs/fuse/passthrough.c
> @@ -167,7 +167,7 @@ struct fuse_backing *fuse_passthrough_open(struct file *file, int backing_id)
> 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 cbc384a1aa09..77e90e4124e0 100644
> --- a/fs/internal.h
> +++ b/fs/internal.h
> @@ -106,7 +106,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);
> void backing_file_set_user_path(struct file *f, const struct path *path);
>
> static inline void file_put_write_access(struct file *file)
> diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
> index ff3dbd1ca61f..f2f20a611af3 100644
> --- a/fs/overlayfs/dir.c
> +++ b/fs/overlayfs/dir.c
> @@ -1374,7 +1374,7 @@ static int ovl_create_tmpfile(struct file *file, struct dentry *dentry,
> return PTR_ERR(cred);
>
> 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 97bed2286030..27cc07738f33 100644
> --- a/fs/overlayfs/file.c
> +++ b/fs/overlayfs/file.c
> @@ -48,7 +48,7 @@ static struct file *ovl_open_realfile(const struct file *file,
> if (!inode_owner_or_capable(real_idmap, realinode))
> flags &= ~O_NOATIME;
>
> - realfile = backing_file_open(file_user_path(file),
> + realfile = backing_file_open(file,
> flags, realpath, current_cred());
> }
> }
> diff --git a/include/linux/backing-file.h b/include/linux/backing-file.h
> index 1476a6ed1bfd..c939cd222730 100644
> --- a/include/linux/backing-file.h
> +++ b/include/linux/backing-file.h
> @@ -18,10 +18,10 @@ struct backing_file_ctx {
> void (*end_write)(struct kiocb *iocb, 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 8b3dd145b25e..d0d0e8f55589 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -2475,6 +2475,19 @@ struct file *dentry_create(struct path *path, int flags, umode_t mode,
> const struct cred *cred);
> const struct path *backing_file_user_path(const 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 382c56a97bba..584db296e43b 100644
> --- a/include/linux/lsm_audit.h
> +++ b/include/linux/lsm_audit.h
> @@ -94,7 +94,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 8c42b4bde09c..b4958167e381 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -191,6 +191,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,
> @@ -198,6 +201,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 d48bf0ad26f4..b4f8cad53ddb 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 {
> unsigned int lbs_cred;
> unsigned int lbs_file;
> + unsigned int lbs_backing_file;
> unsigned int lbs_ib;
> unsigned int lbs_inode;
> unsigned int lbs_sock;
> diff --git a/include/linux/security.h b/include/linux/security.h
> index ee88dd2d2d1f..8d2d4856934e 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -472,11 +472,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);
> @@ -1141,6 +1147,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)
> {
> @@ -1160,6 +1175,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/lsm.h b/security/lsm.h
> index db77cc83e158..32f808ad4335 100644
> --- a/security/lsm.h
> +++ b/security/lsm.h
> @@ -29,6 +29,7 @@ extern struct lsm_blob_sizes blob_sizes;
>
> /* LSM blob caches */
> extern struct kmem_cache *lsm_file_cache;
> +extern struct kmem_cache *lsm_backing_file_cache;
> extern struct kmem_cache *lsm_inode_cache;
>
> /* LSM blob allocators */
> diff --git a/security/lsm_init.c b/security/lsm_init.c
> index 573e2a7250c4..7c0fd17f1601 100644
> --- a/security/lsm_init.c
> +++ b/security/lsm_init.c
> @@ -293,6 +293,8 @@ static void __init lsm_prepare(struct lsm_info *lsm)
> blobs = lsm->blobs;
> lsm_blob_size_update(&blobs->lbs_cred, &blob_sizes.lbs_cred);
> lsm_blob_size_update(&blobs->lbs_file, &blob_sizes.lbs_file);
> + lsm_blob_size_update(&blobs->lbs_backing_file,
> + &blob_sizes.lbs_backing_file);
> lsm_blob_size_update(&blobs->lbs_ib, &blob_sizes.lbs_ib);
> /* inode blob gets an rcu_head in addition to LSM blobs. */
> if (blobs->lbs_inode && blob_sizes.lbs_inode == 0)
> @@ -441,6 +443,8 @@ int __init security_init(void)
> if (lsm_debug) {
> lsm_pr("blob(cred) size %d\n", blob_sizes.lbs_cred);
> lsm_pr("blob(file) size %d\n", blob_sizes.lbs_file);
> + lsm_pr("blob(backing_file) size %d\n",
> + blob_sizes.lbs_backing_file);
> lsm_pr("blob(ib) size %d\n", blob_sizes.lbs_ib);
> lsm_pr("blob(inode) size %d\n", blob_sizes.lbs_inode);
> lsm_pr("blob(ipc) size %d\n", blob_sizes.lbs_ipc);
> @@ -462,6 +466,11 @@ int __init security_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,
> diff --git a/security/security.c b/security/security.c
> index a26c1474e2e4..048560ef6a1a 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -82,6 +82,7 @@ const struct lsm_id *lsm_idlist[MAX_LSM_COUNT];
> struct lsm_blob_sizes blob_sizes;
>
> struct kmem_cache *lsm_file_cache;
> +struct kmem_cache *lsm_backing_file_cache;
> struct kmem_cache *lsm_inode_cache;
>
> #define SECURITY_HOOK_ACTIVE_KEY(HOOK, IDX) security_hook_active_##HOOK##_##IDX
> @@ -173,6 +174,30 @@ static int lsm_file_alloc(struct file *file)
> return 0;
> }
>
> +/**
> + * 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
> @@ -2418,6 +2443,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
> @@ -2506,6 +2582,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.53.0
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v4 3/3] selinux: fix overlayfs mmap() and mprotect() access checks
2026-04-03 3:08 ` [PATCH v4 3/3] selinux: fix overlayfs mmap() and mprotect() access checks Paul Moore
@ 2026-04-03 6:17 ` Amir Goldstein
2026-04-07 12:14 ` Stephen Smalley
1 sibling, 0 replies; 11+ messages in thread
From: Amir Goldstein @ 2026-04-03 6:17 UTC (permalink / raw)
To: Paul Moore
Cc: linux-security-module, selinux, linux-fsdevel, linux-unionfs,
linux-erofs, Gao Xiang, Christian Brauner
On Fri, Apr 3, 2026 at 5:09 AM Paul Moore <paul@paul-moore.com> wrote:
>
> 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
> Signed-off-by: Paul Moore <paul@paul-moore.com>
Can't say much about selinux implementation, but
for the use of backing file API and the concept solution
Acked-by: Amir Goldstein <amir73il@gmail.com>
Thanks,
Amir.
> ---
> security/selinux/hooks.c | 256 +++++++++++++++++++++---------
> security/selinux/include/objsec.h | 11 ++
> 2 files changed, 196 insertions(+), 71 deletions(-)
>
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index d8224ea113d1..76e0fb7dcb36 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -1745,6 +1745,60 @@ static inline int file_path_has_perm(const struct cred *cred,
> static int bpf_fd_pass(const struct file *file, u32 sid);
> #endif
>
> +static int __file_has_perm(const struct cred *cred, const struct file *file,
> + u32 av, bool bf_user_file)
> +
> +{
> + struct common_audit_data ad;
> + struct inode *inode;
> + u32 ssid = cred_sid(cred);
> + u32 tsid_fd;
> + int rc;
> +
> + 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);
> + 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)
> + return rc;
> + }
> +
> +#ifdef CONFIG_BPF_SYSCALL
> + /* 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. */
> + if (av)
> + return inode_has_perm(cred, inode, av, &ad);
> +
> + 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
> @@ -1753,41 +1807,10 @@ static int bpf_fd_pass(const struct file *file, u32 sid);
> 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 inline int file_has_perm(const struct cred *cred,
> + const struct file *file, u32 av)
> {
> - struct file_security_struct *fsec = selinux_file(file);
> - struct inode *inode = file_inode(file);
> - struct common_audit_data ad;
> - u32 sid = cred_sid(cred);
> - int rc;
> -
> - ad.type = LSM_AUDIT_DATA_FILE;
> - ad.u.file = file;
> -
> - if (sid != fsec->sid) {
> - rc = avc_has_perm(sid, fsec->sid,
> - SECCLASS_FD,
> - FD__USE,
> - &ad);
> - if (rc)
> - goto out;
> - }
> -
> -#ifdef CONFIG_BPF_SYSCALL
> - rc = bpf_fd_pass(file, cred_sid(cred));
> - 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);
> -
> -out:
> - return rc;
> + return __file_has_perm(cred, file, av, false);
> }
>
> /*
> @@ -3825,6 +3848,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.
> @@ -3942,42 +3976,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)
> @@ -3993,36 +4040,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
> @@ -4036,11 +4127,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
> @@ -4048,13 +4143,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)
> @@ -7393,6 +7504,7 @@ struct lsm_blob_sizes selinux_blob_sizes __ro_after_init = {
> .lbs_cred = sizeof(struct cred_security_struct),
> .lbs_task = 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),
> @@ -7498,9 +7610,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 5bddd28ea5cb..b19e5d978e82 100644
> --- a/security/selinux/include/objsec.h
> +++ b/security/selinux/include/objsec.h
> @@ -88,6 +88,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 */
> @@ -195,6 +199,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.53.0
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v4 2/3] lsm: add backing_file LSM hooks
2026-04-03 6:12 ` Amir Goldstein
@ 2026-04-03 21:14 ` Paul Moore
0 siblings, 0 replies; 11+ messages in thread
From: Paul Moore @ 2026-04-03 21:14 UTC (permalink / raw)
To: Amir Goldstein
Cc: linux-security-module, selinux, linux-fsdevel, linux-unionfs,
linux-erofs, Gao Xiang, Christian Brauner
On Fri, Apr 3, 2026 at 2:12 AM Amir Goldstein <amir73il@gmail.com> wrote:
> On Fri, Apr 3, 2026 at 5:09 AM Paul Moore <paul@paul-moore.com> wrote:
> >
> > 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
> > Signed-off-by: Paul Moore <paul@paul-moore.com>
>
> That looks nicer.
>
> Reviewed-by: Amir Goldstein <amir73il@gmail.com>
>
> Thanks,
> Amir.
Thanks for refreshing your review. Since we are at the end of -rc6,
it probably doesn't make much sense to put this in lsm/stable-7.0; I'm
going to merge this into lsm/dev which should give us at least one
week in linux-next before the v7.1 merge window opens. If others want
to add their ACKs/Reviewed-by during that time, I'll update the
branch.
--
paul-moore.com
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v4 1/3] fs: prepare for adding LSM blob to backing_file
2026-04-03 3:08 ` [PATCH v4 1/3] fs: prepare for adding LSM blob to backing_file Paul Moore
@ 2026-04-05 0:14 ` Serge E. Hallyn
0 siblings, 0 replies; 11+ messages in thread
From: Serge E. Hallyn @ 2026-04-05 0:14 UTC (permalink / raw)
To: Paul Moore
Cc: linux-security-module, selinux, linux-fsdevel, linux-unionfs,
linux-erofs, Amir Goldstein, Gao Xiang, Christian Brauner
On Thu, Apr 02, 2026 at 11:08:33PM -0400, Paul Moore wrote:
> From: Amir Goldstein <amir73il@gmail.com>
>
> In preparation to adding LSM blob to backing_file struct, factor out
> helpers init_backing_file() and backing_file_free().
>
> Cc: stable@vger.kernel.org
> Cc: linux-fsdevel@vger.kernel.org
> Cc: linux-unionfs@vger.kernel.org
> Cc: linux-erofs@lists.ozlabs.org
> Signed-off-by: Amir Goldstein <amir73il@gmail.com>
> [PM: use the term "LSM blob", fix comment style to match file]
> Signed-off-by: Paul Moore <paul@paul-moore.com>
Reviewed-by: Serge Hallyn <serge@hallyn.com>
> ---
> fs/file_table.c | 22 ++++++++++++++++++++--
> 1 file changed, 20 insertions(+), 2 deletions(-)
>
> diff --git a/fs/file_table.c b/fs/file_table.c
> index aaa5faaace1e..3b3792903185 100644
> --- a/fs/file_table.c
> +++ b/fs/file_table.c
> @@ -66,6 +66,12 @@ void backing_file_set_user_path(struct file *f, const struct path *path)
> }
> EXPORT_SYMBOL_GPL(backing_file_set_user_path);
>
> +static inline void backing_file_free(struct backing_file *ff)
> +{
> + path_put(&ff->user_path);
> + kmem_cache_free(bfilp_cachep, ff);
> +}
> +
> static inline void file_free(struct file *f)
> {
> security_file_free(f);
> @@ -73,8 +79,7 @@ static inline void file_free(struct file *f)
> percpu_counter_dec(&nr_files);
> put_cred(f->f_cred);
> if (unlikely(f->f_mode & FMODE_BACKING)) {
> - path_put(backing_file_user_path(f));
> - kmem_cache_free(bfilp_cachep, backing_file(f));
> + backing_file_free(backing_file(f));
> } else {
> kmem_cache_free(filp_cachep, f);
> }
> @@ -283,6 +288,12 @@ struct file *alloc_empty_file_noaccount(int flags, const struct cred *cred)
> return f;
> }
>
> +static int init_backing_file(struct backing_file *ff)
> +{
> + memset(&ff->user_path, 0, sizeof(ff->user_path));
> + return 0;
> +}
> +
> /*
> * Variant of alloc_empty_file() that allocates a backing_file container
> * and doesn't check and modify nr_files.
> @@ -305,7 +316,14 @@ struct file *alloc_empty_backing_file(int flags, const struct cred *cred)
> return ERR_PTR(error);
> }
>
> + /* The f_mode flags must be set before fput(). */
> ff->file.f_mode |= FMODE_BACKING | FMODE_NOACCOUNT;
> + error = init_backing_file(ff);
> + if (unlikely(error)) {
> + fput(&ff->file);
> + return ERR_PTR(error);
> + }
> +
> return &ff->file;
> }
> EXPORT_SYMBOL_GPL(alloc_empty_backing_file);
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v4 2/3] lsm: add backing_file LSM hooks
2026-04-03 3:08 ` [PATCH v4 2/3] lsm: add backing_file LSM hooks Paul Moore
2026-04-03 6:12 ` Amir Goldstein
@ 2026-04-05 3:12 ` Serge E. Hallyn
1 sibling, 0 replies; 11+ messages in thread
From: Serge E. Hallyn @ 2026-04-05 3:12 UTC (permalink / raw)
To: Paul Moore
Cc: linux-security-module, selinux, linux-fsdevel, linux-unionfs,
linux-erofs, Amir Goldstein, Gao Xiang, Christian Brauner
On Thu, Apr 02, 2026 at 11:08:34PM -0400, Paul Moore wrote:
> 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
> Signed-off-by: Paul Moore <paul@paul-moore.com>
Reviewed-by: Serge Hallyn <serge@hallyn.com>
> ---
> fs/backing-file.c | 18 ++++--
> fs/erofs/ishare.c | 10 +++-
> fs/file_table.c | 27 +++++++--
> fs/fuse/passthrough.c | 2 +-
> fs/internal.h | 3 +-
> fs/overlayfs/dir.c | 2 +-
> fs/overlayfs/file.c | 2 +-
> 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/lsm.h | 1 +
> security/lsm_init.c | 9 +++
> security/security.c | 102 ++++++++++++++++++++++++++++++++++
> 16 files changed, 206 insertions(+), 17 deletions(-)
>
> diff --git a/fs/backing-file.c b/fs/backing-file.c
> index 45da8600d564..1f3bbfc75882 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;
>
> @@ -336,8 +339,13 @@ int backing_file_mmap(struct file *file, struct vm_area_struct *vma,
>
> vma_set_file(vma, file);
>
> - scoped_with_creds(ctx->cred)
> + scoped_with_creds(ctx->cred) {
> + ret = security_mmap_backing_file(vma, file, user_file);
> + if (ret)
> + return ret;
> +
> ret = vfs_mmap(vma->vm_file, vma);
> + }
>
> if (ctx->accessed)
> ctx->accessed(user_file);
> diff --git a/fs/erofs/ishare.c b/fs/erofs/ishare.c
> index ec433bacc592..6ed66b17359b 100644
> --- a/fs/erofs/ishare.c
> +++ b/fs/erofs/ishare.c
> @@ -4,6 +4,7 @@
> */
> #include <linux/xxhash.h>
> #include <linux/mount.h>
> +#include <linux/security.h>
> #include "internal.h"
> #include "xattr.h"
>
> @@ -106,7 +107,8 @@ static int erofs_ishare_file_open(struct inode *inode, struct file *file)
>
> if (file->f_flags & O_DIRECT)
> return -EINVAL;
> - realfile = alloc_empty_backing_file(O_RDONLY|O_NOATIME, current_cred());
> + realfile = alloc_empty_backing_file(O_RDONLY|O_NOATIME, current_cred(),
> + file);
> if (IS_ERR(realfile))
> return PTR_ERR(realfile);
> ihold(sharedinode);
> @@ -150,8 +152,14 @@ static ssize_t erofs_ishare_file_read_iter(struct kiocb *iocb,
> static int erofs_ishare_mmap(struct file *file, struct vm_area_struct *vma)
> {
> struct file *realfile = file->private_data;
> + int err;
>
> vma_set_file(vma, realfile);
> +
> + err = security_mmap_backing_file(vma, realfile, file);
> + if (err)
> + return err;
> +
> return generic_file_readonly_mmap(file, vma);
> }
>
> diff --git a/fs/file_table.c b/fs/file_table.c
> index 3b3792903185..d19d879b6efc 100644
> --- a/fs/file_table.c
> +++ b/fs/file_table.c
> @@ -50,6 +50,9 @@ struct backing_file {
> struct path user_path;
> freeptr_t bf_freeptr;
> };
> +#ifdef CONFIG_SECURITY
> + void *security;
> +#endif
> };
>
> #define backing_file(f) container_of(f, struct backing_file, file)
> @@ -66,8 +69,21 @@ void backing_file_set_user_path(struct file *f, const struct path *path)
> }
> EXPORT_SYMBOL_GPL(backing_file_set_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);
> kmem_cache_free(bfilp_cachep, ff);
> }
> @@ -288,10 +304,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);
> }
>
> /*
> @@ -301,7 +319,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;
> @@ -318,7 +337,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 72de97c03d0e..f2d08ac2459b 100644
> --- a/fs/fuse/passthrough.c
> +++ b/fs/fuse/passthrough.c
> @@ -167,7 +167,7 @@ struct fuse_backing *fuse_passthrough_open(struct file *file, int backing_id)
> 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 cbc384a1aa09..77e90e4124e0 100644
> --- a/fs/internal.h
> +++ b/fs/internal.h
> @@ -106,7 +106,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);
> void backing_file_set_user_path(struct file *f, const struct path *path);
>
> static inline void file_put_write_access(struct file *file)
> diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
> index ff3dbd1ca61f..f2f20a611af3 100644
> --- a/fs/overlayfs/dir.c
> +++ b/fs/overlayfs/dir.c
> @@ -1374,7 +1374,7 @@ static int ovl_create_tmpfile(struct file *file, struct dentry *dentry,
> return PTR_ERR(cred);
>
> 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 97bed2286030..27cc07738f33 100644
> --- a/fs/overlayfs/file.c
> +++ b/fs/overlayfs/file.c
> @@ -48,7 +48,7 @@ static struct file *ovl_open_realfile(const struct file *file,
> if (!inode_owner_or_capable(real_idmap, realinode))
> flags &= ~O_NOATIME;
>
> - realfile = backing_file_open(file_user_path(file),
> + realfile = backing_file_open(file,
> flags, realpath, current_cred());
> }
> }
> diff --git a/include/linux/backing-file.h b/include/linux/backing-file.h
> index 1476a6ed1bfd..c939cd222730 100644
> --- a/include/linux/backing-file.h
> +++ b/include/linux/backing-file.h
> @@ -18,10 +18,10 @@ struct backing_file_ctx {
> void (*end_write)(struct kiocb *iocb, 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 8b3dd145b25e..d0d0e8f55589 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -2475,6 +2475,19 @@ struct file *dentry_create(struct path *path, int flags, umode_t mode,
> const struct cred *cred);
> const struct path *backing_file_user_path(const 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 382c56a97bba..584db296e43b 100644
> --- a/include/linux/lsm_audit.h
> +++ b/include/linux/lsm_audit.h
> @@ -94,7 +94,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 8c42b4bde09c..b4958167e381 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -191,6 +191,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,
> @@ -198,6 +201,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 d48bf0ad26f4..b4f8cad53ddb 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 {
> unsigned int lbs_cred;
> unsigned int lbs_file;
> + unsigned int lbs_backing_file;
> unsigned int lbs_ib;
> unsigned int lbs_inode;
> unsigned int lbs_sock;
> diff --git a/include/linux/security.h b/include/linux/security.h
> index ee88dd2d2d1f..8d2d4856934e 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -472,11 +472,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);
> @@ -1141,6 +1147,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)
> {
> @@ -1160,6 +1175,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/lsm.h b/security/lsm.h
> index db77cc83e158..32f808ad4335 100644
> --- a/security/lsm.h
> +++ b/security/lsm.h
> @@ -29,6 +29,7 @@ extern struct lsm_blob_sizes blob_sizes;
>
> /* LSM blob caches */
> extern struct kmem_cache *lsm_file_cache;
> +extern struct kmem_cache *lsm_backing_file_cache;
> extern struct kmem_cache *lsm_inode_cache;
>
> /* LSM blob allocators */
> diff --git a/security/lsm_init.c b/security/lsm_init.c
> index 573e2a7250c4..7c0fd17f1601 100644
> --- a/security/lsm_init.c
> +++ b/security/lsm_init.c
> @@ -293,6 +293,8 @@ static void __init lsm_prepare(struct lsm_info *lsm)
> blobs = lsm->blobs;
> lsm_blob_size_update(&blobs->lbs_cred, &blob_sizes.lbs_cred);
> lsm_blob_size_update(&blobs->lbs_file, &blob_sizes.lbs_file);
> + lsm_blob_size_update(&blobs->lbs_backing_file,
> + &blob_sizes.lbs_backing_file);
> lsm_blob_size_update(&blobs->lbs_ib, &blob_sizes.lbs_ib);
> /* inode blob gets an rcu_head in addition to LSM blobs. */
> if (blobs->lbs_inode && blob_sizes.lbs_inode == 0)
> @@ -441,6 +443,8 @@ int __init security_init(void)
> if (lsm_debug) {
> lsm_pr("blob(cred) size %d\n", blob_sizes.lbs_cred);
> lsm_pr("blob(file) size %d\n", blob_sizes.lbs_file);
> + lsm_pr("blob(backing_file) size %d\n",
> + blob_sizes.lbs_backing_file);
> lsm_pr("blob(ib) size %d\n", blob_sizes.lbs_ib);
> lsm_pr("blob(inode) size %d\n", blob_sizes.lbs_inode);
> lsm_pr("blob(ipc) size %d\n", blob_sizes.lbs_ipc);
> @@ -462,6 +466,11 @@ int __init security_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,
> diff --git a/security/security.c b/security/security.c
> index a26c1474e2e4..048560ef6a1a 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -82,6 +82,7 @@ const struct lsm_id *lsm_idlist[MAX_LSM_COUNT];
> struct lsm_blob_sizes blob_sizes;
>
> struct kmem_cache *lsm_file_cache;
> +struct kmem_cache *lsm_backing_file_cache;
> struct kmem_cache *lsm_inode_cache;
>
> #define SECURITY_HOOK_ACTIVE_KEY(HOOK, IDX) security_hook_active_##HOOK##_##IDX
> @@ -173,6 +174,30 @@ static int lsm_file_alloc(struct file *file)
> return 0;
> }
>
> +/**
> + * 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
> @@ -2418,6 +2443,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
> @@ -2506,6 +2582,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.53.0
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v4 3/3] selinux: fix overlayfs mmap() and mprotect() access checks
2026-04-03 3:08 ` [PATCH v4 3/3] selinux: fix overlayfs mmap() and mprotect() access checks Paul Moore
2026-04-03 6:17 ` Amir Goldstein
@ 2026-04-07 12:14 ` Stephen Smalley
2026-04-07 14:35 ` Paul Moore
1 sibling, 1 reply; 11+ messages in thread
From: Stephen Smalley @ 2026-04-07 12:14 UTC (permalink / raw)
To: Paul Moore, Ondrej Mosnacek
Cc: linux-security-module, selinux, linux-fsdevel, linux-unionfs,
linux-erofs, Amir Goldstein, Gao Xiang, Christian Brauner
On Thu, Apr 2, 2026 at 11:09 PM Paul Moore <paul@paul-moore.com> wrote:
>
> 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
> Signed-off-by: Paul Moore <paul@paul-moore.com>
Do you have tests for these changes showing the before and after (i.e.
failing without your patches, passing with them)? I tried running an
earlier set from Ondrej but they failed.
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v4 3/3] selinux: fix overlayfs mmap() and mprotect() access checks
2026-04-07 12:14 ` Stephen Smalley
@ 2026-04-07 14:35 ` Paul Moore
0 siblings, 0 replies; 11+ messages in thread
From: Paul Moore @ 2026-04-07 14:35 UTC (permalink / raw)
To: Stephen Smalley
Cc: Ondrej Mosnacek, linux-security-module, selinux, linux-fsdevel,
linux-unionfs, linux-erofs, Amir Goldstein, Gao Xiang,
Christian Brauner
On Tue, Apr 7, 2026 at 8:14 AM Stephen Smalley
<stephen.smalley.work@gmail.com> wrote:
> On Thu, Apr 2, 2026 at 11:09 PM Paul Moore <paul@paul-moore.com> wrote:
> >
> > 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
> > Signed-off-by: Paul Moore <paul@paul-moore.com>
>
> Do you have tests for these changes showing the before and after (i.e.
> failing without your patches, passing with them)? I tried running an
> earlier set from Ondrej but they failed.
A few months ago I sent you and Ondrej some feedback on those early
tests from Ondrej, but yes, I also had problems with Ondrej's tests.
I've been using a hacked up combination of the existing tests, some of
Ondrej's additions, and an additional debug/test patch to ensure the
labeling is correct. It's far from ideal, but I didn't invest time in
test development as I assumed Ondrej would continue his efforts there
(unfortunately it doesn't appear that he has?), and I wanted to focus
on getting a solution as soon as possible for obvious reasons.
--
paul-moore.com
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-04-07 14:35 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-03 3:08 [PATCH v4 0/3] Fix incorrect overlayfs mmap() and mprotect() LSM access controls Paul Moore
2026-04-03 3:08 ` [PATCH v4 1/3] fs: prepare for adding LSM blob to backing_file Paul Moore
2026-04-05 0:14 ` Serge E. Hallyn
2026-04-03 3:08 ` [PATCH v4 2/3] lsm: add backing_file LSM hooks Paul Moore
2026-04-03 6:12 ` Amir Goldstein
2026-04-03 21:14 ` Paul Moore
2026-04-05 3:12 ` Serge E. Hallyn
2026-04-03 3:08 ` [PATCH v4 3/3] selinux: fix overlayfs mmap() and mprotect() access checks Paul Moore
2026-04-03 6:17 ` Amir Goldstein
2026-04-07 12:14 ` Stephen Smalley
2026-04-07 14:35 ` Paul Moore
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox