On 4/22/2021 6:46 AM, Roberto Sassu wrote: >> From: Casey Schaufler [mailto:casey@xxxxxxxxxxxxxxxx] >> Sent: Thursday, April 22, 2021 12:44 AM >> On 4/21/2021 9:19 AM, Roberto Sassu wrote: >>> In preparation for moving EVM to the LSM infrastructure, this patch >>> replaces the name, value, len triple with the xattr array pointer provided >>> by security_inode_init_security(). LSMs are expected to call the new >>> function lsm_find_xattr_slot() to find the first unused slot of the array >>> where the xattr should be written. >>> >>> This patch modifies also SELinux and Smack to search for an unused slot, to >>> have a consistent behavior across LSMs (the unmodified version would >>> overwrite the xattr set by the first LSM in the chain). It is also >>> desirable to have the modification in those LSMs, as they are likely used >>> as a reference for the development of new LSMs. >> This looks better than V1. One safety comment below. >> >>> Signed-off-by: Roberto Sassu <roberto.sassu@xxxxxxxxxx> >>> --- >>> include/linux/lsm_hook_defs.h | 4 ++-- >>> include/linux/lsm_hooks.h | 18 +++++++++++++++--- >>> security/security.c | 13 +++++++------ >>> security/selinux/hooks.c | 13 ++++++------- >>> security/smack/smack_lsm.c | 20 +++++++++----------- >>> 5 files changed, 39 insertions(+), 29 deletions(-) >>> >>> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h >>> index 477a597db013..afb9dd122f60 100644 >>> --- a/include/linux/lsm_hook_defs.h >>> +++ b/include/linux/lsm_hook_defs.h >>> @@ -111,8 +111,8 @@ LSM_HOOK(int, 0, path_notify, const struct path >> *path, u64 mask, >>> LSM_HOOK(int, 0, inode_alloc_security, struct inode *inode) >>> LSM_HOOK(void, LSM_RET_VOID, inode_free_security, struct inode *inode) >>> LSM_HOOK(int, 0, inode_init_security, struct inode *inode, >>> - struct inode *dir, const struct qstr *qstr, const char **name, >>> - void **value, size_t *len) >>> + struct inode *dir, const struct qstr *qstr, struct xattr *xattrs, >>> + void *fs_data) >>> 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 c5498f5174ce..e8c9bac29b9d 100644 >>> --- a/include/linux/lsm_hooks.h >>> +++ b/include/linux/lsm_hooks.h >>> @@ -27,6 +27,7 @@ >>> >>> #include <linux/security.h> >>> #include <linux/init.h> >>> +#include <linux/xattr.h> >>> #include <linux/rculist.h> >>> >>> /** >>> @@ -227,9 +228,11 @@ >>> * @inode contains the inode structure of the newly created inode. >>> * @dir contains the inode structure of the parent directory. >>> * @qstr contains the last path component of the new object >>> - * @name will be set to the allocated name suffix (e.g. selinux). >>> - * @value will be set to the allocated attribute value. >>> - * @len will be set to the length of the value. >>> + * @xattrs contains the full array of xattrs allocated by LSMs where >>> + * ->name will be set to the allocated name suffix (e.g. selinux). >>> + * ->value will be set to the allocated attribute value. >>> + * ->len will be set to the length of the value. >>> + * @fs_data contains filesystem-specific data. >>> * Returns 0 if @name and @value have been successfully set, >>> * -EOPNOTSUPP if no security attribute is needed, or >>> * -ENOMEM on memory allocation failure. >>> @@ -1661,4 +1664,13 @@ static inline void security_delete_hooks(struct >> security_hook_list *hooks, >>> extern int lsm_inode_alloc(struct inode *inode); >>> >> Some "security researcher" with a fuzz tester is going to manage to dump junk >> into the slots and ruin your week. I suggest a simple change to make bounds >> checking >> possible. It should never happen, but if that was sufficient people would >> love C >> string processing better. >> >>> +static inline struct xattr *lsm_find_xattr_slot(struct xattr *xattrs) >> +static inline struct xattr *lsm_find_xattr_slot(struct xattr *xattrs, int available) > Ok. I looked at how I should do that. Initially, I thought that I could > use a global variable storing the number of inode_init_security > implementations, determined at LSM registration time. Then, > I realized that this would not work, as the number of array elements > when security_old_inode_init_security() is called is 1. You can address that by expanding the call_int_hook MACRO in security_old_inode_init_security() in place and change it to stop after the first call. The two callers of security_old_inode_init_security() are going to need to be converted to security_inode_init_security() when the "complete" stacking (i.e. SELinux + Smack) anyway, so I don't see that as an issue. Is anyone concerned that ocfs2 and reiserfs aren't EVM capable? > > I modified the patch set to pass also the number of array elements. > > Roberto > > HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063 > Managing Director: Li Peng, Li Jian, Shi Yanli > >>> +{ >>> + struct xattr *slot; >>> + >>> + for (slot = xattrs; slot && slot->name != NULL; slot++) >> + for (slot = xattrs; slot && slot->name != NULL; slot++) >> if (WARN_ON(slot > xattrs[available])) >> return NULL; >> >>> + ; >>> + >>> + return slot; >>> +} >>> #endif /* ! __LINUX_LSM_HOOKS_H */ >>> diff --git a/security/security.c b/security/security.c >>> index 7f14e59c4f8e..2c1fe1496069 100644 >>> --- a/security/security.c >>> +++ b/security/security.c >>> @@ -1037,18 +1037,16 @@ int security_inode_init_security(struct inode >> *inode, struct inode *dir, >>> if (!initxattrs) >>> return call_int_hook(inode_init_security, -EOPNOTSUPP, >> inode, >>> - dir, qstr, NULL, NULL, NULL); >>> + dir, qstr, NULL, fs_data); >>> memset(new_xattrs, 0, sizeof(new_xattrs)); >>> lsm_xattr = new_xattrs; >>> ret = call_int_hook(inode_init_security, -EOPNOTSUPP, inode, dir, qstr, >>> - &lsm_xattr->name, >>> - &lsm_xattr->value, >>> - &lsm_xattr->value_len); >>> + lsm_xattr, fs_data); >>> if (ret) >>> goto out; >>> >>> evm_xattr = lsm_xattr + 1; >>> - ret = evm_inode_init_security(inode, lsm_xattr, evm_xattr); >>> + ret = evm_inode_init_security(inode, new_xattrs, evm_xattr); >>> if (ret) >>> goto out; >>> ret = initxattrs(inode, new_xattrs, fs_data); >>> @@ -1071,10 +1069,13 @@ int security_old_inode_init_security(struct inode >> *inode, struct inode *dir, >>> const struct qstr *qstr, const char **name, >>> void **value, size_t *len) >>> { >>> + struct xattr xattr = { .name = NULL, .value = NULL, .value_len = 0 }; >>> + struct xattr *lsm_xattr = (name && value && len) ? &xattr : NULL; >>> + >>> if (unlikely(IS_PRIVATE(inode))) >>> return -EOPNOTSUPP; >>> return call_int_hook(inode_init_security, -EOPNOTSUPP, inode, dir, >>> - qstr, name, value, len); >>> + qstr, lsm_xattr, NULL); >>> } >>> EXPORT_SYMBOL(security_old_inode_init_security); >>> >>> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c >>> index ddd097790d47..806827eb132a 100644 >>> --- a/security/selinux/hooks.c >>> +++ b/security/selinux/hooks.c >>> @@ -2916,11 +2916,11 @@ 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, >>> - const char **name, >>> - void **value, size_t *len) >>> + struct xattr *xattrs, void *fs_data) >>> { >>> const struct task_security_struct *tsec = selinux_cred(current_cred()); >>> struct superblock_security_struct *sbsec; >>> + struct xattr *xattr = lsm_find_xattr_slot(xattrs); >>> u32 newsid, clen; >>> int rc; >>> char *context; >>> @@ -2947,16 +2947,15 @@ static int selinux_inode_init_security(struct >> inode *inode, struct inode *dir, >>> !(sbsec->flags & SBLABEL_MNT)) >>> return -EOPNOTSUPP; >>> >>> - if (name) >>> - *name = XATTR_SELINUX_SUFFIX; >>> + if (xattr) { >>> + xattr->name = XATTR_SELINUX_SUFFIX; >>> >>> - if (value && len) { >>> rc = security_sid_to_context_force(&selinux_state, newsid, >>> &context, &clen); >>> if (rc) >>> return rc; >>> - *value = context; >>> - *len = clen; >>> + xattr->value = context; >>> + xattr->value_len = clen; >>> } >>> >>> return 0; >>> diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c >>> index 12a45e61c1a5..af7eee0fee52 100644 >>> --- a/security/smack/smack_lsm.c >>> +++ b/security/smack/smack_lsm.c >>> @@ -962,26 +962,24 @@ static int smack_inode_alloc_security(struct inode >> *inode) >>> * @inode: the newly created inode >>> * @dir: containing directory object >>> * @qstr: unused >>> - * @name: where to put the attribute name >>> - * @value: where to put the attribute value >>> - * @len: where to put the length of the attribute >>> + * @xattrs: where to put the attribute >>> * >>> * 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, const char **name, >>> - void **value, size_t *len) >>> + const struct qstr *qstr, >>> + struct xattr *xattrs, void *fs_data) >>> { >>> struct inode_smack *issp = smack_inode(inode); >>> struct smack_known *skp = smk_of_current(); >>> struct smack_known *isp = smk_of_inode(inode); >>> struct smack_known *dsp = smk_of_inode(dir); >>> + struct xattr *xattr = lsm_find_xattr_slot(xattrs); >>> int may; >>> >>> - if (name) >>> - *name = XATTR_SMACK_SUFFIX; >>> + if (xattr) { >>> + xattr->name = XATTR_SMACK_SUFFIX; >>> >>> - if (value && len) { >>> rcu_read_lock(); >>> may = smk_access_entry(skp->smk_known, dsp->smk_known, >>> &skp->smk_rules); >>> @@ -999,11 +997,11 @@ static int smack_inode_init_security(struct inode >> *inode, struct inode *dir, >>> issp->smk_flags |= SMK_INODE_CHANGED; >>> } >>> >>> - *value = kstrdup(isp->smk_known, GFP_NOFS); >>> - if (*value == NULL) >>> + xattr->value = kstrdup(isp->smk_known, GFP_NOFS); >>> + if (xattr->value == NULL) >>> return -ENOMEM; >>> >>> - *len = strlen(isp->smk_known); >>> + xattr->value_len = strlen(isp->smk_known); >>> } >>> >>> return 0;