]> Gentwo Git Trees - linux/.git/commitdiff
memfd,selinux: call security_inode_init_security_anon()
authorThiébaud Weksteen <tweek@google.com>
Thu, 18 Sep 2025 02:04:34 +0000 (12:04 +1000)
committerPaul Moore <paul@paul-moore.com>
Wed, 22 Oct 2025 23:28:27 +0000 (19:28 -0400)
Prior to this change, no security hooks were called at the creation of a
memfd file. It means that, for SELinux as an example, it will receive
the default type of the filesystem that backs the in-memory inode. In
most cases, that would be tmpfs, but if MFD_HUGETLB is passed, it will
be hugetlbfs. Both can be considered implementation details of memfd.

It also means that it is not possible to differentiate between a file
coming from memfd_create and a file coming from a standard tmpfs mount
point.

Additionally, no permission is validated at creation, which differs from
the similar memfd_secret syscall.

Call security_inode_init_security_anon during creation. This ensures
that the file is setup similarly to other anonymous inodes. On SELinux,
it means that the file will receive the security context of its task.

The ability to limit fexecve on memfd has been of interest to avoid
potential pitfalls where /proc/self/exe or similar would be executed
[1][2]. Reuse the "execute_no_trans" and "entrypoint" access vectors,
similarly to the file class. These access vectors may not make sense for
the existing "anon_inode" class. Therefore, define and assign a new
class "memfd_file" to support such access vectors.

Guard these changes behind a new policy capability named "memfd_class".

[1] https://crbug.com/1305267
[2] https://lore.kernel.org/lkml/20221215001205.51969-1-jeffxu@google.com/

Signed-off-by: Thiébaud Weksteen <tweek@google.com>
Reviewed-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Tested-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Acked-by: Hugh Dickins <hughd@google.com>
[PM: subj tweak]
Signed-off-by: Paul Moore <paul@paul-moore.com>
include/linux/memfd.h
mm/memfd.c
security/selinux/hooks.c
security/selinux/include/classmap.h
security/selinux/include/policycap.h
security/selinux/include/policycap_names.h
security/selinux/include/security.h

index 6f606d9573c385a28c315559e0ebcceb9f46c08c..cc74de3dbcfe93d327f45748439e9cf604bba5e7 100644 (file)
@@ -4,6 +4,8 @@
 
 #include <linux/file.h>
 
+#define MEMFD_ANON_NAME "[memfd]"
+
 #ifdef CONFIG_MEMFD_CREATE
 extern long memfd_fcntl(struct file *file, unsigned int cmd, unsigned int arg);
 struct folio *memfd_alloc_folio(struct file *memfd, pgoff_t idx);
