AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/01 01:31 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_LSM",
    "EVM",
    "SECURITY_SELINUX",
    "SECURITY_SMACK"
  ],
  "FocusSymbols": [
    "bpf_init_inode_xattr",
    "security_inode_init_security",
    "evm_inode_init_security",
    "selinux_inode_init_security",
    "smack_inode_init_security"
  ],
  "Reasoning": "The patch introduces a new BPF kfunc `bpf_init_inode_xattr` that allows BPF LSM programs to set xattrs on a new inode during `inode_init_security`. It modifies the `inode_init_security` LSM hook signature to use a new `struct lsm_xattrs` structure, affecting core LSM, EVM, SELinux, and Smack. These are functional changes to reachable core kernel logic and should be fuzzed.",
  "WorthFuzzing": true
}

1/1 2026/07/01 01:31 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 346484b3f9b097984b92dda1a0980c5d2fc3cd99\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Wed Jul 1 01:31:24 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c\nindex 768aca2dc0f034..c4023c82f21e40 100644\n--- a/fs/bpf_fs_kfuncs.c\n+++ b/fs/bpf_fs_kfuncs.c\n@@ -10,6 +10,7 @@\n #include \u003clinux/fsnotify.h\u003e\n #include \u003clinux/file.h\u003e\n #include \u003clinux/kernfs.h\u003e\n+#include \u003clinux/lsm_hooks.h\u003e\n #include \u003clinux/mm.h\u003e\n #include \u003clinux/xattr.h\u003e\n \n@@ -374,6 +375,83 @@ __bpf_kfunc struct inode *bpf_real_inode(struct dentry *dentry)\n \treturn d_real_inode(dentry);\n }\n \n+static int bpf_xattrs_used(const struct lsm_xattrs *ctx)\n+{\n+\tconst size_t prefix_len = sizeof(XATTR_BPF_LSM_SUFFIX) - 1;\n+\tunsigned int i, n = 0;\n+\n+\tfor (i = 0; i \u003c ctx-\u003exattr_count; i++) {\n+\t\tconst char *name = ctx-\u003exattrs[i].name;\n+\n+\t\tif (name \u0026\u0026 !strncmp(name, XATTR_BPF_LSM_SUFFIX, prefix_len))\n+\t\t\tn++;\n+\t}\n+\treturn n;\n+}\n+\n+/**\n+ * bpf_init_inode_xattr - set an xattr on a new inode from inode_init_security\n+ * @xattrs: inode_init_security xattr state from the hook context\n+ * @name__str: xattr name (e.g., \"bpf.file_label\")\n+ * @value_p: dynptr containing the xattr value\n+ *\n+ * Only callable from lsm/inode_init_security programs.\n+ *\n+ * Return: 0 on success, negative error on failure.\n+ */\n+__bpf_kfunc int bpf_init_inode_xattr(struct lsm_xattrs *xattrs,\n+\t\t\t\t     const char *name__str,\n+\t\t\t\t     const struct bpf_dynptr *value_p)\n+{\n+\tstruct bpf_dynptr_kern *value_ptr = (struct bpf_dynptr_kern *)value_p;\n+\tsize_t name_len;\n+\tvoid *xattr_value;\n+\tstruct xattr *xattr;\n+\tconst void *value;\n+\tu32 value_len;\n+\n+\tif (!xattrs || !xattrs-\u003exattrs || !name__str)\n+\t\treturn -EINVAL;\n+\tif (bpf_xattrs_used(xattrs) \u003e= BPF_LSM_INODE_INIT_XATTRS)\n+\t\treturn -ENOSPC;\n+\n+\tname_len = strlen(name__str);\n+\tif (name_len == 0 || name_len \u003e XATTR_NAME_MAX)\n+\t\treturn -EINVAL;\n+\tif (strncmp(name__str, XATTR_BPF_LSM_SUFFIX,\n+\t\t    sizeof(XATTR_BPF_LSM_SUFFIX) - 1))\n+\t\treturn -EPERM;\n+\n+\tvalue_len = __bpf_dynptr_size(value_ptr);\n+\tif (value_len == 0 || value_len \u003e XATTR_SIZE_MAX)\n+\t\treturn -EINVAL;\n+\n+\tvalue = __bpf_dynptr_data(value_ptr, value_len);\n+\tif (!value)\n+\t\treturn -EINVAL;\n+\n+\t/* Combine xattr value + name into one allocation. */\n+\txattr_value = kmalloc(value_len + name_len + 1, GFP_NOFS);\n+\tif (!xattr_value)\n+\t\treturn -ENOMEM;\n+\n+\tmemcpy(xattr_value, value, value_len);\n+\tmemcpy(xattr_value + value_len, name__str, name_len);\n+\t((char *)xattr_value)[value_len + name_len] = '\\0';\n+\n+\txattr = lsm_get_xattr_slot(xattrs);\n+\tif (!xattr) {\n+\t\tkfree(xattr_value);\n+\t\treturn -ENOSPC;\n+\t}\n+\n+\txattr-\u003evalue = xattr_value;\n+\txattr-\u003ename = (const char *)xattr_value + value_len;\n+\txattr-\u003evalue_len = value_len;\n+\n+\treturn 0;\n+}\n+\n __bpf_kfunc_end_defs();\n \n BTF_KFUNCS_START(bpf_fs_kfunc_set_ids)\n@@ -385,6 +463,7 @@ BTF_ID_FLAGS(func, bpf_get_file_xattr, KF_SLEEPABLE)\n BTF_ID_FLAGS(func, bpf_set_dentry_xattr, KF_SLEEPABLE)\n BTF_ID_FLAGS(func, bpf_remove_dentry_xattr, KF_SLEEPABLE)\n BTF_ID_FLAGS(func, bpf_real_inode, KF_SLEEPABLE | KF_RET_NULL)\n+BTF_ID_FLAGS(func, bpf_init_inode_xattr, KF_SLEEPABLE)\n BTF_KFUNCS_END(bpf_fs_kfunc_set_ids)\n \n static int bpf_fs_kfuncs_filter(const struct bpf_prog *prog, u32 kfunc_id)\ndiff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h\nindex 143775a27a2a0b..b655c708818e59 100644\n--- a/include/linux/bpf_lsm.h\n+++ b/include/linux/bpf_lsm.h\n@@ -19,6 +19,9 @@\n #include \u003clinux/lsm_hook_defs.h\u003e\n #undef LSM_HOOK\n \n+/* max bpf xattrs per inode */\n+#define BPF_LSM_INODE_INIT_XATTRS 4\n+\n struct bpf_storage_blob {\n \tstruct bpf_local_storage __rcu *storage;\n };\ndiff --git a/include/linux/evm.h b/include/linux/evm.h\nindex 913f4573b2038e..528f360f3308de 100644\n--- a/include/linux/evm.h\n+++ b/include/linux/evm.h\n@@ -12,6 +12,8 @@\n #include \u003clinux/integrity.h\u003e\n #include \u003clinux/xattr.h\u003e\n \n+struct lsm_xattrs;\n+\n #ifdef CONFIG_EVM\n extern int evm_set_key(void *key, size_t keylen);\n extern enum integrity_status evm_verifyxattr(struct dentry *dentry,\n@@ -21,8 +23,8 @@ extern enum integrity_status evm_verifyxattr(struct dentry *dentry,\n int evm_fix_hmac(struct dentry *dentry, const char *xattr_name,\n \t\t const char *xattr_value, size_t xattr_value_len);\n int evm_inode_init_security(struct inode *inode, struct inode *dir,\n-\t\t\t    const struct qstr *qstr, struct xattr *xattrs,\n-\t\t\t    int *xattr_count);\n+\t\t\t    const struct qstr *qstr,\n+\t\t\t    struct lsm_xattrs *xattrs);\n extern bool evm_revalidate_status(const char *xattr_name);\n extern int evm_protected_xattr_if_enabled(const char *req_xattr_name);\n extern int evm_read_protected_xattrs(struct dentry *dentry, u8 *buffer,\n@@ -63,8 +65,7 @@ static inline int evm_fix_hmac(struct dentry *dentry, const char *xattr_name,\n \n static inline int evm_inode_init_security(struct inode *inode, struct inode *dir,\n \t\t\t\t\t  const struct qstr *qstr,\n-\t\t\t\t\t  struct xattr *xattrs,\n-\t\t\t\t\t  int *xattr_count)\n+\t\t\t\t\t  struct lsm_xattrs *xattrs)\n {\n \treturn 0;\n }\ndiff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h\nindex 65c9609ec20770..5b2de7865ce81a 100644\n--- a/include/linux/lsm_hook_defs.h\n+++ b/include/linux/lsm_hook_defs.h\n@@ -116,8 +116,8 @@ LSM_HOOK(int, 0, inode_alloc_security, struct inode *inode)\n LSM_HOOK(void, LSM_RET_VOID, inode_free_security, struct inode *inode)\n LSM_HOOK(void, LSM_RET_VOID, inode_free_security_rcu, void *inode_security)\n LSM_HOOK(int, -EOPNOTSUPP, inode_init_security, struct inode *inode,\n-\t struct inode *dir, const struct qstr *qstr, struct xattr *xattrs,\n-\t int *xattr_count)\n+\t struct inode *dir, const struct qstr *qstr,\n+\t struct lsm_xattrs *xattrs)\n LSM_HOOK(int, 0, inode_init_security_anon, struct inode *inode,\n \t const struct qstr *name, const struct inode *context_inode)\n LSM_HOOK(int, 0, inode_create, struct inode *dir, struct dentry *dentry,\ndiff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h\nindex b4f8cad53ddb03..7afe06a8d4c6c5 100644\n--- a/include/linux/lsm_hooks.h\n+++ b/include/linux/lsm_hooks.h\n@@ -200,20 +200,18 @@ extern struct lsm_static_calls_table static_calls_table __ro_after_init;\n \n /**\n  * lsm_get_xattr_slot - Return the next available slot and increment the index\n- * @xattrs: array storing LSM-provided xattrs\n- * @xattr_count: number of already stored xattrs (updated)\n+ * @ctx: xattr state shared by inode_init_security hooks\n  *\n- * Retrieve the first available slot in the @xattrs array to fill with an xattr,\n- * and increment @xattr_count.\n+ * Retrieve the first available slot in the @ctx-\u003exattrs array to fill with an\n+ * xattr, and increment @ctx-\u003exattr_count.\n  *\n- * Return: The slot to fill in @xattrs if non-NULL, NULL otherwise.\n+ * Return: The slot to fill in @ctx-\u003exattrs if non-NULL, NULL otherwise.\n  */\n-static inline struct xattr *lsm_get_xattr_slot(struct xattr *xattrs,\n-\t\t\t\t\t       int *xattr_count)\n+static inline struct xattr *lsm_get_xattr_slot(struct lsm_xattrs *ctx)\n {\n-\tif (unlikely(!xattrs))\n+\tif (unlikely(!ctx || !ctx-\u003exattrs))\n \t\treturn NULL;\n-\treturn \u0026xattrs[(*xattr_count)++];\n+\treturn \u0026ctx-\u003exattrs[ctx-\u003exattr_count++];\n }\n \n #endif /* ! __LINUX_LSM_HOOKS_H */\ndiff --git a/include/linux/security.h b/include/linux/security.h\nindex 153e9043058f88..0be590c4068950 100644\n--- a/include/linux/security.h\n+++ b/include/linux/security.h\n@@ -68,6 +68,11 @@ struct watch;\n struct watch_notification;\n struct lsm_ctx;\n \n+struct lsm_xattrs {\n+\tstruct xattr *xattrs;\n+\tunsigned int xattr_count;\n+};\n+\n /* Default (no) options for the capable function */\n #define CAP_OPT_NONE 0x0\n /* If capable should audit the security request */\ndiff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c\nindex 564071a92d7d20..1c3f84a9242039 100644\n--- a/kernel/bpf/bpf_lsm.c\n+++ b/kernel/bpf/bpf_lsm.c\n@@ -315,6 +315,7 @@ BTF_ID(func, bpf_lsm_inode_create)\n BTF_ID(func, bpf_lsm_inode_free_security)\n BTF_ID(func, bpf_lsm_inode_getattr)\n BTF_ID(func, bpf_lsm_inode_getxattr)\n+BTF_ID(func, bpf_lsm_inode_init_security)\n BTF_ID(func, bpf_lsm_inode_mknod)\n BTF_ID(func, bpf_lsm_inode_need_killpriv)\n BTF_ID(func, bpf_lsm_inode_post_setxattr)\ndiff --git a/security/bpf/hooks.c b/security/bpf/hooks.c\nindex 40efde233f3ab3..d7c44c5c0e3046 100644\n--- a/security/bpf/hooks.c\n+++ b/security/bpf/hooks.c\n@@ -30,6 +30,7 @@ static int __init bpf_lsm_init(void)\n \n struct lsm_blob_sizes bpf_lsm_blob_sizes __ro_after_init = {\n \t.lbs_inode = sizeof(struct bpf_storage_blob),\n+\t.lbs_xattr_count = BPF_LSM_INODE_INIT_XATTRS,\n };\n \n DEFINE_LSM(bpf) = {\ndiff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c\nindex b59e3f121b8a89..b7158fc63543e0 100644\n--- a/security/integrity/evm/evm_main.c\n+++ b/security/integrity/evm/evm_main.c\n@@ -1062,14 +1062,16 @@ static int evm_inode_copy_up_xattr(struct dentry *src, const char *name)\n  * evm_inode_init_security - initializes security.evm HMAC value\n  */\n int evm_inode_init_security(struct inode *inode, struct inode *dir,\n-\t\t\t    const struct qstr *qstr, struct xattr *xattrs,\n-\t\t\t    int *xattr_count)\n+\t\t\t    const struct qstr *qstr,\n+\t\t\t    struct lsm_xattrs *lsm_xattrs)\n {\n \tstruct evm_xattr *xattr_data;\n \tstruct xattr *xattr, *evm_xattr;\n+\tstruct xattr *xattrs;\n \tbool evm_protected_xattrs = false;\n \tint rc;\n \n+\txattrs = lsm_xattrs ? lsm_xattrs-\u003exattrs : NULL;\n \tif (!(evm_initialized \u0026 EVM_INIT_HMAC) || !xattrs)\n \t\treturn 0;\n \n@@ -1087,7 +1089,7 @@ int evm_inode_init_security(struct inode *inode, struct inode *dir,\n \tif (!evm_protected_xattrs)\n \t\treturn 0;\n \n-\tevm_xattr = lsm_get_xattr_slot(xattrs, xattr_count);\n+\tevm_xattr = lsm_get_xattr_slot(lsm_xattrs);\n \t/*\n \t * Array terminator (xattr name = NULL) must be the first non-filled\n \t * xattr slot.\ndiff --git a/security/security.c b/security/security.c\nindex 71aea8fdf01429..2ad7f09c1a6110 100644\n--- a/security/security.c\n+++ b/security/security.c\n@@ -1333,8 +1333,8 @@ int security_inode_init_security(struct inode *inode, struct inode *dir,\n \t\t\t\t const initxattrs initxattrs, void *fs_data)\n {\n \tstruct lsm_static_call *scall;\n-\tstruct xattr *new_xattrs = NULL;\n-\tint ret = -EOPNOTSUPP, xattr_count = 0;\n+\tstruct lsm_xattrs xattrs = {};\n+\tint ret = -EOPNOTSUPP;\n \n \tif (unlikely(IS_PRIVATE(inode)))\n \t\treturn 0;\n@@ -1344,15 +1344,15 @@ int security_inode_init_security(struct inode *inode, struct inode *dir,\n \n \tif (initxattrs) {\n \t\t/* Allocate +1 as terminator. */\n-\t\tnew_xattrs = kcalloc(blob_sizes.lbs_xattr_count + 1,\n-\t\t\t\t     sizeof(*new_xattrs), GFP_NOFS);\n-\t\tif (!new_xattrs)\n+\t\txattrs.xattrs = kcalloc(blob_sizes.lbs_xattr_count + 1,\n+\t\t\t\t\tsizeof(*xattrs.xattrs), GFP_NOFS);\n+\t\tif (!xattrs.xattrs)\n \t\t\treturn -ENOMEM;\n \t}\n \n \tlsm_for_each_hook(scall, inode_init_security) {\n-\t\tret = scall-\u003ehl-\u003ehook.inode_init_security(inode, dir, qstr, new_xattrs,\n-\t\t\t\t\t\t  \u0026xattr_count);\n+\t\tret = scall-\u003ehl-\u003ehook.inode_init_security(inode, dir, qstr,\n+\t\t\t\t\t\t\t  \u0026xattrs);\n \t\tif (ret \u0026\u0026 ret != -EOPNOTSUPP)\n \t\t\tgoto out;\n \t\t/*\n@@ -1364,14 +1364,14 @@ int security_inode_init_security(struct inode *inode, struct inode *dir,\n \t}\n \n \t/* If initxattrs() is NULL, xattr_count is zero, skip the call. */\n-\tif (!xattr_count)\n+\tif (!xattrs.xattr_count)\n \t\tgoto out;\n \n-\tret = initxattrs(inode, new_xattrs, fs_data);\n+\tret = initxattrs(inode, xattrs.xattrs, fs_data);\n out:\n-\tfor (; xattr_count \u003e 0; xattr_count--)\n-\t\tkfree(new_xattrs[xattr_count - 1].value);\n-\tkfree(new_xattrs);\n+\tfor (; xattrs.xattr_count \u003e 0; xattrs.xattr_count--)\n+\t\tkfree(xattrs.xattrs[xattrs.xattr_count - 1].value);\n+\tkfree(xattrs.xattrs);\n \treturn (ret == -EOPNOTSUPP) ? 0 : ret;\n }\n EXPORT_SYMBOL(security_inode_init_security);\ndiff --git a/security/selinux/hooks.c b/security/selinux/hooks.c\nindex 1a713d96206f59..6bba6b212e17c5 100644\n--- a/security/selinux/hooks.c\n+++ b/security/selinux/hooks.c\n@@ -2962,7 +2962,7 @@ static int selinux_dentry_create_files_as(struct dentry *dentry, int mode,\n \n static int selinux_inode_init_security(struct inode *inode, struct inode *dir,\n \t\t\t\t       const struct qstr *qstr,\n-\t\t\t\t       struct xattr *xattrs, int *xattr_count)\n+\t\t\t\t       struct lsm_xattrs *xattrs)\n {\n \tconst struct cred_security_struct *crsec = selinux_cred(current_cred());\n \tstruct superblock_security_struct *sbsec;\n@@ -2992,7 +2992,7 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir,\n \t    !(sbsec-\u003eflags \u0026 SBLABEL_MNT))\n \t\treturn -EOPNOTSUPP;\n \n-\txattr = lsm_get_xattr_slot(xattrs, xattr_count);\n+\txattr = lsm_get_xattr_slot(xattrs);\n \tif (xattr) {\n \t\trc = security_sid_to_context_force(newsid,\n \t\t\t\t\t\t   \u0026context, \u0026clen);\ndiff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c\nindex ff115068c5c06f..4501078430ca05 100644\n--- a/security/smack/smack_lsm.c\n+++ b/security/smack/smack_lsm.c\n@@ -981,10 +981,10 @@ smk_rule_transmutes(struct smack_known *subject,\n }\n \n static int\n-xattr_dupval(struct xattr *xattrs, int *xattr_count,\n+xattr_dupval(struct lsm_xattrs *xattrs,\n \t     const char *name, const void *value, unsigned int vallen)\n {\n-\tstruct xattr * const xattr = lsm_get_xattr_slot(xattrs, xattr_count);\n+\tstruct xattr * const xattr = lsm_get_xattr_slot(xattrs);\n \n \tif (!xattr)\n \t\treturn 0;\n@@ -1003,14 +1003,13 @@ xattr_dupval(struct xattr *xattrs, int *xattr_count,\n  * @inode: the newly created inode\n  * @dir: containing directory object\n  * @qstr: unused\n- * @xattrs: where to put the attributes\n- * @xattr_count: current number of LSM-provided xattrs (updated)\n+ * @xattrs: where to put attributes and update count\n  *\n  * Returns 0 if it all works out, -ENOMEM if there's no memory\n  */\n static int smack_inode_init_security(struct inode *inode, struct inode *dir,\n \t\t\t\t     const struct qstr *qstr,\n-\t\t\t\t     struct xattr *xattrs, int *xattr_count)\n+\t\t\t\t     struct lsm_xattrs *xattrs)\n {\n \tstruct task_smack *tsp = smack_cred(current_cred());\n \tstruct inode_smack * const issp = smack_inode(inode);\n@@ -1057,21 +1056,19 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir,\n \t\tif (S_ISDIR(inode-\u003ei_mode)) {\n \t\t\ttransflag = SMK_INODE_TRANSMUTE;\n \n-\t\t\tif (xattr_dupval(xattrs, xattr_count,\n-\t\t\t\tXATTR_SMACK_TRANSMUTE,\n-\t\t\t\tTRANS_TRUE,\n-\t\t\t\tTRANS_TRUE_SIZE\n-\t\t\t))\n+\t\t\tif (xattr_dupval(xattrs,\n+\t\t\t\t\t XATTR_SMACK_TRANSMUTE,\n+\t\t\t\t\t TRANS_TRUE,\n+\t\t\t\t\t TRANS_TRUE_SIZE))\n \t\t\t\trc = -ENOMEM;\n \t\t}\n \t}\n \n \tif (rc == 0)\n-\t\tif (xattr_dupval(xattrs, xattr_count,\n-\t\t\t    XATTR_SMACK_SUFFIX,\n-\t\t\t    issp-\u003esmk_inode-\u003esmk_known,\n-\t\t     strlen(issp-\u003esmk_inode-\u003esmk_known)\n-\t\t))\n+\t\tif (xattr_dupval(xattrs,\n+\t\t\t\t XATTR_SMACK_SUFFIX,\n+\t\t\t\t issp-\u003esmk_inode-\u003esmk_known,\n+\t\t\t\t strlen(issp-\u003esmk_inode-\u003esmk_known)))\n \t\t\trc = -ENOMEM;\n instant_inode:\n \tissp-\u003esmk_flags |= (SMK_INODE_INSTANT | transflag);\ndiff --git a/tools/testing/selftests/bpf/bpf_kfuncs.h b/tools/testing/selftests/bpf/bpf_kfuncs.h\nindex ae71e9b6905186..2639f9f941955b 100644\n--- a/tools/testing/selftests/bpf/bpf_kfuncs.h\n+++ b/tools/testing/selftests/bpf/bpf_kfuncs.h\n@@ -92,4 +92,9 @@ extern int bpf_set_dentry_xattr(struct dentry *dentry, const char *name__str,\n \t\t\t\tconst struct bpf_dynptr *value_p, int flags) __ksym __weak;\n extern int bpf_remove_dentry_xattr(struct dentry *dentry, const char *name__str) __ksym __weak;\n \n+struct lsm_xattrs;\n+extern int bpf_init_inode_xattr(struct lsm_xattrs *xattrs,\n+\t\t\t\tconst char *name__str,\n+\t\t\t\tconst struct bpf_dynptr *value_p) __ksym __weak;\n+\n #endif\ndiff --git a/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c b/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c\nindex 43a26ec69a8e36..8b2e0d433aea2f 100644\n--- a/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c\n+++ b/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c\n@@ -10,6 +10,7 @@\n #include \"test_get_xattr.skel.h\"\n #include \"test_set_remove_xattr.skel.h\"\n #include \"test_fsverity.skel.h\"\n+#include \"test_init_inode_xattr.skel.h\"\n \n static const char testfile[] = \"/tmp/test_progs_fs_kfuncs\";\n \n@@ -268,6 +269,116 @@ static void test_fsverity(void)\n \tremove(testfile);\n }\n \n+static void test_init_inode_xattr(void)\n+{\n+\tstruct test_init_inode_xattr *skel = NULL;\n+\tint fd = -1, err;\n+\tchar value_out[64];\n+\tconst char *testfile_new = \"/tmp/test_progs_fs_kfuncs_new\";\n+\n+\tskel = test_init_inode_xattr__open_and_load();\n+\tif (!ASSERT_OK_PTR(skel, \"test_init_inode_xattr__open_and_load\"))\n+\t\treturn;\n+\n+\tskel-\u003ebss-\u003emonitored_pid = getpid();\n+\terr = test_init_inode_xattr__attach(skel);\n+\tif (!ASSERT_OK(err, \"test_init_inode_xattr__attach\"))\n+\t\tgoto out;\n+\n+\t/* Trigger inode_init_security */\n+\tfd = open(testfile_new, O_CREAT | O_RDWR, 0644);\n+\tif (!ASSERT_GE(fd, 0, \"create_file\"))\n+\t\tgoto out;\n+\n+\tASSERT_EQ(skel-\u003edata-\u003einit_result, 0, \"init_result\");\n+\n+\t/* initxattrs prepends \"security.\" to the name. */\n+\terr = getxattr(testfile_new, \"security.bpf.test_label\", value_out,\n+\t\t       sizeof(value_out));\n+\tif (err \u003c 0 \u0026\u0026 errno == ENODATA) {\n+\t\tprintf(\"%s:SKIP:filesystem did not apply LSM xattrs\\n\",\n+\t\t       __func__);\n+\t\ttest__skip();\n+\t\tgoto out;\n+\t}\n+\tif (!ASSERT_GE(err, 0, \"getxattr\"))\n+\t\tgoto out;\n+\n+\tASSERT_EQ(err, (int)sizeof(skel-\u003edata-\u003exattr_value), \"xattr_size\");\n+\tASSERT_EQ(strncmp(value_out, \"unconfined_u:object_r:user_home_t:s0\",\n+\t\t\t  sizeof(\"unconfined_u:object_r:user_home_t:s0\")), 0,\n+\t\t  \"xattr_value\");\n+\n+out:\n+\tclose(fd);\n+\ttest_init_inode_xattr__destroy(skel);\n+\tremove(testfile_new);\n+}\n+\n+/* Keep in sync with BPF_LSM_INODE_INIT_XATTRS in include/linux/bpf_lsm.h. */\n+#define INIT_INODE_XATTR_MAX 4\n+\n+/*\n+ * Programs may attach to inode_init_security without an attach-time limit, but\n+ * the kfunc only lets BPF claim INIT_INODE_XATTR_MAX xattr slots per inode.\n+ * Calls beyond that budget are rejected at runtime with -ENOSPC.\n+ */\n+static void test_init_inode_xattr_slot_limit(void)\n+{\n+\tstruct test_init_inode_xattr *skel[INIT_INODE_XATTR_MAX + 1] = {};\n+\tstruct bpf_link *link[INIT_INODE_XATTR_MAX + 1] = {};\n+\tconst char *testfile_slot = \"/tmp/test_progs_fs_kfuncs_slot\";\n+\tint ok = 0, nospc = 0, other = 0;\n+\tint i, fd = -1;\n+\n+\t/* All programs attach successfully; there is no attach-time cap. */\n+\tfor (i = 0; i \u003c= INIT_INODE_XATTR_MAX; i++) {\n+\t\tskel[i] = test_init_inode_xattr__open_and_load();\n+\t\tif (!ASSERT_OK_PTR(skel[i], \"open_and_load\"))\n+\t\t\tgoto out;\n+\n+\t\tskel[i]-\u003ebss-\u003emonitored_pid = getpid();\n+\n+\t\tlink[i] = bpf_program__attach_lsm(skel[i]-\u003eprogs.test_init_inode_xattr);\n+\t\tif (!ASSERT_OK_PTR(link[i], \"attach\"))\n+\t\t\tgoto out;\n+\t}\n+\n+\t/* Trigger inode_init_security once with all programs attached. */\n+\tfd = open(testfile_slot, O_CREAT | O_RDWR, 0644);\n+\tif (!ASSERT_GE(fd, 0, \"create_file\"))\n+\t\tgoto out;\n+\n+\t/*\n+\t * Exactly INIT_INODE_XATTR_MAX programs claim a slot; the program past\n+\t * the budget gets -ENOSPC. The order in which programs run is not\n+\t * guaranteed, so count results instead of indexing.\n+\t */\n+\tfor (i = 0; i \u003c= INIT_INODE_XATTR_MAX; i++) {\n+\t\tint res = skel[i]-\u003edata-\u003einit_result;\n+\n+\t\tif (res == 0)\n+\t\t\tok++;\n+\t\telse if (res == -ENOSPC)\n+\t\t\tnospc++;\n+\t\telse\n+\t\t\tother++;\n+\t}\n+\n+\tASSERT_EQ(ok, INIT_INODE_XATTR_MAX, \"slots_within_budget\");\n+\tASSERT_EQ(nospc, 1, \"slot_over_budget\");\n+\tASSERT_EQ(other, 0, \"unexpected_result\");\n+\n+out:\n+\tif (fd \u003e= 0)\n+\t\tclose(fd);\n+\tfor (i = 0; i \u003c= INIT_INODE_XATTR_MAX; i++) {\n+\t\tbpf_link__destroy(link[i]);\n+\t\ttest_init_inode_xattr__destroy(skel[i]);\n+\t}\n+\tremove(testfile_slot);\n+}\n+\n void test_fs_kfuncs(void)\n {\n \t/* Matches xattr_names in progs/test_get_xattr.c */\n@@ -288,4 +399,10 @@ void test_fs_kfuncs(void)\n \n \tif (test__start_subtest(\"fsverity\"))\n \t\ttest_fsverity();\n+\n+\tif (test__start_subtest(\"init_inode_xattr\"))\n+\t\ttest_init_inode_xattr();\n+\n+\tif (test__start_subtest(\"init_inode_xattr_slot_limit\"))\n+\t\ttest_init_inode_xattr_slot_limit();\n }\ndiff --git a/tools/testing/selftests/bpf/progs/test_init_inode_xattr.c b/tools/testing/selftests/bpf/progs/test_init_inode_xattr.c\nnew file mode 100644\nindex 00000000000000..cb378db957aa89\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/test_init_inode_xattr.c\n@@ -0,0 +1,31 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright (c) 2026 Cisco Systems, Inc. */\n+\n+#include \"vmlinux.h\"\n+#include \u003cbpf/bpf_tracing.h\u003e\n+#include \"bpf_kfuncs.h\"\n+\n+char _license[] SEC(\"license\") = \"GPL\";\n+\n+__u32 monitored_pid;\n+int init_result = -1;\n+\n+static const char xattr_name[] = \"bpf.test_label\";\n+char xattr_value[] = \"unconfined_u:object_r:user_home_t:s0\";\n+\n+SEC(\"lsm.s/inode_init_security\")\n+int BPF_PROG(test_init_inode_xattr, struct inode *inode, struct inode *dir,\n+\t     const struct qstr *qstr, struct lsm_xattrs *xattrs)\n+{\n+\tstruct bpf_dynptr value_ptr;\n+\t__u32 pid;\n+\n+\tpid = bpf_get_current_pid_tgid() \u003e\u003e 32;\n+\tif (pid != monitored_pid)\n+\t\treturn 0;\n+\n+\tbpf_dynptr_from_mem(xattr_value, sizeof(xattr_value), 0, \u0026value_ptr);\n+\tinit_result = bpf_init_inode_xattr(xattrs, xattr_name, \u0026value_ptr);\n+\n+\treturn 0;\n+}\n"
}

2/1 2026/07/01 01:31 agent patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_LSM",
    "EVM",
    "SECURITY_SELINUX",
    "SECURITY_SMACK"
  ],
  "FocusSymbols": [
    "bpf_init_inode_xattr",
    "security_inode_init_security",
    "evm_inode_init_security",
    "selinux_inode_init_security",
    "smack_inode_init_security"
  ],
  "Reasoning": "The patch introduces a new BPF kfunc `bpf_init_inode_xattr` that allows BPF LSM programs to set xattrs on a new inode during `inode_init_security`. It modifies the `inode_init_security` LSM hook signature to use a new `struct lsm_xattrs` structure, affecting core LSM, EVM, SELinux, and Smack. These are functional changes to reachable core kernel logic and should be fuzzed.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit 346484b3f9b097984b92dda1a0980c5d2fc3cd99
Author: syz-cluster <triage@syzkaller.com>
Date:   Wed Jul 1 01:31:24 2026 +0000

    syz-cluster: applied patch under review

diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c
index 768aca2dc0f034..c4023c82f21e40 100644
--- a/fs/bpf_fs_kfuncs.c
+++ b/fs/bpf_fs_kfuncs.c
@@ -10,6 +10,7 @@
 #include <linux/fsnotify.h>
 #include <linux/file.h>
 #include <linux/kernfs.h>