index 1d109c1acf211b3c3c777ffda539a8089a0b7536..a61acbe5ded3b9ee73935f6d828c9d3f11d3c022 100644 (file)
@@ -433,6 +433,8 @@ static struct file *alloc_file(const char *name, unsigned int flags)
 {
        unsigned int *file_seals;
        struct file *file;
+       struct inode *inode;
+       int err = 0;
 
        if (flags & MFD_HUGETLB) {
                file = hugetlb_file_setup(name, 0, VM_NORESERVE,
@@ -444,12 +446,20 @@ static struct file *alloc_file(const char *name, unsigned int flags)
        }
        if (IS_ERR(file))
                return file;
+
+       inode = file_inode(file);
+       err = security_inode_init_security_anon(inode,
+                       &QSTR(MEMFD_ANON_NAME), NULL);
+       if (err) {
+               fput(file);
+               file = ERR_PTR(err);
+               return file;
+       }
+
        file->f_mode |= FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE;
        file->f_flags |= O_LARGEFILE;
 
        if (flags & MFD_NOEXEC_SEAL) {
-               struct inode *inode = file_inode(file);
-
                inode->i_mode &= ~0111;
                file_seals = memfd_file_seals_ptr(file);
                if (file_seals) {
index dfc22da42f307d00ac6a477b833fbdf8bb54e228..a22b1920242f200a94aebb167bcb45d081e0c876 100644 (file)
@@ -93,6 +93,7 @@
 #include <linux/fanotify.h>
 #include <linux/io_uring/cmd.h>
 #include <uapi/linux/lsm.h>
+#include <linux/memfd.h>
 
 #include "avc.h"
 #include "objsec.h"
@@ -2319,6 +2320,10 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm)
        new_tsec = selinux_cred(bprm->cred);
        isec = inode_security(inode);
 
+       if (WARN_ON(isec->sclass != SECCLASS_FILE &&
+                   isec->sclass != SECCLASS_MEMFD_FILE))
+               return -EACCES;
+
        /* Default to the current task SID. */
        new_tsec->sid = old_tsec->sid;
        new_tsec->osid = old_tsec->sid;
@@ -2371,8 +2376,8 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm)
        ad.u.file = bprm->file;
 
        if (new_tsec->sid == old_tsec->sid) {
-               rc = avc_has_perm(old_tsec->sid, isec->sid,
-                                 SECCLASS_FILE, FILE__EXECUTE_NO_TRANS, &ad);
+               rc = avc_has_perm(old_tsec->sid, isec->sid, isec->sclass,
+                                 FILE__EXECUTE_NO_TRANS, &ad);
                if (rc)
                        return rc;
        } else {
@@ -2382,8 +2387,8 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm)
                if (rc)
                        return rc;
 
-               rc = avc_has_perm(new_tsec->sid, isec->sid,
-                                 SECCLASS_FILE, FILE__ENTRYPOINT, &ad);
+               rc = avc_has_perm(new_tsec->sid, isec->sid, isec->sclass,
+                                 FILE__ENTRYPOINT, &ad);
                if (rc)
                        return rc;
 
@@ -2978,10 +2983,18 @@ static int selinux_inode_init_security_anon(struct inode *inode,
        struct common_audit_data ad;
        struct inode_security_struct *isec;
        int rc;
+       bool is_memfd = false;
 
        if (unlikely(!selinux_initialized()))
                return 0;
 
+       if (name != NULL && name->name != NULL &&
+           !strcmp(name->name, MEMFD_ANON_NAME)) {
+               if (!selinux_policycap_memfd_class())
+                       return 0;
+               is_memfd = true;
+       }
+
        isec = selinux_inode(inode);
 
        /*
@@ -3001,7 +3014,10 @@ static int selinux_inode_init_security_anon(struct inode *inode,
                isec->sclass = context_isec->sclass;
                isec->sid = context_isec->sid;
        } else {
-               isec->sclass = SECCLASS_ANON_INODE;
+               if (is_memfd)
+                       isec->sclass = SECCLASS_MEMFD_FILE;
+               else
+                       isec->sclass = SECCLASS_ANON_INODE;
                rc = security_transition_sid(
                        sid, sid,
                        isec->sclass, name, &isec->sid);
index 5665aa5e7853e1e647d3123d04c150d8cdd13182..3ec85142771fcc1da02cff7d655572fa8400780a 100644 (file)
@@ -179,6 +179,8 @@ const struct security_class_mapping secclass_map[] = {
        { "anon_inode", { COMMON_FILE_PERMS, NULL } },
        { "io_uring", { "override_creds", "sqpoll", "cmd", "allowed", NULL } },
        { "user_namespace", { "create", NULL } },
+       { "memfd_file",
+         { COMMON_FILE_PERMS, "execute_no_trans", "entrypoint", NULL } },
        /* last one */ { NULL, {} }
 };
 
index 135a969f873cad612ec9c3756678d187579f9a1e..231d02227e593a6d891cdc2fd5ced5b9a4b03506 100644 (file)
@@ -18,6 +18,7 @@ enum {
        POLICYDB_CAP_NETIF_WILDCARD,
        POLICYDB_CAP_GENFS_SECLABEL_WILDCARD,
        POLICYDB_CAP_FUNCTIONFS_SECLABEL,
+       POLICYDB_CAP_MEMFD_CLASS,
        __POLICYDB_CAP_MAX
 };
 #define POLICYDB_CAP_MAX (__POLICYDB_CAP_MAX - 1)
index ff88828876517166cbd7ed2cff09a9820bd81c3c..454dab37bda3fcac3eaa34b288e4348686ba0334 100644 (file)
@@ -21,6 +21,7 @@ const char *const selinux_policycap_names[__POLICYDB_CAP_MAX] = {
        "netif_wildcard",
        "genfs_seclabel_wildcard",
        "functionfs_seclabel",
+       "memfd_class",
 };
 /* clang-format on */
 
index 0f954a40d3fc7454429b6839f47cfb6a831f8021..5d1dad8058b1456a5abebb539dce3b9c8a4ccd11 100644 (file)
@@ -209,6 +209,11 @@ static inline bool selinux_policycap_functionfs_seclabel(void)
                selinux_state.policycap[POLICYDB_CAP_FUNCTIONFS_SECLABEL]);
 }
 
+static inline bool selinux_policycap_memfd_class(void)
+{
+       return READ_ONCE(selinux_state.policycap[POLICYDB_CAP_MEMFD_CLASS]);
+}
+
 struct selinux_policy_convert_data;
 
 struct selinux_load_state {