+#include <linux/lsm_hooks.h>
 #include <linux/mm.h>
 #include <linux/xattr.h>
 
@@ -374,6 +375,83 @@ __bpf_kfunc struct inode *bpf_real_inode(struct dentry *dentry)
 	return d_real_inode(dentry);
 }
 
+static int bpf_xattrs_used(const struct lsm_xattrs *ctx)
+{
+	const size_t prefix_len = sizeof(XATTR_BPF_LSM_SUFFIX) - 1;
+	unsigned int i, n = 0;
+
+	for (i = 0; i < ctx->xattr_count; i++) {
+		const char *name = ctx->xattrs[i].name;
+
+		if (name && !strncmp(name, XATTR_BPF_LSM_SUFFIX, prefix_len))
+			n++;
+	}
+	return n;
+}
+
+/**
+ * bpf_init_inode_xattr - set an xattr on a new inode from inode_init_security
+ * @xattrs: inode_init_security xattr state from the hook context
+ * @name__str: xattr name (e.g., "bpf.file_label")
+ * @value_p: dynptr containing the xattr value
+ *
+ * Only callable from lsm/inode_init_security programs.
+ *
+ * Return: 0 on success, negative error on failure.
+ */
+__bpf_kfunc int bpf_init_inode_xattr(struct lsm_xattrs *xattrs,
+				     const char *name__str,
+				     const struct bpf_dynptr *value_p)
+{
+	struct bpf_dynptr_kern *value_ptr = (struct bpf_dynptr_kern *)value_p;
+	size_t name_len;
+	void *xattr_value;
+	struct xattr *xattr;
+	const void *value;
+	u32 value_len;
+
+	if (!xattrs || !xattrs->xattrs || !name__str)
+		return -EINVAL;
+	if (bpf_xattrs_used(xattrs) >= BPF_LSM_INODE_INIT_XATTRS)
+		return -ENOSPC;
+
+	name_len = strlen(name__str);
+	if (name_len == 0 || name_len > XATTR_NAME_MAX)
+		return -EINVAL;
+	if (strncmp(name__str, XATTR_BPF_LSM_SUFFIX,
+		    sizeof(XATTR_BPF_LSM_SUFFIX) - 1))
+		return -EPERM;
+
+	value_len = __bpf_dynptr_size(value_ptr);
+	if (value_len == 0 || value_len > XATTR_SIZE_MAX)
+		return -EINVAL;
+
+	value = __bpf_dynptr_data(value_ptr, value_len);
+	if (!value)
+		return -EINVAL;
+
+	/* Combine xattr value + name into one allocation. */
+	xattr_value = kmalloc(value_len + name_len + 1, GFP_NOFS);
+	if (!xattr_value)
+		return -ENOMEM;
+
+	memcpy(xattr_value, value, value_len);
+	memcpy(xattr_value + value_len, name__str, name_len);
+	((char *)xattr_value)[value_len + name_len] = '\0';
+
+	xattr = lsm_get_xattr_slot(xattrs);
+	if (!xattr) {
+		kfree(xattr_value);
+		return -ENOSPC;
+	}
+
+	xattr->value = xattr_value;
+	xattr->name = (const char *)xattr_value + value_len;
+	xattr->value_len = value_len;
+
+	return 0;
+}
+
 __bpf_kfunc_end_defs();
 
 BTF_KFUNCS_START(bpf_fs_kfunc_set_ids)
@@ -385,6 +463,7 @@ BTF_ID_FLAGS(func, bpf_get_file_xattr, KF_SLEEPABLE)
 BTF_ID_FLAGS(func, bpf_set_dentry_xattr, KF_SLEEPABLE)
 BTF_ID_FLAGS(func, bpf_remove_dentry_xattr, KF_SLEEPABLE)
 BTF_ID_FLAGS(func, bpf_real_inode, KF_SLEEPABLE | KF_RET_NULL)
+BTF_ID_FLAGS(func, bpf_init_inode_xattr, KF_SLEEPABLE)
 BTF_KFUNCS_END(bpf_fs_kfunc_set_ids)
 
 static int bpf_fs_kfuncs_filter(const struct bpf_prog *prog, u32 kfunc_id)
diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h
index 143775a27a2a0b..b655c708818e59 100644
--- a/include/linux/bpf_lsm.h
+++ b/include/linux/bpf_lsm.h
@@ -19,6 +19,9 @@
 #include <linux/lsm_hook_defs.h>
 #undef LSM_HOOK
 
+/* max bpf xattrs per inode */
+#define BPF_LSM_INODE_INIT_XATTRS 4
+
 struct bpf_storage_blob {
 	struct bpf_local_storage __rcu *storage;
 };
diff --git a/include/linux/evm.h b/include/linux/evm.h
index 913f4573b2038e..528f360f3308de 100644
--- a/include/linux/evm.h
+++ b/include/linux/evm.h
@@ -12,6 +12,8 @@
 #include <linux/integrity.h>
 #include <linux/xattr.h>
 
+struct lsm_xattrs;
+
 #ifdef CONFIG_EVM
 extern int evm_set_key(void *key, size_t keylen);
 extern enum integrity_status evm_verifyxattr(struct dentry *dentry,
@@ -21,8 +23,8 @@ extern enum integrity_status evm_verifyxattr(struct dentry *dentry,
 int evm_fix_hmac(struct dentry *dentry, const char *xattr_name,
 		 const char *xattr_value, size_t xattr_value_len);
 int evm_inode_init_security(struct inode *inode, struct inode *dir,
-			    const struct qstr *qstr, struct xattr *xattrs,
-			    int *xattr_count);
+			    const struct qstr *qstr,
+			    struct lsm_xattrs *xattrs);
 extern bool evm_revalidate_status(const char *xattr_name);
 extern int evm_protected_xattr_if_enabled(const char *req_xattr_name);
 extern int evm_read_protected_xattrs(struct dentry *dentry, u8 *buffer,
@@ -63,8 +65,7 @@ static inline int evm_fix_hmac(struct dentry *dentry, const char *xattr_name,
 
 static inline int evm_inode_init_security(struct inode *inode, struct inode *dir,
 					  const struct qstr *qstr,
-					  struct xattr *xattrs,
-					  int *xattr_count)
+					  struct lsm_xattrs *xattrs)
 {
 	return 0;
 }
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 65c9609ec20770..5b2de7865ce81a 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -116,8 +116,8 @@ LSM_HOOK(int, 0, inode_alloc_security, struct inode *inode)
 LSM_HOOK(void, LSM_RET_VOID, inode_free_security, struct inode *inode)
 LSM_HOOK(void, LSM_RET_VOID, inode_free_security_rcu, void *inode_security)
 LSM_HOOK(int, -EOPNOTSUPP, inode_init_security, struct inode *inode,
-	 struct inode *dir, const struct qstr *qstr, struct xattr *xattrs,
-	 int *xattr_count)
+	 struct inode *dir, const struct qstr *qstr,
+	 struct lsm_xattrs *xattrs)
 LSM_HOOK(int, 0, inode_init_security_anon, struct inode *inode,
 	 const struct qstr *name, const struct inode *context_inode)
 LSM_HOOK(int, 0, inode_create, struct inode *dir, struct dentry *dentry,
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index b4f8cad53ddb03..7afe06a8d4c6c5 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -200,20 +200,18 @@ extern struct lsm_static_calls_table static_calls_table __ro_after_init;
 
 /**
  * lsm_get_xattr_slot - Return the next available slot and increment the index
- * @xattrs: array storing LSM-provided xattrs
- * @xattr_count: number of already stored xattrs (updated)
+ * @ctx: xattr state shared by inode_init_security hooks
  *
- * Retrieve the first available slot in the @xattrs array to fill with an xattr,
- * and increment @xattr_count.
+ * Retrieve the first available slot in the @ctx->xattrs array to fill with an
+ * xattr, and increment @ctx->xattr_count.
  *
- * Return: The slot to fill in @xattrs if non-NULL, NULL otherwise.
+ * Return: The slot to fill in @ctx->xattrs if non-NULL, NULL otherwise.
  */
-static inline struct xattr *lsm_get_xattr_slot(struct xattr *xattrs,
-					       int *xattr_count)
+static inline struct xattr *lsm_get_xattr_slot(struct lsm_xattrs *ctx)
 {
-	if (unlikely(!xattrs))
+	if (unlikely(!ctx || !ctx->xattrs))
 		return NULL;
-	return &xattrs[(*xattr_count)++];
+	return &ctx->xattrs[ctx->xattr_count++];
 }
 
 #endif /* ! __LINUX_LSM_HOOKS_H */
diff --git a/include/linux/security.h b/include/linux/security.h
index 153e9043058f88..0be590c4068950 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -68,6 +68,11 @@ struct watch;
 struct watch_notification;
 struct lsm_ctx;
 
+struct lsm_xattrs {
+	struct xattr *xattrs;
+	unsigned int xattr_count;
+};
+
 /* Default (no) options for the capable function */
 #define CAP_OPT_NONE 0x0
 /* If capable should audit the security request */
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index 564071a92d7d20..1c3f84a9242039 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -315,6 +315,7 @@ BTF_ID(func, bpf_lsm_inode_create)
 BTF_ID(func, bpf_lsm_inode_free_security)
 BTF_ID(func, bpf_lsm_inode_getattr)
 BTF_ID(func, bpf_lsm_inode_getxattr)
+BTF_ID(func, bpf_lsm_inode_init_security)
 BTF_ID(func, bpf_lsm_inode_mknod)
 BTF_ID(func, bpf_lsm_inode_need_killpriv)
 BTF_ID(func, bpf_lsm_inode_post_setxattr)
diff --git a/security/bpf/hooks.c b/security/bpf/hooks.c
index 40efde233f3ab3..d7c44c5c0e3046 100644
--- a/security/bpf/hooks.c
+++ b/security/bpf/hooks.c
@@ -30,6 +30,7 @@ static int __init bpf_lsm_init(void)
 
 struct lsm_blob_sizes bpf_lsm_blob_sizes __ro_after_init = {
 	.lbs_inode = sizeof(struct bpf_storage_blob),
+	.lbs_xattr_count = BPF_LSM_INODE_INIT_XATTRS,
 };
 
 DEFINE_LSM(bpf) = {
diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c
index b59e3f121b8a89..b7158fc63543e0 100644
--- a/security/integrity/evm/evm_main.c
+++ b/security/integrity/evm/evm_main.c
@@ -1062,14 +1062,16 @@ static int evm_inode_copy_up_xattr(struct dentry *src, const char *name)
  * evm_inode_init_security - initializes security.evm HMAC value
  */
 int evm_inode_init_security(struct inode *inode, struct inode *dir,
-			    const struct qstr *qstr, struct xattr *xattrs,
-			    int *xattr_count)
+			    const struct qstr *qstr,
+			    struct lsm_xattrs *lsm_xattrs)
 {
 	struct evm_xattr *xattr_data;
 	struct xattr *xattr, *evm_xattr;
+	struct xattr *xattrs;
 	bool evm_protected_xattrs = false;
 	int rc;
 
+	xattrs = lsm_xattrs ? lsm_xattrs->xattrs : NULL;
 	if (!(evm_initialized & EVM_INIT_HMAC) || !xattrs)
 		return 0;
 
@@ -1087,7 +1089,7 @@ int evm_inode_init_security(struct inode *inode, struct inode *dir,
 	if (!evm_protected_xattrs)
 		return 0;
 
-	evm_xattr = lsm_get_xattr_slot(xattrs, xattr_count);
+	evm_xattr = lsm_get_xattr_slot(lsm_xattrs);
 	/*
 	 * Array terminator (xattr name = NULL) must be the first non-filled
 	 * xattr slot.
diff --git a/security/security.c b/security/security.c
index 71aea8fdf01429..2ad7f09c1a6110 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1333,8 +1333,8 @@ int security_inode_init_security(struct inode *inode, struct inode *dir,
 				 const initxattrs initxattrs, void *fs_data)
 {
 	struct lsm_static_call *scall;
-	struct xattr *new_xattrs = NULL;
-	int ret = -EOPNOTSUPP, xattr_count = 0;
+	struct lsm_xattrs xattrs = {};
+	int ret = -EOPNOTSUPP;
 
 	if (unlikely(IS_PRIVATE(inode)))
 		return 0;
@@ -1344,15 +1344,15 @@ int security_inode_init_security(struct inode *inode, struct inode *dir,
 
 	if (initxattrs) {
 		/* Allocate +1 as terminator. */
-		new_xattrs = kcalloc(blob_sizes.lbs_xattr_count + 1,
-				     sizeof(*new_xattrs), GFP_NOFS);
-		if (!new_xattrs)
+		xattrs.xattrs = kcalloc(blob_sizes.lbs_xattr_count + 1,
+					sizeof(*xattrs.xattrs), GFP_NOFS);
+		if (!xattrs.xattrs)
 			return -ENOMEM;
 	}
 
 	lsm_for_each_hook(scall, inode_init_security) {
-		ret = scall->hl->hook.inode_init_security(inode, dir, qstr, new_xattrs,
-						  &xattr_count);
+		ret = scall->hl->hook.inode_init_security(inode, dir, qstr,
+							  &xattrs);
 		if (ret && ret != -EOPNOTSUPP)
 			goto out;
 		/*
@@ -1364,14 +1364,14 @@ int security_inode_init_security(struct inode *inode, struct inode *dir,
 	}
 
 	/* If initxattrs() is NULL, xattr_count is zero, skip the call. */
-	if (!xattr_count)
+	if (!xattrs.xattr_count)
 		goto out;
 
-	ret = initxattrs(inode, new_xattrs, fs_data);
+	ret = initxattrs(inode, xattrs.xattrs, fs_data);
 out:
-	for (; xattr_count > 0; xattr_count--)
-		kfree(new_xattrs[xattr_count - 1].value);
-	kfree(new_xattrs);
+	for (; xattrs.xattr_count > 0; xattrs.xattr_count--)
+		kfree(xattrs.xattrs[xattrs.xattr_count - 1].value);
+	kfree(xattrs.xattrs);
 	return (ret == -EOPNOTSUPP) ? 0 : ret;
 }
 EXPORT_SYMBOL(security_inode_init_security);
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 1a713d96206f59..6bba6b212e17c5 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2962,7 +2962,7 @@ static int selinux_dentry_create_files_as(struct dentry *dentry, int mode,
 
 static int selinux_inode_init_security(struct inode *inode, struct inode *dir,
 				       const struct qstr *qstr,
-				       struct xattr *xattrs, int *xattr_count)
+				       struct lsm_xattrs *xattrs)
 {
 	const struct cred_security_struct *crsec = selinux_cred(current_cred());
 	struct superblock_security_struct *sbsec;
@@ -2992,7 +2992,7 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir,
 	    !(sbsec->flags & SBLABEL_MNT))
 		return -EOPNOTSUPP;
 
-	xattr = lsm_get_xattr_slot(xattrs, xattr_count);
+	xattr = lsm_get_xattr_slot(xattrs);
 	if (xattr) {
 		rc = security_sid_to_context_force(newsid,
 						   &context, &clen);
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index ff115068c5c06f..4501078430ca05 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -981,10 +981,10 @@ smk_rule_transmutes(struct smack_known *subject,
 }
 
 static int
-xattr_dupval(struct xattr *xattrs, int *xattr_count,
+xattr_dupval(struct lsm_xattrs *xattrs,
 	     const char *name, const void *value, unsigned int vallen)
 {
-	struct xattr * const xattr = lsm_get_xattr_slot(xattrs, xattr_count);
+	struct xattr * const xattr = lsm_get_xattr_slot(xattrs);
 
 	if (!xattr)
 		return 0;
@@ -1003,14 +1003,13 @@ xattr_dupval(struct xattr *xattrs, int *xattr_count,
  * @inode: the newly created inode
  * @dir: containing directory object
  * @qstr: unused
- * @xattrs: where to put the attributes
- * @xattr_count: current number of LSM-provided xattrs (updated)
+ * @xattrs: where to put attributes and update count
  *
  * Returns 0 if it all works out, -ENOMEM if there's no memory
  */
 static int smack_inode_init_security(struct inode *inode, struct inode *dir,
 				     const struct qstr *qstr,
-				     struct xattr *xattrs, int *xattr_count)
+				     struct lsm_xattrs *xattrs)
 {
 	struct task_smack *tsp = smack_cred(current_cred());
 	struct inode_smack * const issp = smack_inode(inode);
@@ -1057,21 +1056,19 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir,
 		if (S_ISDIR(inode->i_mode)) {
 			transflag = SMK_INODE_TRANSMUTE;
 
-			if (xattr_dupval(xattrs, xattr_count,
-				XATTR_SMACK_TRANSMUTE,
-				TRANS_TRUE,
-				TRANS_TRUE_SIZE
-			))
+			if (xattr_dupval(xattrs,
+					 XATTR_SMACK_TRANSMUTE,
+					 TRANS_TRUE,
+					 TRANS_TRUE_SIZE))
 				rc = -ENOMEM;
 		}
 	}
 
 	if (rc == 0)
-		if (xattr_dupval(xattrs, xattr_count,
-			    XATTR_SMACK_SUFFIX,
-			    issp->smk_inode->smk_known,
-		     strlen(issp->smk_inode->smk_known)
-		))
+		if (xattr_dupval(xattrs,
+				 XATTR_SMACK_SUFFIX,
+				 issp->smk_inode->smk_known,
+				 strlen(issp->smk_inode->smk_known)))
 			rc = -ENOMEM;
 instant_inode:
 	issp->smk_flags |= (SMK_INODE_INSTANT | transflag);
diff --git a/tools/testing/selftests/bpf/bpf_kfuncs.h b/tools/testing/selftests/bpf/bpf_kfuncs.h
index ae71e9b6905186..2639f9f941955b 100644
--- a/tools/testing/selftests/bpf/bpf_kfuncs.h
+++ b/tools/testing/selftests/bpf/bpf_kfuncs.h
@@ -92,4 +92,9 @@ extern int bpf_set_dentry_xattr(struct dentry *dentry, const char *name__str,
 				const struct bpf_dynptr *value_p, int flags) __ksym __weak;
 extern int bpf_remove_dentry_xattr(struct dentry *dentry, const char *name__str) __ksym __weak;
 
+struct lsm_xattrs;
+extern int bpf_init_inode_xattr(struct lsm_xattrs *xattrs,
+				const char *name__str,
+				const struct bpf_dynptr *value_p) __ksym __weak;
+
 #endif
diff --git a/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c b/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c
index 43a26ec69a8e36..8b2e0d433aea2f 100644
--- a/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c
+++ b/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c
@@ -10,6 +10,7 @@
 #include "test_get_xattr.skel.h"
 #include "test_set_remove_xattr.skel.h"
 #include "test_fsverity.skel.h"
+#include "test_init_inode_xattr.skel.h"
 
 static const char testfile[] = "/tmp/test_progs_fs_kfuncs";
 
@@ -268,6 +269,116 @@ static void test_fsverity(void)
 	remove(testfile);
 }
 
+static void test_init_inode_xattr(void)
+{
+	struct test_init_inode_xattr *skel = NULL;
+	int fd = -1, err;
+	char value_out[64];
+	const char *testfile_new = "/tmp/test_progs_fs_kfuncs_new";
+
+	skel = test_init_inode_xattr__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "test_init_inode_xattr__open_and_load"))
+		return;
+
+	skel->bss->monitored_pid = getpid();
+	err = test_init_inode_xattr__attach(skel);
+	if (!ASSERT_OK(err, "test_init_inode_xattr__attach"))
+		goto out;
+
+	/* Trigger inode_init_security */
+	fd = open(testfile_new, O_CREAT | O_RDWR, 0644);
+	if (!ASSERT_GE(fd, 0, "create_file"))
+		goto out;
+
+	ASSERT_EQ(skel->data->init_result, 0, "init_result");
+
+	/* initxattrs prepends "security." to the name. */
+	err = getxattr(testfile_new, "security.bpf.test_label", value_out,
+		       sizeof(value_out));
+	if (err < 0 && errno == ENODATA) {
+		printf("%s:SKIP:filesystem did not apply LSM xattrs\n",
+		       __func__);
+		test__skip();
+		goto out;
+	}
+	if (!ASSERT_GE(err, 0, "getxattr"))
+		goto out;
+
+	ASSERT_EQ(err, (int)sizeof(skel->data->xattr_value), "xattr_size");
+	ASSERT_EQ(strncmp(value_out, "unconfined_u:object_r:user_home_t:s0",
+			  sizeof("unconfined_u:object_r:user_home_t:s0")), 0,
+		  "xattr_value");
+
+out:
+	close(fd);
+	test_init_inode_xattr__destroy(skel);
+	remove(testfile_new);
+}
+
+/* Keep in sync with BPF_LSM_INODE_INIT_XATTRS in include/linux/bpf_lsm.h. */
+#define INIT_INODE_XATTR_MAX 4
+
+/*
+ * Programs may attach to inode_init_security without an attach-time limit, but
+ * the kfunc only lets BPF claim INIT_INODE_XATTR_MAX xattr slots per inode.
+ * Calls beyond that budget are rejected at runtime with -ENOSPC.
+ */
+static void test_init_inode_xattr_slot_limit(void)
+{
+	struct test_init_inode_xattr *skel[INIT_INODE_XATTR_MAX + 1] = {};
+	struct bpf_link *link[INIT_INODE_XATTR_MAX + 1] = {};
+	const char *testfile_slot = "/tmp/test_progs_fs_kfuncs_slot";
+	int ok = 0, nospc = 0, other = 0;
+	int i, fd = -1;
+
+	/* All programs attach successfully; there is no attach-time cap. */
+	for (i = 0; i <= INIT_INODE_XATTR_MAX; i++) {
+		skel[i] = test_init_inode_xattr__open_and_load();
+		if (!ASSERT_OK_PTR(skel[i], "open_and_load"))
+			goto out;
+
+		skel[i]->bss->monitored_pid = getpid();
+
+		link[i] = bpf_program__attach_lsm(skel[i]->progs.test_init_inode_xattr);
+		if (!ASSERT_OK_PTR(link[i], "attach"))
+			goto out;
+	}
+
+	/* Trigger inode_init_security once with all programs attached. */
+	fd = open(testfile_slot, O_CREAT | O_RDWR, 0644);
+	if (!ASSERT_GE(fd, 0, "create_file"))
+		goto out;
+
+	/*
+	 * Exactly INIT_INODE_XATTR_MAX programs claim a slot; the program past
+	 * the budget gets -ENOSPC. The order in which programs run is not
+	 * guaranteed, so count results instead of indexing.
+	 */
+	for (i = 0; i <= INIT_INODE_XATTR_MAX; i++) {
+		int res = skel[i]->data->init_result;
+
+		if (res == 0)
+			ok++;
+		else if (res == -ENOSPC)
+			nospc++;
+		else
+			other++;
+	}
+
+	ASSERT_EQ(ok, INIT_INODE_XATTR_MAX, "slots_within_budget");
+	ASSERT_EQ(nospc, 1, "slot_over_budget");
+	ASSERT_EQ(other, 0, "unexpected_result");
+
+out:
+	if (fd >= 0)
+		close(fd);
+	for (i = 0; i <= INIT_INODE_XATTR_MAX; i++) {
+		bpf_link__destroy(link[i]);
+		test_init_inode_xattr__destroy(skel[i]);
+	}
+	remove(testfile_slot);
+}
+
 void test_fs_kfuncs(void)
 {
 	/* Matches xattr_names in progs/test_get_xattr.c */
@@ -288,4 +399,10 @@ void test_fs_kfuncs(void)
 
 	if (test__start_subtest("fsverity"))
 		test_fsverity();
+
+	if (test__start_subtest("init_inode_xattr"))
+		test_init_inode_xattr();
+
+	if (test__start_subtest("init_inode_xattr_slot_limit"))
+		test_init_inode_xattr_slot_limit();
 }
diff --git a/tools/testing/selftests/bpf/progs/test_init_inode_xattr.c b/tools/testing/selftests/bpf/progs/test_init_inode_xattr.c
new file mode 100644
index 00000000000000..cb378db957aa89
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_init_inode_xattr.c
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Cisco Systems, Inc. */
+
+#include "vmlinux.h"
+#include <bpf/bpf_tracing.h>
+#include "bpf_kfuncs.h"
+
+char _license[] SEC("license") = "GPL";
+
+__u32 monitored_pid;
+int init_result = -1;
+
+static const char xattr_name[] = "bpf.test_label";
+char xattr_value[] = "unconfined_u:object_r:user_home_t:s0";
+
+SEC("lsm.s/inode_init_security")
+int BPF_PROG(test_init_inode_xattr, struct inode *inode, struct inode *dir,
+	     const struct qstr *qstr, struct lsm_xattrs *xattrs)
+{
+	struct bpf_dynptr value_ptr;
+	__u32 pid;
+
+	pid = bpf_get_current_pid_tgid() >> 32;
+	if (pid != monitored_pid)
+		return 0;
+
+	bpf_dynptr_from_mem(xattr_value, sizeof(xattr_value), 0, &value_ptr);
+	init_result = bpf_init_inode_xattr(xattrs, xattr_name, &value_ptr);
+
+	return 0;
+}

3/2 2026/07/01 01:31 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 10482
							output: 21
							thoughts: 390
						

Thoughts:
**Analyzing the New BPF Kfunc for Inode Xattr Initialization**

Okay, so I've got this patch here that introduces a new BPF kfunc, `bpf_init_inode_xattr`. This is pretty interesting because it's designed to let BPF LSM programs set extended attributes (xattrs) on a *new* inode right during the `inode_init_security` process. Looking at the files it touches, it's impacting a wide range of security areas: `fs/bpf_fs_kfuncs.c`, the various `include/linux/*` headers related to BPF, EVM, and LSM hooks, as well as core security components in `kernel/bpf/bpf_lsm.c`, `security/bpf/hooks.c`, `security/integrity/evm/evm_main.c`, `security/security.c`, SELinux, and Smack. That's a serious number of touchpoints!

First, I need to confirm that this new kfunc is properly registered and accessible. I see it's added to `bpf_fs_kfunc_set_ids` in `fs/bpf_fs_kfuncs.c`. Good, that makes sense. It's also modifying the `inode_init_security` LSM hook signature; instead of the older `struct xattr *xattrs, int *xattr_count`, it's now using `struct lsm_xattrs *xattrs`. That's a significant functional change for those critical subsystems – LSM, EVM, SELinux, Smack, and BPF. This could potentially cause some headaches if not properly handled, so it's critical we pay attention to how this is used across the codebase.

The next step is to consider fuzzing. Given the kernel-level integration, and the fact that we're talking about new xattr setting capabilities, `bpf_init_inode_xattr` is absolutely a prime candidate for thorough fuzzing.

Now, let's nail down the configuration prerequisites. It's almost certain that BPF LSM functionality will require `CONFIG_BPF_LSM`. Let me check if the kfunc itself in `fs/bpf_fs_kfuncs.c` has any conditional compilation flags associated with it to make sure it's enabled under the appropriate scenarios. I'll need to double check the Kconfig, of course.



4/2 2026/07/01 01:31 tool grepper
0m Args:
{
  "Expression": "bpf_fs_kfuncs"
}

Results:
{
  "Output": "fs/Makefile=130=obj-$(CONFIG_ZONEFS_FS)\t\t+= zonefs/\nfs/Makefile:131:obj-$(CONFIG_BPF_LSM)\t\t+= bpf_fs_kfuncs.o\nfs/Makefile-132-obj-$(CONFIG_RESCTRL_FS)\t+= resctrl/\n--\nfs/bpf_fs_kfuncs.c=467=BTF_KFUNCS_END(bpf_fs_kfunc_set_ids)\nfs/bpf_fs_kfuncs.c-468-\nfs/bpf_fs_kfuncs.c:469:static int bpf_fs_kfuncs_filter(const struct bpf_prog *prog, u32 kfunc_id)\nfs/bpf_fs_kfuncs.c-470-{\n--\nfs/bpf_fs_kfuncs.c=507=static const struct btf_kfunc_id_set bpf_fs_kfunc_set = {\n--\nfs/bpf_fs_kfuncs.c-509-\t.set = \u0026bpf_fs_kfunc_set_ids,\nfs/bpf_fs_kfuncs.c:510:\t.filter = bpf_fs_kfuncs_filter,\nfs/bpf_fs_kfuncs.c-511-};\nfs/bpf_fs_kfuncs.c-512-\nfs/bpf_fs_kfuncs.c:513:static int __init bpf_fs_kfuncs_init(void)\nfs/bpf_fs_kfuncs.c-514-{\n--\nfs/bpf_fs_kfuncs.c-517-\nfs/bpf_fs_kfuncs.c:518:late_initcall(bpf_fs_kfuncs_init);\n"
}

5/2 2026/07/01 01:31 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 11865
							output: 23
							thoughts: 127
						

Thoughts:
**Kernel Configuration Investigation**

Okay, so it looks like `CONFIG_BPF_LSM` is a definite requirement here. I need to make a mental note of that. Now, I should probably check if there are any *other* configuration options that I'll need to enable for this to work correctly. Gotta be thorough.

Right, the `bpf_init_inode_xattr` kfunc is defined in `fs/bpf_fs_kfuncs.c`. That's good to know, that file is my next target. And the hook, `inode_init_security`, that's in `security/security.c`, and I know that's only compiled if `CONFIG_SECURITY` is enabled which isn't a problem for me. Okay, so that kfunc utilizes `lsm_get_xattr_slot`.

Alright, I'm going to dive right into `bpf_init_inode_xattr` in `fs/bpf_fs_kfuncs.c`. Let's see what that function is really doing under the hood, I'll need to see how `lsm_get_xattr_slot` is being used in the context of inodes and extended attributes.



6/2 2026/07/01 01:31 tool grepper
0m Args:
{
  "Expression": "bpf_init_inode_xattr"
}

Results:
{
  "Output": "fs/bpf_fs_kfuncs.c=378=static int bpf_xattrs_used(const struct lsm_xattrs *ctx)\n--\nfs/bpf_fs_kfuncs.c-392-/**\nfs/bpf_fs_kfuncs.c:393: * bpf_init_inode_xattr - set an xattr on a new inode from inode_init_security\nfs/bpf_fs_kfuncs.c-394- * @xattrs: inode_init_security xattr state from the hook context\n--\nfs/bpf_fs_kfuncs.c-401- */\nfs/bpf_fs_kfuncs.c:402:__bpf_kfunc int bpf_init_inode_xattr(struct lsm_xattrs *xattrs,\nfs/bpf_fs_kfuncs.c-403-\t\t\t\t     const char *name__str,\n--\nfs/bpf_fs_kfuncs.c=465=BTF_ID_FLAGS(func, bpf_real_inode, KF_SLEEPABLE | KF_RET_NULL)\nfs/bpf_fs_kfuncs.c:466:BTF_ID_FLAGS(func, bpf_init_inode_xattr, KF_SLEEPABLE)\nfs/bpf_fs_kfuncs.c-467-BTF_KFUNCS_END(bpf_fs_kfunc_set_ids)\n--\ntools/testing/selftests/bpf/bpf_kfuncs.h=95=struct lsm_xattrs;\ntools/testing/selftests/bpf/bpf_kfuncs.h:96:extern int bpf_init_inode_xattr(struct lsm_xattrs *xattrs,\ntools/testing/selftests/bpf/bpf_kfuncs.h-97-\t\t\t\tconst char *name__str,\n--\ntools/testing/selftests/bpf/progs/test_init_inode_xattr.c=17=int BPF_PROG(test_init_inode_xattr, struct inode *inode, struct inode *dir,\n--\ntools/testing/selftests/bpf/progs/test_init_inode_xattr.c-27-\tbpf_dynptr_from_mem(xattr_value, sizeof(xattr_value), 0, \u0026value_ptr);\ntools/testing/selftests/bpf/progs/test_init_inode_xattr.c:28:\tinit_result = bpf_init_inode_xattr(xattrs, xattr_name, \u0026value_ptr);\ntools/testing/selftests/bpf/progs/test_init_inode_xattr.c-29-\n"
}

7/2 2026/07/01 01:31 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 12952
							output: 21
							thoughts: 55
						

Thoughts:
**Analyzing the Xattr Initialization Process**

Okay, so the function `bpf_init_inode_xattr` is clearly the key player here. It seems like this is where the core initialization happens. But then there's also `security_inode_init_security` that's being modified, which is interesting. Given my expertise, I suspect this has to do with integrating BPF functionality with the existing security framework during inode initialization. I really need to take a look at the implementation of `security_inode_init_security` to understand how the changes impact the security context. Let's start by digging into `security/security.c` and see what's changed there. Understanding the interaction between these two functions will be crucial to grasping the complete picture.



8/2 2026/07/01 01:31 tool grepper
0m Args:
{
  "Expression": "security_inode_init_security"
}

Results:
{
  "Output": "fs/anon_inodes.c=93=static struct file_system_type anon_inode_fs_type = {\n--\nfs/anon_inodes.c-106- * The function ensures proper security initialization through the LSM hook\nfs/anon_inodes.c:107: * security_inode_init_security_anon().\nfs/anon_inodes.c-108- *\n--\nfs/anon_inodes.c=111=struct inode *anon_inode_make_secure_inode(struct super_block *sb, const char *name,\n--\nfs/anon_inodes.c-121-\tinode-\u003ei_op = \u0026anon_inode_operations;\nfs/anon_inodes.c:122:\terror =\tsecurity_inode_init_security_anon(inode, \u0026QSTR(name),\nfs/anon_inodes.c-123-\t\t\t\t\t\t  context_inode);\n--\nfs/btrfs/xattr.c=521=int btrfs_xattr_security_init(struct btrfs_trans_handle *trans,\n--\nfs/btrfs/xattr.c-524-{\nfs/btrfs/xattr.c:525:\treturn security_inode_init_security(inode, dir, qstr,\nfs/btrfs/xattr.c-526-\t\t\t\t\t    \u0026btrfs_initxattrs, trans);\n--\nfs/ext2/xattr_security.c=48=ext2_init_security(struct inode *inode, struct inode *dir,\n--\nfs/ext2/xattr_security.c-50-{\nfs/ext2/xattr_security.c:51:\treturn security_inode_init_security(inode, dir, qstr,\nfs/ext2/xattr_security.c-52-\t\t\t\t\t    \u0026ext2_initxattrs, NULL);\n--\nfs/ext4/xattr_security.c=55=ext4_init_security(handle_t *handle, struct inode *inode, struct inode *dir,\n--\nfs/ext4/xattr_security.c-57-{\nfs/ext4/xattr_security.c:58:\treturn security_inode_init_security(inode, dir, qstr,\nfs/ext4/xattr_security.c-59-\t\t\t\t\t    \u0026ext4_initxattrs, handle);\n--\nfs/f2fs/xattr.c=155=int f2fs_init_security(struct inode *inode, struct inode *dir,\n--\nfs/f2fs/xattr.c-157-{\nfs/f2fs/xattr.c:158:\treturn security_inode_init_security(inode, dir, qstr,\nfs/f2fs/xattr.c-159-\t\t\t\tf2fs_initxattrs, ifolio);\n--\nfs/gfs2/inode.c=700=static int gfs2_create_inode(struct inode *dir, struct dentry *dentry,\n--\nfs/gfs2/inode.c-891-\nfs/gfs2/inode.c:892:\terror = security_inode_init_security(\u0026ip-\u003ei_inode, \u0026dip-\u003ei_inode, name,\nfs/gfs2/inode.c-893-\t\t\t\t\t     \u0026gfs2_initxattrs, NULL);\n--\nfs/hfsplus/xattr_security.c=68=int hfsplus_init_security(struct inode *inode, struct inode *dir,\n--\nfs/hfsplus/xattr_security.c-70-{\nfs/hfsplus/xattr_security.c:71:\treturn security_inode_init_security(inode, dir, qstr,\nfs/hfsplus/xattr_security.c-72-\t\t\t\t\t\u0026hfsplus_initxattrs, NULL);\n--\nfs/jffs2/security.c=43=int jffs2_init_security(struct inode *inode, struct inode *dir,\n--\nfs/jffs2/security.c-45-{\nfs/jffs2/security.c:46:\treturn security_inode_init_security(inode, dir, qstr,\nfs/jffs2/security.c-47-\t\t\t\t\t    \u0026jffs2_initxattrs, NULL);\n--\nfs/jfs/xattr.c=1049=int jfs_init_security(tid_t tid, struct inode *inode, struct inode *dir,\n--\nfs/jfs/xattr.c-1051-{\nfs/jfs/xattr.c:1052:\treturn security_inode_init_security(inode, dir, qstr,\nfs/jfs/xattr.c-1053-\t\t\t\t\t    \u0026jfs_initxattrs, \u0026tid);\n--\nfs/ocfs2/xattr.c=7308=int ocfs2_init_security_get(struct inode *inode,\n--\nfs/ocfs2/xattr.c-7318-\tif (si) {\nfs/ocfs2/xattr.c:7319:\t\tret = security_inode_init_security(inode, dir, qstr,\nfs/ocfs2/xattr.c-7320-\t\t\t\t\t\t   \u0026ocfs2_initxattrs, si);\nfs/ocfs2/xattr.c-7321-\t\t/*\nfs/ocfs2/xattr.c:7322:\t\t * security_inode_init_security() does not return -EOPNOTSUPP,\nfs/ocfs2/xattr.c-7323-\t\t * we have to check the xattr ourselves.\n--\nfs/ocfs2/xattr.c-7330-\nfs/ocfs2/xattr.c:7331:\treturn security_inode_init_security(inode, dir, qstr,\nfs/ocfs2/xattr.c-7332-\t\t\t\t\t    \u0026ocfs2_initxattrs, NULL);\n--\nfs/ramfs/inode.c=98=ramfs_mknod(struct mnt_idmap *idmap, struct inode *dir,\n--\nfs/ramfs/inode.c-104-\tif (inode) {\nfs/ramfs/inode.c:105:\t\terror = security_inode_init_security(inode, dir,\nfs/ramfs/inode.c-106-\t\t\t\t\t\t     \u0026dentry-\u003ed_name, NULL,\n--\nfs/ramfs/inode.c=136=static int ramfs_symlink(struct mnt_idmap *idmap, struct inode *dir,\n--\nfs/ramfs/inode.c-145-\nfs/ramfs/inode.c:146:\t\terror = security_inode_init_security(inode, dir,\nfs/ramfs/inode.c-147-\t\t\t\t\t\t     \u0026dentry-\u003ed_name, NULL,\n--\nfs/ramfs/inode.c=166=static int ramfs_tmpfile(struct mnt_idmap *idmap,\n--\nfs/ramfs/inode.c-175-\nfs/ramfs/inode.c:176:\terror = security_inode_init_security(inode, dir,\nfs/ramfs/inode.c-177-\t\t\t\t\t     \u0026file_dentry(file)-\u003ed_name, NULL,\n--\nfs/ubifs/xattr.c=635=int ubifs_init_security(struct inode *dentry, struct inode *inode,\n--\nfs/ubifs/xattr.c-639-\nfs/ubifs/xattr.c:640:\terr = security_inode_init_security(inode, dentry, qstr,\nfs/ubifs/xattr.c-641-\t\t\t\t\t   \u0026init_xattrs, NULL);\n--\nfs/xfs/xfs_iops.c=86=xfs_inode_init_security(\n--\nfs/xfs/xfs_iops.c-90-{\nfs/xfs/xfs_iops.c:91:\treturn security_inode_init_security(inode, dir, qstr,\nfs/xfs/xfs_iops.c-92-\t\t\t\t\t     \u0026xfs_initxattrs, NULL);\n--\ninclude/linux/security.h=272=extern int mmap_min_addr_handler(const struct ctl_table *table, int write,\n--\ninclude/linux/security.h-275-\ninclude/linux/security.h:276:/* security_inode_init_security callback function to write xattrs */\ninclude/linux/security.h-277-typedef int (*initxattrs) (struct inode *inode,\n--\ninclude/linux/security.h=405=void security_inode_free(struct inode *inode);\ninclude/linux/security.h:406:int security_inode_init_security(struct inode *inode, struct inode *dir,\ninclude/linux/security.h-407-\t\t\t\t const struct qstr *qstr,\ninclude/linux/security.h-408-\t\t\t\t initxattrs initxattrs, void *fs_data);\ninclude/linux/security.h:409:int security_inode_init_security_anon(struct inode *inode,\ninclude/linux/security.h-410-\t\t\t\t      const struct qstr *name,\n--\ninclude/linux/security.h=885=static inline int security_dentry_create_files_as(struct dentry *dentry,\n--\ninclude/linux/security.h-893-\ninclude/linux/security.h:894:static inline int security_inode_init_security(struct inode *inode,\ninclude/linux/security.h-895-\t\t\t\t\t\tstruct inode *dir,\n--\ninclude/linux/security.h-902-\ninclude/linux/security.h:903:static inline int security_inode_init_security_anon(struct inode *inode,\ninclude/linux/security.h-904-\t\t\t\t\t\t    const struct qstr *name,\n--\nkernel/bpf/inode.c=179=static struct dentry *bpf_mkdir(struct mnt_idmap *idmap, struct inode *dir,\n--\nkernel/bpf/inode.c-188-\nkernel/bpf/inode.c:189:\tret = security_inode_init_security(inode, dir, \u0026dentry-\u003ed_name,\nkernel/bpf/inode.c-190-\t\t\t\t\t   bpf_fs_initxattrs, NULL);\n--\nkernel/bpf/inode.c=363=static int bpf_mkobj_ops(struct dentry *dentry, umode_t mode, void *raw,\n--\nkernel/bpf/inode.c-374-\nkernel/bpf/inode.c:375:\tret = security_inode_init_security(inode, dir, \u0026dentry-\u003ed_name,\nkernel/bpf/inode.c-376-\t\t\t\t\t   bpf_fs_initxattrs, NULL);\n--\nkernel/bpf/inode.c=427=static int bpf_symlink(struct mnt_idmap *idmap, struct inode *dir,\n--\nkernel/bpf/inode.c-446-\nkernel/bpf/inode.c:447:\tret = security_inode_init_security(inode, dir, \u0026dentry-\u003ed_name,\nkernel/bpf/inode.c-448-\t\t\t\t\t   bpf_fs_initxattrs, NULL);\n--\nmm/memfd.c=458=struct file *memfd_alloc_file(const char *name, unsigned int flags)\n--\nmm/memfd.c-476-\tinode = file_inode(file);\nmm/memfd.c:477:\terr = security_inode_init_security_anon(inode,\nmm/memfd.c-478-\t\t\t\u0026QSTR(MEMFD_ANON_NAME), NULL);\n--\nmm/shmem.c=3850=shmem_mknod(struct mnt_idmap *idmap, struct inode *dir,\n--\nmm/shmem.c-3866-\t\tgoto out_iput;\nmm/shmem.c:3867:\terror = security_inode_init_security(inode, dir, \u0026dentry-\u003ed_name,\nmm/shmem.c-3868-\t\t\t\t\t     shmem_initxattrs, NULL);\n--\nmm/shmem.c=3889=shmem_tmpfile(struct mnt_idmap *idmap, struct inode *dir,\n--\nmm/shmem.c-3900-\t}\nmm/shmem.c:3901:\terror = security_inode_init_security(inode, dir, NULL,\nmm/shmem.c-3902-\t\t\t\t\t     shmem_initxattrs, NULL);\n--\nmm/shmem.c=4079=static int shmem_symlink(struct mnt_idmap *idmap, struct inode *dir,\n--\nmm/shmem.c-4096-\nmm/shmem.c:4097:\terror = security_inode_init_security(inode, dir, \u0026dentry-\u003ed_name,\nmm/shmem.c-4098-\t\t\t\t\t     shmem_initxattrs, NULL);\n--\nmm/shmem.c=4190=static int shmem_fileattr_set(struct mnt_idmap *idmap,\n--\nmm/shmem.c-4224-/*\nmm/shmem.c:4225: * Callback for security_inode_init_security() for acquiring xattrs.\nmm/shmem.c-4226- */\n--\nsecurity/integrity/evm/evm_main.c=1064=int evm_inode_init_security(struct inode *inode, struct inode *dir,\n--\nsecurity/integrity/evm/evm_main.c-1078-\t/*\nsecurity/integrity/evm/evm_main.c:1079:\t * security_inode_init_security() makes sure that the xattrs array is\nsecurity/integrity/evm/evm_main.c-1080-\t * contiguous, there is enough space for security.evm, and that there is\n--\nsecurity/security.c=1302=EXPORT_SYMBOL(security_dentry_create_files_as);\n--\nsecurity/security.c-1304-/**\nsecurity/security.c:1305: * security_inode_init_security() - Initialize an inode's LSM context\nsecurity/security.c-1306- * @inode: the inode\n--\nsecurity/security.c-1330- */\nsecurity/security.c:1331:int security_inode_init_security(struct inode *inode, struct inode *dir,\nsecurity/security.c-1332-\t\t\t\t const struct qstr *qstr,\n--\nsecurity/security.c-1376-}\nsecurity/security.c:1377:EXPORT_SYMBOL(security_inode_init_security);\nsecurity/security.c-1378-\nsecurity/security.c-1379-/**\nsecurity/security.c:1380: * security_inode_init_security_anon() - Initialize an anonymous inode\nsecurity/security.c-1381- * @inode: the inode\n--\nsecurity/security.c-1390- */\nsecurity/security.c:1391:int security_inode_init_security_anon(struct inode *inode,\nsecurity/security.c-1392-\t\t\t\t      const struct qstr *name,\n"
}

9/2 2026/07/01 01:31 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 17027
							output: 23
							thoughts: 230
						

Thoughts:
**Analyzing the Intersection of BPF, LSM, and Filesystem Initialization**

Okay, so the `security_inode_init_security` function is a key player here, called by numerous filesystems. It's the point of entry. Then we have `bpf_init_inode_xattr`, a kfunc, being invoked from a BPF program. Interesting. That test in `tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c`, `test_init_inode_xattr`, is showing us the direct link – the BPF program attaches to `lsm.s/inode_init_security` and then calls `bpf_init_inode_xattr`. Makes perfect sense.

Now, let's look at the configuration requirements. `CONFIG_BPF_LSM` is definitely a must-have. Since `CONFIG_BPF_LSM` implies `CONFIG_SECURITY`, we're good there. Nothing about network security here, so `CONFIG_SECURITY_NETWORK` is irrelevant, I think. And since `CONFIG_BPF_LSM` implies `CONFIG_BPF_SYSCALL`, that's covered as well. So, these are the dependencies.

I'm going to revisit `bpf_init_inode_xattr` itself in `fs/bpf_fs_kfuncs.c` one more time. Need to ensure I understand that kfunc fully in this context. It's the linchpin.



10/2 2026/07/01 01:31 tool grepper
0m Args:
{
  "Expression": "bpf_init_inode_xattr"
}

Results:
{
  "Output": "fs/bpf_fs_kfuncs.c=378=static int bpf_xattrs_used(const struct lsm_xattrs *ctx)\n--\nfs/bpf_fs_kfuncs.c-392-/**\nfs/bpf_fs_kfuncs.c:393: * bpf_init_inode_xattr - set an xattr on a new inode from inode_init_security\nfs/bpf_fs_kfuncs.c-394- * @xattrs: inode_init_security xattr state from the hook context\n--\nfs/bpf_fs_kfuncs.c-401- */\nfs/bpf_fs_kfuncs.c:402:__bpf_kfunc int bpf_init_inode_xattr(struct lsm_xattrs *xattrs,\nfs/bpf_fs_kfuncs.c-403-\t\t\t\t     const char *name__str,\n--\nfs/bpf_fs_kfuncs.c=465=BTF_ID_FLAGS(func, bpf_real_inode, KF_SLEEPABLE | KF_RET_NULL)\nfs/bpf_fs_kfuncs.c:466:BTF_ID_FLAGS(func, bpf_init_inode_xattr, KF_SLEEPABLE)\nfs/bpf_fs_kfuncs.c-467-BTF_KFUNCS_END(bpf_fs_kfunc_set_ids)\n--\ntools/testing/selftests/bpf/bpf_kfuncs.h=95=struct lsm_xattrs;\ntools/testing/selftests/bpf/bpf_kfuncs.h:96:extern int bpf_init_inode_xattr(struct lsm_xattrs *xattrs,\ntools/testing/selftests/bpf/bpf_kfuncs.h-97-\t\t\t\tconst char *name__str,\n--\ntools/testing/selftests/bpf/progs/test_init_inode_xattr.c=17=int BPF_PROG(test_init_inode_xattr, struct inode *inode, struct inode *dir,\n--\ntools/testing/selftests/bpf/progs/test_init_inode_xattr.c-27-\tbpf_dynptr_from_mem(xattr_value, sizeof(xattr_value), 0, \u0026value_ptr);\ntools/testing/selftests/bpf/progs/test_init_inode_xattr.c:28:\tinit_result = bpf_init_inode_xattr(xattrs, xattr_name, \u0026value_ptr);\ntools/testing/selftests/bpf/progs/test_init_inode_xattr.c-29-\n"
}

11/2 2026/07/01 01:31 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18277
							output: 23
							thoughts: 263
						

Thoughts:
**My Analysis of LSM and BPF Integration**

Okay, so I'm diving into this new BPF kfunc, `bpf_init_inode_xattr`. That's the primary symbol I need to focus on. I see that `security_inode_init_security` is also getting tweaked to use `struct lsm_xattrs`, which makes sense because that's a central LSM function. The introduction of `bpf_init_inode_xattr` as a new BPF kfunc is the key here.

First things first: I'm adding both `bpf_init_inode_xattr` and `security_inode_init_security` to my `FocusSymbols` list. And, of course, I'll need to enable `BPF_LSM` in my configuration.

Now, let's see if we need to bring `EVM` into the equation. Yep, I see that `evm_inode_init_security` has been modified, so I'll add `EVM` to `EnableConfigs` to make sure those EVM code paths are included in my analysis. It's also a good idea to consider the impacts on SELinux and SMACK, so let's check those modifications as well. While `SECURITY_SELINUX` and `SECURITY_SMACK` are probably usually enabled anyway, it's prudent to explicitly list them in `EnableConfigs` just to be completely thorough. I'll need to examine `security/selinux/hooks.c` and `security/smack/smack_lsm.c` to understand the full implications.



12/2 2026/07/01 01:31 tool grepper
0m Args:
{
  "Expression": "selinux_inode_init_security"
}

Results:
{
  "Output": "security/selinux/hooks.c=2942=static int selinux_dentry_create_files_as(struct dentry *dentry, int mode,\n--\nsecurity/selinux/hooks.c-2962-\nsecurity/selinux/hooks.c:2963:static int selinux_inode_init_security(struct inode *inode, struct inode *dir,\nsecurity/selinux/hooks.c-2964-\t\t\t\t       const struct qstr *qstr,\n--\nsecurity/selinux/hooks.c-3008-\nsecurity/selinux/hooks.c:3009:static int selinux_inode_init_security_anon(struct inode *inode,\nsecurity/selinux/hooks.c-3010-\t\t\t\t\t    const struct qstr *name,\n--\nsecurity/selinux/hooks.c=7529=static struct security_hook_list selinux_hooks[] __ro_after_init = {\n--\nsecurity/selinux/hooks.c-7567-\tLSM_HOOK_INIT(inode_free_security, selinux_inode_free_security),\nsecurity/selinux/hooks.c:7568:\tLSM_HOOK_INIT(inode_init_security, selinux_inode_init_security),\nsecurity/selinux/hooks.c:7569:\tLSM_HOOK_INIT(inode_init_security_anon, selinux_inode_init_security_anon),\nsecurity/selinux/hooks.c-7570-\tLSM_HOOK_INIT(inode_create, selinux_inode_create),\n"
}

13/2 2026/07/01 01:32 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 19326
							output: 21
							thoughts: 103
						

Thoughts:
**My Configuration Updates**

Alright, time to get this configuration updated. I need to add `SECURITY_SELINUX`, `SECURITY_SMACK`, `EVM`, and `BPF_LSM` to `EnableConfigs`. These are critical for the security and functionality I'm targeting. The next step is to ensure that their corresponding initialization functions are used. Specifically, I need to focus on these symbols: `bpf_init_inode_xattr`, `security_inode_init_security`, `evm_inode_init_security`, `selinux_inode_init_security`, and `smack_inode_init_security`. Now, before I move on, I should probably check that `bpf_xattrs_used` is set up correctly. This should be an easy check, but it's important to make sure all components are accounted for.



14/2 2026/07/01 01:32 tool grepper
0m Args:
{
  "Expression": "bpf_xattrs_used"
}

Results:
{
  "Output": "fs/bpf_fs_kfuncs.c=373=__bpf_kfunc struct inode *bpf_real_inode(struct dentry *dentry)\n--\nfs/bpf_fs_kfuncs.c-377-\nfs/bpf_fs_kfuncs.c:378:static int bpf_xattrs_used(const struct lsm_xattrs *ctx)\nfs/bpf_fs_kfuncs.c-379-{\n--\nfs/bpf_fs_kfuncs.c=402=__bpf_kfunc int bpf_init_inode_xattr(struct lsm_xattrs *xattrs,\n--\nfs/bpf_fs_kfuncs.c-414-\t\treturn -EINVAL;\nfs/bpf_fs_kfuncs.c:415:\tif (bpf_xattrs_used(xattrs) \u003e= BPF_LSM_INODE_INIT_XATTRS)\nfs/bpf_fs_kfuncs.c-416-\t\treturn -ENOSPC;\n"
}

15/2 2026/07/01 01:32 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 19903
							output: 212
							thoughts: 277
						

Thoughts:
**My Fuzzing Strategy: Expanding the BPF LSM Coverage**

Okay, so I need to incorporate this new `bpf_init_inode_xattr` kfunc into my fuzzing strategy. Initially, I thought I could directly target `bpf_xattrs_used`, but that's a static symbol. Syzkaller can't work with that directly, but it's called by `bpf_init_inode_xattr`. Aha! `bpf_init_inode_xattr` *is* the kfunc I need.

Let's break this down for the fuzzing setup:

*   **`WorthFuzzing: true`**: Absolutely, this is a clear candidate.
*   **`FocusSymbols:`**

    *   `bpf_init_inode_xattr`: This is the crucial entry point, the primary target.
    *   `security_inode_init_security`
    *   `evm_inode_init_security`
    *   `selinux_inode_init_security`
    *   `smack_inode_init_security`:  All of these are *critically* important because they are the LSM hooks that are being modified to use the new `bpf_init_inode_xattr` functionality. These are the touch points that need thorough coverage.

*   **`EnableConfigs:`**

    *   `BPF_LSM`: Obviously, this is fundamental.
    *   `EVM`
    *   `SECURITY_SELINUX`
    *   `SECURITY_SMACK`: Need to make sure all these are enabled since the kfunc changes LSM hooks that these leverage.

The reasoning here is solid. The patch introduces a novel mechanism for BPF LSM programs to manage xattrs on new inodes. It's a significant functional change within reachable core kernel logic, and the modification of the `inode_init_security` hook to use the `struct lsm_xattrs` structure across multiple LSMs (core LSM, EVM, SELinux, and Smack) means this is a prime target for fuzzing. The cross-LSM changes make this especially interesting for finding interactions and potential conflicts. Time to generate some test cases and see what we can shake loose.



16/2 2026/07/01 01:32 tool set-results
0m Args:
{
  "EnableConfigs": [
    "BPF_LSM",
    "EVM",
    "SECURITY_SELINUX",
    "SECURITY_SMACK"
  ],
  "FocusSymbols": [
    "bpf_init_inode_xattr",
    "security_inode_init_security",
    "evm_inode_init_security",
    "selinux_inode_init_security",
    "smack_inode_init_security"
  ],
  "Reasoning": "The patch introduces a new BPF kfunc `bpf_init_inode_xattr` that allows BPF LSM programs to set xattrs on a new inode during `inode_init_security`. It modifies the `inode_init_security` LSM hook signature to use a new `struct lsm_xattrs` structure, affecting core LSM, EVM, SELinux, and Smack. These are functional changes to reachable core kernel logic and should be fuzzed.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "BPF_LSM",
    "EVM",
    "SECURITY_SELINUX",
    "SECURITY_SMACK"
  ],
  "FocusSymbols": [
    "bpf_init_inode_xattr",
    "security_inode_init_security",
    "evm_inode_init_security",
    "selinux_inode_init_security",
    "smack_inode_init_security"
  ],
  "Reasoning": "The patch introduces a new BPF kfunc `bpf_init_inode_xattr` that allows BPF LSM programs to set xattrs on a new inode during `inode_init_security`. It modifies the `inode_init_security` LSM hook signature to use a new `struct lsm_xattrs` structure, affecting core LSM, EVM, SELinux, and Smack. These are functional changes to reachable core kernel logic and should be fuzzed.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)