From 9d8f13ba3f4833219e50767b022b82cd0da930eb Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Mon, 6 Jun 2011 15:29:25 -0400 Subject: [PATCH 01/95] security: new security_inode_init_security API adds function callback This patch changes the security_inode_init_security API by adding a filesystem specific callback to write security extended attributes. This change is in preparation for supporting the initialization of multiple LSM xattrs and the EVM xattr. Initially the callback function walks an array of xattrs, writing each xattr separately, but could be optimized to write multiple xattrs at once. For existing security_inode_init_security() calls, which have not yet been converted to use the new callback function, such as those in reiserfs and ocfs2, this patch defines security_old_inode_init_security(). Signed-off-by: Mimi Zohar --- fs/btrfs/xattr.c | 56 +++++++++++++++++----------------- fs/ext2/xattr_security.c | 34 +++++++++++---------- fs/ext3/xattr_security.c | 36 ++++++++++++---------- fs/ext4/xattr_security.c | 36 ++++++++++++---------- fs/gfs2/inode.c | 38 +++++++++++------------ fs/jffs2/security.c | 37 +++++++++++----------- fs/jfs/xattr.c | 59 ++++++++++++++++++------------------ fs/ocfs2/xattr.c | 38 ++++++++++++++--------- fs/reiserfs/xattr_security.c | 4 +-- fs/xfs/linux-2.6/xfs_iops.c | 39 ++++++++++++------------ include/linux/security.h | 17 ++++++++--- include/linux/xattr.h | 6 ++++ mm/shmem.c | 4 +-- security/security.c | 39 ++++++++++++++++++++++-- 14 files changed, 255 insertions(+), 188 deletions(-) diff --git a/fs/btrfs/xattr.c b/fs/btrfs/xattr.c index 5366fe452ab0..a039e6ed4ce0 100644 --- a/fs/btrfs/xattr.c +++ b/fs/btrfs/xattr.c @@ -360,36 +360,36 @@ int btrfs_removexattr(struct dentry *dentry, const char *name) XATTR_REPLACE); } +int btrfs_initxattrs(struct inode *inode, const struct xattr *xattr_array, + void *fs_info) +{ + const struct xattr *xattr; + struct btrfs_trans_handle *trans = fs_info; + char *name; + int err = 0; + + for (xattr = xattr_array; xattr->name != NULL; xattr++) { + name = kmalloc(XATTR_SECURITY_PREFIX_LEN + + strlen(xattr->name) + 1, GFP_NOFS); + if (!name) { + err = -ENOMEM; + break; + } + strcpy(name, XATTR_SECURITY_PREFIX); + strcpy(name + XATTR_SECURITY_PREFIX_LEN, xattr->name); + err = __btrfs_setxattr(trans, inode, name, + xattr->value, xattr->value_len, 0); + kfree(name); + if (err < 0) + break; + } + return err; +} + int btrfs_xattr_security_init(struct btrfs_trans_handle *trans, struct inode *inode, struct inode *dir, const struct qstr *qstr) { - int err; - size_t len; - void *value; - char *suffix; - char *name; - - err = security_inode_init_security(inode, dir, qstr, &suffix, &value, - &len); - if (err) { - if (err == -EOPNOTSUPP) - return 0; - return err; - } - - name = kmalloc(XATTR_SECURITY_PREFIX_LEN + strlen(suffix) + 1, - GFP_NOFS); - if (!name) { - err = -ENOMEM; - } else { - strcpy(name, XATTR_SECURITY_PREFIX); - strcpy(name + XATTR_SECURITY_PREFIX_LEN, suffix); - err = __btrfs_setxattr(trans, inode, name, value, len, 0); - kfree(name); - } - - kfree(suffix); - kfree(value); - return err; + return security_inode_init_security(inode, dir, qstr, + &btrfs_initxattrs, trans); } diff --git a/fs/ext2/xattr_security.c b/fs/ext2/xattr_security.c index 5d979b4347b0..c922adc8ef41 100644 --- a/fs/ext2/xattr_security.c +++ b/fs/ext2/xattr_security.c @@ -46,26 +46,28 @@ ext2_xattr_security_set(struct dentry *dentry, const char *name, value, size, flags); } +int ext2_initxattrs(struct inode *inode, const struct xattr *xattr_array, + void *fs_info) +{ + const struct xattr *xattr; + int err = 0; + + for (xattr = xattr_array; xattr->name != NULL; xattr++) { + err = ext2_xattr_set(inode, EXT2_XATTR_INDEX_SECURITY, + xattr->name, xattr->value, + xattr->value_len, 0); + if (err < 0) + break; + } + return err; +} + int ext2_init_security(struct inode *inode, struct inode *dir, const struct qstr *qstr) { - int err; - size_t len; - void *value; - char *name; - - err = security_inode_init_security(inode, dir, qstr, &name, &value, &len); - if (err) { - if (err == -EOPNOTSUPP) - return 0; - return err; - } - err = ext2_xattr_set(inode, EXT2_XATTR_INDEX_SECURITY, - name, value, len, 0); - kfree(name); - kfree(value); - return err; + return security_inode_init_security(inode, dir, qstr, + &ext2_initxattrs, NULL); } const struct xattr_handler ext2_xattr_security_handler = { diff --git a/fs/ext3/xattr_security.c b/fs/ext3/xattr_security.c index b8d9f83aa5c5..3c218b8a51d4 100644 --- a/fs/ext3/xattr_security.c +++ b/fs/ext3/xattr_security.c @@ -48,26 +48,30 @@ ext3_xattr_security_set(struct dentry *dentry, const char *name, name, value, size, flags); } +int ext3_initxattrs(struct inode *inode, const struct xattr *xattr_array, + void *fs_info) +{ + const struct xattr *xattr; + handle_t *handle = fs_info; + int err = 0; + + for (xattr = xattr_array; xattr->name != NULL; xattr++) { + err = ext3_xattr_set_handle(handle, inode, + EXT3_XATTR_INDEX_SECURITY, + xattr->name, xattr->value, + xattr->value_len, 0); + if (err < 0) + break; + } + return err; +} + int ext3_init_security(handle_t *handle, struct inode *inode, struct inode *dir, const struct qstr *qstr) { - int err; - size_t len; - void *value; - char *name; - - err = security_inode_init_security(inode, dir, qstr, &name, &value, &len); - if (err) { - if (err == -EOPNOTSUPP) - return 0; - return err; - } - err = ext3_xattr_set_handle(handle, inode, EXT3_XATTR_INDEX_SECURITY, - name, value, len, 0); - kfree(name); - kfree(value); - return err; + return security_inode_init_security(inode, dir, qstr, + &ext3_initxattrs, handle); } const struct xattr_handler ext3_xattr_security_handler = { diff --git a/fs/ext4/xattr_security.c b/fs/ext4/xattr_security.c index 007c3bfbf094..34e4350dd4d9 100644 --- a/fs/ext4/xattr_security.c +++ b/fs/ext4/xattr_security.c @@ -48,26 +48,30 @@ ext4_xattr_security_set(struct dentry *dentry, const char *name, name, value, size, flags); } +int ext4_initxattrs(struct inode *inode, const struct xattr *xattr_array, + void *fs_info) +{ + const struct xattr *xattr; + handle_t *handle = fs_info; + int err = 0; + + for (xattr = xattr_array; xattr->name != NULL; xattr++) { + err = ext4_xattr_set_handle(handle, inode, + EXT4_XATTR_INDEX_SECURITY, + xattr->name, xattr->value, + xattr->value_len, 0); + if (err < 0) + break; + } + return err; +} + int ext4_init_security(handle_t *handle, struct inode *inode, struct inode *dir, const struct qstr *qstr) { - int err; - size_t len; - void *value; - char *name; - - err = security_inode_init_security(inode, dir, qstr, &name, &value, &len); - if (err) { - if (err == -EOPNOTSUPP) - return 0; - return err; - } - err = ext4_xattr_set_handle(handle, inode, EXT4_XATTR_INDEX_SECURITY, - name, value, len, 0); - kfree(name); - kfree(value); - return err; + return security_inode_init_security(inode, dir, qstr, + &ext4_initxattrs, handle); } const struct xattr_handler ext4_xattr_security_handler = { diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 03e0c529063e..1d3a1a651721 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -624,29 +624,27 @@ fail: return error; } +int gfs2_initxattrs(struct inode *inode, const struct xattr *xattr_array, + void *fs_info) +{ + const struct xattr *xattr; + int err = 0; + + for (xattr = xattr_array; xattr->name != NULL; xattr++) { + err = __gfs2_xattr_set(inode, xattr->name, xattr->value, + xattr->value_len, 0, + GFS2_EATYPE_SECURITY); + if (err < 0) + break; + } + return err; +} + static int gfs2_security_init(struct gfs2_inode *dip, struct gfs2_inode *ip, const struct qstr *qstr) { - int err; - size_t len; - void *value; - char *name; - - err = security_inode_init_security(&ip->i_inode, &dip->i_inode, qstr, - &name, &value, &len); - - if (err) { - if (err == -EOPNOTSUPP) - return 0; - return err; - } - - err = __gfs2_xattr_set(&ip->i_inode, name, value, len, 0, - GFS2_EATYPE_SECURITY); - kfree(value); - kfree(name); - - return err; + return security_inode_init_security(&ip->i_inode, &dip->i_inode, qstr, + &gfs2_initxattrs, NULL); } /** diff --git a/fs/jffs2/security.c b/fs/jffs2/security.c index cfeb7164b085..0f20208df602 100644 --- a/fs/jffs2/security.c +++ b/fs/jffs2/security.c @@ -22,26 +22,29 @@ #include #include "nodelist.h" -/* ---- Initial Security Label Attachment -------------- */ +/* ---- Initial Security Label(s) Attachment callback --- */ +int jffs2_initxattrs(struct inode *inode, const struct xattr *xattr_array, + void *fs_info) +{ + const struct xattr *xattr; + int err = 0; + + for (xattr = xattr_array; xattr->name != NULL; xattr++) { + err = do_jffs2_setxattr(inode, JFFS2_XPREFIX_SECURITY, + xattr->name, xattr->value, + xattr->value_len, 0); + if (err < 0) + break; + } + return err; +} + +/* ---- Initial Security Label(s) Attachment ----------- */ int jffs2_init_security(struct inode *inode, struct inode *dir, const struct qstr *qstr) { - int rc; - size_t len; - void *value; - char *name; - - rc = security_inode_init_security(inode, dir, qstr, &name, &value, &len); - if (rc) { - if (rc == -EOPNOTSUPP) - return 0; - return rc; - } - rc = do_jffs2_setxattr(inode, JFFS2_XPREFIX_SECURITY, name, value, len, 0); - - kfree(name); - kfree(value); - return rc; + return security_inode_init_security(inode, dir, qstr, + &jffs2_initxattrs, NULL); } /* ---- XATTR Handler for "security.*" ----------------- */ diff --git a/fs/jfs/xattr.c b/fs/jfs/xattr.c index 24838f1eeee5..e982509292f8 100644 --- a/fs/jfs/xattr.c +++ b/fs/jfs/xattr.c @@ -1091,38 +1091,37 @@ int jfs_removexattr(struct dentry *dentry, const char *name) } #ifdef CONFIG_JFS_SECURITY +int jfs_initxattrs(struct inode *inode, const struct xattr *xattr_array, + void *fs_info) +{ + const struct xattr *xattr; + tid_t *tid = fs_info; + char *name; + int err = 0; + + for (xattr = xattr_array; xattr->name != NULL; xattr++) { + name = kmalloc(XATTR_SECURITY_PREFIX_LEN + + strlen(xattr->name) + 1, GFP_NOFS); + if (!name) { + err = -ENOMEM; + break; + } + strcpy(name, XATTR_SECURITY_PREFIX); + strcpy(name + XATTR_SECURITY_PREFIX_LEN, xattr->name); + + err = __jfs_setxattr(*tid, inode, name, + xattr->value, xattr->value_len, 0); + kfree(name); + if (err < 0) + break; + } + return err; +} + int jfs_init_security(tid_t tid, struct inode *inode, struct inode *dir, const struct qstr *qstr) { - int rc; - size_t len; - void *value; - char *suffix; - char *name; - - rc = security_inode_init_security(inode, dir, qstr, &suffix, &value, - &len); - if (rc) { - if (rc == -EOPNOTSUPP) - return 0; - return rc; - } - name = kmalloc(XATTR_SECURITY_PREFIX_LEN + 1 + strlen(suffix), - GFP_NOFS); - if (!name) { - rc = -ENOMEM; - goto kmalloc_failed; - } - strcpy(name, XATTR_SECURITY_PREFIX); - strcpy(name + XATTR_SECURITY_PREFIX_LEN, suffix); - - rc = __jfs_setxattr(tid, inode, name, value, len, 0); - - kfree(name); -kmalloc_failed: - kfree(suffix); - kfree(value); - - return rc; + return security_inode_init_security(inode, dir, qstr, + &jfs_initxattrs, &tid); } #endif diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index 81ecf9c0bf0a..194fb22ef79d 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -7185,20 +7185,9 @@ int ocfs2_init_security_and_acl(struct inode *dir, { int ret = 0; struct buffer_head *dir_bh = NULL; - struct ocfs2_security_xattr_info si = { - .enable = 1, - }; - ret = ocfs2_init_security_get(inode, dir, qstr, &si); + ret = ocfs2_init_security_get(inode, dir, qstr, NULL); if (!ret) { - ret = ocfs2_xattr_set(inode, OCFS2_XATTR_INDEX_SECURITY, - si.name, si.value, si.value_len, - XATTR_CREATE); - if (ret) { - mlog_errno(ret); - goto leave; - } - } else if (ret != -EOPNOTSUPP) { mlog_errno(ret); goto leave; } @@ -7255,6 +7244,22 @@ static int ocfs2_xattr_security_set(struct dentry *dentry, const char *name, name, value, size, flags); } +int ocfs2_initxattrs(struct inode *inode, const struct xattr *xattr_array, + void *fs_info) +{ + const struct xattr *xattr; + int err = 0; + + for (xattr = xattr_array; xattr->name != NULL; xattr++) { + err = ocfs2_xattr_set(inode, OCFS2_XATTR_INDEX_SECURITY, + xattr->name, xattr->value, + xattr->value_len, XATTR_CREATE); + if (err) + break; + } + return err; +} + int ocfs2_init_security_get(struct inode *inode, struct inode *dir, const struct qstr *qstr, @@ -7263,8 +7268,13 @@ int ocfs2_init_security_get(struct inode *inode, /* check whether ocfs2 support feature xattr */ if (!ocfs2_supports_xattr(OCFS2_SB(dir->i_sb))) return -EOPNOTSUPP; - return security_inode_init_security(inode, dir, qstr, &si->name, - &si->value, &si->value_len); + if (si) + return security_old_inode_init_security(inode, dir, qstr, + &si->name, &si->value, + &si->value_len); + + return security_inode_init_security(inode, dir, qstr, + &ocfs2_initxattrs, NULL); } int ocfs2_init_security_set(handle_t *handle, diff --git a/fs/reiserfs/xattr_security.c b/fs/reiserfs/xattr_security.c index ef66c18a9332..534668fa41be 100644 --- a/fs/reiserfs/xattr_security.c +++ b/fs/reiserfs/xattr_security.c @@ -66,8 +66,8 @@ int reiserfs_security_init(struct inode *dir, struct inode *inode, if (IS_PRIVATE(dir)) return 0; - error = security_inode_init_security(inode, dir, qstr, &sec->name, - &sec->value, &sec->length); + error = security_old_inode_init_security(inode, dir, qstr, &sec->name, + &sec->value, &sec->length); if (error) { if (error == -EOPNOTSUPP) error = 0; diff --git a/fs/xfs/linux-2.6/xfs_iops.c b/fs/xfs/linux-2.6/xfs_iops.c index d44d92cd12b1..27a3658b830f 100644 --- a/fs/xfs/linux-2.6/xfs_iops.c +++ b/fs/xfs/linux-2.6/xfs_iops.c @@ -93,37 +93,38 @@ xfs_mark_inode_dirty( mark_inode_dirty(inode); } + +int xfs_initxattrs(struct inode *inode, const struct xattr *xattr_array, + void *fs_info) +{ + const struct xattr *xattr; + struct xfs_inode *ip = XFS_I(inode); + int error = 0; + + for (xattr = xattr_array; xattr->name != NULL; xattr++) { + error = xfs_attr_set(ip, xattr->name, xattr->value, + xattr->value_len, ATTR_SECURE); + if (error < 0) + break; + } + return error; +} + /* * Hook in SELinux. This is not quite correct yet, what we really need * here (as we do for default ACLs) is a mechanism by which creation of * these attrs can be journalled at inode creation time (along with the * inode, of course, such that log replay can't cause these to be lost). */ + STATIC int xfs_init_security( struct inode *inode, struct inode *dir, const struct qstr *qstr) { - struct xfs_inode *ip = XFS_I(inode); - size_t length; - void *value; - unsigned char *name; - int error; - - error = security_inode_init_security(inode, dir, qstr, (char **)&name, - &value, &length); - if (error) { - if (error == -EOPNOTSUPP) - return 0; - return -error; - } - - error = xfs_attr_set(ip, name, value, length, ATTR_SECURE); - - kfree(name); - kfree(value); - return error; + return security_inode_init_security(inode, dir, qstr, + &xfs_initxattrs, NULL); } static void diff --git a/include/linux/security.h b/include/linux/security.h index 8ce59ef3e5af..6a20c7025495 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -36,6 +36,7 @@ #include #include #include +#include #include /* Maximum number of letters for an LSM name string */ @@ -147,6 +148,10 @@ extern int mmap_min_addr_handler(struct ctl_table *table, int write, void __user *buffer, size_t *lenp, loff_t *ppos); #endif +/* security_inode_init_security callback function to write xattrs */ +typedef int (*initxattrs) (struct inode *inode, + const struct xattr *xattr_array, void *fs_data); + #ifdef CONFIG_SECURITY struct security_mnt_opts { @@ -1704,8 +1709,11 @@ int security_sb_parse_opts_str(char *options, struct security_mnt_opts *opts); int security_inode_alloc(struct inode *inode); void security_inode_free(struct inode *inode); int security_inode_init_security(struct inode *inode, struct inode *dir, - const struct qstr *qstr, char **name, - void **value, size_t *len); + const struct qstr *qstr, + initxattrs initxattrs, void *fs_data); +int security_old_inode_init_security(struct inode *inode, struct inode *dir, + const struct qstr *qstr, char **name, + void **value, size_t *len); int security_inode_create(struct inode *dir, struct dentry *dentry, int mode); int security_inode_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry); @@ -2035,9 +2043,8 @@ static inline void security_inode_free(struct inode *inode) static inline int security_inode_init_security(struct inode *inode, struct inode *dir, const struct qstr *qstr, - char **name, - void **value, - size_t *len) + initxattrs initxattrs, + void *fs_data) { return -EOPNOTSUPP; } diff --git a/include/linux/xattr.h b/include/linux/xattr.h index aed54c50aa66..7a378662ddff 100644 --- a/include/linux/xattr.h +++ b/include/linux/xattr.h @@ -67,6 +67,12 @@ struct xattr_handler { size_t size, int flags, int handler_flags); }; +struct xattr { + char *name; + void *value; + size_t value_len; +}; + ssize_t xattr_getsecurity(struct inode *, const char *, void *, size_t); ssize_t vfs_getxattr(struct dentry *, const char *, void *, size_t); ssize_t vfs_listxattr(struct dentry *d, char *list, size_t size); diff --git a/mm/shmem.c b/mm/shmem.c index fcedf5464eb7..01c19c62d685 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -1878,7 +1878,7 @@ shmem_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev) inode = shmem_get_inode(dir->i_sb, dir, mode, dev, VM_NORESERVE); if (inode) { error = security_inode_init_security(inode, dir, - &dentry->d_name, NULL, + &dentry->d_name, NULL, NULL); if (error) { if (error != -EOPNOTSUPP) { @@ -2018,7 +2018,7 @@ static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s if (!inode) return -ENOSPC; - error = security_inode_init_security(inode, dir, &dentry->d_name, NULL, + error = security_inode_init_security(inode, dir, &dentry->d_name, NULL, NULL); if (error) { if (error != -EOPNOTSUPP) { diff --git a/security/security.c b/security/security.c index 4ba6d4cc061f..3464d58a5766 100644 --- a/security/security.c +++ b/security/security.c @@ -18,6 +18,8 @@ #include #include +#define MAX_LSM_XATTR 1 + /* Boot-time LSM user choice */ static __initdata char chosen_lsm[SECURITY_NAME_MAX + 1] = CONFIG_DEFAULT_SECURITY; @@ -339,15 +341,46 @@ void security_inode_free(struct inode *inode) } int security_inode_init_security(struct inode *inode, struct inode *dir, - const struct qstr *qstr, char **name, - void **value, size_t *len) + const struct qstr *qstr, + const initxattrs initxattrs, void *fs_data) +{ + struct xattr new_xattrs[MAX_LSM_XATTR + 1]; + struct xattr *lsm_xattr; + int ret; + + if (unlikely(IS_PRIVATE(inode))) + return -EOPNOTSUPP; + + memset(new_xattrs, 0, sizeof new_xattrs); + if (!initxattrs) + return security_ops->inode_init_security(inode, dir, qstr, + NULL, NULL, NULL); + lsm_xattr = new_xattrs; + ret = security_ops->inode_init_security(inode, dir, qstr, + &lsm_xattr->name, + &lsm_xattr->value, + &lsm_xattr->value_len); + if (ret) + goto out; + ret = initxattrs(inode, new_xattrs, fs_data); +out: + kfree(lsm_xattr->name); + kfree(lsm_xattr->value); + + return (ret == -EOPNOTSUPP) ? 0 : ret; +} +EXPORT_SYMBOL(security_inode_init_security); + +int security_old_inode_init_security(struct inode *inode, struct inode *dir, + const struct qstr *qstr, char **name, + void **value, size_t *len) { if (unlikely(IS_PRIVATE(inode))) return -EOPNOTSUPP; return security_ops->inode_init_security(inode, dir, qstr, name, value, len); } -EXPORT_SYMBOL(security_inode_init_security); +EXPORT_SYMBOL(security_old_inode_init_security); #ifdef CONFIG_SECURITY_PATH int security_path_mknod(struct path *dir, struct dentry *dentry, int mode, From f381c272224f5f158f5cff64f8f3481fa0eee8b3 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Wed, 9 Mar 2011 14:13:22 -0500 Subject: [PATCH 02/95] integrity: move ima inode integrity data management Move the inode integrity data(iint) management up to the integrity directory in order to share the iint among the different integrity models. Changelog: - don't define MAX_DIGEST_SIZE - rename several globally visible 'ima_' prefixed functions, structs, locks, etc to 'integrity_' - replace '20' with SHA1_DIGEST_SIZE - reflect location change in appropriate Kconfig and Makefiles - remove unnecessary initialization of iint_initialized to 0 - rebased on current ima_iint.c - define integrity_iint_store/lock as static There should be no other functional changes. Signed-off-by: Mimi Zohar Acked-by: Serge Hallyn --- include/linux/ima.h | 13 --- include/linux/integrity.h | 30 ++++++ security/Kconfig | 2 +- security/Makefile | 4 +- security/integrity/Kconfig | 6 ++ security/integrity/Makefile | 10 ++ security/integrity/iint.c | 170 ++++++++++++++++++++++++++++++ security/integrity/ima/Kconfig | 1 + security/integrity/ima/Makefile | 2 +- security/integrity/ima/ima.h | 29 ++--- security/integrity/ima/ima_api.c | 7 +- security/integrity/ima/ima_iint.c | 169 ----------------------------- security/integrity/ima/ima_main.c | 12 +-- security/integrity/integrity.h | 35 ++++++ security/security.c | 3 +- 15 files changed, 277 insertions(+), 216 deletions(-) create mode 100644 include/linux/integrity.h create mode 100644 security/integrity/Kconfig create mode 100644 security/integrity/Makefile create mode 100644 security/integrity/iint.c delete mode 100644 security/integrity/ima/ima_iint.c create mode 100644 security/integrity/integrity.h diff --git a/include/linux/ima.h b/include/linux/ima.h index 09e6e62f9953..6ac8e50c6cf5 100644 --- a/include/linux/ima.h +++ b/include/linux/ima.h @@ -15,8 +15,6 @@ struct linux_binprm; #ifdef CONFIG_IMA extern int ima_bprm_check(struct linux_binprm *bprm); -extern int ima_inode_alloc(struct inode *inode); -extern void ima_inode_free(struct inode *inode); extern int ima_file_check(struct file *file, int mask); extern void ima_file_free(struct file *file); extern int ima_file_mmap(struct file *file, unsigned long prot); @@ -27,16 +25,6 @@ static inline int ima_bprm_check(struct linux_binprm *bprm) return 0; } -static inline int ima_inode_alloc(struct inode *inode) -{ - return 0; -} - -static inline void ima_inode_free(struct inode *inode) -{ - return; -} - static inline int ima_file_check(struct file *file, int mask) { return 0; @@ -51,6 +39,5 @@ static inline int ima_file_mmap(struct file *file, unsigned long prot) { return 0; } - #endif /* CONFIG_IMA_H */ #endif /* _LINUX_IMA_H */ diff --git a/include/linux/integrity.h b/include/linux/integrity.h new file mode 100644 index 000000000000..905981247327 --- /dev/null +++ b/include/linux/integrity.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2009 IBM Corporation + * Author: Mimi Zohar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + */ + +#ifndef _LINUX_INTEGRITY_H +#define _LINUX_INTEGRITY_H + +#include + +#ifdef CONFIG_INTEGRITY +extern int integrity_inode_alloc(struct inode *inode); +extern void integrity_inode_free(struct inode *inode); + +#else +static inline int integrity_inode_alloc(struct inode *inode) +{ + return 0; +} + +static inline void integrity_inode_free(struct inode *inode) +{ + return; +} +#endif /* CONFIG_INTEGRITY_H */ +#endif /* _LINUX_INTEGRITY_H */ diff --git a/security/Kconfig b/security/Kconfig index e0f08b52e4ab..22847a889081 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -186,7 +186,7 @@ source security/smack/Kconfig source security/tomoyo/Kconfig source security/apparmor/Kconfig -source security/integrity/ima/Kconfig +source security/integrity/Kconfig choice prompt "Default security module" diff --git a/security/Makefile b/security/Makefile index 8bb0fe9e1ca9..a5e502f8a05b 100644 --- a/security/Makefile +++ b/security/Makefile @@ -24,5 +24,5 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/built-in.o obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o # Object integrity file lists -subdir-$(CONFIG_IMA) += integrity/ima -obj-$(CONFIG_IMA) += integrity/ima/built-in.o +subdir-$(CONFIG_INTEGRITY) += integrity +obj-$(CONFIG_INTEGRITY) += integrity/built-in.o diff --git a/security/integrity/Kconfig b/security/integrity/Kconfig new file mode 100644 index 000000000000..270469155681 --- /dev/null +++ b/security/integrity/Kconfig @@ -0,0 +1,6 @@ +# +config INTEGRITY + def_bool y + depends on IMA + +source security/integrity/ima/Kconfig diff --git a/security/integrity/Makefile b/security/integrity/Makefile new file mode 100644 index 000000000000..6eddd61b84e8 --- /dev/null +++ b/security/integrity/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for caching inode integrity data (iint) +# + +obj-$(CONFIG_INTEGRITY) += integrity.o + +integrity-y := iint.o + +subdir-$(CONFIG_IMA) += ima +obj-$(CONFIG_IMA) += ima/built-in.o diff --git a/security/integrity/iint.c b/security/integrity/iint.c new file mode 100644 index 000000000000..d17de48bd6cc --- /dev/null +++ b/security/integrity/iint.c @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2008 IBM Corporation + * + * Authors: + * Mimi Zohar + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * File: integrity_iint.c + * - implements the integrity hooks: integrity_inode_alloc, + * integrity_inode_free + * - cache integrity information associated with an inode + * using a rbtree tree. + */ +#include +#include +#include +#include +#include "integrity.h" + +static struct rb_root integrity_iint_tree = RB_ROOT; +static DEFINE_SPINLOCK(integrity_iint_lock); +static struct kmem_cache *iint_cache __read_mostly; + +int iint_initialized; + +/* + * __integrity_iint_find - return the iint associated with an inode + */ +static struct integrity_iint_cache *__integrity_iint_find(struct inode *inode) +{ + struct integrity_iint_cache *iint; + struct rb_node *n = integrity_iint_tree.rb_node; + + assert_spin_locked(&integrity_iint_lock); + + while (n) { + iint = rb_entry(n, struct integrity_iint_cache, rb_node); + + if (inode < iint->inode) + n = n->rb_left; + else if (inode > iint->inode) + n = n->rb_right; + else + break; + } + if (!n) + return NULL; + + return iint; +} + +/* + * integrity_iint_find - return the iint associated with an inode + */ +struct integrity_iint_cache *integrity_iint_find(struct inode *inode) +{ + struct integrity_iint_cache *iint; + + if (!IS_IMA(inode)) + return NULL; + + spin_lock(&integrity_iint_lock); + iint = __integrity_iint_find(inode); + spin_unlock(&integrity_iint_lock); + + return iint; +} + +static void iint_free(struct integrity_iint_cache *iint) +{ + iint->version = 0; + iint->flags = 0UL; + kmem_cache_free(iint_cache, iint); +} + +/** + * integrity_inode_alloc - allocate an iint associated with an inode + * @inode: pointer to the inode + */ +int integrity_inode_alloc(struct inode *inode) +{ + struct rb_node **p; + struct rb_node *new_node, *parent = NULL; + struct integrity_iint_cache *new_iint, *test_iint; + int rc; + + new_iint = kmem_cache_alloc(iint_cache, GFP_NOFS); + if (!new_iint) + return -ENOMEM; + + new_iint->inode = inode; + new_node = &new_iint->rb_node; + + mutex_lock(&inode->i_mutex); /* i_flags */ + spin_lock(&integrity_iint_lock); + + p = &integrity_iint_tree.rb_node; + while (*p) { + parent = *p; + test_iint = rb_entry(parent, struct integrity_iint_cache, + rb_node); + rc = -EEXIST; + if (inode < test_iint->inode) + p = &(*p)->rb_left; + else if (inode > test_iint->inode) + p = &(*p)->rb_right; + else + goto out_err; + } + + inode->i_flags |= S_IMA; + rb_link_node(new_node, parent, p); + rb_insert_color(new_node, &integrity_iint_tree); + + spin_unlock(&integrity_iint_lock); + mutex_unlock(&inode->i_mutex); /* i_flags */ + + return 0; +out_err: + spin_unlock(&integrity_iint_lock); + mutex_unlock(&inode->i_mutex); /* i_flags */ + iint_free(new_iint); + + return rc; +} + +/** + * integrity_inode_free - called on security_inode_free + * @inode: pointer to the inode + * + * Free the integrity information(iint) associated with an inode. + */ +void integrity_inode_free(struct inode *inode) +{ + struct integrity_iint_cache *iint; + + if (!IS_IMA(inode)) + return; + + spin_lock(&integrity_iint_lock); + iint = __integrity_iint_find(inode); + rb_erase(&iint->rb_node, &integrity_iint_tree); + spin_unlock(&integrity_iint_lock); + + iint_free(iint); +} + +static void init_once(void *foo) +{ + struct integrity_iint_cache *iint = foo; + + memset(iint, 0, sizeof *iint); + iint->version = 0; + iint->flags = 0UL; + mutex_init(&iint->mutex); +} + +static int __init integrity_iintcache_init(void) +{ + iint_cache = + kmem_cache_create("iint_cache", sizeof(struct integrity_iint_cache), + 0, SLAB_PANIC, init_once); + iint_initialized = 1; + return 0; +} +security_initcall(integrity_iintcache_init); diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index b6ecfd4d8d78..19c053b82303 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -3,6 +3,7 @@ config IMA bool "Integrity Measurement Architecture(IMA)" depends on SECURITY + select INTEGRITY select SECURITYFS select CRYPTO select CRYPTO_HMAC diff --git a/security/integrity/ima/Makefile b/security/integrity/ima/Makefile index 787c4cb916cd..5690c021de8f 100644 --- a/security/integrity/ima/Makefile +++ b/security/integrity/ima/Makefile @@ -6,4 +6,4 @@ obj-$(CONFIG_IMA) += ima.o ima-y := ima_fs.o ima_queue.o ima_init.o ima_main.o ima_crypto.o ima_api.o \ - ima_policy.o ima_iint.o ima_audit.o + ima_policy.o ima_audit.o diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 08408bd71462..29d97af5e9a4 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -24,11 +24,13 @@ #include #include +#include "../integrity.h" + enum ima_show_type { IMA_SHOW_BINARY, IMA_SHOW_ASCII }; enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8 }; /* digest size for IMA, fits SHA1 or MD5 */ -#define IMA_DIGEST_SIZE 20 +#define IMA_DIGEST_SIZE SHA1_DIGEST_SIZE #define IMA_EVENT_NAME_LEN_MAX 255 #define IMA_HASH_BITS 9 @@ -96,34 +98,21 @@ static inline unsigned long ima_hash_key(u8 *digest) return hash_long(*digest, IMA_HASH_BITS); } -/* iint cache flags */ -#define IMA_MEASURED 0x01 - -/* integrity data associated with an inode */ -struct ima_iint_cache { - struct rb_node rb_node; /* rooted in ima_iint_tree */ - struct inode *inode; /* back pointer to inode in question */ - u64 version; /* track inode changes */ - unsigned char flags; - u8 digest[IMA_DIGEST_SIZE]; - struct mutex mutex; /* protects: version, flags, digest */ -}; - /* LIM API function definitions */ int ima_must_measure(struct inode *inode, int mask, int function); -int ima_collect_measurement(struct ima_iint_cache *iint, struct file *file); -void ima_store_measurement(struct ima_iint_cache *iint, struct file *file, +int ima_collect_measurement(struct integrity_iint_cache *iint, + struct file *file); +void ima_store_measurement(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename); int ima_store_template(struct ima_template_entry *entry, int violation, struct inode *inode); -void ima_template_show(struct seq_file *m, void *e, - enum ima_show_type show); +void ima_template_show(struct seq_file *m, void *e, enum ima_show_type show); /* rbtree tree calls to lookup, insert, delete * integrity data associated with an inode. */ -struct ima_iint_cache *ima_iint_insert(struct inode *inode); -struct ima_iint_cache *ima_iint_find(struct inode *inode); +struct integrity_iint_cache *integrity_iint_insert(struct inode *inode); +struct integrity_iint_cache *integrity_iint_find(struct inode *inode); /* IMA policy related functions */ enum ima_hooks { FILE_CHECK = 1, FILE_MMAP, BPRM_CHECK }; diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index da36d2c085a4..0d50df04ccc4 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -126,7 +126,8 @@ int ima_must_measure(struct inode *inode, int mask, int function) * * Return 0 on success, error code otherwise */ -int ima_collect_measurement(struct ima_iint_cache *iint, struct file *file) +int ima_collect_measurement(struct integrity_iint_cache *iint, + struct file *file) { int result = -EEXIST; @@ -156,8 +157,8 @@ int ima_collect_measurement(struct ima_iint_cache *iint, struct file *file) * * Must be called with iint->mutex held. */ -void ima_store_measurement(struct ima_iint_cache *iint, struct file *file, - const unsigned char *filename) +void ima_store_measurement(struct integrity_iint_cache *iint, + struct file *file, const unsigned char *filename) { const char *op = "add_template_measure"; const char *audit_cause = "ENOMEM"; diff --git a/security/integrity/ima/ima_iint.c b/security/integrity/ima/ima_iint.c deleted file mode 100644 index 4ae73040ab7b..000000000000 --- a/security/integrity/ima/ima_iint.c +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2008 IBM Corporation - * - * Authors: - * Mimi Zohar - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. - * - * File: ima_iint.c - * - implements the IMA hooks: ima_inode_alloc, ima_inode_free - * - cache integrity information associated with an inode - * using a rbtree tree. - */ -#include -#include -#include -#include -#include "ima.h" - -static struct rb_root ima_iint_tree = RB_ROOT; -static DEFINE_SPINLOCK(ima_iint_lock); -static struct kmem_cache *iint_cache __read_mostly; - -int iint_initialized = 0; - -/* - * __ima_iint_find - return the iint associated with an inode - */ -static struct ima_iint_cache *__ima_iint_find(struct inode *inode) -{ - struct ima_iint_cache *iint; - struct rb_node *n = ima_iint_tree.rb_node; - - assert_spin_locked(&ima_iint_lock); - - while (n) { - iint = rb_entry(n, struct ima_iint_cache, rb_node); - - if (inode < iint->inode) - n = n->rb_left; - else if (inode > iint->inode) - n = n->rb_right; - else - break; - } - if (!n) - return NULL; - - return iint; -} - -/* - * ima_iint_find - return the iint associated with an inode - */ -struct ima_iint_cache *ima_iint_find(struct inode *inode) -{ - struct ima_iint_cache *iint; - - if (!IS_IMA(inode)) - return NULL; - - spin_lock(&ima_iint_lock); - iint = __ima_iint_find(inode); - spin_unlock(&ima_iint_lock); - - return iint; -} - -static void iint_free(struct ima_iint_cache *iint) -{ - iint->version = 0; - iint->flags = 0UL; - kmem_cache_free(iint_cache, iint); -} - -/** - * ima_inode_alloc - allocate an iint associated with an inode - * @inode: pointer to the inode - */ -int ima_inode_alloc(struct inode *inode) -{ - struct rb_node **p; - struct rb_node *new_node, *parent = NULL; - struct ima_iint_cache *new_iint, *test_iint; - int rc; - - new_iint = kmem_cache_alloc(iint_cache, GFP_NOFS); - if (!new_iint) - return -ENOMEM; - - new_iint->inode = inode; - new_node = &new_iint->rb_node; - - mutex_lock(&inode->i_mutex); /* i_flags */ - spin_lock(&ima_iint_lock); - - p = &ima_iint_tree.rb_node; - while (*p) { - parent = *p; - test_iint = rb_entry(parent, struct ima_iint_cache, rb_node); - - rc = -EEXIST; - if (inode < test_iint->inode) - p = &(*p)->rb_left; - else if (inode > test_iint->inode) - p = &(*p)->rb_right; - else - goto out_err; - } - - inode->i_flags |= S_IMA; - rb_link_node(new_node, parent, p); - rb_insert_color(new_node, &ima_iint_tree); - - spin_unlock(&ima_iint_lock); - mutex_unlock(&inode->i_mutex); /* i_flags */ - - return 0; -out_err: - spin_unlock(&ima_iint_lock); - mutex_unlock(&inode->i_mutex); /* i_flags */ - iint_free(new_iint); - - return rc; -} - -/** - * ima_inode_free - called on security_inode_free - * @inode: pointer to the inode - * - * Free the integrity information(iint) associated with an inode. - */ -void ima_inode_free(struct inode *inode) -{ - struct ima_iint_cache *iint; - - if (!IS_IMA(inode)) - return; - - spin_lock(&ima_iint_lock); - iint = __ima_iint_find(inode); - rb_erase(&iint->rb_node, &ima_iint_tree); - spin_unlock(&ima_iint_lock); - - iint_free(iint); -} - -static void init_once(void *foo) -{ - struct ima_iint_cache *iint = foo; - - memset(iint, 0, sizeof *iint); - iint->version = 0; - iint->flags = 0UL; - mutex_init(&iint->mutex); -} - -static int __init ima_iintcache_init(void) -{ - iint_cache = - kmem_cache_create("iint_cache", sizeof(struct ima_iint_cache), 0, - SLAB_PANIC, init_once); - iint_initialized = 1; - return 0; -} -security_initcall(ima_iintcache_init); diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 39d66dc2b8e9..25f9fe762896 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -82,7 +82,7 @@ out: "open_writers"); } -static void ima_check_last_writer(struct ima_iint_cache *iint, +static void ima_check_last_writer(struct integrity_iint_cache *iint, struct inode *inode, struct file *file) { @@ -105,12 +105,12 @@ static void ima_check_last_writer(struct ima_iint_cache *iint, void ima_file_free(struct file *file) { struct inode *inode = file->f_dentry->d_inode; - struct ima_iint_cache *iint; + struct integrity_iint_cache *iint; if (!iint_initialized || !S_ISREG(inode->i_mode)) return; - iint = ima_iint_find(inode); + iint = integrity_iint_find(inode); if (!iint) return; @@ -121,7 +121,7 @@ static int process_measurement(struct file *file, const unsigned char *filename, int mask, int function) { struct inode *inode = file->f_dentry->d_inode; - struct ima_iint_cache *iint; + struct integrity_iint_cache *iint; int rc = 0; if (!ima_initialized || !S_ISREG(inode->i_mode)) @@ -131,9 +131,9 @@ static int process_measurement(struct file *file, const unsigned char *filename, if (rc != 0) return rc; retry: - iint = ima_iint_find(inode); + iint = integrity_iint_find(inode); if (!iint) { - rc = ima_inode_alloc(inode); + rc = integrity_inode_alloc(inode); if (!rc || rc == -EEXIST) goto retry; return rc; diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h new file mode 100644 index 000000000000..7351836325a8 --- /dev/null +++ b/security/integrity/integrity.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2009-2010 IBM Corporation + * + * Authors: + * Mimi Zohar + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + */ + +#include +#include +#include + +/* iint cache flags */ +#define IMA_MEASURED 0x01 + +/* integrity data associated with an inode */ +struct integrity_iint_cache { + struct rb_node rb_node; /* rooted in integrity_iint_tree */ + struct inode *inode; /* back pointer to inode in question */ + u64 version; /* track inode changes */ + unsigned char flags; + u8 digest[SHA1_DIGEST_SIZE]; + struct mutex mutex; /* protects: version, flags, digest */ +}; + +/* rbtree tree calls to lookup, insert, delete + * integrity data associated with an inode. + */ +struct integrity_iint_cache *integrity_iint_insert(struct inode *inode); +struct integrity_iint_cache *integrity_iint_find(struct inode *inode); diff --git a/security/security.c b/security/security.c index 3464d58a5766..947fdcfbc83e 100644 --- a/security/security.c +++ b/security/security.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #define MAX_LSM_XATTR 1 @@ -336,7 +337,7 @@ int security_inode_alloc(struct inode *inode) void security_inode_free(struct inode *inode) { - ima_inode_free(inode); + integrity_inode_free(inode); security_ops->inode_free_security(inode); } From 1601fbad2b14e0b8d4dbb55e749bfe31e972818a Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Wed, 9 Mar 2011 14:23:34 -0500 Subject: [PATCH 03/95] xattr: define vfs_getxattr_alloc and vfs_xattr_cmp vfs_getxattr_alloc() and vfs_xattr_cmp() are two new kernel xattr helper functions. vfs_getxattr_alloc() first allocates memory for the requested xattr and then retrieves it. vfs_xattr_cmp() compares a given value with the contents of an extended attribute. Signed-off-by: Mimi Zohar Acked-by: Serge Hallyn --- fs/xattr.c | 58 +++++++++++++++++++++++++++++++++++++++++++ include/linux/xattr.h | 5 +++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/fs/xattr.c b/fs/xattr.c index f060663ab70c..851808c92b30 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -166,6 +166,64 @@ out_noalloc: } EXPORT_SYMBOL_GPL(xattr_getsecurity); +/* + * vfs_getxattr_alloc - allocate memory, if necessary, before calling getxattr + * + * Allocate memory, if not already allocated, or re-allocate correct size, + * before retrieving the extended attribute. + * + * Returns the result of alloc, if failed, or the getxattr operation. + */ +ssize_t +vfs_getxattr_alloc(struct dentry *dentry, const char *name, char **xattr_value, + size_t xattr_size, gfp_t flags) +{ + struct inode *inode = dentry->d_inode; + char *value = *xattr_value; + int error; + + error = xattr_permission(inode, name, MAY_READ); + if (error) + return error; + + if (!inode->i_op->getxattr) + return -EOPNOTSUPP; + + error = inode->i_op->getxattr(dentry, name, NULL, 0); + if (error < 0) + return error; + + if (!value || (error > xattr_size)) { + value = krealloc(*xattr_value, error + 1, flags); + if (!value) + return -ENOMEM; + memset(value, 0, error + 1); + } + + error = inode->i_op->getxattr(dentry, name, value, error); + *xattr_value = value; + return error; +} + +/* Compare an extended attribute value with the given value */ +int vfs_xattr_cmp(struct dentry *dentry, const char *xattr_name, + const char *value, size_t size, gfp_t flags) +{ + char *xattr_value = NULL; + int rc; + + rc = vfs_getxattr_alloc(dentry, xattr_name, &xattr_value, 0, flags); + if (rc < 0) + return rc; + + if ((rc != size) || (memcmp(xattr_value, value, rc) != 0)) + rc = -EINVAL; + else + rc = 0; + kfree(xattr_value); + return rc; +} + ssize_t vfs_getxattr(struct dentry *dentry, const char *name, void *value, size_t size) { diff --git a/include/linux/xattr.h b/include/linux/xattr.h index 7a378662ddff..4703f6bd1f53 100644 --- a/include/linux/xattr.h +++ b/include/linux/xattr.h @@ -84,7 +84,10 @@ ssize_t generic_getxattr(struct dentry *dentry, const char *name, void *buffer, ssize_t generic_listxattr(struct dentry *dentry, char *buffer, size_t buffer_size); int generic_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags); int generic_removexattr(struct dentry *dentry, const char *name); - +ssize_t vfs_getxattr_alloc(struct dentry *dentry, const char *name, + char **xattr_value, size_t size, gfp_t flags); +int vfs_xattr_cmp(struct dentry *dentry, const char *xattr_name, + const char *value, size_t size, gfp_t flags); #endif /* __KERNEL__ */ #endif /* _LINUX_XATTR_H */ From 66dbc325afcef909043c30e90930a36823fc734c Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Tue, 15 Mar 2011 16:12:09 -0400 Subject: [PATCH 04/95] evm: re-release EVM protects a file's security extended attributes(xattrs) against integrity attacks. This patchset provides the framework and an initial method. The initial method maintains an HMAC-sha1 value across the security extended attributes, storing the HMAC value as the extended attribute 'security.evm'. Other methods of validating the integrity of a file's metadata will be posted separately (eg. EVM-digital-signatures). While this patchset does authenticate the security xattrs, and cryptographically binds them to the inode, coming extensions will bind other directory and inode metadata for more complete protection. To help simplify the review and upstreaming process, each extension will be posted separately (eg. IMA-appraisal, IMA-appraisal-directory). For a general overview of the proposed Linux integrity subsystem, refer to Dave Safford's whitepaper: http://downloads.sf.net/project/linux-ima/linux-ima/Integrity_overview.pdf. EVM depends on the Kernel Key Retention System to provide it with a trusted/encrypted key for the HMAC-sha1 operation. The key is loaded onto the root's keyring using keyctl. Until EVM receives notification that the key has been successfully loaded onto the keyring (echo 1 > /evm), EVM can not create or validate the 'security.evm' xattr, but returns INTEGRITY_UNKNOWN. Loading the key and signaling EVM should be done as early as possible. Normally this is done in the initramfs, which has already been measured as part of the trusted boot. For more information on creating and loading existing trusted/encrypted keys, refer to Documentation/keys-trusted-encrypted.txt. A sample dracut patch, which loads the trusted/encrypted key and enables EVM, is available from http://linux-ima.sourceforge.net/#EVM. Based on the LSMs enabled, the set of EVM protected security xattrs is defined at compile. EVM adds the following three calls to the existing security hooks: evm_inode_setxattr(), evm_inode_post_setxattr(), and evm_inode_removexattr. To initialize and update the 'security.evm' extended attribute, EVM defines three calls: evm_inode_post_init(), evm_inode_post_setattr() and evm_inode_post_removexattr() hooks. To verify the integrity of a security xattr, EVM exports evm_verifyxattr(). Changelog v7: - Fixed URL in EVM ABI documentation Changelog v6: (based on Serge Hallyn's review) - fix URL in patch description - remove evm_hmac_size definition - use SHA1_DIGEST_SIZE (removed both MAX_DIGEST_SIZE and evm_hmac_size) - moved linux include before other includes - test for crypto_hash_setkey failure - fail earlier for invalid key - clear entire encrypted key, even on failure - check xattr name length before comparing xattr names Changelog: - locking based on i_mutex, remove evm_mutex - using trusted/encrypted keys for storing the EVM key used in the HMAC-sha1 operation. - replaced crypto hash with shash (Dmitry Kasatkin) - support for additional methods of verifying the security xattrs (Dmitry Kasatkin) - iint not allocated for all regular files, but only for those appraised - Use cap_sys_admin in lieu of cap_mac_admin - Use __vfs_setxattr_noperm(), without permission checks, from EVM Signed-off-by: Mimi Zohar Acked-by: Serge Hallyn --- Documentation/ABI/testing/evm | 23 +++ include/linux/integrity.h | 7 + include/linux/xattr.h | 3 + security/integrity/Kconfig | 3 +- security/integrity/Makefile | 2 + security/integrity/evm/Kconfig | 12 ++ security/integrity/evm/Makefile | 6 + security/integrity/evm/evm.h | 33 ++++ security/integrity/evm/evm_crypto.c | 183 ++++++++++++++++++ security/integrity/evm/evm_main.c | 284 ++++++++++++++++++++++++++++ security/integrity/evm/evm_secfs.c | 108 +++++++++++ security/integrity/iint.c | 1 + security/integrity/integrity.h | 1 + 13 files changed, 665 insertions(+), 1 deletion(-) create mode 100644 Documentation/ABI/testing/evm create mode 100644 security/integrity/evm/Kconfig create mode 100644 security/integrity/evm/Makefile create mode 100644 security/integrity/evm/evm.h create mode 100644 security/integrity/evm/evm_crypto.c create mode 100644 security/integrity/evm/evm_main.c create mode 100644 security/integrity/evm/evm_secfs.c diff --git a/Documentation/ABI/testing/evm b/Documentation/ABI/testing/evm new file mode 100644 index 000000000000..8374d4557e5d --- /dev/null +++ b/Documentation/ABI/testing/evm @@ -0,0 +1,23 @@ +What: security/evm +Date: March 2011 +Contact: Mimi Zohar +Description: + EVM protects a file's security extended attributes(xattrs) + against integrity attacks. The initial method maintains an + HMAC-sha1 value across the extended attributes, storing the + value as the extended attribute 'security.evm'. + + EVM depends on the Kernel Key Retention System to provide it + with a trusted/encrypted key for the HMAC-sha1 operation. + The key is loaded onto the root's keyring using keyctl. Until + EVM receives notification that the key has been successfully + loaded onto the keyring (echo 1 > /evm), EVM + can not create or validate the 'security.evm' xattr, but + returns INTEGRITY_UNKNOWN. Loading the key and signaling EVM + should be done as early as possible. Normally this is done + in the initramfs, which has already been measured as part + of the trusted boot. For more information on creating and + loading existing trusted/encrypted keys, refer to: + Documentation/keys-trusted-encrypted.txt. (A sample dracut + patch, which loads the trusted/encrypted key and enables + EVM, is available from http://linux-ima.sourceforge.net/#EVM.) diff --git a/include/linux/integrity.h b/include/linux/integrity.h index 905981247327..e715a2abcea2 100644 --- a/include/linux/integrity.h +++ b/include/linux/integrity.h @@ -12,6 +12,13 @@ #include +enum integrity_status { + INTEGRITY_PASS = 0, + INTEGRITY_FAIL, + INTEGRITY_NOLABEL, + INTEGRITY_UNKNOWN, +}; + #ifdef CONFIG_INTEGRITY extern int integrity_inode_alloc(struct inode *inode); extern void integrity_inode_free(struct inode *inode); diff --git a/include/linux/xattr.h b/include/linux/xattr.h index 4703f6bd1f53..b20cb965c322 100644 --- a/include/linux/xattr.h +++ b/include/linux/xattr.h @@ -30,6 +30,9 @@ #define XATTR_USER_PREFIX_LEN (sizeof (XATTR_USER_PREFIX) - 1) /* Security namespace */ +#define XATTR_EVM_SUFFIX "evm" +#define XATTR_NAME_EVM XATTR_SECURITY_PREFIX XATTR_EVM_SUFFIX + #define XATTR_SELINUX_SUFFIX "selinux" #define XATTR_NAME_SELINUX XATTR_SECURITY_PREFIX XATTR_SELINUX_SUFFIX diff --git a/security/integrity/Kconfig b/security/integrity/Kconfig index 270469155681..4bf00acf7937 100644 --- a/security/integrity/Kconfig +++ b/security/integrity/Kconfig @@ -1,6 +1,7 @@ # config INTEGRITY def_bool y - depends on IMA + depends on IMA || EVM source security/integrity/ima/Kconfig +source security/integrity/evm/Kconfig diff --git a/security/integrity/Makefile b/security/integrity/Makefile index 6eddd61b84e8..0ae44aea6516 100644 --- a/security/integrity/Makefile +++ b/security/integrity/Makefile @@ -8,3 +8,5 @@ integrity-y := iint.o subdir-$(CONFIG_IMA) += ima obj-$(CONFIG_IMA) += ima/built-in.o +subdir-$(CONFIG_EVM) += evm +obj-$(CONFIG_EVM) += evm/built-in.o diff --git a/security/integrity/evm/Kconfig b/security/integrity/evm/Kconfig new file mode 100644 index 000000000000..73f654099a4b --- /dev/null +++ b/security/integrity/evm/Kconfig @@ -0,0 +1,12 @@ +config EVM + boolean "EVM support" + depends on SECURITY && KEYS && ENCRYPTED_KEYS + select CRYPTO_HMAC + select CRYPTO_MD5 + select CRYPTO_SHA1 + default n + help + EVM protects a file's security extended attributes against + integrity attacks. + + If you are unsure how to answer this question, answer N. diff --git a/security/integrity/evm/Makefile b/security/integrity/evm/Makefile new file mode 100644 index 000000000000..0787d262b9e3 --- /dev/null +++ b/security/integrity/evm/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for building the Extended Verification Module(EVM) +# +obj-$(CONFIG_EVM) += evm.o + +evm-y := evm_main.o evm_crypto.o evm_secfs.o diff --git a/security/integrity/evm/evm.h b/security/integrity/evm/evm.h new file mode 100644 index 000000000000..375dc3e6015c --- /dev/null +++ b/security/integrity/evm/evm.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2005-2010 IBM Corporation + * + * Authors: + * Mimi Zohar + * Kylene Hall + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * File: evm.h + * + */ +#include +#include "../integrity.h" + +extern int evm_initialized; +extern char *evm_hmac; + +/* List of EVM protected security xattrs */ +extern char *evm_config_xattrnames[]; + +extern int evm_init_key(void); +extern int evm_update_evmxattr(struct dentry *dentry, + const char *req_xattr_name, + const char *req_xattr_value, + size_t req_xattr_value_len); +extern int evm_calc_hmac(struct dentry *dentry, const char *req_xattr_name, + const char *req_xattr_value, + size_t req_xattr_value_len, char *digest); +extern int evm_init_secfs(void); +extern void evm_cleanup_secfs(void); diff --git a/security/integrity/evm/evm_crypto.c b/security/integrity/evm/evm_crypto.c new file mode 100644 index 000000000000..d49bb002f3da --- /dev/null +++ b/security/integrity/evm/evm_crypto.c @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2005-2010 IBM Corporation + * + * Authors: + * Mimi Zohar + * Kylene Hall + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * File: evm_crypto.c + * Using root's kernel master key (kmk), calculate the HMAC + */ + +#include +#include +#include +#include +#include +#include "evm.h" + +#define EVMKEY "evm-key" +#define MAX_KEY_SIZE 128 +static unsigned char evmkey[MAX_KEY_SIZE]; +static int evmkey_len = MAX_KEY_SIZE; + +static int init_desc(struct hash_desc *desc) +{ + int rc; + + desc->tfm = crypto_alloc_hash(evm_hmac, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(desc->tfm)) { + pr_info("Can not allocate %s (reason: %ld)\n", + evm_hmac, PTR_ERR(desc->tfm)); + rc = PTR_ERR(desc->tfm); + return rc; + } + desc->flags = 0; + rc = crypto_hash_setkey(desc->tfm, evmkey, evmkey_len); + if (rc) + goto out; + rc = crypto_hash_init(desc); +out: + if (rc) + crypto_free_hash(desc->tfm); + return rc; +} + +/* Protect against 'cutting & pasting' security.evm xattr, include inode + * specific info. + * + * (Additional directory/file metadata needs to be added for more complete + * protection.) + */ +static void hmac_add_misc(struct hash_desc *desc, struct inode *inode, + char *digest) +{ + struct h_misc { + unsigned long ino; + __u32 generation; + uid_t uid; + gid_t gid; + umode_t mode; + } hmac_misc; + struct scatterlist sg[1]; + + memset(&hmac_misc, 0, sizeof hmac_misc); + hmac_misc.ino = inode->i_ino; + hmac_misc.generation = inode->i_generation; + hmac_misc.uid = inode->i_uid; + hmac_misc.gid = inode->i_gid; + hmac_misc.mode = inode->i_mode; + sg_init_one(sg, &hmac_misc, sizeof hmac_misc); + crypto_hash_update(desc, sg, sizeof hmac_misc); + crypto_hash_final(desc, digest); +} + +/* + * Calculate the HMAC value across the set of protected security xattrs. + * + * Instead of retrieving the requested xattr, for performance, calculate + * the hmac using the requested xattr value. Don't alloc/free memory for + * each xattr, but attempt to re-use the previously allocated memory. + */ +int evm_calc_hmac(struct dentry *dentry, const char *req_xattr_name, + const char *req_xattr_value, size_t req_xattr_value_len, + char *digest) +{ + struct inode *inode = dentry->d_inode; + struct hash_desc desc; + struct scatterlist sg[1]; + char **xattrname; + size_t xattr_size = 0; + char *xattr_value = NULL; + int error; + int size; + + if (!inode->i_op || !inode->i_op->getxattr) + return -EOPNOTSUPP; + error = init_desc(&desc); + if (error) + return error; + + error = -ENODATA; + for (xattrname = evm_config_xattrnames; *xattrname != NULL; xattrname++) { + if ((req_xattr_name && req_xattr_value) + && !strcmp(*xattrname, req_xattr_name)) { + error = 0; + sg_init_one(sg, req_xattr_value, req_xattr_value_len); + crypto_hash_update(&desc, sg, req_xattr_value_len); + continue; + } + size = vfs_getxattr_alloc(dentry, *xattrname, + &xattr_value, xattr_size, GFP_NOFS); + if (size == -ENOMEM) { + error = -ENOMEM; + goto out; + } + if (size < 0) + continue; + + error = 0; + xattr_size = size; + sg_init_one(sg, xattr_value, xattr_size); + crypto_hash_update(&desc, sg, xattr_size); + } + hmac_add_misc(&desc, inode, digest); + kfree(xattr_value); +out: + crypto_free_hash(desc.tfm); + return error; +} + +/* + * Calculate the hmac and update security.evm xattr + * + * Expects to be called with i_mutex locked. + */ +int evm_update_evmxattr(struct dentry *dentry, const char *xattr_name, + const char *xattr_value, size_t xattr_value_len) +{ + struct inode *inode = dentry->d_inode; + u8 hmac[SHA1_DIGEST_SIZE]; + int rc = 0; + + rc = evm_calc_hmac(dentry, xattr_name, xattr_value, + xattr_value_len, hmac); + if (rc == 0) + rc = __vfs_setxattr_noperm(dentry, XATTR_NAME_EVM, + hmac, SHA1_DIGEST_SIZE, 0); + else if (rc == -ENODATA) + rc = inode->i_op->removexattr(dentry, XATTR_NAME_EVM); + return rc; +} + +/* + * Get the key from the TPM for the SHA1-HMAC + */ +int evm_init_key(void) +{ + struct key *evm_key; + struct encrypted_key_payload *ekp; + int rc = 0; + + evm_key = request_key(&key_type_encrypted, EVMKEY, NULL); + if (IS_ERR(evm_key)) + return -ENOENT; + + down_read(&evm_key->sem); + ekp = evm_key->payload.data; + if (ekp->decrypted_datalen > MAX_KEY_SIZE) { + rc = -EINVAL; + goto out; + } + memcpy(evmkey, ekp->decrypted_data, ekp->decrypted_datalen); +out: + /* burn the original key contents */ + memset(ekp->decrypted_data, 0, ekp->decrypted_datalen); + up_read(&evm_key->sem); + key_put(evm_key); + return rc; +} diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c new file mode 100644 index 000000000000..a8fa45fef8f1 --- /dev/null +++ b/security/integrity/evm/evm_main.c @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2005-2010 IBM Corporation + * + * Author: + * Mimi Zohar + * Kylene Hall + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * File: evm_main.c + * implements evm_inode_setxattr, evm_inode_post_setxattr, + * evm_inode_removexattr, and evm_verifyxattr + */ + +#include +#include +#include +#include +#include "evm.h" + +int evm_initialized; + +char *evm_hmac = "hmac(sha1)"; + +char *evm_config_xattrnames[] = { +#ifdef CONFIG_SECURITY_SELINUX + XATTR_NAME_SELINUX, +#endif +#ifdef CONFIG_SECURITY_SMACK + XATTR_NAME_SMACK, +#endif + XATTR_NAME_CAPS, + NULL +}; + +/* + * evm_verify_hmac - calculate and compare the HMAC with the EVM xattr + * + * Compute the HMAC on the dentry's protected set of extended attributes + * and compare it against the stored security.evm xattr. (For performance, + * use the previoulsy retrieved xattr value and length to calculate the + * HMAC.) + * + * Returns integrity status + */ +static enum integrity_status evm_verify_hmac(struct dentry *dentry, + const char *xattr_name, + char *xattr_value, + size_t xattr_value_len, + struct integrity_iint_cache *iint) +{ + char hmac_val[SHA1_DIGEST_SIZE]; + int rc; + + if (iint->hmac_status != INTEGRITY_UNKNOWN) + return iint->hmac_status; + + memset(hmac_val, 0, sizeof hmac_val); + rc = evm_calc_hmac(dentry, xattr_name, xattr_value, + xattr_value_len, hmac_val); + if (rc < 0) + return INTEGRITY_UNKNOWN; + + rc = vfs_xattr_cmp(dentry, XATTR_NAME_EVM, hmac_val, sizeof hmac_val, + GFP_NOFS); + if (rc < 0) + goto err_out; + iint->hmac_status = INTEGRITY_PASS; + return iint->hmac_status; + +err_out: + switch (rc) { + case -ENODATA: /* file not labelled */ + iint->hmac_status = INTEGRITY_NOLABEL; + break; + case -EINVAL: + iint->hmac_status = INTEGRITY_FAIL; + break; + default: + iint->hmac_status = INTEGRITY_UNKNOWN; + } + return iint->hmac_status; +} + +static int evm_protected_xattr(const char *req_xattr_name) +{ + char **xattrname; + int namelen; + int found = 0; + + namelen = strlen(req_xattr_name); + for (xattrname = evm_config_xattrnames; *xattrname != NULL; xattrname++) { + if ((strlen(*xattrname) == namelen) + && (strncmp(req_xattr_name, *xattrname, namelen) == 0)) { + found = 1; + break; + } + } + return found; +} + +/** + * evm_verifyxattr - verify the integrity of the requested xattr + * @dentry: object of the verify xattr + * @xattr_name: requested xattr + * @xattr_value: requested xattr value + * @xattr_value_len: requested xattr value length + * + * Calculate the HMAC for the given dentry and verify it against the stored + * security.evm xattr. For performance, use the xattr value and length + * previously retrieved to calculate the HMAC. + * + * Returns the xattr integrity status. + * + * This function requires the caller to lock the inode's i_mutex before it + * is executed. + */ +enum integrity_status evm_verifyxattr(struct dentry *dentry, + const char *xattr_name, + void *xattr_value, size_t xattr_value_len) +{ + struct inode *inode = dentry->d_inode; + struct integrity_iint_cache *iint; + enum integrity_status status; + + if (!evm_initialized || !evm_protected_xattr(xattr_name)) + return INTEGRITY_UNKNOWN; + + iint = integrity_iint_find(inode); + if (!iint) + return INTEGRITY_UNKNOWN; + status = evm_verify_hmac(dentry, xattr_name, xattr_value, + xattr_value_len, iint); + return status; +} +EXPORT_SYMBOL_GPL(evm_verifyxattr); + +/* + * evm_protect_xattr - protect the EVM extended attribute + * + * Prevent security.evm from being modified or removed. + */ +static int evm_protect_xattr(struct dentry *dentry, const char *xattr_name, + const void *xattr_value, size_t xattr_value_len) +{ + if (strcmp(xattr_name, XATTR_NAME_EVM) == 0) { + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + } + return 0; +} + +/** + * evm_inode_setxattr - protect the EVM extended attribute + * @dentry: pointer to the affected dentry + * @xattr_name: pointer to the affected extended attribute name + * @xattr_value: pointer to the new extended attribute value + * @xattr_value_len: pointer to the new extended attribute value length + * + * Prevent 'security.evm' from being modified + */ +int evm_inode_setxattr(struct dentry *dentry, const char *xattr_name, + const void *xattr_value, size_t xattr_value_len) +{ + return evm_protect_xattr(dentry, xattr_name, xattr_value, + xattr_value_len); +} + +/** + * evm_inode_removexattr - protect the EVM extended attribute + * @dentry: pointer to the affected dentry + * @xattr_name: pointer to the affected extended attribute name + * + * Prevent 'security.evm' from being removed. + */ +int evm_inode_removexattr(struct dentry *dentry, const char *xattr_name) +{ + return evm_protect_xattr(dentry, xattr_name, NULL, 0); +} + +/** + * evm_inode_post_setxattr - update 'security.evm' to reflect the changes + * @dentry: pointer to the affected dentry + * @xattr_name: pointer to the affected extended attribute name + * @xattr_value: pointer to the new extended attribute value + * @xattr_value_len: pointer to the new extended attribute value length + * + * Update the HMAC stored in 'security.evm' to reflect the change. + * + * No need to take the i_mutex lock here, as this function is called from + * __vfs_setxattr_noperm(). The caller of which has taken the inode's + * i_mutex lock. + */ +void evm_inode_post_setxattr(struct dentry *dentry, const char *xattr_name, + const void *xattr_value, size_t xattr_value_len) +{ + if (!evm_initialized || !evm_protected_xattr(xattr_name)) + return; + + evm_update_evmxattr(dentry, xattr_name, xattr_value, xattr_value_len); + return; +} + +/** + * evm_inode_post_removexattr - update 'security.evm' after removing the xattr + * @dentry: pointer to the affected dentry + * @xattr_name: pointer to the affected extended attribute name + * + * Update the HMAC stored in 'security.evm' to reflect removal of the xattr. + */ +void evm_inode_post_removexattr(struct dentry *dentry, const char *xattr_name) +{ + struct inode *inode = dentry->d_inode; + + if (!evm_initialized || !evm_protected_xattr(xattr_name)) + return; + + mutex_lock(&inode->i_mutex); + evm_update_evmxattr(dentry, xattr_name, NULL, 0); + mutex_unlock(&inode->i_mutex); + return; +} + +/** + * evm_inode_post_setattr - update 'security.evm' after modifying metadata + * @dentry: pointer to the affected dentry + * @ia_valid: for the UID and GID status + * + * For now, update the HMAC stored in 'security.evm' to reflect UID/GID + * changes. + * + * This function is called from notify_change(), which expects the caller + * to lock the inode's i_mutex. + */ +void evm_inode_post_setattr(struct dentry *dentry, int ia_valid) +{ + if (!evm_initialized) + return; + + if (ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID)) + evm_update_evmxattr(dentry, NULL, NULL, 0); + return; +} + +static struct crypto_hash *tfm_hmac; /* preload crypto alg */ +static int __init init_evm(void) +{ + int error; + + tfm_hmac = crypto_alloc_hash(evm_hmac, 0, CRYPTO_ALG_ASYNC); + error = evm_init_secfs(); + if (error < 0) { + printk(KERN_INFO "EVM: Error registering secfs\n"); + goto err; + } +err: + return error; +} + +static void __exit cleanup_evm(void) +{ + evm_cleanup_secfs(); + crypto_free_hash(tfm_hmac); +} + +/* + * evm_display_config - list the EVM protected security extended attributes + */ +static int __init evm_display_config(void) +{ + char **xattrname; + + for (xattrname = evm_config_xattrnames; *xattrname != NULL; xattrname++) + printk(KERN_INFO "EVM: %s\n", *xattrname); + return 0; +} + +pure_initcall(evm_display_config); +late_initcall(init_evm); + +MODULE_DESCRIPTION("Extended Verification Module"); +MODULE_LICENSE("GPL"); diff --git a/security/integrity/evm/evm_secfs.c b/security/integrity/evm/evm_secfs.c new file mode 100644 index 000000000000..ac7629950578 --- /dev/null +++ b/security/integrity/evm/evm_secfs.c @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2010 IBM Corporation + * + * Authors: + * Mimi Zohar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * File: evm_secfs.c + * - Used to signal when key is on keyring + * - Get the key and enable EVM + */ + +#include +#include +#include "evm.h" + +static struct dentry *evm_init_tpm; + +/** + * evm_read_key - read() for /evm + * + * @filp: file pointer, not actually used + * @buf: where to put the result + * @count: maximum to send along + * @ppos: where to start + * + * Returns number of bytes read or error code, as appropriate + */ +static ssize_t evm_read_key(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char temp[80]; + ssize_t rc; + + if (*ppos != 0) + return 0; + + sprintf(temp, "%d", evm_initialized); + rc = simple_read_from_buffer(buf, count, ppos, temp, strlen(temp)); + + return rc; +} + +/** + * evm_write_key - write() for /evm + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start + * + * Used to signal that key is on the kernel key ring. + * - get the integrity hmac key from the kernel key ring + * - create list of hmac protected extended attributes + * Returns number of bytes written or error code, as appropriate + */ +static ssize_t evm_write_key(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + char temp[80]; + int i, error; + + if (!capable(CAP_SYS_ADMIN) || evm_initialized) + return -EPERM; + + if (count >= sizeof(temp) || count == 0) + return -EINVAL; + + if (copy_from_user(temp, buf, count) != 0) + return -EFAULT; + + temp[count] = '\0'; + + if ((sscanf(temp, "%d", &i) != 1) || (i != 1)) + return -EINVAL; + + error = evm_init_key(); + if (!error) { + evm_initialized = 1; + pr_info("EVM: initialized\n"); + } else + pr_err("EVM: initialization failed\n"); + return count; +} + +static const struct file_operations evm_key_ops = { + .read = evm_read_key, + .write = evm_write_key, +}; + +int __init evm_init_secfs(void) +{ + int error = 0; + + evm_init_tpm = securityfs_create_file("evm", S_IRUSR | S_IRGRP, + NULL, NULL, &evm_key_ops); + if (!evm_init_tpm || IS_ERR(evm_init_tpm)) + error = -EFAULT; + return error; +} + +void __exit evm_cleanup_secfs(void) +{ + if (evm_init_tpm) + securityfs_remove(evm_init_tpm); +} diff --git a/security/integrity/iint.c b/security/integrity/iint.c index d17de48bd6cc..991df20709b0 100644 --- a/security/integrity/iint.c +++ b/security/integrity/iint.c @@ -157,6 +157,7 @@ static void init_once(void *foo) iint->version = 0; iint->flags = 0UL; mutex_init(&iint->mutex); + iint->hmac_status = INTEGRITY_UNKNOWN; } static int __init integrity_iintcache_init(void) diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 7351836325a8..397a46b3992f 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -26,6 +26,7 @@ struct integrity_iint_cache { unsigned char flags; u8 digest[SHA1_DIGEST_SIZE]; struct mutex mutex; /* protects: version, flags, digest */ + enum integrity_status hmac_status; }; /* rbtree tree calls to lookup, insert, delete From 6be5cc5246f807fd8ede9f5f1bb2826f2c598658 Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Wed, 9 Mar 2011 14:28:20 -0500 Subject: [PATCH 05/95] evm: add support for different security.evm data types EVM protects a file's security extended attributes(xattrs) against integrity attacks. The current patchset maintains an HMAC-sha1 value across the security xattrs, storing the value as the extended attribute 'security.evm'. We anticipate other methods for protecting the security extended attributes. This patch reserves the first byte of 'security.evm' as a place holder for the type of method. Changelog v6: - move evm_ima_xattr_type definition to security/integrity/integrity.h - defined a structure for the EVM xattr called evm_ima_xattr_data (based on Serge Hallyn's suggestion) - removed unnecessary memset Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar Acked-by: Serge Hallyn --- include/linux/integrity.h | 1 + security/integrity/evm/evm_crypto.c | 11 +++++++---- security/integrity/evm/evm_main.c | 10 +++++----- security/integrity/integrity.h | 11 +++++++++++ 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/include/linux/integrity.h b/include/linux/integrity.h index e715a2abcea2..968443385678 100644 --- a/include/linux/integrity.h +++ b/include/linux/integrity.h @@ -19,6 +19,7 @@ enum integrity_status { INTEGRITY_UNKNOWN, }; +/* List of EVM protected security xattrs */ #ifdef CONFIG_INTEGRITY extern int integrity_inode_alloc(struct inode *inode); extern void integrity_inode_free(struct inode *inode); diff --git a/security/integrity/evm/evm_crypto.c b/security/integrity/evm/evm_crypto.c index d49bb002f3da..c631b99bda95 100644 --- a/security/integrity/evm/evm_crypto.c +++ b/security/integrity/evm/evm_crypto.c @@ -141,14 +141,17 @@ int evm_update_evmxattr(struct dentry *dentry, const char *xattr_name, const char *xattr_value, size_t xattr_value_len) { struct inode *inode = dentry->d_inode; - u8 hmac[SHA1_DIGEST_SIZE]; + struct evm_ima_xattr_data xattr_data; int rc = 0; rc = evm_calc_hmac(dentry, xattr_name, xattr_value, - xattr_value_len, hmac); - if (rc == 0) + xattr_value_len, xattr_data.digest); + if (rc == 0) { + xattr_data.type = EVM_XATTR_HMAC; rc = __vfs_setxattr_noperm(dentry, XATTR_NAME_EVM, - hmac, SHA1_DIGEST_SIZE, 0); + &xattr_data, + sizeof(xattr_data), 0); + } else if (rc == -ENODATA) rc = inode->i_op->removexattr(dentry, XATTR_NAME_EVM); return rc; diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index a8fa45fef8f1..c0580dd15ec0 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -51,20 +51,20 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, size_t xattr_value_len, struct integrity_iint_cache *iint) { - char hmac_val[SHA1_DIGEST_SIZE]; + struct evm_ima_xattr_data xattr_data; int rc; if (iint->hmac_status != INTEGRITY_UNKNOWN) return iint->hmac_status; - memset(hmac_val, 0, sizeof hmac_val); rc = evm_calc_hmac(dentry, xattr_name, xattr_value, - xattr_value_len, hmac_val); + xattr_value_len, xattr_data.digest); if (rc < 0) return INTEGRITY_UNKNOWN; - rc = vfs_xattr_cmp(dentry, XATTR_NAME_EVM, hmac_val, sizeof hmac_val, - GFP_NOFS); + xattr_data.type = EVM_XATTR_HMAC; + rc = vfs_xattr_cmp(dentry, XATTR_NAME_EVM, (u8 *)&xattr_data, + sizeof xattr_data, GFP_NOFS); if (rc < 0) goto err_out; iint->hmac_status = INTEGRITY_PASS; diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 397a46b3992f..7efbf560b7d5 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -18,6 +18,17 @@ /* iint cache flags */ #define IMA_MEASURED 0x01 +enum evm_ima_xattr_type { + IMA_XATTR_DIGEST = 0x01, + EVM_XATTR_HMAC, + EVM_IMA_XATTR_DIGSIG, +}; + +struct evm_ima_xattr_data { + u8 type; + u8 digest[SHA1_DIGEST_SIZE]; +} __attribute__((packed)); + /* integrity data associated with an inode */ struct integrity_iint_cache { struct rb_node rb_node; /* rooted in integrity_iint_tree */ From 3e1be52d6c6b21d9080dd886c0e609e009831562 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Wed, 9 Mar 2011 14:38:26 -0500 Subject: [PATCH 06/95] security: imbed evm calls in security hooks Imbed the evm calls evm_inode_setxattr(), evm_inode_post_setxattr(), evm_inode_removexattr() in the security hooks. evm_inode_setxattr() protects security.evm xattr. evm_inode_post_setxattr() and evm_inode_removexattr() updates the hmac associated with an inode. (Assumes an LSM module protects the setting/removing of xattr.) Changelog: - Don't define evm_verifyxattr(), unless CONFIG_INTEGRITY is enabled. - xattr_name is a 'const', value is 'void *' Signed-off-by: Mimi Zohar Acked-by: Serge Hallyn --- include/linux/evm.h | 56 +++++++++++++++++++++++++++++++ security/integrity/evm/evm_main.c | 1 + security/security.c | 16 +++++++-- 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 include/linux/evm.h diff --git a/include/linux/evm.h b/include/linux/evm.h new file mode 100644 index 000000000000..8b4e9e3b395e --- /dev/null +++ b/include/linux/evm.h @@ -0,0 +1,56 @@ +/* + * evm.h + * + * Copyright (c) 2009 IBM Corporation + * Author: Mimi Zohar + */ + +#ifndef _LINUX_EVM_H +#define _LINUX_EVM_H + +#include + +#ifdef CONFIG_EVM +extern enum integrity_status evm_verifyxattr(struct dentry *dentry, + const char *xattr_name, + void *xattr_value, + size_t xattr_value_len); +extern int evm_inode_setxattr(struct dentry *dentry, const char *name, + const void *value, size_t size); +extern void evm_inode_post_setxattr(struct dentry *dentry, + const char *xattr_name, + const void *xattr_value, + size_t xattr_value_len); +extern int evm_inode_removexattr(struct dentry *dentry, const char *xattr_name); +#else +#ifdef CONFIG_INTEGRITY +static inline enum integrity_status evm_verifyxattr(struct dentry *dentry, + const char *xattr_name, + void *xattr_value, + size_t xattr_value_len) +{ + return INTEGRITY_UNKNOWN; +} +#endif + +static inline int evm_inode_setxattr(struct dentry *dentry, const char *name, + const void *value, size_t size) +{ + return 0; +} + +static inline void evm_inode_post_setxattr(struct dentry *dentry, + const char *xattr_name, + const void *xattr_value, + size_t xattr_value_len) +{ + return; +} + +static inline int evm_inode_removexattr(struct dentry *dentry, + const char *xattr_name) +{ + return 0; +} +#endif /* CONFIG_EVM_H */ +#endif /* LINUX_EVM_H */ diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index c0580dd15ec0..1746c3669c6f 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "evm.h" int evm_initialized; diff --git a/security/security.c b/security/security.c index 947fdcfbc83e..21a79b3d1e8e 100644 --- a/security/security.c +++ b/security/security.c @@ -18,6 +18,7 @@ #include #include #include +#include #define MAX_LSM_XATTR 1 @@ -580,9 +581,14 @@ int security_inode_getattr(struct vfsmount *mnt, struct dentry *dentry) int security_inode_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { + int ret; + if (unlikely(IS_PRIVATE(dentry->d_inode))) return 0; - return security_ops->inode_setxattr(dentry, name, value, size, flags); + ret = security_ops->inode_setxattr(dentry, name, value, size, flags); + if (ret) + return ret; + return evm_inode_setxattr(dentry, name, value, size); } void security_inode_post_setxattr(struct dentry *dentry, const char *name, @@ -591,6 +597,7 @@ void security_inode_post_setxattr(struct dentry *dentry, const char *name, if (unlikely(IS_PRIVATE(dentry->d_inode))) return; security_ops->inode_post_setxattr(dentry, name, value, size, flags); + evm_inode_post_setxattr(dentry, name, value, size); } int security_inode_getxattr(struct dentry *dentry, const char *name) @@ -609,9 +616,14 @@ int security_inode_listxattr(struct dentry *dentry) int security_inode_removexattr(struct dentry *dentry, const char *name) { + int ret; + if (unlikely(IS_PRIVATE(dentry->d_inode))) return 0; - return security_ops->inode_removexattr(dentry, name); + ret = security_ops->inode_removexattr(dentry, name); + if (ret) + return ret; + return evm_inode_removexattr(dentry, name); } int security_inode_need_killpriv(struct dentry *dentry) From c7b87de23b6fd5dfbe5c36601f29d6c515056343 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Wed, 9 Mar 2011 14:39:18 -0500 Subject: [PATCH 07/95] evm: evm_inode_post_removexattr When an EVM protected extended attribute is removed, update 'security.evm'. Signed-off-by: Mimi Zohar Acked-by: Serge Hallyn --- fs/xattr.c | 5 ++++- include/linux/evm.h | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/fs/xattr.c b/fs/xattr.c index 851808c92b30..67583de8218c 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -301,8 +302,10 @@ vfs_removexattr(struct dentry *dentry, const char *name) error = inode->i_op->removexattr(dentry, name); mutex_unlock(&inode->i_mutex); - if (!error) + if (!error) { fsnotify_xattr(dentry); + evm_inode_post_removexattr(dentry, name); + } return error; } EXPORT_SYMBOL_GPL(vfs_removexattr); diff --git a/include/linux/evm.h b/include/linux/evm.h index 8b4e9e3b395e..a730782da563 100644 --- a/include/linux/evm.h +++ b/include/linux/evm.h @@ -22,6 +22,8 @@ extern void evm_inode_post_setxattr(struct dentry *dentry, const void *xattr_value, size_t xattr_value_len); extern int evm_inode_removexattr(struct dentry *dentry, const char *xattr_name); +extern void evm_inode_post_removexattr(struct dentry *dentry, + const char *xattr_name); #else #ifdef CONFIG_INTEGRITY static inline enum integrity_status evm_verifyxattr(struct dentry *dentry, @@ -52,5 +54,12 @@ static inline int evm_inode_removexattr(struct dentry *dentry, { return 0; } + +static inline void evm_inode_post_removexattr(struct dentry *dentry, + const char *xattr_name) +{ + return; +} + #endif /* CONFIG_EVM_H */ #endif /* LINUX_EVM_H */ From 975d294373d8c1c913ad2bf4eb93966d4c7ca38f Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Wed, 9 Mar 2011 14:39:57 -0500 Subject: [PATCH 08/95] evm: imbed evm_inode_post_setattr Changing the inode's metadata may require the 'security.evm' extended attribute to be re-calculated and updated. Signed-off-by: Mimi Zohar Acked-by: Serge Hallyn --- fs/attr.c | 5 ++++- include/linux/evm.h | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/fs/attr.c b/fs/attr.c index caf2aa521e2b..5ad45d3cc20a 100644 --- a/fs/attr.c +++ b/fs/attr.c @@ -13,6 +13,7 @@ #include #include #include +#include /** * inode_change_ok - check if attribute changes to an inode are allowed @@ -243,8 +244,10 @@ int notify_change(struct dentry * dentry, struct iattr * attr) if (ia_valid & ATTR_SIZE) up_write(&dentry->d_inode->i_alloc_sem); - if (!error) + if (!error) { fsnotify_change(dentry, ia_valid); + evm_inode_post_setattr(dentry, ia_valid); + } return error; } diff --git a/include/linux/evm.h b/include/linux/evm.h index a730782da563..33a92471e463 100644 --- a/include/linux/evm.h +++ b/include/linux/evm.h @@ -15,6 +15,7 @@ extern enum integrity_status evm_verifyxattr(struct dentry *dentry, const char *xattr_name, void *xattr_value, size_t xattr_value_len); +extern void evm_inode_post_setattr(struct dentry *dentry, int ia_valid); extern int evm_inode_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size); extern void evm_inode_post_setxattr(struct dentry *dentry, @@ -35,6 +36,11 @@ static inline enum integrity_status evm_verifyxattr(struct dentry *dentry, } #endif +static inline void evm_inode_post_setattr(struct dentry *dentry, int ia_valid) +{ + return; +} + static inline int evm_inode_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size) { From cb72318069d5e92eb74840118732c66eb38c812f Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Wed, 9 Mar 2011 14:40:44 -0500 Subject: [PATCH 09/95] evm: add evm_inode_init_security to initialize new files Initialize 'security.evm' for new files. Changelog v7: - renamed evm_inode_post_init_security to evm_inode_init_security - moved struct xattr definition to earlier patch - allocate xattr name Changelog v6: - Use 'struct evm_ima_xattr_data' Signed-off-by: Mimi Zohar --- include/linux/evm.h | 11 +++++++++ security/integrity/evm/evm.h | 3 +++ security/integrity/evm/evm_crypto.c | 20 +++++++++++++++ security/integrity/evm/evm_main.c | 38 +++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+) diff --git a/include/linux/evm.h b/include/linux/evm.h index 33a92471e463..7c10761916a2 100644 --- a/include/linux/evm.h +++ b/include/linux/evm.h @@ -9,6 +9,7 @@ #define _LINUX_EVM_H #include +#include #ifdef CONFIG_EVM extern enum integrity_status evm_verifyxattr(struct dentry *dentry, @@ -25,6 +26,9 @@ extern void evm_inode_post_setxattr(struct dentry *dentry, extern int evm_inode_removexattr(struct dentry *dentry, const char *xattr_name); extern void evm_inode_post_removexattr(struct dentry *dentry, const char *xattr_name); +extern int evm_inode_init_security(struct inode *inode, + const struct xattr *xattr_array, + struct xattr *evm); #else #ifdef CONFIG_INTEGRITY static inline enum integrity_status evm_verifyxattr(struct dentry *dentry, @@ -67,5 +71,12 @@ static inline void evm_inode_post_removexattr(struct dentry *dentry, return; } +static inline int evm_inode_init_security(struct inode *inode, + const struct xattr *xattr_array, + struct xattr *evm) +{ + return -EOPNOTSUPP; +} + #endif /* CONFIG_EVM_H */ #endif /* LINUX_EVM_H */ diff --git a/security/integrity/evm/evm.h b/security/integrity/evm/evm.h index 375dc3e6015c..a45d0d630a30 100644 --- a/security/integrity/evm/evm.h +++ b/security/integrity/evm/evm.h @@ -12,6 +12,7 @@ * File: evm.h * */ +#include #include #include "../integrity.h" @@ -29,5 +30,7 @@ extern int evm_update_evmxattr(struct dentry *dentry, extern int evm_calc_hmac(struct dentry *dentry, const char *req_xattr_name, const char *req_xattr_value, size_t req_xattr_value_len, char *digest); +extern int evm_init_hmac(struct inode *inode, const struct xattr *xattr, + char *hmac_val); extern int evm_init_secfs(void); extern void evm_cleanup_secfs(void); diff --git a/security/integrity/evm/evm_crypto.c b/security/integrity/evm/evm_crypto.c index c631b99bda95..c9902bddcb9a 100644 --- a/security/integrity/evm/evm_crypto.c +++ b/security/integrity/evm/evm_crypto.c @@ -157,6 +157,26 @@ int evm_update_evmxattr(struct dentry *dentry, const char *xattr_name, return rc; } +int evm_init_hmac(struct inode *inode, const struct xattr *lsm_xattr, + char *hmac_val) +{ + struct hash_desc desc; + struct scatterlist sg[1]; + int error; + + error = init_desc(&desc); + if (error != 0) { + printk(KERN_INFO "init_desc failed\n"); + return error; + } + + sg_init_one(sg, lsm_xattr->value, lsm_xattr->value_len); + crypto_hash_update(&desc, sg, lsm_xattr->value_len); + hmac_add_misc(&desc, inode, hmac_val); + crypto_free_hash(desc.tfm); + return 0; +} + /* * Get the key from the TPM for the SHA1-HMAC */ diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index 1746c3669c6f..23486355f443 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -98,6 +98,12 @@ static int evm_protected_xattr(const char *req_xattr_name) found = 1; break; } + if (strncmp(req_xattr_name, + *xattrname + XATTR_SECURITY_PREFIX_LEN, + strlen(req_xattr_name)) == 0) { + found = 1; + break; + } } return found; } @@ -245,6 +251,38 @@ void evm_inode_post_setattr(struct dentry *dentry, int ia_valid) return; } +/* + * evm_inode_init_security - initializes security.evm + */ +int evm_inode_init_security(struct inode *inode, + const struct xattr *lsm_xattr, + struct xattr *evm_xattr) +{ + struct evm_ima_xattr_data *xattr_data; + int rc; + + if (!evm_initialized || !evm_protected_xattr(lsm_xattr->name)) + return -EOPNOTSUPP; + + xattr_data = kzalloc(sizeof(*xattr_data), GFP_NOFS); + if (!xattr_data) + return -ENOMEM; + + xattr_data->type = EVM_XATTR_HMAC; + rc = evm_init_hmac(inode, lsm_xattr, xattr_data->digest); + if (rc < 0) + goto out; + + evm_xattr->value = xattr_data; + evm_xattr->value_len = sizeof(*xattr_data); + evm_xattr->name = kstrdup(XATTR_EVM_SUFFIX, GFP_NOFS); + return 0; +out: + kfree(xattr_data); + return rc; +} +EXPORT_SYMBOL_GPL(evm_inode_init_security); + static struct crypto_hash *tfm_hmac; /* preload crypto alg */ static int __init init_evm(void) { From 823eb1ccd0b310449e99c822412ea8208334d14c Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Wed, 15 Jun 2011 21:19:10 -0400 Subject: [PATCH 10/95] evm: call evm_inode_init_security from security_inode_init_security Changelog v7: - moved the initialization call to security_inode_init_security, renaming evm_inode_post_init_security to evm_inode_init_security - increase size of xattr array for EVM xattr Signed-off-by: Mimi Zohar --- security/security.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/security/security.c b/security/security.c index 21a79b3d1e8e..181990ae90c1 100644 --- a/security/security.c +++ b/security/security.c @@ -20,7 +20,7 @@ #include #include -#define MAX_LSM_XATTR 1 +#define MAX_LSM_EVM_XATTR 2 /* Boot-time LSM user choice */ static __initdata char chosen_lsm[SECURITY_NAME_MAX + 1] = @@ -346,8 +346,8 @@ int security_inode_init_security(struct inode *inode, struct inode *dir, const struct qstr *qstr, const initxattrs initxattrs, void *fs_data) { - struct xattr new_xattrs[MAX_LSM_XATTR + 1]; - struct xattr *lsm_xattr; + struct xattr new_xattrs[MAX_LSM_EVM_XATTR + 1]; + struct xattr *lsm_xattr, *evm_xattr, *xattr; int ret; if (unlikely(IS_PRIVATE(inode))) @@ -364,11 +364,17 @@ int security_inode_init_security(struct inode *inode, struct inode *dir, &lsm_xattr->value_len); if (ret) goto out; + + evm_xattr = lsm_xattr + 1; + ret = evm_inode_init_security(inode, lsm_xattr, evm_xattr); + if (ret) + goto out; ret = initxattrs(inode, new_xattrs, fs_data); out: - kfree(lsm_xattr->name); - kfree(lsm_xattr->value); - + for (xattr = new_xattrs; xattr->name != NULL; xattr++) { + kfree(xattr->name); + kfree(xattr->value); + } return (ret == -EOPNOTSUPP) ? 0 : ret; } EXPORT_SYMBOL(security_inode_init_security); From d46eb3699502ba221e81e88e6c6594e2a7818532 Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Wed, 9 Mar 2011 15:07:36 -0500 Subject: [PATCH 11/95] evm: crypto hash replaced by shash Using shash is more efficient, because the algorithm is allocated only once. Only the descriptor to store the hash state needs to be allocated for every operation. Changelog v6: - check for crypto_shash_setkey failure Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar --- security/integrity/evm/evm.h | 2 + security/integrity/evm/evm_crypto.c | 92 ++++++++++++++++------------- security/integrity/evm/evm_main.c | 6 +- 3 files changed, 56 insertions(+), 44 deletions(-) diff --git a/security/integrity/evm/evm.h b/security/integrity/evm/evm.h index a45d0d630a30..d320f5197437 100644 --- a/security/integrity/evm/evm.h +++ b/security/integrity/evm/evm.h @@ -19,6 +19,8 @@ extern int evm_initialized; extern char *evm_hmac; +extern struct crypto_shash *hmac_tfm; + /* List of EVM protected security xattrs */ extern char *evm_config_xattrnames[]; diff --git a/security/integrity/evm/evm_crypto.c b/security/integrity/evm/evm_crypto.c index c9902bddcb9a..5dd5b140242c 100644 --- a/security/integrity/evm/evm_crypto.c +++ b/security/integrity/evm/evm_crypto.c @@ -16,8 +16,8 @@ #include #include #include -#include #include +#include #include "evm.h" #define EVMKEY "evm-key" @@ -25,26 +25,42 @@ static unsigned char evmkey[MAX_KEY_SIZE]; static int evmkey_len = MAX_KEY_SIZE; -static int init_desc(struct hash_desc *desc) +struct crypto_shash *hmac_tfm; + +static struct shash_desc *init_desc(void) { int rc; + struct shash_desc *desc; - desc->tfm = crypto_alloc_hash(evm_hmac, 0, CRYPTO_ALG_ASYNC); - if (IS_ERR(desc->tfm)) { - pr_info("Can not allocate %s (reason: %ld)\n", - evm_hmac, PTR_ERR(desc->tfm)); - rc = PTR_ERR(desc->tfm); - return rc; + if (hmac_tfm == NULL) { + hmac_tfm = crypto_alloc_shash(evm_hmac, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(hmac_tfm)) { + pr_err("Can not allocate %s (reason: %ld)\n", + evm_hmac, PTR_ERR(hmac_tfm)); + rc = PTR_ERR(hmac_tfm); + hmac_tfm = NULL; + return ERR_PTR(rc); + } } - desc->flags = 0; - rc = crypto_hash_setkey(desc->tfm, evmkey, evmkey_len); + + desc = kmalloc(sizeof(*desc) + crypto_shash_descsize(hmac_tfm), + GFP_KERNEL); + if (!desc) + return ERR_PTR(-ENOMEM); + + desc->tfm = hmac_tfm; + desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP; + + rc = crypto_shash_setkey(hmac_tfm, evmkey, evmkey_len); if (rc) goto out; - rc = crypto_hash_init(desc); + rc = crypto_shash_init(desc); out: - if (rc) - crypto_free_hash(desc->tfm); - return rc; + if (rc) { + kfree(desc); + return ERR_PTR(rc); + } + return desc; } /* Protect against 'cutting & pasting' security.evm xattr, include inode @@ -53,7 +69,7 @@ out: * (Additional directory/file metadata needs to be added for more complete * protection.) */ -static void hmac_add_misc(struct hash_desc *desc, struct inode *inode, +static void hmac_add_misc(struct shash_desc *desc, struct inode *inode, char *digest) { struct h_misc { @@ -63,7 +79,6 @@ static void hmac_add_misc(struct hash_desc *desc, struct inode *inode, gid_t gid; umode_t mode; } hmac_misc; - struct scatterlist sg[1]; memset(&hmac_misc, 0, sizeof hmac_misc); hmac_misc.ino = inode->i_ino; @@ -71,9 +86,8 @@ static void hmac_add_misc(struct hash_desc *desc, struct inode *inode, hmac_misc.uid = inode->i_uid; hmac_misc.gid = inode->i_gid; hmac_misc.mode = inode->i_mode; - sg_init_one(sg, &hmac_misc, sizeof hmac_misc); - crypto_hash_update(desc, sg, sizeof hmac_misc); - crypto_hash_final(desc, digest); + crypto_shash_update(desc, (const u8 *)&hmac_misc, sizeof hmac_misc); + crypto_shash_final(desc, digest); } /* @@ -88,8 +102,7 @@ int evm_calc_hmac(struct dentry *dentry, const char *req_xattr_name, char *digest) { struct inode *inode = dentry->d_inode; - struct hash_desc desc; - struct scatterlist sg[1]; + struct shash_desc *desc; char **xattrname; size_t xattr_size = 0; char *xattr_value = NULL; @@ -98,17 +111,17 @@ int evm_calc_hmac(struct dentry *dentry, const char *req_xattr_name, if (!inode->i_op || !inode->i_op->getxattr) return -EOPNOTSUPP; - error = init_desc(&desc); - if (error) - return error; + desc = init_desc(); + if (IS_ERR(desc)) + return PTR_ERR(desc); error = -ENODATA; for (xattrname = evm_config_xattrnames; *xattrname != NULL; xattrname++) { if ((req_xattr_name && req_xattr_value) && !strcmp(*xattrname, req_xattr_name)) { error = 0; - sg_init_one(sg, req_xattr_value, req_xattr_value_len); - crypto_hash_update(&desc, sg, req_xattr_value_len); + crypto_shash_update(desc, (const u8 *)req_xattr_value, + req_xattr_value_len); continue; } size = vfs_getxattr_alloc(dentry, *xattrname, @@ -122,13 +135,13 @@ int evm_calc_hmac(struct dentry *dentry, const char *req_xattr_name, error = 0; xattr_size = size; - sg_init_one(sg, xattr_value, xattr_size); - crypto_hash_update(&desc, sg, xattr_size); + crypto_shash_update(desc, (const u8 *)xattr_value, xattr_size); } - hmac_add_misc(&desc, inode, digest); - kfree(xattr_value); + hmac_add_misc(desc, inode, digest); + out: - crypto_free_hash(desc.tfm); + kfree(xattr_value); + kfree(desc); return error; } @@ -160,20 +173,17 @@ int evm_update_evmxattr(struct dentry *dentry, const char *xattr_name, int evm_init_hmac(struct inode *inode, const struct xattr *lsm_xattr, char *hmac_val) { - struct hash_desc desc; - struct scatterlist sg[1]; - int error; + struct shash_desc *desc; - error = init_desc(&desc); - if (error != 0) { + desc = init_desc(); + if (IS_ERR(desc)) { printk(KERN_INFO "init_desc failed\n"); - return error; + return PTR_ERR(desc); } - sg_init_one(sg, lsm_xattr->value, lsm_xattr->value_len); - crypto_hash_update(&desc, sg, lsm_xattr->value_len); - hmac_add_misc(&desc, inode, hmac_val); - crypto_free_hash(desc.tfm); + crypto_shash_update(desc, lsm_xattr->value, lsm_xattr->value_len); + hmac_add_misc(desc, inode, hmac_val); + kfree(desc); return 0; } diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index 23486355f443..b65adb5b06c8 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "evm.h" int evm_initialized; @@ -283,12 +284,10 @@ out: } EXPORT_SYMBOL_GPL(evm_inode_init_security); -static struct crypto_hash *tfm_hmac; /* preload crypto alg */ static int __init init_evm(void) { int error; - tfm_hmac = crypto_alloc_hash(evm_hmac, 0, CRYPTO_ALG_ASYNC); error = evm_init_secfs(); if (error < 0) { printk(KERN_INFO "EVM: Error registering secfs\n"); @@ -301,7 +300,8 @@ err: static void __exit cleanup_evm(void) { evm_cleanup_secfs(); - crypto_free_hash(tfm_hmac); + if (hmac_tfm) + crypto_free_shash(hmac_tfm); } /* From 2960e6cb5f7c662b8edb6b0d2edc72095b4f5672 Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Fri, 6 May 2011 11:34:13 +0300 Subject: [PATCH 12/95] evm: additional parameter to pass integrity cache entry 'iint' Additional iint parameter allows to skip lookup in the cache. Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar --- include/linux/evm.h | 8 ++++++-- security/integrity/evm/evm_main.c | 18 ++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/include/linux/evm.h b/include/linux/evm.h index 7c10761916a2..6d4e89b020c5 100644 --- a/include/linux/evm.h +++ b/include/linux/evm.h @@ -11,11 +11,14 @@ #include #include +struct integrity_iint_cache; + #ifdef CONFIG_EVM extern enum integrity_status evm_verifyxattr(struct dentry *dentry, const char *xattr_name, void *xattr_value, - size_t xattr_value_len); + size_t xattr_value_len, + struct integrity_iint_cache *iint); extern void evm_inode_post_setattr(struct dentry *dentry, int ia_valid); extern int evm_inode_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size); @@ -34,7 +37,8 @@ extern int evm_inode_init_security(struct inode *inode, static inline enum integrity_status evm_verifyxattr(struct dentry *dentry, const char *xattr_name, void *xattr_value, - size_t xattr_value_len) + size_t xattr_value_len, + struct integrity_iint_cache *iint) { return INTEGRITY_UNKNOWN; } diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index b65adb5b06c8..0fa8261c3655 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -127,21 +127,19 @@ static int evm_protected_xattr(const char *req_xattr_name) */ enum integrity_status evm_verifyxattr(struct dentry *dentry, const char *xattr_name, - void *xattr_value, size_t xattr_value_len) + void *xattr_value, size_t xattr_value_len, + struct integrity_iint_cache *iint) { - struct inode *inode = dentry->d_inode; - struct integrity_iint_cache *iint; - enum integrity_status status; - if (!evm_initialized || !evm_protected_xattr(xattr_name)) return INTEGRITY_UNKNOWN; - iint = integrity_iint_find(inode); - if (!iint) - return INTEGRITY_UNKNOWN; - status = evm_verify_hmac(dentry, xattr_name, xattr_value, + if (!iint) { + iint = integrity_iint_find(dentry->d_inode); + if (!iint) + return INTEGRITY_UNKNOWN; + } + return evm_verify_hmac(dentry, xattr_name, xattr_value, xattr_value_len, iint); - return status; } EXPORT_SYMBOL_GPL(evm_verifyxattr); From 6d38ca01c0c2d6c2e46ec1984db9ada6bad6ca26 Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Fri, 6 May 2011 11:34:14 +0300 Subject: [PATCH 13/95] evm: evm_verify_hmac must not return INTEGRITY_UNKNOWN If EVM is not supported or enabled, evm_verify_hmac() returns INTEGRITY_UNKNOWN, which ima_appraise_measurement() ignores and sets the appraisal status based solely on the security.ima verification. evm_verify_hmac() also returns INTEGRITY_UNKNOWN for other failures, such as temporary failures like -ENOMEM, resulting in possible attack vectors. This patch changes the default return code for temporary/unexpected failures, like -ENOMEM, from INTEGRITY_UNKNOWN to INTEGRITY_FAIL, making evm_verify_hmac() fail safe. As a result, failures need to be re-evaluated in order to catch both temporary errors, such as the -ENOMEM, as well as errors that have been resolved in fix mode. Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar --- security/integrity/evm/evm_main.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index 0fa8261c3655..bfe44dff61bb 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -56,13 +56,15 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, struct evm_ima_xattr_data xattr_data; int rc; - if (iint->hmac_status != INTEGRITY_UNKNOWN) + if (iint->hmac_status == INTEGRITY_PASS) return iint->hmac_status; + /* if status is not PASS, try to check again - against -ENOMEM */ + rc = evm_calc_hmac(dentry, xattr_name, xattr_value, xattr_value_len, xattr_data.digest); if (rc < 0) - return INTEGRITY_UNKNOWN; + goto err_out; xattr_data.type = EVM_XATTR_HMAC; rc = vfs_xattr_cmp(dentry, XATTR_NAME_EVM, (u8 *)&xattr_data, @@ -77,11 +79,8 @@ err_out: case -ENODATA: /* file not labelled */ iint->hmac_status = INTEGRITY_NOLABEL; break; - case -EINVAL: - iint->hmac_status = INTEGRITY_FAIL; - break; default: - iint->hmac_status = INTEGRITY_UNKNOWN; + iint->hmac_status = INTEGRITY_FAIL; } return iint->hmac_status; } From 24e0198efe0df50034ec1c14b2d7b5bb0f66d54a Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Fri, 6 May 2011 11:34:17 +0300 Subject: [PATCH 14/95] evm: replace hmac_status with evm_status We will use digital signatures in addtion to hmac. Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar --- security/integrity/evm/evm_main.c | 14 +++++++------- security/integrity/iint.c | 2 +- security/integrity/integrity.h | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index bfe44dff61bb..eb07f9d13c24 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -56,8 +56,8 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, struct evm_ima_xattr_data xattr_data; int rc; - if (iint->hmac_status == INTEGRITY_PASS) - return iint->hmac_status; + if (iint->evm_status == INTEGRITY_PASS) + return iint->evm_status; /* if status is not PASS, try to check again - against -ENOMEM */ @@ -71,18 +71,18 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, sizeof xattr_data, GFP_NOFS); if (rc < 0) goto err_out; - iint->hmac_status = INTEGRITY_PASS; - return iint->hmac_status; + iint->evm_status = INTEGRITY_PASS; + return iint->evm_status; err_out: switch (rc) { case -ENODATA: /* file not labelled */ - iint->hmac_status = INTEGRITY_NOLABEL; + iint->evm_status = INTEGRITY_NOLABEL; break; default: - iint->hmac_status = INTEGRITY_FAIL; + iint->evm_status = INTEGRITY_FAIL; } - return iint->hmac_status; + return iint->evm_status; } static int evm_protected_xattr(const char *req_xattr_name) diff --git a/security/integrity/iint.c b/security/integrity/iint.c index 991df20709b0..0a23e075e1d2 100644 --- a/security/integrity/iint.c +++ b/security/integrity/iint.c @@ -157,7 +157,7 @@ static void init_once(void *foo) iint->version = 0; iint->flags = 0UL; mutex_init(&iint->mutex); - iint->hmac_status = INTEGRITY_UNKNOWN; + iint->evm_status = INTEGRITY_UNKNOWN; } static int __init integrity_iintcache_init(void) diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 7efbf560b7d5..880bbee2f534 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -37,7 +37,7 @@ struct integrity_iint_cache { unsigned char flags; u8 digest[SHA1_DIGEST_SIZE]; struct mutex mutex; /* protects: version, flags, digest */ - enum integrity_status hmac_status; + enum integrity_status evm_status; }; /* rbtree tree calls to lookup, insert, delete From 7102ebcd65c1cdb5d5a87c7c5cf7a46f5afb0cac Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Thu, 12 May 2011 18:33:20 -0400 Subject: [PATCH 15/95] evm: permit only valid security.evm xattrs to be updated In addition to requiring CAP_SYS_ADMIN permission to modify/delete security.evm, prohibit invalid security.evm xattrs from changing, unless in fixmode. This patch prevents inadvertent 'fixing' of security.evm to reflect offline modifications. Changelog v7: - rename boot paramater 'evm_mode' to 'evm' Reported-by: Roberto Sassu Signed-off-by: Mimi Zohar --- Documentation/kernel-parameters.txt | 6 +++ security/integrity/evm/evm_main.c | 77 +++++++++++++++++++++++------ 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index fd248a318211..db97ff1da8c0 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -48,6 +48,7 @@ parameter is applicable: EDD BIOS Enhanced Disk Drive Services (EDD) is enabled EFI EFI Partitioning (GPT) is enabled EIDE EIDE/ATAPI support is enabled. + EVM Extended Verification Module FB The frame buffer device is enabled. GCOV GCOV profiling is enabled. HW Appropriate hardware is enabled. @@ -750,6 +751,11 @@ bytes respectively. Such letter suffixes can also be entirely omitted. This option is obsoleted by the "netdev=" option, which has equivalent usage. See its documentation for details. + evm= [EVM] + Format: { "fix" } + Permit 'security.evm' to be updated regardless of + current integrity status. + failslab= fail_page_alloc= fail_make_request=[KNL] diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index eb07f9d13c24..94d66af07aa4 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -37,13 +37,25 @@ char *evm_config_xattrnames[] = { NULL }; +static int evm_fixmode; +static int __init evm_set_fixmode(char *str) +{ + if (strncmp(str, "fix", 3) == 0) + evm_fixmode = 1; + return 0; +} +__setup("evm=", evm_set_fixmode); + /* * evm_verify_hmac - calculate and compare the HMAC with the EVM xattr * * Compute the HMAC on the dentry's protected set of extended attributes - * and compare it against the stored security.evm xattr. (For performance, - * use the previoulsy retrieved xattr value and length to calculate the - * HMAC.) + * and compare it against the stored security.evm xattr. + * + * For performance: + * - use the previoulsy retrieved xattr value and length to calculate the + * HMAC.) + * - cache the verification result in the iint, when available. * * Returns integrity status */ @@ -54,9 +66,10 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, struct integrity_iint_cache *iint) { struct evm_ima_xattr_data xattr_data; + enum integrity_status evm_status; int rc; - if (iint->evm_status == INTEGRITY_PASS) + if (iint && iint->evm_status == INTEGRITY_PASS) return iint->evm_status; /* if status is not PASS, try to check again - against -ENOMEM */ @@ -71,18 +84,21 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, sizeof xattr_data, GFP_NOFS); if (rc < 0) goto err_out; - iint->evm_status = INTEGRITY_PASS; - return iint->evm_status; + evm_status = INTEGRITY_PASS; + goto out; err_out: switch (rc) { case -ENODATA: /* file not labelled */ - iint->evm_status = INTEGRITY_NOLABEL; + evm_status = INTEGRITY_NOLABEL; break; default: - iint->evm_status = INTEGRITY_FAIL; + evm_status = INTEGRITY_FAIL; } - return iint->evm_status; +out: + if (iint) + iint->evm_status = evm_status; + return evm_status; } static int evm_protected_xattr(const char *req_xattr_name) @@ -157,6 +173,22 @@ static int evm_protect_xattr(struct dentry *dentry, const char *xattr_name, return 0; } +/* + * evm_verify_current_integrity - verify the dentry's metadata integrity + * @dentry: pointer to the affected dentry + * + * Verify and return the dentry's metadata integrity. The exceptions are + * before EVM is initialized or in 'fix' mode. + */ +static enum integrity_status evm_verify_current_integrity(struct dentry *dentry) +{ + struct inode *inode = dentry->d_inode; + + if (!evm_initialized || !S_ISREG(inode->i_mode) || evm_fixmode) + return 0; + return evm_verify_hmac(dentry, NULL, NULL, 0, NULL); +} + /** * evm_inode_setxattr - protect the EVM extended attribute * @dentry: pointer to the affected dentry @@ -164,13 +196,22 @@ static int evm_protect_xattr(struct dentry *dentry, const char *xattr_name, * @xattr_value: pointer to the new extended attribute value * @xattr_value_len: pointer to the new extended attribute value length * - * Prevent 'security.evm' from being modified + * Updating 'security.evm' requires CAP_SYS_ADMIN privileges and that + * the current value is valid. */ int evm_inode_setxattr(struct dentry *dentry, const char *xattr_name, const void *xattr_value, size_t xattr_value_len) { - return evm_protect_xattr(dentry, xattr_name, xattr_value, - xattr_value_len); + + enum integrity_status evm_status; + int ret; + + ret = evm_protect_xattr(dentry, xattr_name, xattr_value, + xattr_value_len); + if (ret) + return ret; + evm_status = evm_verify_current_integrity(dentry); + return evm_status == INTEGRITY_PASS ? 0 : -EPERM; } /** @@ -178,11 +219,19 @@ int evm_inode_setxattr(struct dentry *dentry, const char *xattr_name, * @dentry: pointer to the affected dentry * @xattr_name: pointer to the affected extended attribute name * - * Prevent 'security.evm' from being removed. + * Removing 'security.evm' requires CAP_SYS_ADMIN privileges and that + * the current value is valid. */ int evm_inode_removexattr(struct dentry *dentry, const char *xattr_name) { - return evm_protect_xattr(dentry, xattr_name, NULL, 0); + enum integrity_status evm_status; + int ret; + + ret = evm_protect_xattr(dentry, xattr_name, NULL, 0); + if (ret) + return ret; + evm_status = evm_verify_current_integrity(dentry); + return evm_status == INTEGRITY_PASS ? 0 : -EPERM; } /** From 817b54aa45db03437c6d09a7693fc6926eb8e822 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Fri, 13 May 2011 12:53:38 -0400 Subject: [PATCH 16/95] evm: add evm_inode_setattr to prevent updating an invalid security.evm Permit changing of security.evm only when valid, unless in fixmode. Reported-by: Roberto Sassu Signed-off-by: Mimi Zohar --- include/linux/evm.h | 6 ++++++ security/integrity/evm/evm_main.c | 15 +++++++++++++++ security/security.c | 7 ++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/include/linux/evm.h b/include/linux/evm.h index 6d4e89b020c5..db5556dcdd27 100644 --- a/include/linux/evm.h +++ b/include/linux/evm.h @@ -19,6 +19,7 @@ extern enum integrity_status evm_verifyxattr(struct dentry *dentry, void *xattr_value, size_t xattr_value_len, struct integrity_iint_cache *iint); +extern int evm_inode_setattr(struct dentry *dentry, struct iattr *attr); extern void evm_inode_post_setattr(struct dentry *dentry, int ia_valid); extern int evm_inode_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size); @@ -44,6 +45,11 @@ static inline enum integrity_status evm_verifyxattr(struct dentry *dentry, } #endif +static int evm_inode_setattr(struct dentry *dentry, struct iattr *attr) +{ + return 0; +} + static inline void evm_inode_post_setattr(struct dentry *dentry, int ia_valid) { return; diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index 94d66af07aa4..8fc5b5d7ceaa 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -277,6 +277,21 @@ void evm_inode_post_removexattr(struct dentry *dentry, const char *xattr_name) return; } +/** + * evm_inode_setattr - prevent updating an invalid EVM extended attribute + * @dentry: pointer to the affected dentry + */ +int evm_inode_setattr(struct dentry *dentry, struct iattr *attr) +{ + unsigned int ia_valid = attr->ia_valid; + enum integrity_status evm_status; + + if (ia_valid & ~(ATTR_MODE | ATTR_UID | ATTR_GID)) + return 0; + evm_status = evm_verify_current_integrity(dentry); + return evm_status == INTEGRITY_PASS ? 0 : -EPERM; +} + /** * evm_inode_post_setattr - update 'security.evm' after modifying metadata * @dentry: pointer to the affected dentry diff --git a/security/security.c b/security/security.c index 181990ae90c1..19251ccb2de0 100644 --- a/security/security.c +++ b/security/security.c @@ -571,9 +571,14 @@ int security_inode_exec_permission(struct inode *inode, unsigned int flags) int security_inode_setattr(struct dentry *dentry, struct iattr *attr) { + int ret; + if (unlikely(IS_PRIVATE(dentry->d_inode))) return 0; - return security_ops->inode_setattr(dentry, attr); + ret = security_ops->inode_setattr(dentry, attr); + if (ret) + return ret; + return evm_inode_setattr(dentry, attr); } EXPORT_SYMBOL_GPL(security_inode_setattr); From 260ff4fbdadb983814d3c44ce1af00db091e1d5d Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Mon, 1 Aug 2011 17:10:26 -0400 Subject: [PATCH 17/95] doc: Update the MAINTAINERS info for Paul Moore My @hp.com will no longer be valid starting August 5, 2011 so an update is necessary. My new email address is employer independent so we don't have to worry about doing this again any time soon. Signed-off-by: Paul Moore Signed-off-by: Paul Moore Signed-off-by: James Morris --- MAINTAINERS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 7b2e9e85e427..ce36ca5d0346 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4396,10 +4396,10 @@ F: net/*/netfilter/ F: net/netfilter/ NETLABEL -M: Paul Moore +M: Paul Moore W: http://netlabel.sf.net L: netdev@vger.kernel.org -S: Supported +S: Maintained F: Documentation/netlabel/ F: include/net/netlabel.h F: net/netlabel/ @@ -4457,7 +4457,7 @@ F: include/net/ip* F: arch/x86/net/* NETWORKING [LABELED] (NetLabel, CIPSO, Labeled IPsec, SECMARK) -M: Paul Moore +M: Paul Moore L: netdev@vger.kernel.org S: Maintained From 8959deef0fafeb6e5ede7efd237f74a5a6c9b472 Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Mon, 1 Aug 2011 17:10:33 -0400 Subject: [PATCH 18/95] doc: Update the email address for Paul Moore in various source files My @hp.com will no longer be valid starting August 5, 2011 so an update is necessary. My new email address is employer independent so we don't have to worry about doing this again any time soon. Signed-off-by: Paul Moore Signed-off-by: Paul Moore Signed-off-by: James Morris --- include/net/cipso_ipv4.h | 2 +- include/net/netlabel.h | 2 +- net/netlabel/Makefile | 2 -- net/netlabel/netlabel_addrlist.c | 2 +- net/netlabel/netlabel_addrlist.h | 2 +- net/netlabel/netlabel_cipso_v4.c | 2 +- net/netlabel/netlabel_cipso_v4.h | 2 +- net/netlabel/netlabel_domainhash.c | 2 +- net/netlabel/netlabel_domainhash.h | 2 +- net/netlabel/netlabel_kapi.c | 2 +- net/netlabel/netlabel_mgmt.c | 2 +- net/netlabel/netlabel_mgmt.h | 2 +- net/netlabel/netlabel_unlabeled.c | 2 +- net/netlabel/netlabel_unlabeled.h | 2 +- net/netlabel/netlabel_user.c | 2 +- net/netlabel/netlabel_user.h | 2 +- security/selinux/hooks.c | 2 +- security/selinux/include/netif.h | 2 +- security/selinux/include/netlabel.h | 2 +- security/selinux/include/netnode.h | 2 +- security/selinux/include/netport.h | 2 +- security/selinux/netif.c | 2 +- security/selinux/netlabel.c | 2 +- security/selinux/netnode.c | 2 +- security/selinux/netport.c | 2 +- security/selinux/selinuxfs.c | 2 +- security/selinux/ss/ebitmap.c | 2 +- security/selinux/ss/mls.c | 2 +- security/selinux/ss/mls.h | 2 +- security/selinux/ss/policydb.c | 2 +- security/selinux/ss/services.c | 2 +- security/smack/smack_lsm.c | 2 +- 32 files changed, 31 insertions(+), 33 deletions(-) diff --git a/include/net/cipso_ipv4.h b/include/net/cipso_ipv4.h index 3b938743514b..9808877c2ab9 100644 --- a/include/net/cipso_ipv4.h +++ b/include/net/cipso_ipv4.h @@ -8,7 +8,7 @@ * have chosen to adopt the protocol and over the years it has become a * de-facto standard for labeled networking. * - * Author: Paul Moore + * Author: Paul Moore * */ diff --git a/include/net/netlabel.h b/include/net/netlabel.h index f21a16ee3705..f67440970d7e 100644 --- a/include/net/netlabel.h +++ b/include/net/netlabel.h @@ -4,7 +4,7 @@ * The NetLabel system manages static and dynamic label mappings for network * protocols such as CIPSO and RIPSO. * - * Author: Paul Moore + * Author: Paul Moore * */ diff --git a/net/netlabel/Makefile b/net/netlabel/Makefile index ea750e9df65f..d2732fc952e2 100644 --- a/net/netlabel/Makefile +++ b/net/netlabel/Makefile @@ -1,8 +1,6 @@ # # Makefile for the NetLabel subsystem. # -# Feb 9, 2006, Paul Moore -# # base objects obj-y := netlabel_user.o netlabel_kapi.o diff --git a/net/netlabel/netlabel_addrlist.c b/net/netlabel/netlabel_addrlist.c index c0519139679e..96b749dacc34 100644 --- a/net/netlabel/netlabel_addrlist.c +++ b/net/netlabel/netlabel_addrlist.c @@ -6,7 +6,7 @@ * system manages static and dynamic label mappings for network protocols such * as CIPSO and RIPSO. * - * Author: Paul Moore + * Author: Paul Moore * */ diff --git a/net/netlabel/netlabel_addrlist.h b/net/netlabel/netlabel_addrlist.h index 2b9644e19de0..fdbc1d2c7352 100644 --- a/net/netlabel/netlabel_addrlist.h +++ b/net/netlabel/netlabel_addrlist.h @@ -6,7 +6,7 @@ * system manages static and dynamic label mappings for network protocols such * as CIPSO and RIPSO. * - * Author: Paul Moore + * Author: Paul Moore * */ diff --git a/net/netlabel/netlabel_cipso_v4.c b/net/netlabel/netlabel_cipso_v4.c index dd53a36d89af..6bf878335d94 100644 --- a/net/netlabel/netlabel_cipso_v4.c +++ b/net/netlabel/netlabel_cipso_v4.c @@ -5,7 +5,7 @@ * NetLabel system manages static and dynamic label mappings for network * protocols such as CIPSO and RIPSO. * - * Author: Paul Moore + * Author: Paul Moore * */ diff --git a/net/netlabel/netlabel_cipso_v4.h b/net/netlabel/netlabel_cipso_v4.h index af7f3355103e..d24d774bfd62 100644 --- a/net/netlabel/netlabel_cipso_v4.h +++ b/net/netlabel/netlabel_cipso_v4.h @@ -5,7 +5,7 @@ * NetLabel system manages static and dynamic label mappings for network * protocols such as CIPSO and RIPSO. * - * Author: Paul Moore + * Author: Paul Moore * */ diff --git a/net/netlabel/netlabel_domainhash.c b/net/netlabel/netlabel_domainhash.c index 2aa975e5452d..7d8083cde34f 100644 --- a/net/netlabel/netlabel_domainhash.c +++ b/net/netlabel/netlabel_domainhash.c @@ -6,7 +6,7 @@ * system manages static and dynamic label mappings for network protocols such * as CIPSO and RIPSO. * - * Author: Paul Moore + * Author: Paul Moore * */ diff --git a/net/netlabel/netlabel_domainhash.h b/net/netlabel/netlabel_domainhash.h index 0261dda3f2d2..bfcc0f7024c5 100644 --- a/net/netlabel/netlabel_domainhash.h +++ b/net/netlabel/netlabel_domainhash.h @@ -6,7 +6,7 @@ * system manages static and dynamic label mappings for network protocols such * as CIPSO and RIPSO. * - * Author: Paul Moore + * Author: Paul Moore * */ diff --git a/net/netlabel/netlabel_kapi.c b/net/netlabel/netlabel_kapi.c index b528dd928d3c..58107d060846 100644 --- a/net/netlabel/netlabel_kapi.c +++ b/net/netlabel/netlabel_kapi.c @@ -5,7 +5,7 @@ * system manages static and dynamic label mappings for network protocols such * as CIPSO and RIPSO. * - * Author: Paul Moore + * Author: Paul Moore * */ diff --git a/net/netlabel/netlabel_mgmt.c b/net/netlabel/netlabel_mgmt.c index dff8a0809245..bfa555869775 100644 --- a/net/netlabel/netlabel_mgmt.c +++ b/net/netlabel/netlabel_mgmt.c @@ -5,7 +5,7 @@ * NetLabel system manages static and dynamic label mappings for network * protocols such as CIPSO and RIPSO. * - * Author: Paul Moore + * Author: Paul Moore * */ diff --git a/net/netlabel/netlabel_mgmt.h b/net/netlabel/netlabel_mgmt.h index 8db37f4c10f7..5a9f31ce5799 100644 --- a/net/netlabel/netlabel_mgmt.h +++ b/net/netlabel/netlabel_mgmt.h @@ -5,7 +5,7 @@ * NetLabel system manages static and dynamic label mappings for network * protocols such as CIPSO and RIPSO. * - * Author: Paul Moore + * Author: Paul Moore * */ diff --git a/net/netlabel/netlabel_unlabeled.c b/net/netlabel/netlabel_unlabeled.c index f1ecf848e3ac..e6e823656f9d 100644 --- a/net/netlabel/netlabel_unlabeled.c +++ b/net/netlabel/netlabel_unlabeled.c @@ -5,7 +5,7 @@ * NetLabel system. The NetLabel system manages static and dynamic label * mappings for network protocols such as CIPSO and RIPSO. * - * Author: Paul Moore + * Author: Paul Moore * */ diff --git a/net/netlabel/netlabel_unlabeled.h b/net/netlabel/netlabel_unlabeled.h index 0bc8dc3f9e3c..700af49022a0 100644 --- a/net/netlabel/netlabel_unlabeled.h +++ b/net/netlabel/netlabel_unlabeled.h @@ -5,7 +5,7 @@ * NetLabel system. The NetLabel system manages static and dynamic label * mappings for network protocols such as CIPSO and RIPSO. * - * Author: Paul Moore + * Author: Paul Moore * */ diff --git a/net/netlabel/netlabel_user.c b/net/netlabel/netlabel_user.c index a3fd75ac3fa5..9fae63f10298 100644 --- a/net/netlabel/netlabel_user.c +++ b/net/netlabel/netlabel_user.c @@ -5,7 +5,7 @@ * NetLabel system manages static and dynamic label mappings for network * protocols such as CIPSO and RIPSO. * - * Author: Paul Moore + * Author: Paul Moore * */ diff --git a/net/netlabel/netlabel_user.h b/net/netlabel/netlabel_user.h index f4fc4c9ad567..81969785e279 100644 --- a/net/netlabel/netlabel_user.h +++ b/net/netlabel/netlabel_user.h @@ -5,7 +5,7 @@ * NetLabel system manages static and dynamic label mappings for network * protocols such as CIPSO and RIPSO. * - * Author: Paul Moore + * Author: Paul Moore * */ diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index a38316b2e3f6..266a2292451d 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -14,7 +14,7 @@ * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. * * Copyright (C) 2006, 2007, 2009 Hewlett-Packard Development Company, L.P. - * Paul Moore + * Paul Moore * Copyright (C) 2007 Hitachi Software Engineering Co., Ltd. * Yuichi Nakamura * diff --git a/security/selinux/include/netif.h b/security/selinux/include/netif.h index ce23edd128b3..43d507242b42 100644 --- a/security/selinux/include/netif.h +++ b/security/selinux/include/netif.h @@ -8,7 +8,7 @@ * * Copyright (C) 2003 Red Hat, Inc., James Morris * Copyright (C) 2007 Hewlett-Packard Development Company, L.P. - * Paul Moore, + * Paul Moore * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, diff --git a/security/selinux/include/netlabel.h b/security/selinux/include/netlabel.h index cf2f628e6e28..8c59b8f150e8 100644 --- a/security/selinux/include/netlabel.h +++ b/security/selinux/include/netlabel.h @@ -1,7 +1,7 @@ /* * SELinux interface to the NetLabel subsystem * - * Author : Paul Moore + * Author: Paul Moore * */ diff --git a/security/selinux/include/netnode.h b/security/selinux/include/netnode.h index 1b94450d11d2..df7a5ed6c694 100644 --- a/security/selinux/include/netnode.h +++ b/security/selinux/include/netnode.h @@ -6,7 +6,7 @@ * needed to reduce the lookup overhead since most of these queries happen on * a per-packet basis. * - * Author: Paul Moore + * Author: Paul Moore * */ diff --git a/security/selinux/include/netport.h b/security/selinux/include/netport.h index 8991752eaf93..4d965b83d735 100644 --- a/security/selinux/include/netport.h +++ b/security/selinux/include/netport.h @@ -5,7 +5,7 @@ * mapping is maintained as part of the normal policy but a fast cache is * needed to reduce the lookup overhead. * - * Author: Paul Moore + * Author: Paul Moore * */ diff --git a/security/selinux/netif.c b/security/selinux/netif.c index 58cc481c93d5..326f22cbe405 100644 --- a/security/selinux/netif.c +++ b/security/selinux/netif.c @@ -8,7 +8,7 @@ * * Copyright (C) 2003 Red Hat, Inc., James Morris * Copyright (C) 2007 Hewlett-Packard Development Company, L.P. - * Paul Moore + * Paul Moore * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, diff --git a/security/selinux/netlabel.c b/security/selinux/netlabel.c index c3bf3ed07b06..da4b8b233280 100644 --- a/security/selinux/netlabel.c +++ b/security/selinux/netlabel.c @@ -4,7 +4,7 @@ * This file provides the necessary glue to tie NetLabel into the SELinux * subsystem. * - * Author: Paul Moore + * Author: Paul Moore * */ diff --git a/security/selinux/netnode.c b/security/selinux/netnode.c index 8b691a863186..3bf46abaa688 100644 --- a/security/selinux/netnode.c +++ b/security/selinux/netnode.c @@ -6,7 +6,7 @@ * needed to reduce the lookup overhead since most of these queries happen on * a per-packet basis. * - * Author: Paul Moore + * Author: Paul Moore * * This code is heavily based on the "netif" concept originally developed by * James Morris diff --git a/security/selinux/netport.c b/security/selinux/netport.c index ae76e298de7d..0b62bd112461 100644 --- a/security/selinux/netport.c +++ b/security/selinux/netport.c @@ -5,7 +5,7 @@ * mapping is maintained as part of the normal policy but a fast cache is * needed to reduce the lookup overhead. * - * Author: Paul Moore + * Author: Paul Moore * * This code is heavily based on the "netif" concept originally developed by * James Morris diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index de7900ef53da..55d92cbb177a 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -2,7 +2,7 @@ * * Added conditional policy language extensions * - * Updated: Hewlett-Packard + * Updated: Hewlett-Packard * * Added support for the policy capability bitmap * diff --git a/security/selinux/ss/ebitmap.c b/security/selinux/ss/ebitmap.c index d42951fcbe87..30f119b1d1ec 100644 --- a/security/selinux/ss/ebitmap.c +++ b/security/selinux/ss/ebitmap.c @@ -4,7 +4,7 @@ * Author : Stephen Smalley, */ /* - * Updated: Hewlett-Packard + * Updated: Hewlett-Packard * * Added support to import/export the NetLabel category bitmap * diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c index e96174216bc9..fbf9c5816c71 100644 --- a/security/selinux/ss/mls.c +++ b/security/selinux/ss/mls.c @@ -11,7 +11,7 @@ * Copyright (C) 2004-2006 Trusted Computer Solutions, Inc. */ /* - * Updated: Hewlett-Packard + * Updated: Hewlett-Packard * * Added support to import/export the MLS label from NetLabel * diff --git a/security/selinux/ss/mls.h b/security/selinux/ss/mls.h index 037bf9d82d41..e4369e3e6366 100644 --- a/security/selinux/ss/mls.h +++ b/security/selinux/ss/mls.h @@ -11,7 +11,7 @@ * Copyright (C) 2004-2006 Trusted Computer Solutions, Inc. */ /* - * Updated: Hewlett-Packard + * Updated: Hewlett-Packard * * Added support to import/export the MLS label from NetLabel * diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index d246aca3f4fb..2381d0ded228 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -13,7 +13,7 @@ * * Added conditional policy language extensions * - * Updated: Hewlett-Packard + * Updated: Hewlett-Packard * * Added support for the policy capability bitmap * diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 973e00e34fa9..f6917bc0aa05 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -13,7 +13,7 @@ * * Added conditional policy language extensions * - * Updated: Hewlett-Packard + * Updated: Hewlett-Packard * * Added support for NetLabel * Added support for the policy capability bitmap diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index f375eb2e1957..b9c5e149903b 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -9,7 +9,7 @@ * * Copyright (C) 2007 Casey Schaufler * Copyright (C) 2009 Hewlett-Packard Development Company, L.P. - * Paul Moore + * Paul Moore * Copyright (C) 2010 Nokia Corporation * * This program is free software; you can redistribute it and/or modify From 0b024d2446474c6a7c47573af5a35db83f557ce3 Mon Sep 17 00:00:00 2001 From: James Morris Date: Tue, 9 Aug 2011 11:33:36 +1000 Subject: [PATCH 20/95] EVM: ensure trusted and encypted key symbols are available to EVM Select trusted and encrypted keys if EVM is selected, to ensure the requisite symbols are available. Otherwise, these can be selected as modules while EVM is static, leading to a kernel build failure. Signed-off-by: James Morris --- security/integrity/evm/Kconfig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/security/integrity/evm/Kconfig b/security/integrity/evm/Kconfig index 73f654099a4b..444877d9239b 100644 --- a/security/integrity/evm/Kconfig +++ b/security/integrity/evm/Kconfig @@ -1,9 +1,11 @@ config EVM boolean "EVM support" - depends on SECURITY && KEYS && ENCRYPTED_KEYS + depends on SECURITY && KEYS select CRYPTO_HMAC select CRYPTO_MD5 select CRYPTO_SHA1 + select ENCRYPTED_KEYS + select TRUSTED_KEYS default n help EVM protects a file's security extended attributes against From e1c9b23adbe86c725738402857397d7a29f9d6ef Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Thu, 11 Aug 2011 00:22:51 -0400 Subject: [PATCH 21/95] evm: building without EVM enabled fixes - Missing 'inline' on evm_inode_setattr() definition. Introduced by commit 817b54aa45db ("evm: add evm_inode_setattr to prevent updating an invalid security.evm"). - Missing security_old_inode_init_security() stub function definition. Caused by commit 9d8f13ba3f48 ("security: new security_inode_init_security API adds function callback"). Reported-by: Stephen Rothwell Signed-off-by: Mimi Zohar Signed-off-by: James Morris --- include/linux/evm.h | 2 +- include/linux/security.h | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/include/linux/evm.h b/include/linux/evm.h index db5556dcdd27..62deb6557d37 100644 --- a/include/linux/evm.h +++ b/include/linux/evm.h @@ -45,7 +45,7 @@ static inline enum integrity_status evm_verifyxattr(struct dentry *dentry, } #endif -static int evm_inode_setattr(struct dentry *dentry, struct iattr *attr) +static inline int evm_inode_setattr(struct dentry *dentry, struct iattr *attr) { return 0; } diff --git a/include/linux/security.h b/include/linux/security.h index 1c528b19a329..f399cf10e2a8 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -2048,6 +2048,13 @@ static inline int security_inode_init_security(struct inode *inode, return -EOPNOTSUPP; } +int security_old_inode_init_security(struct inode *inode, struct inode *dir, + const struct qstr *qstr, char **name, + void **value, size_t *len) +{ + return -EOPNOTSUPP; +} + static inline int security_inode_create(struct inode *dir, struct dentry *dentry, int mode) From 5a4730ba9517cf2793175991243436a24b1db18f Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Thu, 11 Aug 2011 00:22:52 -0400 Subject: [PATCH 22/95] evm: fix evm_inode_init_security return code evm_inode_init_security() should return 0, when EVM is not enabled. (Returning an error is a remnant of evm_inode_post_init_security.) Signed-off-by: Mimi Zohar Signed-off-by: James Morris --- include/linux/evm.h | 2 +- security/integrity/evm/evm_main.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/evm.h b/include/linux/evm.h index 62deb6557d37..ea603c9e775d 100644 --- a/include/linux/evm.h +++ b/include/linux/evm.h @@ -85,7 +85,7 @@ static inline int evm_inode_init_security(struct inode *inode, const struct xattr *xattr_array, struct xattr *evm) { - return -EOPNOTSUPP; + return 0; } #endif /* CONFIG_EVM_H */ diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index 8fc5b5d7ceaa..f0127e536f84 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -324,7 +324,7 @@ int evm_inode_init_security(struct inode *inode, int rc; if (!evm_initialized || !evm_protected_xattr(lsm_xattr->name)) - return -EOPNOTSUPP; + return 0; xattr_data = kzalloc(sizeof(*xattr_data), GFP_NOFS); if (!xattr_data) From f995e74087402c482c55c29bf11da8bcf631245a Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Thu, 11 Aug 2011 16:00:47 -0400 Subject: [PATCH 23/95] CIFS: remove local xattr definitions Local XATTR_TRUSTED_PREFIX_LEN and XATTR_SECURITY_PREFIX_LEN definitions redefined ones in 'linux/xattr.h'. This was caused by commit 9d8f13ba3f48 ("security: new security_inode_init_security API adds function callback") including 'linux/xattr.h' in 'linux/security.h'. In file included from include/linux/security.h:39, from include/net/sock.h:54, from fs/cifs/cifspdu.h:25, from fs/cifs/xattr.c:26: This patch removes the local definitions. Reported-by: Stephen Rothwell Signed-off-by: Mimi Zohar Signed-off-by: James Morris --- fs/cifs/xattr.c | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/fs/cifs/xattr.c b/fs/cifs/xattr.c index 2a22fb2989e4..c32308882148 100644 --- a/fs/cifs/xattr.c +++ b/fs/cifs/xattr.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "cifsfs.h" #include "cifspdu.h" #include "cifsglob.h" @@ -31,17 +32,9 @@ #define MAX_EA_VALUE_SIZE 65535 #define CIFS_XATTR_DOS_ATTRIB "user.DosAttrib" #define CIFS_XATTR_CIFS_ACL "system.cifs_acl" -#define CIFS_XATTR_USER_PREFIX "user." -#define CIFS_XATTR_SYSTEM_PREFIX "system." -#define CIFS_XATTR_OS2_PREFIX "os2." -#define CIFS_XATTR_SECURITY_PREFIX "security." -#define CIFS_XATTR_TRUSTED_PREFIX "trusted." -#define XATTR_TRUSTED_PREFIX_LEN 8 -#define XATTR_SECURITY_PREFIX_LEN 9 + /* BB need to add server (Samba e.g) support for security and trusted prefix */ - - int cifs_removexattr(struct dentry *direntry, const char *ea_name) { int rc = -EOPNOTSUPP; @@ -76,8 +69,8 @@ int cifs_removexattr(struct dentry *direntry, const char *ea_name) } if (ea_name == NULL) { cFYI(1, "Null xattr names not supported"); - } else if (strncmp(ea_name, CIFS_XATTR_USER_PREFIX, 5) - && (strncmp(ea_name, CIFS_XATTR_OS2_PREFIX, 4))) { + } else if (strncmp(ea_name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN) + && (strncmp(ea_name, XATTR_OS2_PREFIX, XATTR_OS2_PREFIX_LEN))) { cFYI(1, "illegal xattr request %s (only user namespace supported)", ea_name); @@ -88,7 +81,7 @@ int cifs_removexattr(struct dentry *direntry, const char *ea_name) if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_XATTR) goto remove_ea_exit; - ea_name += 5; /* skip past user. prefix */ + ea_name += XATTR_USER_PREFIX_LEN; /* skip past user. prefix */ rc = CIFSSMBSetEA(xid, pTcon, full_path, ea_name, NULL, (__u16)0, cifs_sb->local_nls, cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR); @@ -149,21 +142,23 @@ int cifs_setxattr(struct dentry *direntry, const char *ea_name, if (ea_name == NULL) { cFYI(1, "Null xattr names not supported"); - } else if (strncmp(ea_name, CIFS_XATTR_USER_PREFIX, 5) == 0) { + } else if (strncmp(ea_name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN) + == 0) { if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_XATTR) goto set_ea_exit; if (strncmp(ea_name, CIFS_XATTR_DOS_ATTRIB, 14) == 0) cFYI(1, "attempt to set cifs inode metadata"); - ea_name += 5; /* skip past user. prefix */ + ea_name += XATTR_USER_PREFIX_LEN; /* skip past user. prefix */ rc = CIFSSMBSetEA(xid, pTcon, full_path, ea_name, ea_value, (__u16)value_size, cifs_sb->local_nls, cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR); - } else if (strncmp(ea_name, CIFS_XATTR_OS2_PREFIX, 4) == 0) { + } else if (strncmp(ea_name, XATTR_OS2_PREFIX, XATTR_OS2_PREFIX_LEN) + == 0) { if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_XATTR) goto set_ea_exit; - ea_name += 4; /* skip past os2. prefix */ + ea_name += XATTR_OS2_PREFIX_LEN; /* skip past os2. prefix */ rc = CIFSSMBSetEA(xid, pTcon, full_path, ea_name, ea_value, (__u16)value_size, cifs_sb->local_nls, cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR); @@ -269,7 +264,8 @@ ssize_t cifs_getxattr(struct dentry *direntry, const char *ea_name, /* return alt name if available as pseudo attr */ if (ea_name == NULL) { cFYI(1, "Null xattr names not supported"); - } else if (strncmp(ea_name, CIFS_XATTR_USER_PREFIX, 5) == 0) { + } else if (strncmp(ea_name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN) + == 0) { if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_XATTR) goto get_ea_exit; @@ -277,15 +273,15 @@ ssize_t cifs_getxattr(struct dentry *direntry, const char *ea_name, cFYI(1, "attempt to query cifs inode metadata"); /* revalidate/getattr then populate from inode */ } /* BB add else when above is implemented */ - ea_name += 5; /* skip past user. prefix */ + ea_name += XATTR_USER_PREFIX_LEN; /* skip past user. prefix */ rc = CIFSSMBQAllEAs(xid, pTcon, full_path, ea_name, ea_value, buf_size, cifs_sb->local_nls, cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR); - } else if (strncmp(ea_name, CIFS_XATTR_OS2_PREFIX, 4) == 0) { + } else if (strncmp(ea_name, XATTR_OS2_PREFIX, XATTR_OS2_PREFIX_LEN) == 0) { if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_XATTR) goto get_ea_exit; - ea_name += 4; /* skip past os2. prefix */ + ea_name += XATTR_OS2_PREFIX_LEN; /* skip past os2. prefix */ rc = CIFSSMBQAllEAs(xid, pTcon, full_path, ea_name, ea_value, buf_size, cifs_sb->local_nls, cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR); @@ -339,10 +335,10 @@ ssize_t cifs_getxattr(struct dentry *direntry, const char *ea_name, cFYI(1, "Query CIFS ACL not supported yet"); #endif /* CONFIG_CIFS_ACL */ } else if (strncmp(ea_name, - CIFS_XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN) == 0) { + XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN) == 0) { cFYI(1, "Trusted xattr namespace not supported yet"); } else if (strncmp(ea_name, - CIFS_XATTR_SECURITY_PREFIX, XATTR_SECURITY_PREFIX_LEN) == 0) { + XATTR_SECURITY_PREFIX, XATTR_SECURITY_PREFIX_LEN) == 0) { cFYI(1, "Security xattr namespace not supported yet"); } else cFYI(1, From 4d49f6710bfbd2271feab074f8c1053387e5d9fe Mon Sep 17 00:00:00 2001 From: Zhi Li Date: Thu, 11 Aug 2011 13:27:50 +0800 Subject: [PATCH 24/95] capabilities: do not grant full privs for setuid w/ file caps + no effective caps A task (when !SECURE_NOROOT) which executes a setuid-root binary will obtain root privileges while executing that binary. If the binary also has effective capabilities set, then only those capabilities will be granted. The rationale is that the same binary can carry both setuid-root and the minimal file capability set, so that on a filesystem not supporting file caps the binary can still be executed with privilege, while on a filesystem supporting file caps it will run with minimal privilege. This special case currently does NOT happen if there are file capabilities but no effective capabilities. Since capability-aware programs can very well start with empty pE but populated pP and move those caps to pE when needed. In other words, if the file has file capabilities but NOT effective capabilities, then we should do the same thing as if there were file capabilities, and not grant full root privileges. This patchset does that. (Changelog by Serge Hallyn). Signed-off-by: Zhi Li Acked-by: Serge Hallyn Signed-off-by: James Morris --- security/commoncap.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/security/commoncap.c b/security/commoncap.c index a93b3b733079..0f620c564fa8 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -332,7 +332,8 @@ int cap_inode_killpriv(struct dentry *dentry) */ static inline int bprm_caps_from_vfs_caps(struct cpu_vfs_cap_data *caps, struct linux_binprm *bprm, - bool *effective) + bool *effective, + bool *has_cap) { struct cred *new = bprm->cred; unsigned i; @@ -341,6 +342,9 @@ static inline int bprm_caps_from_vfs_caps(struct cpu_vfs_cap_data *caps, if (caps->magic_etc & VFS_CAP_FLAGS_EFFECTIVE) *effective = true; + if (caps->magic_etc & VFS_CAP_REVISION_MASK) + *has_cap = true; + CAP_FOR_EACH_U32(i) { __u32 permitted = caps->permitted.cap[i]; __u32 inheritable = caps->inheritable.cap[i]; @@ -424,7 +428,7 @@ int get_vfs_caps_from_disk(const struct dentry *dentry, struct cpu_vfs_cap_data * its xattrs and, if present, apply them to the proposed credentials being * constructed by execve(). */ -static int get_file_caps(struct linux_binprm *bprm, bool *effective) +static int get_file_caps(struct linux_binprm *bprm, bool *effective, bool *has_cap) { struct dentry *dentry; int rc = 0; @@ -450,7 +454,7 @@ static int get_file_caps(struct linux_binprm *bprm, bool *effective) goto out; } - rc = bprm_caps_from_vfs_caps(&vcaps, bprm, effective); + rc = bprm_caps_from_vfs_caps(&vcaps, bprm, effective, has_cap); if (rc == -EINVAL) printk(KERN_NOTICE "%s: cap_from_disk returned %d for %s\n", __func__, rc, bprm->filename); @@ -475,11 +479,11 @@ int cap_bprm_set_creds(struct linux_binprm *bprm) { const struct cred *old = current_cred(); struct cred *new = bprm->cred; - bool effective; + bool effective, has_cap; int ret; effective = false; - ret = get_file_caps(bprm, &effective); + ret = get_file_caps(bprm, &effective, &has_cap); if (ret < 0) return ret; @@ -489,7 +493,7 @@ int cap_bprm_set_creds(struct linux_binprm *bprm) * for a setuid root binary run by a non-root user. Do set it * for a root user just to cause least surprise to an admin. */ - if (effective && new->uid != 0 && new->euid == 0) { + if (has_cap && new->uid != 0 && new->euid == 0) { warn_setuid_and_fcaps_mixed(bprm->filename); goto skip; } From 7d8db1808a2001077a9f966180c5e4f7cc20d4c7 Mon Sep 17 00:00:00 2001 From: Serge Hallyn Date: Mon, 15 Aug 2011 08:29:50 -0500 Subject: [PATCH 25/95] capabilities: initialize has_cap Initialize has_cap in cap_bprm_set_creds() Reported-by: Andrew G. Morgan Signed-off-by: Serge Hallyn Signed-off-by: James Morris --- security/commoncap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/commoncap.c b/security/commoncap.c index 0f620c564fa8..ee4f8486e5f5 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -479,7 +479,7 @@ int cap_bprm_set_creds(struct linux_binprm *bprm) { const struct cred *old = current_cred(); struct cred *new = bprm->cred; - bool effective, has_cap; + bool effective, has_cap = false; int ret; effective = false; From 1e39f384bb01b0395b69cb70c2cacae65012f203 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Mon, 15 Aug 2011 09:09:16 -0400 Subject: [PATCH 26/95] evm: fix build problems - Make the previously missing security_old_inode_init_security() stub function definition static inline. - The stub security_inode_init_security() function previously returned -EOPNOTSUPP and relied on the callers to change it to 0. The stub security/security_old_inode_init_security() functions now return 0. Reported-by: Stephen Rothwell Signed-off-by: Mimi Zohar Signed-off-by: James Morris --- include/linux/security.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/include/linux/security.h b/include/linux/security.h index f399cf10e2a8..d9f7ec41ba51 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -2045,14 +2045,16 @@ static inline int security_inode_init_security(struct inode *inode, initxattrs initxattrs, void *fs_data) { - return -EOPNOTSUPP; + return 0; } -int security_old_inode_init_security(struct inode *inode, struct inode *dir, - const struct qstr *qstr, char **name, - void **value, size_t *len) +static inline int security_old_inode_init_security(struct inode *inode, + struct inode *dir, + const struct qstr *qstr, + char **name, void **value, + size_t *len) { - return -EOPNOTSUPP; + return 0; } static inline int security_inode_create(struct inode *dir, From 09f464bf0961aba3cd917d4939597bafb269fb95 Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Tue, 16 Aug 2011 20:34:05 +0200 Subject: [PATCH 27/95] tomoyo: remove tomoyo_gc_thread()->daemonize() daemonize() is only needed when a user-space task does kernel_thread(). tomoyo_gc_thread() is kthread_create()'ed and thus it doesn't need the soon-to-be-deprecated daemonize(). Signed-off-by: Oleg Nesterov Acked-by: Tejun Heo Acked-by: Matt Fleming Signed-off-by: James Morris --- security/tomoyo/gc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/tomoyo/gc.c b/security/tomoyo/gc.c index ae135fbbbe95..5295b5caaa21 100644 --- a/security/tomoyo/gc.c +++ b/security/tomoyo/gc.c @@ -660,7 +660,7 @@ static int tomoyo_gc_thread(void *unused) static DEFINE_MUTEX(tomoyo_gc_mutex); if (!mutex_trylock(&tomoyo_gc_mutex)) goto out; - daemonize("GC for TOMOYO"); + do { tomoyo_collect_entry(); if (list_empty(&tomoyo_gc_list)) From dbe5ad17ec62fbd3be7789f9a5ab71d23da8acf0 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Wed, 17 Aug 2011 18:51:36 -0400 Subject: [PATCH 28/95] evm: add Kconfig TCG_TPM dependency Although the EVM encrypted-key should be encrypted/decrypted using a trusted-key, a user-defined key could be used instead. When using a user- defined key, a TCG_TPM dependency should not be required. Unfortunately, the encrypted-key code needs to be refactored a bit in order to remove this dependency. This patch adds the TCG_TPM dependency. Reported-by: Stephen Rothwell , Randy Dunlap Signed-off-by: Mimi Zohar Signed-off-by: James Morris --- security/integrity/evm/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/integrity/evm/Kconfig b/security/integrity/evm/Kconfig index 444877d9239b..884617d4aad0 100644 --- a/security/integrity/evm/Kconfig +++ b/security/integrity/evm/Kconfig @@ -1,6 +1,6 @@ config EVM boolean "EVM support" - depends on SECURITY && KEYS + depends on SECURITY && KEYS && TCG_TPM select CRYPTO_HMAC select CRYPTO_MD5 select CRYPTO_SHA1 From c5532b09bf40c398f2acfdd8f100c796d1d3f881 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Wed, 17 Aug 2011 18:52:24 -0400 Subject: [PATCH 29/95] evm: add MAINTAINERS entry Update the MAINTAINERS file with an entry for EVM. Reported-by: Randy Dunlap Signed-off-by: Mimi Zohar Signed-off-by: James Morris --- MAINTAINERS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 51d42fbc8dc4..02a60b8e503b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2555,6 +2555,11 @@ S: Maintained F: Documentation/filesystems/ext4.txt F: fs/ext4/ +Extended Verification Module (EVM) +M: Mimi Zohar +S: Supported +F: security/integrity/evm/ + F71805F HARDWARE MONITORING DRIVER M: Jean Delvare L: lm-sensors@lm-sensors.org From 995995378f996a8aa1cf4e4ddc0f79fbfd45496f Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 22 Aug 2011 14:08:33 +0100 Subject: [PATCH 30/95] KEYS: If install_session_keyring() is given a keyring, it should install it If install_session_keyring() is given a keyring, it should install it rather than just creating a new one anyway. This was accidentally broken in: commit d84f4f992cbd76e8f39c488cf0c5d123843923b1 Author: David Howells Date: Fri Nov 14 10:39:23 2008 +1100 Subject: CRED: Inaugurate COW credentials The impact of that commit is that pam_keyinit no longer works correctly if 'force' isn't specified against a login process. This is because: keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 0) now always creates a new session keyring and thus the check whether the session keyring and the user-session keyring are the same is always false. This leads pam_keyinit to conclude that a session keyring is installed and it shouldn't be revoked by pam_keyinit here if 'revoke' is specified. Any system that specifies 'force' against pam_keyinit in the PAM configuration files for login methods (login, ssh, su -l, kdm, etc.) is not affected since that bypasses the broken check and forces the creation of a new session keyring anyway (for which the revoke flag is not cleared) - and any subsequent call to pam_keyinit really does have a session keyring already installed, and so the check works correctly there. Reverting to the previous behaviour will cause the kernel to subscribe the process to the user-session keyring as its session keyring if it doesn't have a session keyring of its own. pam_keyinit will detect this and install a new session keyring anyway (and won't clear the revert flag). This can be tested by commenting out pam_keyinit in the /etc/pam.d files and running the following program a couple of times in a row: #include #include #include int main(int argc, char *argv[]) { key_serial_t uk, usk, sk; uk = keyctl_get_keyring_ID(KEY_SPEC_USER_KEYRING, 0); usk = keyctl_get_keyring_ID(KEY_SPEC_USER_SESSION_KEYRING, 0); sk = keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 0); printf("keys: %08x %08x %08x\n", uk, usk, sk); return 0; } Without the patch, I see: keys: 3884e281 24c4dfcf 22825f8e keys: 3884e281 24c4dfcf 068772be With the patch, I see: keys: 26be9c83 0e755ce0 0e755ce0 keys: 26be9c83 0e755ce0 0e755ce0 As can be seen, with the patch, the session keyring is the same as the user-session keyring each time; without the patch a new session keyring is generated each time. Reported-by: Greg Wettstein Signed-off-by: David Howells Tested-by: Greg Wettstein Signed-off-by: James Morris --- security/keys/process_keys.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c index a3063eb3dc23..3bc6071ad633 100644 --- a/security/keys/process_keys.c +++ b/security/keys/process_keys.c @@ -270,7 +270,7 @@ static int install_session_keyring(struct key *keyring) if (!new) return -ENOMEM; - ret = install_session_keyring_to_cred(new, NULL); + ret = install_session_keyring_to_cred(new, keyring); if (ret < 0) { abort_creds(new); return ret; From 3ecf1b4f347210e39b156177e5b8a26ff8d00279 Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 22 Aug 2011 14:08:43 +0100 Subject: [PATCH 31/95] KEYS: keyctl_get_keyring_ID() should create a session keyring if create flag set The keyctl call: keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1) should create a session keyring if the process doesn't have one of its own because the create flag argument is set - rather than subscribing to and returning the user-session keyring as: keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 0) will do. This can be tested by commenting out pam_keyinit in the /etc/pam.d files and running the following program a couple of times in a row: #include #include #include int main(int argc, char *argv[]) { key_serial_t uk, usk, sk, nsk; uk = keyctl_get_keyring_ID(KEY_SPEC_USER_KEYRING, 0); usk = keyctl_get_keyring_ID(KEY_SPEC_USER_SESSION_KEYRING, 0); sk = keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 0); nsk = keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1); printf("keys: %08x %08x %08x %08x\n", uk, usk, sk, nsk); return 0; } Without this patch, I see: keys: 3975ddc7 119c0c66 119c0c66 119c0c66 keys: 3975ddc7 119c0c66 119c0c66 119c0c66 With this patch, I see: keys: 2cb4997b 34112878 34112878 17db2ce3 keys: 2cb4997b 34112878 34112878 39f3c73e As can be seen, the session keyring starts off the same as the user-session keyring each time, but with the patch a new session keyring is created when the create flag is set. Reported-by: Greg Wettstein Signed-off-by: David Howells Tested-by: Greg Wettstein Signed-off-by: James Morris --- security/keys/process_keys.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c index 3bc6071ad633..1068cb1939b3 100644 --- a/security/keys/process_keys.c +++ b/security/keys/process_keys.c @@ -589,12 +589,22 @@ try_again: ret = install_user_keyrings(); if (ret < 0) goto error; - ret = install_session_keyring( - cred->user->session_keyring); + if (lflags & KEY_LOOKUP_CREATE) + ret = join_session_keyring(NULL); + else + ret = install_session_keyring( + cred->user->session_keyring); if (ret < 0) goto error; goto reget_creds; + } else if (cred->tgcred->session_keyring == + cred->user->session_keyring && + lflags & KEY_LOOKUP_CREATE) { + ret = join_session_keyring(NULL); + if (ret < 0) + goto error; + goto reget_creds; } rcu_read_lock(); From 6d528b082294f0ddabd6368297546a2c0b67d4fe Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 22 Aug 2011 14:08:51 +0100 Subject: [PATCH 32/95] KEYS: __key_link() should use the RCU deref wrapper for keyring payloads __key_link() should use the RCU deref wrapper rcu_dereference_locked_keyring() for accessing keyring payloads rather than calling rcu_dereference_protected() directly. Signed-off-by: David Howells Signed-off-by: James Morris --- security/keys/keyring.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/security/keys/keyring.c b/security/keys/keyring.c index 30e242f7bd0e..37a7f3b28852 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -860,8 +860,7 @@ void __key_link(struct key *keyring, struct key *key, kenter("%d,%d,%p", keyring->serial, key->serial, nklist); - klist = rcu_dereference_protected(keyring->payload.subscriptions, - rwsem_is_locked(&keyring->sem)); + klist = rcu_dereference_locked_keyring(keyring); atomic_inc(&key->usage); From 012146d0728f85f7a5c7c36fb84bba33e2760507 Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 22 Aug 2011 14:09:00 +0100 Subject: [PATCH 33/95] CRED: Fix prepare_kernel_cred() to provide a new thread_group_cred struct Fix prepare_kernel_cred() to provide a new, separate thread_group_cred struct otherwise when using request_key() ____call_usermodehelper() calls umh_keys_init() with the new creds pointing to init_tgcred, which umh_keys_init() then blithely alters. The problem can be demonstrated by: # keyctl request2 user a debug:a @s 249681132 # grep req /proc/keys 079906a5 I--Q-- 1 perm 1f3f0000 0 0 keyring _req.249681132: 1/4 38ef1626 IR---- 1 expd 0b010000 0 0 .request_ key:ee1d4ec pid:4371 ci:1 The keyring _req.XXXX should have gone away, but something (init_tgcred) is pinning it. That key actually requested can then be removed and a new one created: # keyctl unlink 249681132 1 links removed [root@andromeda ~]# grep req /proc/keys 116cecac IR---- 1 expd 0b010000 0 0 .request_ key:eeb4911 pid:4379 ci:1 36d1cbf8 I--Q-- 1 perm 1f3f0000 0 0 keyring _req.250300689: 1/4 which causes the old _req keyring to go away and a new one to take its place. This is a consequence of the changes in: commit 879669961b11e7f40b518784863a259f735a72bf Author: David Howells Date: Fri Jun 17 11:25:59 2011 +0100 KEYS/DNS: Fix ____call_usermodehelper() to not lose the session keyring and: commit 17f60a7da150fdd0cfb9756f86a262daa72c835f Author: Eric Paris Date: Fri Apr 1 17:07:50 2011 -0400 capabilites: allow the application of capability limits to usermode helpers After this patch is applied, the _req keyring and the .request_key key are cleaned up. Signed-off-by: David Howells cc: Eric Paris Signed-off-by: James Morris --- kernel/cred.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/kernel/cred.c b/kernel/cred.c index 174fa84eca30..f1e22556bc49 100644 --- a/kernel/cred.c +++ b/kernel/cred.c @@ -646,6 +646,9 @@ void __init cred_init(void) */ struct cred *prepare_kernel_cred(struct task_struct *daemon) { +#ifdef CONFIG_KEYS + struct thread_group_cred *tgcred; +#endif const struct cred *old; struct cred *new; @@ -653,6 +656,12 @@ struct cred *prepare_kernel_cred(struct task_struct *daemon) if (!new) return NULL; + tgcred = kmalloc(sizeof(*tgcred), GFP_KERNEL); + if (!tgcred) { + kmem_cache_free(cred_jar, new); + return NULL; + } + kdebug("prepare_kernel_cred() alloc %p", new); if (daemon) @@ -669,8 +678,11 @@ struct cred *prepare_kernel_cred(struct task_struct *daemon) get_group_info(new->group_info); #ifdef CONFIG_KEYS - atomic_inc(&init_tgcred.usage); - new->tgcred = &init_tgcred; + atomic_set(&tgcred->usage, 1); + spin_lock_init(&tgcred->lock); + tgcred->process_keyring = NULL; + tgcred->session_keyring = NULL; + new->tgcred = tgcred; new->request_key_auth = NULL; new->thread_keyring = NULL; new->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING; From 8bc16deabce7649e480e94b648c88d4e90c34352 Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 22 Aug 2011 14:09:11 +0100 Subject: [PATCH 34/95] KEYS: Move the unreferenced key reaper to the keys garbage collector file Move the unreferenced key reaper function to the keys garbage collector file as that's a more appropriate place with the dead key link reaper. Signed-off-by: David Howells Signed-off-by: James Morris --- security/keys/gc.c | 87 +++++++++++++++++++++++++++++++++++++--- security/keys/internal.h | 2 + security/keys/key.c | 72 +-------------------------------- 3 files changed, 85 insertions(+), 76 deletions(-) diff --git a/security/keys/gc.c b/security/keys/gc.c index 89df6b5f203c..b23db3fbb32d 100644 --- a/security/keys/gc.c +++ b/security/keys/gc.c @@ -10,6 +10,8 @@ */ #include +#include +#include #include #include "internal.h" @@ -19,12 +21,18 @@ unsigned key_gc_delay = 5 * 60; /* - * Reaper + * Reaper for unused keys. + */ +static void key_gc_unused_keys(struct work_struct *work); +DECLARE_WORK(key_gc_unused_work, key_gc_unused_keys); + +/* + * Reaper for links from keyrings to dead keys. */ static void key_gc_timer_func(unsigned long); -static void key_garbage_collector(struct work_struct *); +static void key_gc_dead_links(struct work_struct *); static DEFINE_TIMER(key_gc_timer, key_gc_timer_func, 0, 0); -static DECLARE_WORK(key_gc_work, key_garbage_collector); +static DECLARE_WORK(key_gc_work, key_gc_dead_links); static key_serial_t key_gc_cursor; /* the last key the gc considered */ static bool key_gc_again; static unsigned long key_gc_executing; @@ -108,10 +116,12 @@ do_gc: } /* - * Garbage collector for keys. This involves scanning the keyrings for dead, - * expired and revoked keys that have overstayed their welcome + * Garbage collector for links to dead keys. + * + * This involves scanning the keyrings for dead, expired and revoked keys that + * have overstayed their welcome */ -static void key_garbage_collector(struct work_struct *work) +static void key_gc_dead_links(struct work_struct *work) { struct rb_node *rb; key_serial_t cursor; @@ -220,3 +230,68 @@ reached_the_end: } kleave(" [end]"); } + +/* + * Garbage collector for unused keys. + * + * This is done in process context so that we don't have to disable interrupts + * all over the place. key_put() schedules this rather than trying to do the + * cleanup itself, which means key_put() doesn't have to sleep. + */ +static void key_gc_unused_keys(struct work_struct *work) +{ + struct rb_node *_n; + struct key *key; + +go_again: + /* look for a dead key in the tree */ + spin_lock(&key_serial_lock); + + for (_n = rb_first(&key_serial_tree); _n; _n = rb_next(_n)) { + key = rb_entry(_n, struct key, serial_node); + + if (atomic_read(&key->usage) == 0) + goto found_dead_key; + } + + spin_unlock(&key_serial_lock); + return; + +found_dead_key: + /* we found a dead key - once we've removed it from the tree, we can + * drop the lock */ + rb_erase(&key->serial_node, &key_serial_tree); + spin_unlock(&key_serial_lock); + + key_check(key); + + security_key_free(key); + + /* deal with the user's key tracking and quota */ + if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { + spin_lock(&key->user->lock); + key->user->qnkeys--; + key->user->qnbytes -= key->quotalen; + spin_unlock(&key->user->lock); + } + + atomic_dec(&key->user->nkeys); + if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) + atomic_dec(&key->user->nikeys); + + key_user_put(key->user); + + /* now throw away the key memory */ + if (key->type->destroy) + key->type->destroy(key); + + kfree(key->description); + +#ifdef KEY_DEBUGGING + key->magic = KEY_DEBUG_MAGIC_X; +#endif + kmem_cache_free(key_jar, key); + + /* there may, of course, be more than one key to destroy */ + goto go_again; +} diff --git a/security/keys/internal.h b/security/keys/internal.h index f375152a2500..a7cd1a682321 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -75,6 +75,7 @@ extern unsigned key_quota_maxbytes; #define KEYQUOTA_LINK_BYTES 4 /* a link in a keyring is worth 4 bytes */ +extern struct kmem_cache *key_jar; extern struct rb_root key_serial_tree; extern spinlock_t key_serial_lock; extern struct mutex key_construction_mutex; @@ -146,6 +147,7 @@ extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags, extern long join_session_keyring(const char *name); +extern struct work_struct key_gc_unused_work; extern unsigned key_gc_delay; extern void keyring_gc(struct key *keyring, time_t limit); extern void key_schedule_gc(time_t expiry_at); diff --git a/security/keys/key.c b/security/keys/key.c index f7f9d93f08d9..991a15f1e85f 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -21,7 +21,7 @@ #include #include "internal.h" -static struct kmem_cache *key_jar; +struct kmem_cache *key_jar; struct rb_root key_serial_tree; /* tree of keys indexed by serial */ DEFINE_SPINLOCK(key_serial_lock); @@ -36,9 +36,6 @@ unsigned int key_quota_maxbytes = 20000; /* general key space quota */ static LIST_HEAD(key_types_list); static DECLARE_RWSEM(key_types_sem); -static void key_cleanup(struct work_struct *work); -static DECLARE_WORK(key_cleanup_task, key_cleanup); - /* We serialise key instantiation and link */ DEFINE_MUTEX(key_construction_mutex); @@ -591,71 +588,6 @@ int key_reject_and_link(struct key *key, } EXPORT_SYMBOL(key_reject_and_link); -/* - * Garbage collect keys in process context so that we don't have to disable - * interrupts all over the place. - * - * key_put() schedules this rather than trying to do the cleanup itself, which - * means key_put() doesn't have to sleep. - */ -static void key_cleanup(struct work_struct *work) -{ - struct rb_node *_n; - struct key *key; - -go_again: - /* look for a dead key in the tree */ - spin_lock(&key_serial_lock); - - for (_n = rb_first(&key_serial_tree); _n; _n = rb_next(_n)) { - key = rb_entry(_n, struct key, serial_node); - - if (atomic_read(&key->usage) == 0) - goto found_dead_key; - } - - spin_unlock(&key_serial_lock); - return; - -found_dead_key: - /* we found a dead key - once we've removed it from the tree, we can - * drop the lock */ - rb_erase(&key->serial_node, &key_serial_tree); - spin_unlock(&key_serial_lock); - - key_check(key); - - security_key_free(key); - - /* deal with the user's key tracking and quota */ - if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { - spin_lock(&key->user->lock); - key->user->qnkeys--; - key->user->qnbytes -= key->quotalen; - spin_unlock(&key->user->lock); - } - - atomic_dec(&key->user->nkeys); - if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) - atomic_dec(&key->user->nikeys); - - key_user_put(key->user); - - /* now throw away the key memory */ - if (key->type->destroy) - key->type->destroy(key); - - kfree(key->description); - -#ifdef KEY_DEBUGGING - key->magic = KEY_DEBUG_MAGIC_X; -#endif - kmem_cache_free(key_jar, key); - - /* there may, of course, be more than one key to destroy */ - goto go_again; -} - /** * key_put - Discard a reference to a key. * @key: The key to discard a reference from. @@ -670,7 +602,7 @@ void key_put(struct key *key) key_check(key); if (atomic_dec_and_test(&key->usage)) - schedule_work(&key_cleanup_task); + schedule_work(&key_gc_unused_work); } } EXPORT_SYMBOL(key_put); From b072e9bc2fe9aeff4e104e80e479160349f474a9 Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 22 Aug 2011 14:09:20 +0100 Subject: [PATCH 35/95] KEYS: Make the key reaper non-reentrant Make the key reaper non-reentrant by sticking it on the appropriate system work queue when we queue it. This will allow it to have global state and drop locks. It should probably be non-reentrant already as it may spend a long time holding the key serial spinlock, and so multiple entrants can spend long periods of time just sitting there spinning, waiting to get the lock. Signed-off-by: David Howells Signed-off-by: James Morris --- security/keys/key.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/keys/key.c b/security/keys/key.c index 991a15f1e85f..1f3ed44a83c0 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -602,7 +602,7 @@ void key_put(struct key *key) key_check(key); if (atomic_dec_and_test(&key->usage)) - schedule_work(&key_gc_unused_work); + queue_work(system_nrt_wq, &key_gc_unused_work); } } EXPORT_SYMBOL(key_put); From d199798bdf969873f78d48140600ff0a98a87e69 Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 22 Aug 2011 14:09:28 +0100 Subject: [PATCH 36/95] KEYS: The dead key link reaper should be non-reentrant The dead key link reaper should be non-reentrant as it relies on global state to keep track of where it's got to when it returns to the work queue manager to give it some air. Signed-off-by: David Howells Signed-off-by: James Morris --- security/keys/gc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/security/keys/gc.c b/security/keys/gc.c index b23db3fbb32d..d67e88b791f2 100644 --- a/security/keys/gc.c +++ b/security/keys/gc.c @@ -51,7 +51,7 @@ void key_schedule_gc(time_t gc_at) kenter("%ld", gc_at - now); if (gc_at <= now) { - schedule_work(&key_gc_work); + queue_work(system_nrt_wq, &key_gc_work); } else if (gc_at < key_gc_next_run) { expires = jiffies + (gc_at - now) * HZ; mod_timer(&key_gc_timer, expires); @@ -65,7 +65,7 @@ static void key_gc_timer_func(unsigned long data) { kenter(""); key_gc_next_run = LONG_MAX; - schedule_work(&key_gc_work); + queue_work(system_nrt_wq, &key_gc_work); } /* @@ -206,7 +206,7 @@ gc_released_our_lock: key_gc_new_timer = new_timer; key_gc_again = true; clear_bit(0, &key_gc_executing); - schedule_work(&key_gc_work); + queue_work(system_nrt_wq, &key_gc_work); kleave(" [continue]"); return; From 0c061b5707ab84ebfe8f18f1c9c3110ae5cd6073 Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 22 Aug 2011 14:09:36 +0100 Subject: [PATCH 37/95] KEYS: Correctly destroy key payloads when their keytype is removed unregister_key_type() has code to mark a key as dead and make it unavailable in one loop and then destroy all those unavailable key payloads in the next loop. However, the loop to mark keys dead renders the key undetectable to the second loop by changing the key type pointer also. Fix this by the following means: (1) The key code has two garbage collectors: one deletes unreferenced keys and the other alters keyrings to delete links to old dead, revoked and expired keys. They can end up holding each other up as both want to scan the key serial tree under spinlock. Combine these into a single routine. (2) Move the dead key marking, dead link removal and dead key removal into the garbage collector as a three phase process running over the three cycles of the normal garbage collection procedure. This is tracked by the KEY_GC_REAPING_DEAD_1, _2 and _3 state flags. unregister_key_type() then just unlinks the key type from the list, wakes up the garbage collector and waits for the third phase to complete. (3) Downgrade the key types sem in unregister_key_type() once it has deleted the key type from the list so that it doesn't block the keyctl() syscall. (4) Dead keys that cannot be simply removed in the third phase have their payloads destroyed with the key's semaphore write-locked to prevent interference by the keyctl() syscall. There should be no in-kernel users of dead keys of that type by the point of unregistration, though keyctl() may be holding a reference. (5) Only perform timer recalculation in the GC if the timer actually expired. If it didn't, we'll get another cycle when it goes off - and if the key that actually triggered it has been removed, it's not a problem. (6) Only garbage collect link if the timer expired or if we're doing dead key clean up phase 2. (7) As only key_garbage_collector() is permitted to use rb_erase() on the key serial tree, it doesn't need to revalidate its cursor after dropping the spinlock as the node the cursor points to must still exist in the tree. (8) Drop the spinlock in the GC if there is contention on it or if we need to reschedule. After dealing with that, get the spinlock again and resume scanning. This has been tested in the following ways: (1) Run the keyutils testsuite against it. (2) Using the AF_RXRPC and RxKAD modules to test keytype removal: Load the rxrpc_s key type: # insmod /tmp/af-rxrpc.ko # insmod /tmp/rxkad.ko Create a key (http://people.redhat.com/~dhowells/rxrpc/listen.c): # /tmp/listen & [1] 8173 Find the key: # grep rxrpc_s /proc/keys 091086e1 I--Q-- 1 perm 39390000 0 0 rxrpc_s 52:2 Link it to a session keyring, preferably one with a higher serial number: # keyctl link 0x20e36251 @s Kill the process (the key should remain as it's linked to another place): # fg /tmp/listen ^C Remove the key type: rmmod rxkad rmmod af-rxrpc This can be made a more effective test by altering the following part of the patch: if (unlikely(gc_state & KEY_GC_REAPING_DEAD_2)) { /* Make sure everyone revalidates their keys if we marked a * bunch as being dead and make sure all keyring ex-payloads * are destroyed. */ kdebug("dead sync"); synchronize_rcu(); To call synchronize_rcu() in GC phase 1 instead. That causes that the keyring's old payload content to hang around longer until it's RCU destroyed - which usually happens after GC phase 3 is complete. This allows the destroy_dead_key branch to be tested. Reported-by: Benjamin Coddington Signed-off-by: David Howells Signed-off-by: James Morris --- security/keys/gc.c | 431 ++++++++++++++++++++++++--------------- security/keys/internal.h | 4 +- security/keys/key.c | 51 +---- 3 files changed, 268 insertions(+), 218 deletions(-) diff --git a/security/keys/gc.c b/security/keys/gc.c index d67e88b791f2..bf4d8da5a795 100644 --- a/security/keys/gc.c +++ b/security/keys/gc.c @@ -1,6 +1,6 @@ /* Key garbage collector * - * Copyright (C) 2009 Red Hat, Inc. All Rights Reserved. + * Copyright (C) 2009-2011 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com) * * This program is free software; you can redistribute it and/or @@ -23,21 +23,31 @@ unsigned key_gc_delay = 5 * 60; /* * Reaper for unused keys. */ -static void key_gc_unused_keys(struct work_struct *work); -DECLARE_WORK(key_gc_unused_work, key_gc_unused_keys); +static void key_garbage_collector(struct work_struct *work); +DECLARE_WORK(key_gc_work, key_garbage_collector); /* * Reaper for links from keyrings to dead keys. */ static void key_gc_timer_func(unsigned long); -static void key_gc_dead_links(struct work_struct *); static DEFINE_TIMER(key_gc_timer, key_gc_timer_func, 0, 0); -static DECLARE_WORK(key_gc_work, key_gc_dead_links); -static key_serial_t key_gc_cursor; /* the last key the gc considered */ -static bool key_gc_again; -static unsigned long key_gc_executing; + static time_t key_gc_next_run = LONG_MAX; -static time_t key_gc_new_timer; +static struct key_type *key_gc_dead_keytype; + +static unsigned long key_gc_flags; +#define KEY_GC_KEY_EXPIRED 0 /* A key expired and needs unlinking */ +#define KEY_GC_REAP_KEYTYPE 1 /* A keytype is being unregistered */ +#define KEY_GC_REAPING_KEYTYPE 2 /* Cleared when keytype reaped */ + + +/* + * Any key whose type gets unregistered will be re-typed to this if it can't be + * immediately unlinked. + */ +struct key_type key_type_dead = { + .name = "dead", +}; /* * Schedule a garbage collection run. @@ -50,31 +60,75 @@ void key_schedule_gc(time_t gc_at) kenter("%ld", gc_at - now); - if (gc_at <= now) { + if (gc_at <= now || test_bit(KEY_GC_REAP_KEYTYPE, &key_gc_flags)) { + kdebug("IMMEDIATE"); queue_work(system_nrt_wq, &key_gc_work); } else if (gc_at < key_gc_next_run) { + kdebug("DEFERRED"); + key_gc_next_run = gc_at; expires = jiffies + (gc_at - now) * HZ; mod_timer(&key_gc_timer, expires); } } /* - * The garbage collector timer kicked off + * Some key's cleanup time was met after it expired, so we need to get the + * reaper to go through a cycle finding expired keys. */ static void key_gc_timer_func(unsigned long data) { kenter(""); key_gc_next_run = LONG_MAX; + set_bit(KEY_GC_KEY_EXPIRED, &key_gc_flags); queue_work(system_nrt_wq, &key_gc_work); } +/* + * wait_on_bit() sleep function for uninterruptible waiting + */ +static int key_gc_wait_bit(void *flags) +{ + schedule(); + return 0; +} + +/* + * Reap keys of dead type. + * + * We use three flags to make sure we see three complete cycles of the garbage + * collector: the first to mark keys of that type as being dead, the second to + * collect dead links and the third to clean up the dead keys. We have to be + * careful as there may already be a cycle in progress. + * + * The caller must be holding key_types_sem. + */ +void key_gc_keytype(struct key_type *ktype) +{ + kenter("%s", ktype->name); + + key_gc_dead_keytype = ktype; + set_bit(KEY_GC_REAPING_KEYTYPE, &key_gc_flags); + smp_mb(); + set_bit(KEY_GC_REAP_KEYTYPE, &key_gc_flags); + + kdebug("schedule"); + queue_work(system_nrt_wq, &key_gc_work); + + kdebug("sleep"); + wait_on_bit(&key_gc_flags, KEY_GC_REAPING_KEYTYPE, key_gc_wait_bit, + TASK_UNINTERRUPTIBLE); + + key_gc_dead_keytype = NULL; + kleave(""); +} + /* * Garbage collect pointers from a keyring. * - * Return true if we altered the keyring. + * Not called with any locks held. The keyring's key struct will not be + * deallocated under us as only our caller may deallocate it. */ -static bool key_gc_keyring(struct key *keyring, time_t limit) - __releases(key_serial_lock) +static void key_gc_keyring(struct key *keyring, time_t limit) { struct keyring_list *klist; struct key *key; @@ -101,168 +155,21 @@ static bool key_gc_keyring(struct key *keyring, time_t limit) unlock_dont_gc: rcu_read_unlock(); dont_gc: - kleave(" = false"); - return false; + kleave(" [no gc]"); + return; do_gc: rcu_read_unlock(); - key_gc_cursor = keyring->serial; - key_get(keyring); - spin_unlock(&key_serial_lock); + keyring_gc(keyring, limit); - key_put(keyring); - kleave(" = true"); - return true; + kleave(" [gc]"); } /* - * Garbage collector for links to dead keys. - * - * This involves scanning the keyrings for dead, expired and revoked keys that - * have overstayed their welcome + * Garbage collect an unreferenced, detached key */ -static void key_gc_dead_links(struct work_struct *work) +static noinline void key_gc_unused_key(struct key *key) { - struct rb_node *rb; - key_serial_t cursor; - struct key *key, *xkey; - time_t new_timer = LONG_MAX, limit, now; - - now = current_kernel_time().tv_sec; - kenter("[%x,%ld]", key_gc_cursor, key_gc_new_timer - now); - - if (test_and_set_bit(0, &key_gc_executing)) { - key_schedule_gc(current_kernel_time().tv_sec + 1); - kleave(" [busy; deferring]"); - return; - } - - limit = now; - if (limit > key_gc_delay) - limit -= key_gc_delay; - else - limit = key_gc_delay; - - spin_lock(&key_serial_lock); - - if (unlikely(RB_EMPTY_ROOT(&key_serial_tree))) { - spin_unlock(&key_serial_lock); - clear_bit(0, &key_gc_executing); - return; - } - - cursor = key_gc_cursor; - if (cursor < 0) - cursor = 0; - if (cursor > 0) - new_timer = key_gc_new_timer; - else - key_gc_again = false; - - /* find the first key above the cursor */ - key = NULL; - rb = key_serial_tree.rb_node; - while (rb) { - xkey = rb_entry(rb, struct key, serial_node); - if (cursor < xkey->serial) { - key = xkey; - rb = rb->rb_left; - } else if (cursor > xkey->serial) { - rb = rb->rb_right; - } else { - rb = rb_next(rb); - if (!rb) - goto reached_the_end; - key = rb_entry(rb, struct key, serial_node); - break; - } - } - - if (!key) - goto reached_the_end; - - /* trawl through the keys looking for keyrings */ - for (;;) { - if (key->expiry > limit && key->expiry < new_timer) { - kdebug("will expire %x in %ld", - key_serial(key), key->expiry - limit); - new_timer = key->expiry; - } - - if (key->type == &key_type_keyring && - key_gc_keyring(key, limit)) - /* the gc had to release our lock so that the keyring - * could be modified, so we have to get it again */ - goto gc_released_our_lock; - - rb = rb_next(&key->serial_node); - if (!rb) - goto reached_the_end; - key = rb_entry(rb, struct key, serial_node); - } - -gc_released_our_lock: - kdebug("gc_released_our_lock"); - key_gc_new_timer = new_timer; - key_gc_again = true; - clear_bit(0, &key_gc_executing); - queue_work(system_nrt_wq, &key_gc_work); - kleave(" [continue]"); - return; - - /* when we reach the end of the run, we set the timer for the next one */ -reached_the_end: - kdebug("reached_the_end"); - spin_unlock(&key_serial_lock); - key_gc_new_timer = new_timer; - key_gc_cursor = 0; - clear_bit(0, &key_gc_executing); - - if (key_gc_again) { - /* there may have been a key that expired whilst we were - * scanning, so if we discarded any links we should do another - * scan */ - new_timer = now + 1; - key_schedule_gc(new_timer); - } else if (new_timer < LONG_MAX) { - new_timer += key_gc_delay; - key_schedule_gc(new_timer); - } - kleave(" [end]"); -} - -/* - * Garbage collector for unused keys. - * - * This is done in process context so that we don't have to disable interrupts - * all over the place. key_put() schedules this rather than trying to do the - * cleanup itself, which means key_put() doesn't have to sleep. - */ -static void key_gc_unused_keys(struct work_struct *work) -{ - struct rb_node *_n; - struct key *key; - -go_again: - /* look for a dead key in the tree */ - spin_lock(&key_serial_lock); - - for (_n = rb_first(&key_serial_tree); _n; _n = rb_next(_n)) { - key = rb_entry(_n, struct key, serial_node); - - if (atomic_read(&key->usage) == 0) - goto found_dead_key; - } - - spin_unlock(&key_serial_lock); - return; - -found_dead_key: - /* we found a dead key - once we've removed it from the tree, we can - * drop the lock */ - rb_erase(&key->serial_node, &key_serial_tree); - spin_unlock(&key_serial_lock); - key_check(key); security_key_free(key); @@ -291,7 +198,191 @@ found_dead_key: key->magic = KEY_DEBUG_MAGIC_X; #endif kmem_cache_free(key_jar, key); - - /* there may, of course, be more than one key to destroy */ - goto go_again; +} + +/* + * Garbage collector for unused keys. + * + * This is done in process context so that we don't have to disable interrupts + * all over the place. key_put() schedules this rather than trying to do the + * cleanup itself, which means key_put() doesn't have to sleep. + */ +static void key_garbage_collector(struct work_struct *work) +{ + static u8 gc_state; /* Internal persistent state */ +#define KEY_GC_REAP_AGAIN 0x01 /* - Need another cycle */ +#define KEY_GC_REAPING_LINKS 0x02 /* - We need to reap links */ +#define KEY_GC_SET_TIMER 0x04 /* - We need to restart the timer */ +#define KEY_GC_REAPING_DEAD_1 0x10 /* - We need to mark dead keys */ +#define KEY_GC_REAPING_DEAD_2 0x20 /* - We need to reap dead key links */ +#define KEY_GC_REAPING_DEAD_3 0x40 /* - We need to reap dead keys */ +#define KEY_GC_FOUND_DEAD_KEY 0x80 /* - We found at least one dead key */ + + struct rb_node *cursor; + struct key *key; + time_t new_timer, limit; + + kenter("[%lx,%x]", key_gc_flags, gc_state); + + limit = current_kernel_time().tv_sec; + if (limit > key_gc_delay) + limit -= key_gc_delay; + else + limit = key_gc_delay; + + /* Work out what we're going to be doing in this pass */ + gc_state &= KEY_GC_REAPING_DEAD_1 | KEY_GC_REAPING_DEAD_2; + gc_state <<= 1; + if (test_and_clear_bit(KEY_GC_KEY_EXPIRED, &key_gc_flags)) + gc_state |= KEY_GC_REAPING_LINKS | KEY_GC_SET_TIMER; + + if (test_and_clear_bit(KEY_GC_REAP_KEYTYPE, &key_gc_flags)) + gc_state |= KEY_GC_REAPING_DEAD_1; + kdebug("new pass %x", gc_state); + + new_timer = LONG_MAX; + + /* As only this function is permitted to remove things from the key + * serial tree, if cursor is non-NULL then it will always point to a + * valid node in the tree - even if lock got dropped. + */ + spin_lock(&key_serial_lock); + cursor = rb_first(&key_serial_tree); + +continue_scanning: + while (cursor) { + key = rb_entry(cursor, struct key, serial_node); + cursor = rb_next(cursor); + + if (atomic_read(&key->usage) == 0) + goto found_unreferenced_key; + + if (unlikely(gc_state & KEY_GC_REAPING_DEAD_1)) { + if (key->type == key_gc_dead_keytype) { + gc_state |= KEY_GC_FOUND_DEAD_KEY; + set_bit(KEY_FLAG_DEAD, &key->flags); + key->perm = 0; + goto skip_dead_key; + } + } + + if (gc_state & KEY_GC_SET_TIMER) { + if (key->expiry > limit && key->expiry < new_timer) { + kdebug("will expire %x in %ld", + key_serial(key), key->expiry - limit); + new_timer = key->expiry; + } + } + + if (unlikely(gc_state & KEY_GC_REAPING_DEAD_2)) + if (key->type == key_gc_dead_keytype) + gc_state |= KEY_GC_FOUND_DEAD_KEY; + + if ((gc_state & KEY_GC_REAPING_LINKS) || + unlikely(gc_state & KEY_GC_REAPING_DEAD_2)) { + if (key->type == &key_type_keyring) + goto found_keyring; + } + + if (unlikely(gc_state & KEY_GC_REAPING_DEAD_3)) + if (key->type == key_gc_dead_keytype) + goto destroy_dead_key; + + skip_dead_key: + if (spin_is_contended(&key_serial_lock) || need_resched()) + goto contended; + } + +contended: + spin_unlock(&key_serial_lock); + +maybe_resched: + if (cursor) { + cond_resched(); + spin_lock(&key_serial_lock); + goto continue_scanning; + } + + /* We've completed the pass. Set the timer if we need to and queue a + * new cycle if necessary. We keep executing cycles until we find one + * where we didn't reap any keys. + */ + kdebug("pass complete"); + + if (gc_state & KEY_GC_SET_TIMER && new_timer != (time_t)LONG_MAX) { + new_timer += key_gc_delay; + key_schedule_gc(new_timer); + } + + if (unlikely(gc_state & KEY_GC_REAPING_DEAD_2)) { + /* Make sure everyone revalidates their keys if we marked a + * bunch as being dead and make sure all keyring ex-payloads + * are destroyed. + */ + kdebug("dead sync"); + synchronize_rcu(); + } + + if (unlikely(gc_state & (KEY_GC_REAPING_DEAD_1 | + KEY_GC_REAPING_DEAD_2))) { + if (!(gc_state & KEY_GC_FOUND_DEAD_KEY)) { + /* No remaining dead keys: short circuit the remaining + * keytype reap cycles. + */ + kdebug("dead short"); + gc_state &= ~(KEY_GC_REAPING_DEAD_1 | KEY_GC_REAPING_DEAD_2); + gc_state |= KEY_GC_REAPING_DEAD_3; + } else { + gc_state |= KEY_GC_REAP_AGAIN; + } + } + + if (unlikely(gc_state & KEY_GC_REAPING_DEAD_3)) { + kdebug("dead wake"); + smp_mb(); + clear_bit(KEY_GC_REAPING_KEYTYPE, &key_gc_flags); + wake_up_bit(&key_gc_flags, KEY_GC_REAPING_KEYTYPE); + } + + if (gc_state & KEY_GC_REAP_AGAIN) + queue_work(system_nrt_wq, &key_gc_work); + kleave(" [end %x]", gc_state); + return; + + /* We found an unreferenced key - once we've removed it from the tree, + * we can safely drop the lock. + */ +found_unreferenced_key: + kdebug("unrefd key %d", key->serial); + rb_erase(&key->serial_node, &key_serial_tree); + spin_unlock(&key_serial_lock); + + key_gc_unused_key(key); + gc_state |= KEY_GC_REAP_AGAIN; + goto maybe_resched; + + /* We found a keyring and we need to check the payload for links to + * dead or expired keys. We don't flag another reap immediately as we + * have to wait for the old payload to be destroyed by RCU before we + * can reap the keys to which it refers. + */ +found_keyring: + spin_unlock(&key_serial_lock); + kdebug("scan keyring %d", key->serial); + key_gc_keyring(key, limit); + goto maybe_resched; + + /* We found a dead key that is still referenced. Reset its type and + * destroy its payload with its semaphore held. + */ +destroy_dead_key: + spin_unlock(&key_serial_lock); + kdebug("destroy key %d", key->serial); + down_write(&key->sem); + key->type = &key_type_dead; + if (key_gc_dead_keytype->destroy) + key_gc_dead_keytype->destroy(key); + memset(&key->payload, KEY_DESTROY, sizeof(key->payload)); + up_write(&key->sem); + goto maybe_resched; } diff --git a/security/keys/internal.h b/security/keys/internal.h index a7cd1a682321..c7a7caec4830 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -31,6 +31,7 @@ no_printk(KERN_DEBUG FMT"\n", ##__VA_ARGS__) #endif +extern struct key_type key_type_dead; extern struct key_type key_type_user; /*****************************************************************************/ @@ -147,10 +148,11 @@ extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags, extern long join_session_keyring(const char *name); -extern struct work_struct key_gc_unused_work; +extern struct work_struct key_gc_work; extern unsigned key_gc_delay; extern void keyring_gc(struct key *keyring, time_t limit); extern void key_schedule_gc(time_t expiry_at); +extern void key_gc_keytype(struct key_type *ktype); extern int key_task_permission(const key_ref_t key_ref, const struct cred *cred, diff --git a/security/keys/key.c b/security/keys/key.c index 1f3ed44a83c0..4414abddcb5b 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -39,11 +39,6 @@ static DECLARE_RWSEM(key_types_sem); /* We serialise key instantiation and link */ DEFINE_MUTEX(key_construction_mutex); -/* Any key who's type gets unegistered will be re-typed to this */ -static struct key_type key_type_dead = { - .name = "dead", -}; - #ifdef KEY_DEBUGGING void __key_check(const struct key *key) { @@ -602,7 +597,7 @@ void key_put(struct key *key) key_check(key); if (atomic_dec_and_test(&key->usage)) - queue_work(system_nrt_wq, &key_gc_unused_work); + queue_work(system_nrt_wq, &key_gc_work); } } EXPORT_SYMBOL(key_put); @@ -980,49 +975,11 @@ EXPORT_SYMBOL(register_key_type); */ void unregister_key_type(struct key_type *ktype) { - struct rb_node *_n; - struct key *key; - down_write(&key_types_sem); - - /* withdraw the key type */ list_del_init(&ktype->link); - - /* mark all the keys of this type dead */ - spin_lock(&key_serial_lock); - - for (_n = rb_first(&key_serial_tree); _n; _n = rb_next(_n)) { - key = rb_entry(_n, struct key, serial_node); - - if (key->type == ktype) { - key->type = &key_type_dead; - set_bit(KEY_FLAG_DEAD, &key->flags); - } - } - - spin_unlock(&key_serial_lock); - - /* make sure everyone revalidates their keys */ - synchronize_rcu(); - - /* we should now be able to destroy the payloads of all the keys of - * this type with impunity */ - spin_lock(&key_serial_lock); - - for (_n = rb_first(&key_serial_tree); _n; _n = rb_next(_n)) { - key = rb_entry(_n, struct key, serial_node); - - if (key->type == ktype) { - if (ktype->destroy) - ktype->destroy(key); - memset(&key->payload, KEY_DESTROY, sizeof(key->payload)); - } - } - - spin_unlock(&key_serial_lock); - up_write(&key_types_sem); - - key_schedule_gc(0); + downgrade_write(&key_types_sem); + key_gc_keytype(ktype); + up_read(&key_types_sem); } EXPORT_SYMBOL(unregister_key_type); From 8ad346c62ae91e6376fb9d199ef8557b0c814209 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Tue, 23 Aug 2011 15:23:51 +0800 Subject: [PATCH 38/95] CRED: fix build error due to 'tgcred' undeclared This patch adds CONFIG_KEYS guard for tgcred to fix below build error if CONFIG_KEYS is not configured. CC kernel/cred.o kernel/cred.c: In function 'prepare_kernel_cred': kernel/cred.c:657: error: 'tgcred' undeclared (first use in this function) kernel/cred.c:657: error: (Each undeclared identifier is reported only once kernel/cred.c:657: error: for each function it appears in.) make[1]: *** [kernel/cred.o] Error 1 make: *** [kernel] Error 2 Signed-off-by: Axel Lin Acked-by: David Howells Signed-off-by: James Morris --- kernel/cred.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/cred.c b/kernel/cred.c index f1e22556bc49..d84fc910e2f3 100644 --- a/kernel/cred.c +++ b/kernel/cred.c @@ -656,11 +656,13 @@ struct cred *prepare_kernel_cred(struct task_struct *daemon) if (!new) return NULL; +#ifdef CONFIG_KEYS tgcred = kmalloc(sizeof(*tgcred), GFP_KERNEL); if (!tgcred) { kmem_cache_free(cred_jar, new); return NULL; } +#endif kdebug("prepare_kernel_cred() alloc %p", new); From 403d1d0319ad73b5ccf251745af4c7000331a76b Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Tue, 23 Aug 2011 08:52:10 -0400 Subject: [PATCH 39/95] tpm: suppress durations sysfs output if not read Suppress the output in the 'durations' sysfs entry if they were not read during driver initialization. This is similar to other sysfs entries that return nothing if for some reason sending the commands to the TPM fails. Signed-off-by: Stefan Berger Signed-off-by: James Morris --- drivers/char/tpm/tpm.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/char/tpm/tpm.c b/drivers/char/tpm/tpm.c index caf8012ef47c..e53af7638186 100644 --- a/drivers/char/tpm/tpm.c +++ b/drivers/char/tpm/tpm.c @@ -963,6 +963,9 @@ ssize_t tpm_show_durations(struct device *dev, struct device_attribute *attr, { struct tpm_chip *chip = dev_get_drvdata(dev); + if (chip->vendor.duration[TPM_LONG] == 0) + return 0; + return sprintf(buf, "%d %d %d [%s]\n", jiffies_to_usecs(chip->vendor.duration[TPM_SHORT]), jiffies_to_usecs(chip->vendor.duration[TPM_MEDIUM]), From 852584157c55c1689bcf3809ea44b79870c3e409 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Thu, 25 Aug 2011 21:15:00 +0900 Subject: [PATCH 40/95] TOMOYO: Fix incorrect enforce mode. In tomoyo_get_mode() since 2.6.36, CONFIG::file::execute was by error used in place of CONFIG::file if CONFIG::file::execute was set to other than default. As a result, enforcing mode was not applied in a way documentation says. Signed-off-by: Tetsuo Handa Signed-off-by: James Morris --- security/tomoyo/util.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/security/tomoyo/util.c b/security/tomoyo/util.c index c36bd1107fc8..6a4195a4b93c 100644 --- a/security/tomoyo/util.c +++ b/security/tomoyo/util.c @@ -925,7 +925,8 @@ int tomoyo_get_mode(const struct tomoyo_policy_namespace *ns, const u8 profile, return TOMOYO_CONFIG_DISABLED; mode = tomoyo_profile(ns, profile)->config[index]; if (mode == TOMOYO_CONFIG_USE_DEFAULT) - mode = tomoyo_profile(ns, profile)->config[category]; + mode = tomoyo_profile(ns, profile)->config + [category + TOMOYO_MAX_MAC_INDEX]; if (mode == TOMOYO_CONFIG_USE_DEFAULT) mode = tomoyo_profile(ns, profile)->default_config; return mode & 3; From fc9ff9b7e3eaff3f49bc0fbbddfc1416212e888a Mon Sep 17 00:00:00 2001 From: "rongqing.li@windriver.com" Date: Tue, 6 Sep 2011 11:35:36 +0800 Subject: [PATCH 41/95] security: Fix a typo Fix a typo. Signed-off-by: Roy.Li Signed-off-by: James Morris --- include/linux/security.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/security.h b/include/linux/security.h index d9f7ec41ba51..a14c2d4b22cf 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -1372,7 +1372,7 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts) * @inode_getsecctx: * Returns a string containing all relavent security context information * - * @inode we wish to set the security context of. + * @inode we wish to get the security context of. * @ctx is a pointer in which to place the allocated security context. * @ctxlen points to the place to put the length of @ctx. * This is the main security structure. From 4892722e06694fda1928bac4aa5af5505bd26a4c Mon Sep 17 00:00:00 2001 From: James Morris Date: Wed, 17 Aug 2011 10:34:33 +1000 Subject: [PATCH 42/95] integrity: sparse fix: move iint_initialized to integrity.h Sparse fix: move iint_initialized to integrity.h Signed-off-by: James Morris --- security/integrity/ima/ima.h | 1 - security/integrity/integrity.h | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 29d97af5e9a4..3ccf7acac6df 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -37,7 +37,6 @@ enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8 }; #define IMA_MEASURE_HTABLE_SIZE (1 << IMA_HASH_BITS) /* set during initialization */ -extern int iint_initialized; extern int ima_initialized; extern int ima_used_chip; extern char *ima_hash; diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 880bbee2f534..3143a3c39868 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -45,3 +45,6 @@ struct integrity_iint_cache { */ struct integrity_iint_cache *integrity_iint_insert(struct inode *inode); struct integrity_iint_cache *integrity_iint_find(struct inode *inode); + +/* set during initialization */ +extern int iint_initialized; From 3417d8d5d4d584bd73e2f6265f7a06b51e4a70a1 Mon Sep 17 00:00:00 2001 From: James Morris Date: Wed, 17 Aug 2011 11:05:21 +1000 Subject: [PATCH 43/95] apparmor: sparse fix: make aa_create_aafs static Sparse fix: make aa_create_aafs static. Signed-off-by: James Morris Acked-by: John Johansen --- security/apparmor/apparmorfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 0848292982a2..69ddb47787b2 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -200,7 +200,7 @@ void __init aa_destroy_aafs(void) * * Returns: error on failure */ -int __init aa_create_aafs(void) +static int __init aa_create_aafs(void) { int error; From 56a4ca996181b94b30e6b46509dc28e4ca3cc3f8 Mon Sep 17 00:00:00 2001 From: James Morris Date: Wed, 17 Aug 2011 11:08:43 +1000 Subject: [PATCH 44/95] selinux: sparse fix: make selinux_secmark_refcount static Sparse fix: make selinux_secmark_refcount static. Signed-off-by: James Morris --- security/selinux/hooks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 266a2292451d..e07cf7fcdce2 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -96,7 +96,7 @@ extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm); extern struct security_operations *security_ops; /* SECMARK reference count */ -atomic_t selinux_secmark_refcount = ATOMIC_INIT(0); +static atomic_t selinux_secmark_refcount = ATOMIC_INIT(0); #ifdef CONFIG_SECURITY_SELINUX_DEVELOP int selinux_enforcing; From cc59a582d6081b296e481b8bc9676b5c2faad818 Mon Sep 17 00:00:00 2001 From: James Morris Date: Wed, 17 Aug 2011 11:13:31 +1000 Subject: [PATCH 45/95] selinux: sparse fix: move selinux_complete_init Sparse fix: move selinux_complete_init Signed-off-by: James Morris --- security/selinux/include/security.h | 1 + security/selinux/ss/services.c | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index 3ba4feba048a..8556e1a263c2 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -216,6 +216,7 @@ struct selinux_kernel_status { extern void selinux_status_update_setenforce(int enforcing); extern void selinux_status_update_policyload(int seqno); +extern void selinux_complete_init(void); #endif /* _SELINUX_SECURITY_H_ */ diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index f6917bc0aa05..37c50c602f17 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -1790,7 +1790,6 @@ static void security_load_policycaps(void) POLICYDB_CAPABILITY_OPENPERM); } -extern void selinux_complete_init(void); static int security_preserve_bools(struct policydb *p); /** From 58982b74832917405a483a22beede729e3175376 Mon Sep 17 00:00:00 2001 From: James Morris Date: Wed, 17 Aug 2011 11:17:14 +1000 Subject: [PATCH 46/95] selinux: sparse fix: declare selinux_disable() in security.h Sparse fix: declare selinux_disable() in security.h Signed-off-by: James Morris --- security/selinux/include/security.h | 1 + security/selinux/selinuxfs.c | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index 8556e1a263c2..30002c43436f 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -217,6 +217,7 @@ struct selinux_kernel_status { extern void selinux_status_update_setenforce(int enforcing); extern void selinux_status_update_policyload(int seqno); extern void selinux_complete_init(void); +extern int selinux_disable(void); #endif /* _SELINUX_SECURITY_H_ */ diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 55d92cbb177a..d3677c6c12c6 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -278,7 +278,6 @@ static ssize_t sel_write_disable(struct file *file, const char __user *buf, char *page = NULL; ssize_t length; int new_value; - extern int selinux_disable(void); length = -ENOMEM; if (count >= PAGE_SIZE) From 33f8bf588070e84bb29c3a726758dbb5791fc95e Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 29 Aug 2011 10:40:54 +1000 Subject: [PATCH 47/95] apparmor: sparse fix: include ipc.h Include ipc.h to eliminate sparse warnings. security/apparmor/ipc.c:61:5: warning: symbol 'aa_may_ptrace' was not declared. Should it be static? security/apparmor/ipc.c:83:5: warning: symbol 'aa_ptrace' was not declared. Should it be static Signed-off-by: James Morris Acked-by: John Johansen --- security/apparmor/ipc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c index 649fad88869b..7ee05c6f3c64 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -19,6 +19,7 @@ #include "include/capability.h" #include "include/context.h" #include "include/policy.h" +#include "include/ipc.h" /* call back to audit ptrace fields */ static void audit_cb(struct audit_buffer *ab, void *va) From 32c3df631bc018109136a8f4f941ad591e76a0aa Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 29 Aug 2011 11:15:25 +1000 Subject: [PATCH 48/95] apparmor: sparse fix: add apparmor.h to lib.c Fix the following sparse warnings: security/apparmor/lib.c:37:6: warning: symbol 'aa_split_fqname' was not declared. Should it be static? security/apparmor/lib.c:63:6: warning: symbol 'aa_info_message' was not declared. Should it be static? security/apparmor/lib.c:83:6: warning: symbol 'kvmalloc' was not declared. Should it be static? security/apparmor/lib.c:123:6: warning: symbol 'kvfree' was not declared. Should it be static? Signed-off-by: James Morris --- security/apparmor/lib.c | 1 + 1 file changed, 1 insertion(+) diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index b82e383beb77..9516948041ad 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -18,6 +18,7 @@ #include #include "include/audit.h" +#include "include/apparmor.h" /** From 7ee95850bab6468f8213f36a84e872418d2faa00 Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 29 Aug 2011 11:43:02 +1000 Subject: [PATCH 49/95] apparmor: sparse fix: rename shadowed variables in policy_unpack.c Fix the following warnings: security/apparmor/policy_unpack.c:384:35: warning: symbol 'size' shadows an earlier one security/apparmor/policy_unpack.c:370:24: originally declared here security/apparmor/policy_unpack.c:443:29: warning: symbol 'tmp' shadows an earlier one security/apparmor/policy_unpack.c:434:21: originally declared here Signed-off-by: James Morris Acked-by: John Johansen --- security/apparmor/policy_unpack.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index d6d9a57b5652..741dd13e089b 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -381,11 +381,11 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) profile->file.trans.size = size; for (i = 0; i < size; i++) { char *str; - int c, j, size = unpack_strdup(e, &str, NULL); + int c, j, size2 = unpack_strdup(e, &str, NULL); /* unpack_strdup verifies that the last character is * null termination byte. */ - if (!size) + if (!size2) goto fail; profile->file.trans.table[i] = str; /* verify that name doesn't start with space */ @@ -393,7 +393,7 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) goto fail; /* count internal # of internal \0 */ - for (c = j = 0; j < size - 2; j++) { + for (c = j = 0; j < size2 - 2; j++) { if (!str[j]) c++; } @@ -440,11 +440,11 @@ static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile) if (size > RLIM_NLIMITS) goto fail; for (i = 0; i < size; i++) { - u64 tmp = 0; + u64 tmp2 = 0; int a = aa_map_resource(i); - if (!unpack_u64(e, &tmp, NULL)) + if (!unpack_u64(e, &tmp2, NULL)) goto fail; - profile->rlimits.limits[a].rlim_max = tmp; + profile->rlimits.limits[a].rlim_max = tmp2; } if (!unpack_nameX(e, AA_ARRAYEND, NULL)) goto fail; From cc7db09952faefc86187c67c4adf5cbdb6fe2c1b Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 29 Aug 2011 11:45:44 +1000 Subject: [PATCH 50/95] apparmor: sparse fix: include procattr.h in procattr.c Fix sparse warnings: security/apparmor/procattr.c:35:5: warning: symbol 'aa_getprocattr' was not declared. Should it be static? security/apparmor/procattr.c:113:5: warning: symbol 'aa_setprocattr_changehat' was not declared. Should it be static? security/apparmor/procattr.c:158:5: warning: symbol 'aa_setprocattr_changeprofile' was not declared. Should it be static? security/apparmor/procattr.c:166:5: warning: symbol 'aa_setprocattr_permipc' was not declared. Should it be static? Signed-off-by: James Morris Acked-by: John Johansen --- security/apparmor/procattr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c index 04a2cf8d1b65..1b41c542d376 100644 --- a/security/apparmor/procattr.c +++ b/security/apparmor/procattr.c @@ -16,6 +16,7 @@ #include "include/context.h" #include "include/policy.h" #include "include/domain.h" +#include "include/procattr.h" /** From b97e14520207dccb5cdf93f322e571bf907df104 Mon Sep 17 00:00:00 2001 From: James Morris Date: Tue, 30 Aug 2011 10:18:30 +1000 Subject: [PATCH 51/95] ima: sparse fix: make ima_open_policy static Fixes sparse warning: security/integrity/ima/ima_fs.c:290:5: warning: symbol 'ima_open_policy' was not declared. Should it be static? Signed-off-by: James Morris --- security/integrity/ima/ima_fs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index ef21b96a0b42..e1aa2b482dd2 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -287,7 +287,7 @@ static atomic_t policy_opencount = ATOMIC_INIT(1); /* * ima_open_policy: sequentialize access to the policy file */ -int ima_open_policy(struct inode * inode, struct file * filp) +static int ima_open_policy(struct inode * inode, struct file * filp) { /* No point in being allowed to open it if you aren't going to write */ if (!(filp->f_flags & O_WRONLY)) From d5813a571876c72766f125b1c6e63414f6822c28 Mon Sep 17 00:00:00 2001 From: James Morris Date: Tue, 30 Aug 2011 10:19:50 +1000 Subject: [PATCH 52/95] ima: sparse fix: include linux/ima.h in ima_main.c Fixes sparse warnings: security/integrity/ima/ima_main.c:105:6: warning: symbol 'ima_file_free' was not declared. Should it be static? security/integrity/ima/ima_main.c:167:5: warning: symbol 'ima_file_mmap' was not declared. Should it be static? security/integrity/ima/ima_main.c:192:5: warning: symbol 'ima_bprm_check' was not declared. Should it be static? security/integrity/ima/ima_main.c:211:5: warning: symbol 'ima_file_check' was not declared. Should it be static? Signed-off-by: James Morris --- security/integrity/ima/ima_main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 42dc27007fdd..1eff5cb001e5 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "ima.h" From ad3fa08c4ff84ed87649d72e8497735c85561a3d Mon Sep 17 00:00:00 2001 From: James Morris Date: Tue, 30 Aug 2011 10:50:12 +1000 Subject: [PATCH 53/95] selinux: sparse fix: eliminate warnings for selinuxfs Fixes several sparse warnings for selinuxfs.c Signed-off-by: James Morris --- security/selinux/hooks.c | 5 ----- security/selinux/include/security.h | 3 +++ security/selinux/selinuxfs.c | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index e07cf7fcdce2..4a176b468719 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2097,9 +2097,6 @@ static int selinux_bprm_secureexec(struct linux_binprm *bprm) return (atsecure || cap_bprm_secureexec(bprm)); } -extern struct vfsmount *selinuxfs_mount; -extern struct dentry *selinux_null; - /* Derived from fs/exec.c:flush_old_files. */ static inline void flush_unauthorized_files(const struct cred *cred, struct files_struct *files) @@ -5803,8 +5800,6 @@ static int selinux_disabled; int selinux_disable(void) { - extern void exit_sel_fs(void); - if (ss_initialized) { /* Not permitted after initial policy load. */ return -EINVAL; diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index 30002c43436f..13b626352f0b 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -218,6 +218,9 @@ extern void selinux_status_update_setenforce(int enforcing); extern void selinux_status_update_policyload(int seqno); extern void selinux_complete_init(void); extern int selinux_disable(void); +extern void exit_sel_fs(void); +extern struct dentry *selinux_null; +extern struct vfsmount *selinuxfs_mount; #endif /* _SELINUX_SECURITY_H_ */ diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index d3677c6c12c6..ba2ada5f16a9 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -477,7 +477,7 @@ static struct vm_operations_struct sel_mmap_policy_ops = { .page_mkwrite = sel_mmap_policy_fault, }; -int sel_mmap_policy(struct file *filp, struct vm_area_struct *vma) +static int sel_mmap_policy(struct file *filp, struct vm_area_struct *vma) { if (vma->vm_flags & VM_SHARED) { /* do not allow mprotect to make mapping writable */ From 6a3fbe81179c85eb53054a0f4c8423ffec0276a7 Mon Sep 17 00:00:00 2001 From: James Morris Date: Tue, 30 Aug 2011 12:09:15 +1000 Subject: [PATCH 54/95] selinux: sparse fix: fix warnings in netlink code Fix sparse warnings in SELinux Netlink code. Signed-off-by: James Morris --- security/selinux/hooks.c | 1 - security/selinux/include/security.h | 3 +++ security/selinux/netlink.c | 2 ++ security/selinux/nlmsgtab.c | 1 + security/selinux/selinuxfs.c | 2 -- security/selinux/ss/services.c | 2 -- 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 4a176b468719..1206cee31c79 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -92,7 +92,6 @@ #define NUM_SEL_MNT_OPTS 5 -extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm); extern struct security_operations *security_ops; /* SECMARK reference count */ diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index 13b626352f0b..d871e8ad2103 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -221,6 +221,9 @@ extern int selinux_disable(void); extern void exit_sel_fs(void); extern struct dentry *selinux_null; extern struct vfsmount *selinuxfs_mount; +extern void selnl_notify_setenforce(int val); +extern void selnl_notify_policyload(u32 seqno); +extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm); #endif /* _SELINUX_SECURITY_H_ */ diff --git a/security/selinux/netlink.c b/security/selinux/netlink.c index 36ac257cec9a..ce3f481558d8 100644 --- a/security/selinux/netlink.c +++ b/security/selinux/netlink.c @@ -19,6 +19,8 @@ #include #include +#include "security.h" + static struct sock *selnl; static int selnl_msglen(int msgtype) diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c index 8b02b2137da2..0920ea3bf599 100644 --- a/security/selinux/nlmsgtab.c +++ b/security/selinux/nlmsgtab.c @@ -21,6 +21,7 @@ #include "flask.h" #include "av_permissions.h" +#include "security.h" struct nlmsg_perm { u16 nlmsg_type; diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index ba2ada5f16a9..f46658722c78 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -75,8 +75,6 @@ static char policy_opened; /* global data for policy capabilities */ static struct dentry *policycap_dir; -extern void selnl_notify_setenforce(int val); - /* Check whether a task is allowed to use a security operation. */ static int task_has_security(struct task_struct *tsk, u32 perms) diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 37c50c602f17..185f849a26f6 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -70,8 +70,6 @@ #include "ebitmap.h" #include "audit.h" -extern void selnl_notify_policyload(u32 seqno); - int selinux_policycap_netpeer; int selinux_policycap_openperm; From 0ff53f5ddbaebeac1c2735125901275acc1fecc6 Mon Sep 17 00:00:00 2001 From: James Morris Date: Tue, 30 Aug 2011 12:36:39 +1000 Subject: [PATCH 55/95] selinux: sparse fix: include selinux.h in exports.c Fix warning: security/selinux/exports.c:18:6: warning: symbol 'selinux_is_enabled' was not declared. Should it be static? Signed-off-by: James Morris --- security/selinux/exports.c | 1 + 1 file changed, 1 insertion(+) diff --git a/security/selinux/exports.c b/security/selinux/exports.c index 90664385dead..e75dd94e2d2b 100644 --- a/security/selinux/exports.c +++ b/security/selinux/exports.c @@ -12,6 +12,7 @@ * as published by the Free Software Foundation. */ #include +#include #include "security.h" From 7b98a5857c3fa86cb0a7e5f893643491a8b5b425 Mon Sep 17 00:00:00 2001 From: James Morris Date: Tue, 30 Aug 2011 12:52:32 +1000 Subject: [PATCH 56/95] selinux: sparse fix: fix several warnings in the security server code Fix several sparse warnings in the SELinux security server code. Signed-off-by: James Morris --- security/selinux/hooks.c | 5 +---- security/selinux/include/avc_ss.h | 6 ++++++ security/selinux/ss/conditional.c | 2 +- security/selinux/ss/conditional.h | 1 + security/selinux/ss/policydb.c | 2 -- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 1206cee31c79..e545b9f67072 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -89,6 +89,7 @@ #include "xfrm.h" #include "netlabel.h" #include "audit.h" +#include "avc_ss.h" #define NUM_SEL_MNT_OPTS 5 @@ -278,10 +279,6 @@ static void superblock_free_security(struct super_block *sb) kfree(sbsec); } -/* The security server must be initialized before - any labeling or access decisions can be provided. */ -extern int ss_initialized; - /* The file system's label must be initialized prior to use. */ static const char *labeling_behaviors[6] = { diff --git a/security/selinux/include/avc_ss.h b/security/selinux/include/avc_ss.h index 4677aa519b04..d5c328452df0 100644 --- a/security/selinux/include/avc_ss.h +++ b/security/selinux/include/avc_ss.h @@ -18,5 +18,11 @@ struct security_class_mapping { extern struct security_class_mapping secclass_map[]; +/* + * The security server must be initialized before + * any labeling or access decisions can be provided. + */ +extern int ss_initialized; + #endif /* _SELINUX_AVC_SS_H_ */ diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c index a53373207fb4..2ec904177fe0 100644 --- a/security/selinux/ss/conditional.c +++ b/security/selinux/ss/conditional.c @@ -555,7 +555,7 @@ static int cond_write_av_list(struct policydb *p, return 0; } -int cond_write_node(struct policydb *p, struct cond_node *node, +static int cond_write_node(struct policydb *p, struct cond_node *node, struct policy_file *fp) { struct cond_expr *cur_expr; diff --git a/security/selinux/ss/conditional.h b/security/selinux/ss/conditional.h index 3f209c635295..4d1f87466508 100644 --- a/security/selinux/ss/conditional.h +++ b/security/selinux/ss/conditional.h @@ -13,6 +13,7 @@ #include "avtab.h" #include "symtab.h" #include "policydb.h" +#include "../include/conditional.h" #define COND_EXPR_MAXDEPTH 10 diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index 2381d0ded228..a7f61d52f05c 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -1743,8 +1743,6 @@ static int policydb_bounds_sanity_check(struct policydb *p) return 0; } -extern int ss_initialized; - u16 string_to_security_class(struct policydb *p, const char *name) { struct class_datum *cladatum; From 5dbe3040c74eef18e66951347eda05b153e69328 Mon Sep 17 00:00:00 2001 From: James Morris Date: Tue, 30 Aug 2011 13:48:53 +1000 Subject: [PATCH 57/95] security: sparse fix: Move security_fixup_op to security.h Fix sparse warning by moving declaraion to global header. Signed-off-by: James Morris --- include/linux/security.h | 2 ++ security/security.c | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/include/linux/security.h b/include/linux/security.h index a14c2d4b22cf..19d8e04e1688 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -1660,6 +1660,8 @@ struct security_operations { extern int security_init(void); extern int security_module_enable(struct security_operations *ops); extern int register_security(struct security_operations *ops); +extern void __init security_fixup_ops(struct security_operations *ops); + /* Security operations */ int security_ptrace_access_check(struct task_struct *child, unsigned int mode); diff --git a/security/security.c b/security/security.c index a6328421a055..9ebda054a333 100644 --- a/security/security.c +++ b/security/security.c @@ -26,9 +26,6 @@ static __initdata char chosen_lsm[SECURITY_NAME_MAX + 1] = CONFIG_DEFAULT_SECURITY; -/* things that live in capability.c */ -extern void __init security_fixup_ops(struct security_operations *ops); - static struct security_operations *security_ops; static struct security_operations default_security_ops = { .name = "default", From d58e0da854376841ac99defeb117a83f086715c6 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Sat, 10 Sep 2011 15:22:48 +0900 Subject: [PATCH 58/95] TOMOYO: Add environment variable name restriction support. This patch adds support for checking environment variable's names. Although TOMOYO already provides ability to check argv[]/envp[] passed to execve() requests, file execute /bin/sh exec.envp["LD_LIBRARY_PATH"]="bar" will reject execution of /bin/sh if environment variable LD_LIBRARY_PATH is not defined. To grant execution of /bin/sh if LD_LIBRARY_PATH is not defined, administrators have to specify like file execute /bin/sh exec.envp["LD_LIBRARY_PATH"]="/system/lib" file execute /bin/sh exec.envp["LD_LIBRARY_PATH"]=NULL . Since there are many environment variables whereas conditional checks are applied as "&&", it is difficult to cover all combinations. Therefore, this patch supports conditional checks that are applied as "||", by specifying like file execute /bin/sh misc env LD_LIBRARY_PATH exec.envp["LD_LIBRARY_PATH"]="/system/lib" which means "grant execution of /bin/sh if environment variable is not defined or is defined and its value is /system/lib". Signed-off-by: Tetsuo Handa Signed-off-by: James Morris --- security/tomoyo/Makefile | 2 +- security/tomoyo/common.c | 20 ++++++- security/tomoyo/common.h | 14 +++++ security/tomoyo/domain.c | 95 ++++++++++++++++++++++++++++- security/tomoyo/environ.c | 122 ++++++++++++++++++++++++++++++++++++++ security/tomoyo/gc.c | 9 +++ security/tomoyo/util.c | 14 +++-- 7 files changed, 266 insertions(+), 10 deletions(-) create mode 100644 security/tomoyo/environ.c diff --git a/security/tomoyo/Makefile b/security/tomoyo/Makefile index 95278b71fc21..f7ade960f6cf 100644 --- a/security/tomoyo/Makefile +++ b/security/tomoyo/Makefile @@ -1,4 +1,4 @@ -obj-y = audit.o common.o condition.o domain.o file.o gc.o group.o load_policy.o memory.o mount.o realpath.o securityfs_if.o tomoyo.o util.o +obj-y = audit.o common.o condition.o domain.o environ.o file.o gc.o group.o load_policy.o memory.o mount.o realpath.o securityfs_if.o tomoyo.o util.o $(obj)/policy/profile.conf: @mkdir -p $(obj)/policy/ diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c index c8439cf2a448..d116e1ece3e6 100644 --- a/security/tomoyo/common.c +++ b/security/tomoyo/common.c @@ -20,6 +20,7 @@ const char * const tomoyo_mode[TOMOYO_CONFIG_MAX_MODE] = { /* String table for /sys/kernel/security/tomoyo/profile */ const char * const tomoyo_mac_keywords[TOMOYO_MAX_MAC_INDEX + TOMOYO_MAX_MAC_CATEGORY_INDEX] = { + /* CONFIG::file group */ [TOMOYO_MAC_FILE_EXECUTE] = "execute", [TOMOYO_MAC_FILE_OPEN] = "open", [TOMOYO_MAC_FILE_CREATE] = "create", @@ -43,7 +44,11 @@ const char * const tomoyo_mac_keywords[TOMOYO_MAX_MAC_INDEX [TOMOYO_MAC_FILE_MOUNT] = "mount", [TOMOYO_MAC_FILE_UMOUNT] = "unmount", [TOMOYO_MAC_FILE_PIVOT_ROOT] = "pivot_root", + /* CONFIG::misc group */ + [TOMOYO_MAC_ENVIRON] = "env", + /* CONFIG group */ [TOMOYO_MAX_MAC_INDEX + TOMOYO_MAC_CATEGORY_FILE] = "file", + [TOMOYO_MAX_MAC_INDEX + TOMOYO_MAC_CATEGORY_MISC] = "misc", }; /* String table for conditions. */ @@ -133,7 +138,8 @@ const char * const tomoyo_path_keyword[TOMOYO_MAX_PATH_OPERATION] = { /* String table for categories. */ static const char * const tomoyo_category_keywords [TOMOYO_MAX_MAC_CATEGORY_INDEX] = { - [TOMOYO_MAC_CATEGORY_FILE] = "file", + [TOMOYO_MAC_CATEGORY_FILE] = "file", + [TOMOYO_MAC_CATEGORY_MISC] = "misc", }; /* Permit policy management by non-root user? */ @@ -1036,11 +1042,13 @@ static int tomoyo_write_domain2(struct tomoyo_policy_namespace *ns, static const struct { const char *keyword; int (*write) (struct tomoyo_acl_param *); - } tomoyo_callback[1] = { + } tomoyo_callback[2] = { { "file ", tomoyo_write_file }, + { "misc ", tomoyo_write_misc }, }; u8 i; - for (i = 0; i < 1; i++) { + + for (i = 0; i < ARRAY_SIZE(tomoyo_callback); i++) { if (!tomoyo_str_starts(¶m.data, tomoyo_callback[i].keyword)) continue; @@ -1375,6 +1383,12 @@ static bool tomoyo_print_entry(struct tomoyo_io_buffer *head, tomoyo_print_name_union(head, &ptr->dir_name); tomoyo_print_name_union(head, &ptr->fs_type); tomoyo_print_number_union(head, &ptr->flags); + } else if (acl_type == TOMOYO_TYPE_ENV_ACL) { + struct tomoyo_env_acl *ptr = + container_of(acl, typeof(*ptr), head); + + tomoyo_set_group(head, "misc env "); + tomoyo_set_string(head, ptr->env->name); } if (acl->cond) { head->r.print_cond_part = true; diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h index f7fbaa66e443..63720a328edd 100644 --- a/security/tomoyo/common.h +++ b/security/tomoyo/common.h @@ -196,6 +196,7 @@ enum tomoyo_acl_entry_type_index { TOMOYO_TYPE_PATH_NUMBER_ACL, TOMOYO_TYPE_MKDEV_ACL, TOMOYO_TYPE_MOUNT_ACL, + TOMOYO_TYPE_ENV_ACL, }; /* Index numbers for access controls with one pathname. */ @@ -300,12 +301,14 @@ enum tomoyo_mac_index { TOMOYO_MAC_FILE_MOUNT, TOMOYO_MAC_FILE_UMOUNT, TOMOYO_MAC_FILE_PIVOT_ROOT, + TOMOYO_MAC_ENVIRON, TOMOYO_MAX_MAC_INDEX }; /* Index numbers for category of functionality. */ enum tomoyo_mac_category_index { TOMOYO_MAC_CATEGORY_FILE, + TOMOYO_MAC_CATEGORY_MISC, TOMOYO_MAX_MAC_CATEGORY_INDEX }; @@ -396,6 +399,9 @@ struct tomoyo_request_info { */ u8 operation; } path_number; + struct { + const struct tomoyo_path_info *name; + } environ; struct { const struct tomoyo_path_info *type; const struct tomoyo_path_info *dir; @@ -638,6 +644,12 @@ struct tomoyo_mount_acl { struct tomoyo_number_union flags; }; +/* Structure for "misc env" directive in domain policy. */ +struct tomoyo_env_acl { + struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_ENV_ACL */ + const struct tomoyo_path_info *env; /* environment variable */ +}; + /* Structure for holding a line from /sys/kernel/security/tomoyo/ interface. */ struct tomoyo_acl_param { char *data; @@ -820,6 +832,7 @@ const struct tomoyo_path_info *tomoyo_path_matches_group int tomoyo_check_open_permission(struct tomoyo_domain_info *domain, struct path *path, const int flag); int tomoyo_close_control(struct tomoyo_io_buffer *head); +int tomoyo_env_perm(struct tomoyo_request_info *r, const char *env); int tomoyo_find_next_domain(struct linux_binprm *bprm); int tomoyo_get_mode(const struct tomoyo_policy_namespace *ns, const u8 profile, const u8 index); @@ -860,6 +873,7 @@ int tomoyo_update_policy(struct tomoyo_acl_head *new_entry, const int size, int tomoyo_write_aggregator(struct tomoyo_acl_param *param); int tomoyo_write_file(struct tomoyo_acl_param *param); int tomoyo_write_group(struct tomoyo_acl_param *param, const u8 type); +int tomoyo_write_misc(struct tomoyo_acl_param *param); int tomoyo_write_transition_control(struct tomoyo_acl_param *param, const u8 type); ssize_t tomoyo_read_control(struct tomoyo_io_buffer *head, char __user *buffer, diff --git a/security/tomoyo/domain.c b/security/tomoyo/domain.c index cd0f92d88bb4..5931fb1c04d5 100644 --- a/security/tomoyo/domain.c +++ b/security/tomoyo/domain.c @@ -562,6 +562,92 @@ out: return entry; } +/** + * tomoyo_environ - Check permission for environment variable names. + * + * @ee: Pointer to "struct tomoyo_execve". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_environ(struct tomoyo_execve *ee) +{ + struct tomoyo_request_info *r = &ee->r; + struct linux_binprm *bprm = ee->bprm; + /* env_page.data is allocated by tomoyo_dump_page(). */ + struct tomoyo_page_dump env_page = { }; + char *arg_ptr; /* Size is TOMOYO_EXEC_TMPSIZE bytes */ + int arg_len = 0; + unsigned long pos = bprm->p; + int offset = pos % PAGE_SIZE; + int argv_count = bprm->argc; + int envp_count = bprm->envc; + int error = -ENOMEM; + + ee->r.type = TOMOYO_MAC_ENVIRON; + ee->r.profile = r->domain->profile; + ee->r.mode = tomoyo_get_mode(r->domain->ns, ee->r.profile, + TOMOYO_MAC_ENVIRON); + if (!r->mode || !envp_count) + return 0; + arg_ptr = kzalloc(TOMOYO_EXEC_TMPSIZE, GFP_NOFS); + if (!arg_ptr) + goto out; + while (error == -ENOMEM) { + if (!tomoyo_dump_page(bprm, pos, &env_page)) + goto out; + pos += PAGE_SIZE - offset; + /* Read. */ + while (argv_count && offset < PAGE_SIZE) { + if (!env_page.data[offset++]) + argv_count--; + } + if (argv_count) { + offset = 0; + continue; + } + while (offset < PAGE_SIZE) { + const unsigned char c = env_page.data[offset++]; + + if (c && arg_len < TOMOYO_EXEC_TMPSIZE - 10) { + if (c == '=') { + arg_ptr[arg_len++] = '\0'; + } else if (c == '\\') { + arg_ptr[arg_len++] = '\\'; + arg_ptr[arg_len++] = '\\'; + } else if (c > ' ' && c < 127) { + arg_ptr[arg_len++] = c; + } else { + arg_ptr[arg_len++] = '\\'; + arg_ptr[arg_len++] = (c >> 6) + '0'; + arg_ptr[arg_len++] + = ((c >> 3) & 7) + '0'; + arg_ptr[arg_len++] = (c & 7) + '0'; + } + } else { + arg_ptr[arg_len] = '\0'; + } + if (c) + continue; + if (tomoyo_env_perm(r, arg_ptr)) { + error = -EPERM; + break; + } + if (!--envp_count) { + error = 0; + break; + } + arg_len = 0; + } + offset = 0; + } +out: + if (r->mode != TOMOYO_CONFIG_ENFORCING) + error = 0; + kfree(env_page.data); + kfree(arg_ptr); + return error; +} + /** * tomoyo_find_next_domain - Find a domain. * @@ -581,6 +667,7 @@ int tomoyo_find_next_domain(struct linux_binprm *bprm) bool reject_on_transition_failure = false; struct tomoyo_path_info rn = { }; /* real name */ struct tomoyo_execve *ee = kzalloc(sizeof(*ee), GFP_NOFS); + if (!ee) return -ENOMEM; ee->tmp = kzalloc(TOMOYO_EXEC_TMPSIZE, GFP_NOFS); @@ -713,6 +800,10 @@ int tomoyo_find_next_domain(struct linux_binprm *bprm) bprm->cred->security = domain; if (need_kfree) kfree(rn.name); + if (!retval) { + ee->r.domain = domain; + retval = tomoyo_environ(ee); + } kfree(ee->tmp); kfree(ee->dump.data); kfree(ee); @@ -732,7 +823,8 @@ bool tomoyo_dump_page(struct linux_binprm *bprm, unsigned long pos, struct tomoyo_page_dump *dump) { struct page *page; - /* dump->data is released by tomoyo_finish_execve(). */ + + /* dump->data is released by tomoyo_find_next_domain(). */ if (!dump->data) { dump->data = kzalloc(PAGE_SIZE, GFP_NOFS); if (!dump->data) @@ -753,6 +845,7 @@ bool tomoyo_dump_page(struct linux_binprm *bprm, unsigned long pos, * So do I. */ char *kaddr = kmap_atomic(page, KM_USER0); + dump->page = page; memcpy(dump->data + offset, kaddr + offset, PAGE_SIZE - offset); diff --git a/security/tomoyo/environ.c b/security/tomoyo/environ.c new file mode 100644 index 000000000000..ad4c6e18a437 --- /dev/null +++ b/security/tomoyo/environ.c @@ -0,0 +1,122 @@ +/* + * security/tomoyo/environ.c + * + * Copyright (C) 2005-2011 NTT DATA CORPORATION + */ + +#include "common.h" + +/** + * tomoyo_check_env_acl - Check permission for environment variable's name. + * + * @r: Pointer to "struct tomoyo_request_info". + * @ptr: Pointer to "struct tomoyo_acl_info". + * + * Returns true if granted, false otherwise. + */ +static bool tomoyo_check_env_acl(struct tomoyo_request_info *r, + const struct tomoyo_acl_info *ptr) +{ + const struct tomoyo_env_acl *acl = + container_of(ptr, typeof(*acl), head); + + return tomoyo_path_matches_pattern(r->param.environ.name, acl->env); +} + +/** + * tomoyo_audit_env_log - Audit environment variable name log. + * + * @r: Pointer to "struct tomoyo_request_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_audit_env_log(struct tomoyo_request_info *r) +{ + return tomoyo_supervisor(r, "misc env %s\n", + r->param.environ.name->name); +} + +/** + * tomoyo_env_perm - Check permission for environment variable's name. + * + * @r: Pointer to "struct tomoyo_request_info". + * @env: The name of environment variable. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_env_perm(struct tomoyo_request_info *r, const char *env) +{ + struct tomoyo_path_info environ; + int error; + + if (!env || !*env) + return 0; + environ.name = env; + tomoyo_fill_path_info(&environ); + r->param_type = TOMOYO_TYPE_ENV_ACL; + r->param.environ.name = &environ; + do { + tomoyo_check_acl(r, tomoyo_check_env_acl); + error = tomoyo_audit_env_log(r); + } while (error == TOMOYO_RETRY_REQUEST); + return error; +} + +/** + * tomoyo_same_env_acl - Check for duplicated "struct tomoyo_env_acl" entry. + * + * @a: Pointer to "struct tomoyo_acl_info". + * @b: Pointer to "struct tomoyo_acl_info". + * + * Returns true if @a == @b, false otherwise. + */ +static bool tomoyo_same_env_acl(const struct tomoyo_acl_info *a, + const struct tomoyo_acl_info *b) +{ + const struct tomoyo_env_acl *p1 = container_of(a, typeof(*p1), head); + const struct tomoyo_env_acl *p2 = container_of(b, typeof(*p2), head); + + return p1->env == p2->env; +} + +/** + * tomoyo_write_env - Write "struct tomoyo_env_acl" list. + * + * @param: Pointer to "struct tomoyo_acl_param". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_write_env(struct tomoyo_acl_param *param) +{ + struct tomoyo_env_acl e = { .head.type = TOMOYO_TYPE_ENV_ACL }; + int error = -ENOMEM; + const char *data = tomoyo_read_token(param); + + if (!tomoyo_correct_word(data) || strchr(data, '=')) + return -EINVAL; + e.env = tomoyo_get_name(data); + if (!e.env) + return error; + error = tomoyo_update_domain(&e.head, sizeof(e), param, + tomoyo_same_env_acl, NULL); + tomoyo_put_name(e.env); + return error; +} + +/** + * tomoyo_write_misc - Update environment variable list. + * + * @param: Pointer to "struct tomoyo_acl_param". + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_write_misc(struct tomoyo_acl_param *param) +{ + if (tomoyo_str_starts(¶m->data, "env ")) + return tomoyo_write_env(param); + return -EINVAL; +} diff --git a/security/tomoyo/gc.c b/security/tomoyo/gc.c index 5295b5caaa21..818b07998111 100644 --- a/security/tomoyo/gc.c +++ b/security/tomoyo/gc.c @@ -36,6 +36,7 @@ static const u8 tomoyo_acl_size[] = { [TOMOYO_TYPE_PATH_NUMBER_ACL] = sizeof(struct tomoyo_path_number_acl), [TOMOYO_TYPE_MKDEV_ACL] = sizeof(struct tomoyo_mkdev_acl), [TOMOYO_TYPE_MOUNT_ACL] = sizeof(struct tomoyo_mount_acl), + [TOMOYO_TYPE_ENV_ACL] = sizeof(struct tomoyo_env_acl), }; /** @@ -293,6 +294,14 @@ static void tomoyo_del_acl(struct list_head *element) tomoyo_put_number_union(&entry->flags); } break; + case TOMOYO_TYPE_ENV_ACL: + { + struct tomoyo_env_acl *entry = + container_of(acl, typeof(*entry), head); + + tomoyo_put_name(entry->env); + } + break; } } diff --git a/security/tomoyo/util.c b/security/tomoyo/util.c index 6a4195a4b93c..cb7d507b6312 100644 --- a/security/tomoyo/util.c +++ b/security/tomoyo/util.c @@ -42,6 +42,8 @@ const u8 tomoyo_index2category[TOMOYO_MAX_MAC_INDEX] = { [TOMOYO_MAC_FILE_MOUNT] = TOMOYO_MAC_CATEGORY_FILE, [TOMOYO_MAC_FILE_UMOUNT] = TOMOYO_MAC_CATEGORY_FILE, [TOMOYO_MAC_FILE_PIVOT_ROOT] = TOMOYO_MAC_CATEGORY_FILE, + /* CONFIG::misc group */ + [TOMOYO_MAC_ENVIRON] = TOMOYO_MAC_CATEGORY_MISC, }; /** @@ -920,15 +922,17 @@ int tomoyo_get_mode(const struct tomoyo_policy_namespace *ns, const u8 profile, const u8 index) { u8 mode; - const u8 category = TOMOYO_MAC_CATEGORY_FILE; + struct tomoyo_profile *p; + if (!tomoyo_policy_loaded) return TOMOYO_CONFIG_DISABLED; - mode = tomoyo_profile(ns, profile)->config[index]; + p = tomoyo_profile(ns, profile); + mode = p->config[index]; if (mode == TOMOYO_CONFIG_USE_DEFAULT) - mode = tomoyo_profile(ns, profile)->config - [category + TOMOYO_MAX_MAC_INDEX]; + mode = p->config[tomoyo_index2category[index] + + TOMOYO_MAX_MAC_INDEX]; if (mode == TOMOYO_CONFIG_USE_DEFAULT) - mode = tomoyo_profile(ns, profile)->default_config; + mode = p->default_config; return mode & 3; } From 059d84dbb3897d4ee494a9c842c5dda54316cb47 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Sat, 10 Sep 2011 15:23:54 +0900 Subject: [PATCH 59/95] TOMOYO: Add socket operation restriction support. This patch adds support for permission checks for PF_INET/PF_INET6/PF_UNIX socket's bind()/listen()/connect()/send() operations. Signed-off-by: Tetsuo Handa Signed-off-by: James Morris --- security/tomoyo/Kconfig | 2 + security/tomoyo/Makefile | 2 +- security/tomoyo/common.c | 104 ++++- security/tomoyo/common.h | 127 +++++- security/tomoyo/gc.c | 40 +- security/tomoyo/group.c | 61 ++- security/tomoyo/network.c | 771 +++++++++++++++++++++++++++++++++++++ security/tomoyo/realpath.c | 32 +- security/tomoyo/tomoyo.c | 62 +++ security/tomoyo/util.c | 31 ++ 10 files changed, 1215 insertions(+), 17 deletions(-) create mode 100644 security/tomoyo/network.c diff --git a/security/tomoyo/Kconfig b/security/tomoyo/Kconfig index 7c7f8c16c10f..8eb779b9d77f 100644 --- a/security/tomoyo/Kconfig +++ b/security/tomoyo/Kconfig @@ -1,8 +1,10 @@ config SECURITY_TOMOYO bool "TOMOYO Linux Support" depends on SECURITY + depends on NET select SECURITYFS select SECURITY_PATH + select SECURITY_NETWORK default n help This selects TOMOYO Linux, pathname-based access control. diff --git a/security/tomoyo/Makefile b/security/tomoyo/Makefile index f7ade960f6cf..fc2a8ce40301 100644 --- a/security/tomoyo/Makefile +++ b/security/tomoyo/Makefile @@ -1,4 +1,4 @@ -obj-y = audit.o common.o condition.o domain.o environ.o file.o gc.o group.o load_policy.o memory.o mount.o realpath.o securityfs_if.o tomoyo.o util.o +obj-y = audit.o common.o condition.o domain.o environ.o file.o gc.o group.o load_policy.o memory.o mount.o network.o realpath.o securityfs_if.o tomoyo.o util.o $(obj)/policy/profile.conf: @mkdir -p $(obj)/policy/ diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c index d116e1ece3e6..85d915587a71 100644 --- a/security/tomoyo/common.c +++ b/security/tomoyo/common.c @@ -44,10 +44,27 @@ const char * const tomoyo_mac_keywords[TOMOYO_MAX_MAC_INDEX [TOMOYO_MAC_FILE_MOUNT] = "mount", [TOMOYO_MAC_FILE_UMOUNT] = "unmount", [TOMOYO_MAC_FILE_PIVOT_ROOT] = "pivot_root", + /* CONFIG::network group */ + [TOMOYO_MAC_NETWORK_INET_STREAM_BIND] = "inet_stream_bind", + [TOMOYO_MAC_NETWORK_INET_STREAM_LISTEN] = "inet_stream_listen", + [TOMOYO_MAC_NETWORK_INET_STREAM_CONNECT] = "inet_stream_connect", + [TOMOYO_MAC_NETWORK_INET_DGRAM_BIND] = "inet_dgram_bind", + [TOMOYO_MAC_NETWORK_INET_DGRAM_SEND] = "inet_dgram_send", + [TOMOYO_MAC_NETWORK_INET_RAW_BIND] = "inet_raw_bind", + [TOMOYO_MAC_NETWORK_INET_RAW_SEND] = "inet_raw_send", + [TOMOYO_MAC_NETWORK_UNIX_STREAM_BIND] = "unix_stream_bind", + [TOMOYO_MAC_NETWORK_UNIX_STREAM_LISTEN] = "unix_stream_listen", + [TOMOYO_MAC_NETWORK_UNIX_STREAM_CONNECT] = "unix_stream_connect", + [TOMOYO_MAC_NETWORK_UNIX_DGRAM_BIND] = "unix_dgram_bind", + [TOMOYO_MAC_NETWORK_UNIX_DGRAM_SEND] = "unix_dgram_send", + [TOMOYO_MAC_NETWORK_UNIX_SEQPACKET_BIND] = "unix_seqpacket_bind", + [TOMOYO_MAC_NETWORK_UNIX_SEQPACKET_LISTEN] = "unix_seqpacket_listen", + [TOMOYO_MAC_NETWORK_UNIX_SEQPACKET_CONNECT] = "unix_seqpacket_connect", /* CONFIG::misc group */ [TOMOYO_MAC_ENVIRON] = "env", /* CONFIG group */ [TOMOYO_MAX_MAC_INDEX + TOMOYO_MAC_CATEGORY_FILE] = "file", + [TOMOYO_MAX_MAC_INDEX + TOMOYO_MAC_CATEGORY_NETWORK] = "network", [TOMOYO_MAX_MAC_INDEX + TOMOYO_MAC_CATEGORY_MISC] = "misc", }; @@ -135,11 +152,20 @@ const char * const tomoyo_path_keyword[TOMOYO_MAX_PATH_OPERATION] = { [TOMOYO_TYPE_UMOUNT] = "unmount", }; +/* String table for socket's operation. */ +const char * const tomoyo_socket_keyword[TOMOYO_MAX_NETWORK_OPERATION] = { + [TOMOYO_NETWORK_BIND] = "bind", + [TOMOYO_NETWORK_LISTEN] = "listen", + [TOMOYO_NETWORK_CONNECT] = "connect", + [TOMOYO_NETWORK_SEND] = "send", +}; + /* String table for categories. */ static const char * const tomoyo_category_keywords [TOMOYO_MAX_MAC_CATEGORY_INDEX] = { - [TOMOYO_MAC_CATEGORY_FILE] = "file", - [TOMOYO_MAC_CATEGORY_MISC] = "misc", + [TOMOYO_MAC_CATEGORY_FILE] = "file", + [TOMOYO_MAC_CATEGORY_NETWORK] = "network", + [TOMOYO_MAC_CATEGORY_MISC] = "misc", }; /* Permit policy management by non-root user? */ @@ -1042,8 +1068,10 @@ static int tomoyo_write_domain2(struct tomoyo_policy_namespace *ns, static const struct { const char *keyword; int (*write) (struct tomoyo_acl_param *); - } tomoyo_callback[2] = { + } tomoyo_callback[4] = { { "file ", tomoyo_write_file }, + { "network inet ", tomoyo_write_inet_network }, + { "network unix ", tomoyo_write_unix_network }, { "misc ", tomoyo_write_misc }, }; u8 i; @@ -1375,6 +1403,60 @@ static bool tomoyo_print_entry(struct tomoyo_io_buffer *head, tomoyo_print_number_union(head, &ptr->mode); tomoyo_print_number_union(head, &ptr->major); tomoyo_print_number_union(head, &ptr->minor); + } else if (acl_type == TOMOYO_TYPE_INET_ACL) { + struct tomoyo_inet_acl *ptr = + container_of(acl, typeof(*ptr), head); + const u8 perm = ptr->perm; + + for (bit = 0; bit < TOMOYO_MAX_NETWORK_OPERATION; bit++) { + if (!(perm & (1 << bit))) + continue; + if (first) { + tomoyo_set_group(head, "network inet "); + tomoyo_set_string(head, tomoyo_proto_keyword + [ptr->protocol]); + tomoyo_set_space(head); + first = false; + } else { + tomoyo_set_slash(head); + } + tomoyo_set_string(head, tomoyo_socket_keyword[bit]); + } + if (first) + return true; + tomoyo_set_space(head); + if (ptr->address.group) { + tomoyo_set_string(head, "@"); + tomoyo_set_string(head, ptr->address.group->group_name + ->name); + } else { + char buf[128]; + tomoyo_print_ip(buf, sizeof(buf), &ptr->address); + tomoyo_io_printf(head, "%s", buf); + } + tomoyo_print_number_union(head, &ptr->port); + } else if (acl_type == TOMOYO_TYPE_UNIX_ACL) { + struct tomoyo_unix_acl *ptr = + container_of(acl, typeof(*ptr), head); + const u8 perm = ptr->perm; + + for (bit = 0; bit < TOMOYO_MAX_NETWORK_OPERATION; bit++) { + if (!(perm & (1 << bit))) + continue; + if (first) { + tomoyo_set_group(head, "network unix "); + tomoyo_set_string(head, tomoyo_proto_keyword + [ptr->protocol]); + tomoyo_set_space(head); + first = false; + } else { + tomoyo_set_slash(head); + } + tomoyo_set_string(head, tomoyo_socket_keyword[bit]); + } + if (first) + return true; + tomoyo_print_name_union(head, &ptr->name); } else if (acl_type == TOMOYO_TYPE_MOUNT_ACL) { struct tomoyo_mount_acl *ptr = container_of(acl, typeof(*ptr), head); @@ -1548,8 +1630,9 @@ static const char *tomoyo_transition_type[TOMOYO_MAX_TRANSITION_TYPE] = { /* String table for grouping keywords. */ static const char *tomoyo_group_name[TOMOYO_MAX_GROUP] = { - [TOMOYO_PATH_GROUP] = "path_group ", - [TOMOYO_NUMBER_GROUP] = "number_group ", + [TOMOYO_PATH_GROUP] = "path_group ", + [TOMOYO_NUMBER_GROUP] = "number_group ", + [TOMOYO_ADDRESS_GROUP] = "address_group ", }; /** @@ -1591,7 +1674,7 @@ static int tomoyo_write_exception(struct tomoyo_io_buffer *head) } /** - * tomoyo_read_group - Read "struct tomoyo_path_group"/"struct tomoyo_number_group" list. + * tomoyo_read_group - Read "struct tomoyo_path_group"/"struct tomoyo_number_group"/"struct tomoyo_address_group" list. * * @head: Pointer to "struct tomoyo_io_buffer". * @idx: Index number. @@ -1628,6 +1711,15 @@ static bool tomoyo_read_group(struct tomoyo_io_buffer *head, const int idx) (ptr, struct tomoyo_number_group, head)->number); + } else if (idx == TOMOYO_ADDRESS_GROUP) { + char buffer[128]; + + struct tomoyo_address_group *member = + container_of(ptr, typeof(*member), + head); + tomoyo_print_ip(buffer, sizeof(buffer), + &member->address); + tomoyo_io_printf(head, " %s", buffer); } tomoyo_set_lf(head); } diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h index 63720a328edd..d1c758e7f92b 100644 --- a/security/tomoyo/common.h +++ b/security/tomoyo/common.h @@ -23,6 +23,16 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /********** Constants definitions. **********/ @@ -34,6 +44,12 @@ #define TOMOYO_HASH_BITS 8 #define TOMOYO_MAX_HASH (1u<value_type[1] == b->value_type[1]; } +/** + * tomoyo_same_ipaddr_union - Check for duplicated "struct tomoyo_ipaddr_union" entry. + * + * @a: Pointer to "struct tomoyo_ipaddr_union". + * @b: Pointer to "struct tomoyo_ipaddr_union". + * + * Returns true if @a == @b, false otherwise. + */ +static inline bool tomoyo_same_ipaddr_union +(const struct tomoyo_ipaddr_union *a, const struct tomoyo_ipaddr_union *b) +{ + return !memcmp(a->ip, b->ip, sizeof(a->ip)) && a->group == b->group && + a->is_ipv6 == b->is_ipv6; +} + /** * tomoyo_current_namespace - Get "struct tomoyo_policy_namespace" for current thread. * diff --git a/security/tomoyo/gc.c b/security/tomoyo/gc.c index 818b07998111..7747ceb9a221 100644 --- a/security/tomoyo/gc.c +++ b/security/tomoyo/gc.c @@ -16,6 +16,7 @@ static DEFINE_SPINLOCK(tomoyo_io_buffer_list_lock); /* Size of an element. */ static const u8 tomoyo_element_size[TOMOYO_MAX_POLICY] = { [TOMOYO_ID_GROUP] = sizeof(struct tomoyo_group), + [TOMOYO_ID_ADDRESS_GROUP] = sizeof(struct tomoyo_address_group), [TOMOYO_ID_PATH_GROUP] = sizeof(struct tomoyo_path_group), [TOMOYO_ID_NUMBER_GROUP] = sizeof(struct tomoyo_number_group), [TOMOYO_ID_AGGREGATOR] = sizeof(struct tomoyo_aggregator), @@ -36,6 +37,8 @@ static const u8 tomoyo_acl_size[] = { [TOMOYO_TYPE_PATH_NUMBER_ACL] = sizeof(struct tomoyo_path_number_acl), [TOMOYO_TYPE_MKDEV_ACL] = sizeof(struct tomoyo_mkdev_acl), [TOMOYO_TYPE_MOUNT_ACL] = sizeof(struct tomoyo_mount_acl), + [TOMOYO_TYPE_INET_ACL] = sizeof(struct tomoyo_inet_acl), + [TOMOYO_TYPE_UNIX_ACL] = sizeof(struct tomoyo_unix_acl), [TOMOYO_TYPE_ENV_ACL] = sizeof(struct tomoyo_env_acl), }; @@ -302,6 +305,23 @@ static void tomoyo_del_acl(struct list_head *element) tomoyo_put_name(entry->env); } break; + case TOMOYO_TYPE_INET_ACL: + { + struct tomoyo_inet_acl *entry = + container_of(acl, typeof(*entry), head); + + tomoyo_put_group(entry->address.group); + tomoyo_put_number_union(&entry->port); + } + break; + case TOMOYO_TYPE_UNIX_ACL: + { + struct tomoyo_unix_acl *entry = + container_of(acl, typeof(*entry), head); + + tomoyo_put_name_union(&entry->name); + } + break; } } @@ -430,6 +450,18 @@ static void tomoyo_del_group(struct list_head *element) tomoyo_put_name(group->group_name); } +/** + * tomoyo_del_address_group - Delete members in "struct tomoyo_address_group". + * + * @element: Pointer to "struct list_head". + * + * Returns nothing. + */ +static inline void tomoyo_del_address_group(struct list_head *element) +{ + /* Nothing to do. */ +} + /** * tomoyo_del_number_group - Delete members in "struct tomoyo_number_group". * @@ -527,9 +559,12 @@ static void tomoyo_collect_entry(void) case 0: id = TOMOYO_ID_PATH_GROUP; break; - default: + case 1: id = TOMOYO_ID_NUMBER_GROUP; break; + default: + id = TOMOYO_ID_ADDRESS_GROUP; + break; } list_for_each_entry(group, list, head.list) { if (!tomoyo_collect_member @@ -634,6 +669,9 @@ static bool tomoyo_kfree_entry(void) case TOMOYO_ID_PATH_GROUP: tomoyo_del_path_group(element); break; + case TOMOYO_ID_ADDRESS_GROUP: + tomoyo_del_address_group(element); + break; case TOMOYO_ID_GROUP: tomoyo_del_group(element); break; diff --git a/security/tomoyo/group.c b/security/tomoyo/group.c index 5fb0e1298400..50092534ec54 100644 --- a/security/tomoyo/group.c +++ b/security/tomoyo/group.c @@ -42,7 +42,26 @@ static bool tomoyo_same_number_group(const struct tomoyo_acl_head *a, } /** - * tomoyo_write_group - Write "struct tomoyo_path_group"/"struct tomoyo_number_group" list. + * tomoyo_same_address_group - Check for duplicated "struct tomoyo_address_group" entry. + * + * @a: Pointer to "struct tomoyo_acl_head". + * @b: Pointer to "struct tomoyo_acl_head". + * + * Returns true if @a == @b, false otherwise. + */ +static bool tomoyo_same_address_group(const struct tomoyo_acl_head *a, + const struct tomoyo_acl_head *b) +{ + const struct tomoyo_address_group *p1 = container_of(a, typeof(*p1), + head); + const struct tomoyo_address_group *p2 = container_of(b, typeof(*p2), + head); + + return tomoyo_same_ipaddr_union(&p1->address, &p2->address); +} + +/** + * tomoyo_write_group - Write "struct tomoyo_path_group"/"struct tomoyo_number_group"/"struct tomoyo_address_group" list. * * @param: Pointer to "struct tomoyo_acl_param". * @type: Type of this group. @@ -77,6 +96,14 @@ int tomoyo_write_group(struct tomoyo_acl_param *param, const u8 type) * tomoyo_put_number_union() is not needed because * param->data[0] != '@'. */ + } else { + struct tomoyo_address_group e = { }; + + if (param->data[0] == '@' || + !tomoyo_parse_ipaddr_union(param, &e.address)) + goto out; + error = tomoyo_update_policy(&e.head, sizeof(e), param, + tomoyo_same_address_group); } out: tomoyo_put_group(group); @@ -137,3 +164,35 @@ bool tomoyo_number_matches_group(const unsigned long min, } return matched; } + +/** + * tomoyo_address_matches_group - Check whether the given address matches members of the given address group. + * + * @is_ipv6: True if @address is an IPv6 address. + * @address: An IPv4 or IPv6 address. + * @group: Pointer to "struct tomoyo_address_group". + * + * Returns true if @address matches addresses in @group group, false otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +bool tomoyo_address_matches_group(const bool is_ipv6, const __be32 *address, + const struct tomoyo_group *group) +{ + struct tomoyo_address_group *member; + bool matched = false; + const u8 size = is_ipv6 ? 16 : 4; + + list_for_each_entry_rcu(member, &group->member_list, head.list) { + if (member->head.is_deleted) + continue; + if (member->address.is_ipv6 != is_ipv6) + continue; + if (memcmp(&member->address.ip[0], address, size) > 0 || + memcmp(address, &member->address.ip[1], size) > 0) + continue; + matched = true; + break; + } + return matched; +} diff --git a/security/tomoyo/network.c b/security/tomoyo/network.c new file mode 100644 index 000000000000..97527710a72a --- /dev/null +++ b/security/tomoyo/network.c @@ -0,0 +1,771 @@ +/* + * security/tomoyo/network.c + * + * Copyright (C) 2005-2011 NTT DATA CORPORATION + */ + +#include "common.h" +#include + +/* Structure for holding inet domain socket's address. */ +struct tomoyo_inet_addr_info { + __be16 port; /* In network byte order. */ + const __be32 *address; /* In network byte order. */ + bool is_ipv6; +}; + +/* Structure for holding unix domain socket's address. */ +struct tomoyo_unix_addr_info { + u8 *addr; /* This may not be '\0' terminated string. */ + unsigned int addr_len; +}; + +/* Structure for holding socket address. */ +struct tomoyo_addr_info { + u8 protocol; + u8 operation; + struct tomoyo_inet_addr_info inet; + struct tomoyo_unix_addr_info unix0; +}; + +/* String table for socket's protocols. */ +const char * const tomoyo_proto_keyword[TOMOYO_SOCK_MAX] = { + [SOCK_STREAM] = "stream", + [SOCK_DGRAM] = "dgram", + [SOCK_RAW] = "raw", + [SOCK_SEQPACKET] = "seqpacket", + [0] = " ", /* Dummy for avoiding NULL pointer dereference. */ + [4] = " ", /* Dummy for avoiding NULL pointer dereference. */ +}; + +/** + * tomoyo_parse_ipaddr_union - Parse an IP address. + * + * @param: Pointer to "struct tomoyo_acl_param". + * @ptr: Pointer to "struct tomoyo_ipaddr_union". + * + * Returns true on success, false otherwise. + */ +bool tomoyo_parse_ipaddr_union(struct tomoyo_acl_param *param, + struct tomoyo_ipaddr_union *ptr) +{ + u8 * const min = ptr->ip[0].in6_u.u6_addr8; + u8 * const max = ptr->ip[1].in6_u.u6_addr8; + char *address = tomoyo_read_token(param); + const char *end; + + if (!strchr(address, ':') && + in4_pton(address, -1, min, '-', &end) > 0) { + ptr->is_ipv6 = false; + if (!*end) + ptr->ip[1].s6_addr32[0] = ptr->ip[0].s6_addr32[0]; + else if (*end++ != '-' || + in4_pton(end, -1, max, '\0', &end) <= 0 || *end) + return false; + return true; + } + if (in6_pton(address, -1, min, '-', &end) > 0) { + ptr->is_ipv6 = true; + if (!*end) + memmove(max, min, sizeof(u16) * 8); + else if (*end++ != '-' || + in6_pton(end, -1, max, '\0', &end) <= 0 || *end) + return false; + return true; + } + return false; +} + +/** + * tomoyo_print_ipv4 - Print an IPv4 address. + * + * @buffer: Buffer to write to. + * @buffer_len: Size of @buffer. + * @min_ip: Pointer to __be32. + * @max_ip: Pointer to __be32. + * + * Returns nothing. + */ +static void tomoyo_print_ipv4(char *buffer, const unsigned int buffer_len, + const __be32 *min_ip, const __be32 *max_ip) +{ + snprintf(buffer, buffer_len, "%pI4%c%pI4", min_ip, + *min_ip == *max_ip ? '\0' : '-', max_ip); +} + +/** + * tomoyo_print_ipv6 - Print an IPv6 address. + * + * @buffer: Buffer to write to. + * @buffer_len: Size of @buffer. + * @min_ip: Pointer to "struct in6_addr". + * @max_ip: Pointer to "struct in6_addr". + * + * Returns nothing. + */ +static void tomoyo_print_ipv6(char *buffer, const unsigned int buffer_len, + const struct in6_addr *min_ip, + const struct in6_addr *max_ip) +{ + snprintf(buffer, buffer_len, "%pI6c%c%pI6c", min_ip, + !memcmp(min_ip, max_ip, 16) ? '\0' : '-', max_ip); +} + +/** + * tomoyo_print_ip - Print an IP address. + * + * @buf: Buffer to write to. + * @size: Size of @buf. + * @ptr: Pointer to "struct ipaddr_union". + * + * Returns nothing. + */ +void tomoyo_print_ip(char *buf, const unsigned int size, + const struct tomoyo_ipaddr_union *ptr) +{ + if (ptr->is_ipv6) + tomoyo_print_ipv6(buf, size, &ptr->ip[0], &ptr->ip[1]); + else + tomoyo_print_ipv4(buf, size, &ptr->ip[0].s6_addr32[0], + &ptr->ip[1].s6_addr32[0]); +} + +/* + * Mapping table from "enum tomoyo_network_acl_index" to + * "enum tomoyo_mac_index" for inet domain socket. + */ +static const u8 tomoyo_inet2mac +[TOMOYO_SOCK_MAX][TOMOYO_MAX_NETWORK_OPERATION] = { + [SOCK_STREAM] = { + [TOMOYO_NETWORK_BIND] = TOMOYO_MAC_NETWORK_INET_STREAM_BIND, + [TOMOYO_NETWORK_LISTEN] = + TOMOYO_MAC_NETWORK_INET_STREAM_LISTEN, + [TOMOYO_NETWORK_CONNECT] = + TOMOYO_MAC_NETWORK_INET_STREAM_CONNECT, + }, + [SOCK_DGRAM] = { + [TOMOYO_NETWORK_BIND] = TOMOYO_MAC_NETWORK_INET_DGRAM_BIND, + [TOMOYO_NETWORK_SEND] = TOMOYO_MAC_NETWORK_INET_DGRAM_SEND, + }, + [SOCK_RAW] = { + [TOMOYO_NETWORK_BIND] = TOMOYO_MAC_NETWORK_INET_RAW_BIND, + [TOMOYO_NETWORK_SEND] = TOMOYO_MAC_NETWORK_INET_RAW_SEND, + }, +}; + +/* + * Mapping table from "enum tomoyo_network_acl_index" to + * "enum tomoyo_mac_index" for unix domain socket. + */ +static const u8 tomoyo_unix2mac +[TOMOYO_SOCK_MAX][TOMOYO_MAX_NETWORK_OPERATION] = { + [SOCK_STREAM] = { + [TOMOYO_NETWORK_BIND] = TOMOYO_MAC_NETWORK_UNIX_STREAM_BIND, + [TOMOYO_NETWORK_LISTEN] = + TOMOYO_MAC_NETWORK_UNIX_STREAM_LISTEN, + [TOMOYO_NETWORK_CONNECT] = + TOMOYO_MAC_NETWORK_UNIX_STREAM_CONNECT, + }, + [SOCK_DGRAM] = { + [TOMOYO_NETWORK_BIND] = TOMOYO_MAC_NETWORK_UNIX_DGRAM_BIND, + [TOMOYO_NETWORK_SEND] = TOMOYO_MAC_NETWORK_UNIX_DGRAM_SEND, + }, + [SOCK_SEQPACKET] = { + [TOMOYO_NETWORK_BIND] = + TOMOYO_MAC_NETWORK_UNIX_SEQPACKET_BIND, + [TOMOYO_NETWORK_LISTEN] = + TOMOYO_MAC_NETWORK_UNIX_SEQPACKET_LISTEN, + [TOMOYO_NETWORK_CONNECT] = + TOMOYO_MAC_NETWORK_UNIX_SEQPACKET_CONNECT, + }, +}; + +/** + * tomoyo_same_inet_acl - Check for duplicated "struct tomoyo_inet_acl" entry. + * + * @a: Pointer to "struct tomoyo_acl_info". + * @b: Pointer to "struct tomoyo_acl_info". + * + * Returns true if @a == @b except permission bits, false otherwise. + */ +static bool tomoyo_same_inet_acl(const struct tomoyo_acl_info *a, + const struct tomoyo_acl_info *b) +{ + const struct tomoyo_inet_acl *p1 = container_of(a, typeof(*p1), head); + const struct tomoyo_inet_acl *p2 = container_of(b, typeof(*p2), head); + + return p1->protocol == p2->protocol && + tomoyo_same_ipaddr_union(&p1->address, &p2->address) && + tomoyo_same_number_union(&p1->port, &p2->port); +} + +/** + * tomoyo_same_unix_acl - Check for duplicated "struct tomoyo_unix_acl" entry. + * + * @a: Pointer to "struct tomoyo_acl_info". + * @b: Pointer to "struct tomoyo_acl_info". + * + * Returns true if @a == @b except permission bits, false otherwise. + */ +static bool tomoyo_same_unix_acl(const struct tomoyo_acl_info *a, + const struct tomoyo_acl_info *b) +{ + const struct tomoyo_unix_acl *p1 = container_of(a, typeof(*p1), head); + const struct tomoyo_unix_acl *p2 = container_of(b, typeof(*p2), head); + + return p1->protocol == p2->protocol && + tomoyo_same_name_union(&p1->name, &p2->name); +} + +/** + * tomoyo_merge_inet_acl - Merge duplicated "struct tomoyo_inet_acl" entry. + * + * @a: Pointer to "struct tomoyo_acl_info". + * @b: Pointer to "struct tomoyo_acl_info". + * @is_delete: True for @a &= ~@b, false for @a |= @b. + * + * Returns true if @a is empty, false otherwise. + */ +static bool tomoyo_merge_inet_acl(struct tomoyo_acl_info *a, + struct tomoyo_acl_info *b, + const bool is_delete) +{ + u8 * const a_perm = + &container_of(a, struct tomoyo_inet_acl, head)->perm; + u8 perm = *a_perm; + const u8 b_perm = container_of(b, struct tomoyo_inet_acl, head)->perm; + + if (is_delete) + perm &= ~b_perm; + else + perm |= b_perm; + *a_perm = perm; + return !perm; +} + +/** + * tomoyo_merge_unix_acl - Merge duplicated "struct tomoyo_unix_acl" entry. + * + * @a: Pointer to "struct tomoyo_acl_info". + * @b: Pointer to "struct tomoyo_acl_info". + * @is_delete: True for @a &= ~@b, false for @a |= @b. + * + * Returns true if @a is empty, false otherwise. + */ +static bool tomoyo_merge_unix_acl(struct tomoyo_acl_info *a, + struct tomoyo_acl_info *b, + const bool is_delete) +{ + u8 * const a_perm = + &container_of(a, struct tomoyo_unix_acl, head)->perm; + u8 perm = *a_perm; + const u8 b_perm = container_of(b, struct tomoyo_unix_acl, head)->perm; + + if (is_delete) + perm &= ~b_perm; + else + perm |= b_perm; + *a_perm = perm; + return !perm; +} + +/** + * tomoyo_write_inet_network - Write "struct tomoyo_inet_acl" list. + * + * @param: Pointer to "struct tomoyo_acl_param". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_write_inet_network(struct tomoyo_acl_param *param) +{ + struct tomoyo_inet_acl e = { .head.type = TOMOYO_TYPE_INET_ACL }; + int error = -EINVAL; + u8 type; + const char *protocol = tomoyo_read_token(param); + const char *operation = tomoyo_read_token(param); + + for (e.protocol = 0; e.protocol < TOMOYO_SOCK_MAX; e.protocol++) + if (!strcmp(protocol, tomoyo_proto_keyword[e.protocol])) + break; + for (type = 0; type < TOMOYO_MAX_NETWORK_OPERATION; type++) + if (tomoyo_permstr(operation, tomoyo_socket_keyword[type])) + e.perm |= 1 << type; + if (e.protocol == TOMOYO_SOCK_MAX || !e.perm) + return -EINVAL; + if (param->data[0] == '@') { + param->data++; + e.address.group = + tomoyo_get_group(param, TOMOYO_ADDRESS_GROUP); + if (!e.address.group) + return -ENOMEM; + } else { + if (!tomoyo_parse_ipaddr_union(param, &e.address)) + goto out; + } + if (!tomoyo_parse_number_union(param, &e.port) || + e.port.values[1] > 65535) + goto out; + error = tomoyo_update_domain(&e.head, sizeof(e), param, + tomoyo_same_inet_acl, + tomoyo_merge_inet_acl); +out: + tomoyo_put_group(e.address.group); + tomoyo_put_number_union(&e.port); + return error; +} + +/** + * tomoyo_write_unix_network - Write "struct tomoyo_unix_acl" list. + * + * @param: Pointer to "struct tomoyo_acl_param". + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_write_unix_network(struct tomoyo_acl_param *param) +{ + struct tomoyo_unix_acl e = { .head.type = TOMOYO_TYPE_UNIX_ACL }; + int error; + u8 type; + const char *protocol = tomoyo_read_token(param); + const char *operation = tomoyo_read_token(param); + + for (e.protocol = 0; e.protocol < TOMOYO_SOCK_MAX; e.protocol++) + if (!strcmp(protocol, tomoyo_proto_keyword[e.protocol])) + break; + for (type = 0; type < TOMOYO_MAX_NETWORK_OPERATION; type++) + if (tomoyo_permstr(operation, tomoyo_socket_keyword[type])) + e.perm |= 1 << type; + if (e.protocol == TOMOYO_SOCK_MAX || !e.perm) + return -EINVAL; + if (!tomoyo_parse_name_union(param, &e.name)) + return -EINVAL; + error = tomoyo_update_domain(&e.head, sizeof(e), param, + tomoyo_same_unix_acl, + tomoyo_merge_unix_acl); + tomoyo_put_name_union(&e.name); + return error; +} + +/** + * tomoyo_audit_net_log - Audit network log. + * + * @r: Pointer to "struct tomoyo_request_info". + * @family: Name of socket family ("inet" or "unix"). + * @protocol: Name of protocol in @family. + * @operation: Name of socket operation. + * @address: Name of address. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_audit_net_log(struct tomoyo_request_info *r, + const char *family, const u8 protocol, + const u8 operation, const char *address) +{ + return tomoyo_supervisor(r, "network %s %s %s %s\n", family, + tomoyo_proto_keyword[protocol], + tomoyo_socket_keyword[operation], address); +} + +/** + * tomoyo_audit_inet_log - Audit INET network log. + * + * @r: Pointer to "struct tomoyo_request_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_audit_inet_log(struct tomoyo_request_info *r) +{ + char buf[128]; + int len; + const __be32 *address = r->param.inet_network.address; + + if (r->param.inet_network.is_ipv6) + tomoyo_print_ipv6(buf, sizeof(buf), (const struct in6_addr *) + address, (const struct in6_addr *) address); + else + tomoyo_print_ipv4(buf, sizeof(buf), address, address); + len = strlen(buf); + snprintf(buf + len, sizeof(buf) - len, " %u", + r->param.inet_network.port); + return tomoyo_audit_net_log(r, "inet", r->param.inet_network.protocol, + r->param.inet_network.operation, buf); +} + +/** + * tomoyo_audit_unix_log - Audit UNIX network log. + * + * @r: Pointer to "struct tomoyo_request_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_audit_unix_log(struct tomoyo_request_info *r) +{ + return tomoyo_audit_net_log(r, "unix", r->param.unix_network.protocol, + r->param.unix_network.operation, + r->param.unix_network.address->name); +} + +/** + * tomoyo_check_inet_acl - Check permission for inet domain socket operation. + * + * @r: Pointer to "struct tomoyo_request_info". + * @ptr: Pointer to "struct tomoyo_acl_info". + * + * Returns true if granted, false otherwise. + */ +static bool tomoyo_check_inet_acl(struct tomoyo_request_info *r, + const struct tomoyo_acl_info *ptr) +{ + const struct tomoyo_inet_acl *acl = + container_of(ptr, typeof(*acl), head); + const u8 size = r->param.inet_network.is_ipv6 ? 16 : 4; + + if (!(acl->perm & (1 << r->param.inet_network.operation)) || + !tomoyo_compare_number_union(r->param.inet_network.port, + &acl->port)) + return false; + if (acl->address.group) + return tomoyo_address_matches_group + (r->param.inet_network.is_ipv6, + r->param.inet_network.address, acl->address.group); + return acl->address.is_ipv6 == r->param.inet_network.is_ipv6 && + memcmp(&acl->address.ip[0], + r->param.inet_network.address, size) <= 0 && + memcmp(r->param.inet_network.address, + &acl->address.ip[1], size) <= 0; +} + +/** + * tomoyo_check_unix_acl - Check permission for unix domain socket operation. + * + * @r: Pointer to "struct tomoyo_request_info". + * @ptr: Pointer to "struct tomoyo_acl_info". + * + * Returns true if granted, false otherwise. + */ +static bool tomoyo_check_unix_acl(struct tomoyo_request_info *r, + const struct tomoyo_acl_info *ptr) +{ + const struct tomoyo_unix_acl *acl = + container_of(ptr, typeof(*acl), head); + + return (acl->perm & (1 << r->param.unix_network.operation)) && + tomoyo_compare_name_union(r->param.unix_network.address, + &acl->name); +} + +/** + * tomoyo_inet_entry - Check permission for INET network operation. + * + * @address: Pointer to "struct tomoyo_addr_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_inet_entry(const struct tomoyo_addr_info *address) +{ + const int idx = tomoyo_read_lock(); + struct tomoyo_request_info r; + int error = 0; + const u8 type = tomoyo_inet2mac[address->protocol][address->operation]; + + if (type && tomoyo_init_request_info(&r, NULL, type) + != TOMOYO_CONFIG_DISABLED) { + r.param_type = TOMOYO_TYPE_INET_ACL; + r.param.inet_network.protocol = address->protocol; + r.param.inet_network.operation = address->operation; + r.param.inet_network.is_ipv6 = address->inet.is_ipv6; + r.param.inet_network.address = address->inet.address; + r.param.inet_network.port = ntohs(address->inet.port); + do { + tomoyo_check_acl(&r, tomoyo_check_inet_acl); + error = tomoyo_audit_inet_log(&r); + } while (error == TOMOYO_RETRY_REQUEST); + } + tomoyo_read_unlock(idx); + return error; +} + +/** + * tomoyo_check_inet_address - Check permission for inet domain socket's operation. + * + * @addr: Pointer to "struct sockaddr". + * @addr_len: Size of @addr. + * @port: Port number. + * @address: Pointer to "struct tomoyo_addr_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_check_inet_address(const struct sockaddr *addr, + const unsigned int addr_len, + const u16 port, + struct tomoyo_addr_info *address) +{ + struct tomoyo_inet_addr_info *i = &address->inet; + + switch (addr->sa_family) { + case AF_INET6: + if (addr_len < SIN6_LEN_RFC2133) + goto skip; + i->is_ipv6 = true; + i->address = (__be32 *) + ((struct sockaddr_in6 *) addr)->sin6_addr.s6_addr; + i->port = ((struct sockaddr_in6 *) addr)->sin6_port; + break; + case AF_INET: + if (addr_len < sizeof(struct sockaddr_in)) + goto skip; + i->is_ipv6 = false; + i->address = (__be32 *) + &((struct sockaddr_in *) addr)->sin_addr; + i->port = ((struct sockaddr_in *) addr)->sin_port; + break; + default: + goto skip; + } + if (address->protocol == SOCK_RAW) + i->port = htons(port); + return tomoyo_inet_entry(address); +skip: + return 0; +} + +/** + * tomoyo_unix_entry - Check permission for UNIX network operation. + * + * @address: Pointer to "struct tomoyo_addr_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_unix_entry(const struct tomoyo_addr_info *address) +{ + const int idx = tomoyo_read_lock(); + struct tomoyo_request_info r; + int error = 0; + const u8 type = tomoyo_unix2mac[address->protocol][address->operation]; + + if (type && tomoyo_init_request_info(&r, NULL, type) + != TOMOYO_CONFIG_DISABLED) { + char *buf = address->unix0.addr; + int len = address->unix0.addr_len - sizeof(sa_family_t); + + if (len <= 0) { + buf = "anonymous"; + len = 9; + } else if (buf[0]) { + len = strnlen(buf, len); + } + buf = tomoyo_encode2(buf, len); + if (buf) { + struct tomoyo_path_info addr; + + addr.name = buf; + tomoyo_fill_path_info(&addr); + r.param_type = TOMOYO_TYPE_UNIX_ACL; + r.param.unix_network.protocol = address->protocol; + r.param.unix_network.operation = address->operation; + r.param.unix_network.address = &addr; + do { + tomoyo_check_acl(&r, tomoyo_check_unix_acl); + error = tomoyo_audit_unix_log(&r); + } while (error == TOMOYO_RETRY_REQUEST); + kfree(buf); + } else + error = -ENOMEM; + } + tomoyo_read_unlock(idx); + return error; +} + +/** + * tomoyo_check_unix_address - Check permission for unix domain socket's operation. + * + * @addr: Pointer to "struct sockaddr". + * @addr_len: Size of @addr. + * @address: Pointer to "struct tomoyo_addr_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_check_unix_address(struct sockaddr *addr, + const unsigned int addr_len, + struct tomoyo_addr_info *address) +{ + struct tomoyo_unix_addr_info *u = &address->unix0; + + if (addr->sa_family != AF_UNIX) + return 0; + u->addr = ((struct sockaddr_un *) addr)->sun_path; + u->addr_len = addr_len; + return tomoyo_unix_entry(address); +} + +/** + * tomoyo_kernel_service - Check whether I'm kernel service or not. + * + * Returns true if I'm kernel service, false otherwise. + */ +static bool tomoyo_kernel_service(void) +{ + /* Nothing to do if I am a kernel service. */ + return segment_eq(get_fs(), KERNEL_DS); +} + +/** + * tomoyo_sock_family - Get socket's family. + * + * @sk: Pointer to "struct sock". + * + * Returns one of PF_INET, PF_INET6, PF_UNIX or 0. + */ +static u8 tomoyo_sock_family(struct sock *sk) +{ + u8 family; + + if (tomoyo_kernel_service()) + return 0; + family = sk->sk_family; + switch (family) { + case PF_INET: + case PF_INET6: + case PF_UNIX: + return family; + default: + return 0; + } +} + +/** + * tomoyo_socket_listen_permission - Check permission for listening a socket. + * + * @sock: Pointer to "struct socket". + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_socket_listen_permission(struct socket *sock) +{ + struct tomoyo_addr_info address; + const u8 family = tomoyo_sock_family(sock->sk); + const unsigned int type = sock->type; + struct sockaddr_storage addr; + int addr_len; + + if (!family || (type != SOCK_STREAM && type != SOCK_SEQPACKET)) + return 0; + { + const int error = sock->ops->getname(sock, (struct sockaddr *) + &addr, &addr_len, 0); + + if (error) + return error; + } + address.protocol = type; + address.operation = TOMOYO_NETWORK_LISTEN; + if (family == PF_UNIX) + return tomoyo_check_unix_address((struct sockaddr *) &addr, + addr_len, &address); + return tomoyo_check_inet_address((struct sockaddr *) &addr, addr_len, + 0, &address); +} + +/** + * tomoyo_socket_connect_permission - Check permission for setting the remote address of a socket. + * + * @sock: Pointer to "struct socket". + * @addr: Pointer to "struct sockaddr". + * @addr_len: Size of @addr. + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_socket_connect_permission(struct socket *sock, + struct sockaddr *addr, int addr_len) +{ + struct tomoyo_addr_info address; + const u8 family = tomoyo_sock_family(sock->sk); + const unsigned int type = sock->type; + + if (!family) + return 0; + address.protocol = type; + switch (type) { + case SOCK_DGRAM: + case SOCK_RAW: + address.operation = TOMOYO_NETWORK_SEND; + break; + case SOCK_STREAM: + case SOCK_SEQPACKET: + address.operation = TOMOYO_NETWORK_CONNECT; + break; + default: + return 0; + } + if (family == PF_UNIX) + return tomoyo_check_unix_address(addr, addr_len, &address); + return tomoyo_check_inet_address(addr, addr_len, sock->sk->sk_protocol, + &address); +} + +/** + * tomoyo_socket_bind_permission - Check permission for setting the local address of a socket. + * + * @sock: Pointer to "struct socket". + * @addr: Pointer to "struct sockaddr". + * @addr_len: Size of @addr. + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_socket_bind_permission(struct socket *sock, struct sockaddr *addr, + int addr_len) +{ + struct tomoyo_addr_info address; + const u8 family = tomoyo_sock_family(sock->sk); + const unsigned int type = sock->type; + + if (!family) + return 0; + switch (type) { + case SOCK_STREAM: + case SOCK_DGRAM: + case SOCK_RAW: + case SOCK_SEQPACKET: + address.protocol = type; + address.operation = TOMOYO_NETWORK_BIND; + break; + default: + return 0; + } + if (family == PF_UNIX) + return tomoyo_check_unix_address(addr, addr_len, &address); + return tomoyo_check_inet_address(addr, addr_len, sock->sk->sk_protocol, + &address); +} + +/** + * tomoyo_socket_sendmsg_permission - Check permission for sending a datagram. + * + * @sock: Pointer to "struct socket". + * @msg: Pointer to "struct msghdr". + * @size: Unused. + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_socket_sendmsg_permission(struct socket *sock, struct msghdr *msg, + int size) +{ + struct tomoyo_addr_info address; + const u8 family = tomoyo_sock_family(sock->sk); + const unsigned int type = sock->type; + + if (!msg->msg_name || !family || + (type != SOCK_DGRAM && type != SOCK_RAW)) + return 0; + address.protocol = type; + address.operation = TOMOYO_NETWORK_SEND; + if (family == PF_UNIX) + return tomoyo_check_unix_address((struct sockaddr *) + msg->msg_name, + msg->msg_namelen, &address); + return tomoyo_check_inet_address((struct sockaddr *) msg->msg_name, + msg->msg_namelen, + sock->sk->sk_protocol, &address); +} diff --git a/security/tomoyo/realpath.c b/security/tomoyo/realpath.c index 6c601bd300f3..738bbdf8d4c7 100644 --- a/security/tomoyo/realpath.c +++ b/security/tomoyo/realpath.c @@ -15,17 +15,19 @@ #include "../../fs/internal.h" /** - * tomoyo_encode: Convert binary string to ascii string. + * tomoyo_encode2 - Encode binary string to ascii string. * - * @str: String in binary format. + * @str: String in binary format. + * @str_len: Size of @str in byte. * * Returns pointer to @str in ascii format on success, NULL otherwise. * * This function uses kzalloc(), so caller must kfree() if this function * didn't return NULL. */ -char *tomoyo_encode(const char *str) +char *tomoyo_encode2(const char *str, int str_len) { + int i; int len = 0; const char *p = str; char *cp; @@ -33,8 +35,9 @@ char *tomoyo_encode(const char *str) if (!p) return NULL; - while (*p) { - const unsigned char c = *p++; + for (i = 0; i < str_len; i++) { + const unsigned char c = p[i]; + if (c == '\\') len += 2; else if (c > ' ' && c < 127) @@ -49,8 +52,8 @@ char *tomoyo_encode(const char *str) return NULL; cp0 = cp; p = str; - while (*p) { - const unsigned char c = *p++; + for (i = 0; i < str_len; i++) { + const unsigned char c = p[i]; if (c == '\\') { *cp++ = '\\'; @@ -67,6 +70,21 @@ char *tomoyo_encode(const char *str) return cp0; } +/** + * tomoyo_encode - Encode binary string to ascii string. + * + * @str: String in binary format. + * + * Returns pointer to @str in ascii format on success, NULL otherwise. + * + * This function uses kzalloc(), so caller must kfree() if this function + * didn't return NULL. + */ +char *tomoyo_encode(const char *str) +{ + return str ? tomoyo_encode2(str, strlen(str)) : NULL; +} + /** * tomoyo_get_absolute_path - Get the path of a dentry but ignores chroot'ed root. * diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c index f776400a8f31..4b327b691745 100644 --- a/security/tomoyo/tomoyo.c +++ b/security/tomoyo/tomoyo.c @@ -442,6 +442,64 @@ static int tomoyo_sb_pivotroot(struct path *old_path, struct path *new_path) return tomoyo_path2_perm(TOMOYO_TYPE_PIVOT_ROOT, new_path, old_path); } +/** + * tomoyo_socket_listen - Check permission for listen(). + * + * @sock: Pointer to "struct socket". + * @backlog: Backlog parameter. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_socket_listen(struct socket *sock, int backlog) +{ + return tomoyo_socket_listen_permission(sock); +} + +/** + * tomoyo_socket_connect - Check permission for connect(). + * + * @sock: Pointer to "struct socket". + * @addr: Pointer to "struct sockaddr". + * @addr_len: Size of @addr. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_socket_connect(struct socket *sock, struct sockaddr *addr, + int addr_len) +{ + return tomoyo_socket_connect_permission(sock, addr, addr_len); +} + +/** + * tomoyo_socket_bind - Check permission for bind(). + * + * @sock: Pointer to "struct socket". + * @addr: Pointer to "struct sockaddr". + * @addr_len: Size of @addr. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_socket_bind(struct socket *sock, struct sockaddr *addr, + int addr_len) +{ + return tomoyo_socket_bind_permission(sock, addr, addr_len); +} + +/** + * tomoyo_socket_sendmsg - Check permission for sendmsg(). + * + * @sock: Pointer to "struct socket". + * @msg: Pointer to "struct msghdr". + * @size: Size of message. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_socket_sendmsg(struct socket *sock, struct msghdr *msg, + int size) +{ + return tomoyo_socket_sendmsg_permission(sock, msg, size); +} + /* * tomoyo_security_ops is a "struct security_operations" which is used for * registering TOMOYO. @@ -472,6 +530,10 @@ static struct security_operations tomoyo_security_ops = { .sb_mount = tomoyo_sb_mount, .sb_umount = tomoyo_sb_umount, .sb_pivotroot = tomoyo_sb_pivotroot, + .socket_bind = tomoyo_socket_bind, + .socket_connect = tomoyo_socket_connect, + .socket_listen = tomoyo_socket_listen, + .socket_sendmsg = tomoyo_socket_sendmsg, }; /* Lock for GC. */ diff --git a/security/tomoyo/util.c b/security/tomoyo/util.c index cb7d507b6312..a1c3d9ccebfa 100644 --- a/security/tomoyo/util.c +++ b/security/tomoyo/util.c @@ -42,6 +42,37 @@ const u8 tomoyo_index2category[TOMOYO_MAX_MAC_INDEX] = { [TOMOYO_MAC_FILE_MOUNT] = TOMOYO_MAC_CATEGORY_FILE, [TOMOYO_MAC_FILE_UMOUNT] = TOMOYO_MAC_CATEGORY_FILE, [TOMOYO_MAC_FILE_PIVOT_ROOT] = TOMOYO_MAC_CATEGORY_FILE, + /* CONFIG::network group */ + [TOMOYO_MAC_NETWORK_INET_STREAM_BIND] = + TOMOYO_MAC_CATEGORY_NETWORK, + [TOMOYO_MAC_NETWORK_INET_STREAM_LISTEN] = + TOMOYO_MAC_CATEGORY_NETWORK, + [TOMOYO_MAC_NETWORK_INET_STREAM_CONNECT] = + TOMOYO_MAC_CATEGORY_NETWORK, + [TOMOYO_MAC_NETWORK_INET_DGRAM_BIND] = + TOMOYO_MAC_CATEGORY_NETWORK, + [TOMOYO_MAC_NETWORK_INET_DGRAM_SEND] = + TOMOYO_MAC_CATEGORY_NETWORK, + [TOMOYO_MAC_NETWORK_INET_RAW_BIND] = + TOMOYO_MAC_CATEGORY_NETWORK, + [TOMOYO_MAC_NETWORK_INET_RAW_SEND] = + TOMOYO_MAC_CATEGORY_NETWORK, + [TOMOYO_MAC_NETWORK_UNIX_STREAM_BIND] = + TOMOYO_MAC_CATEGORY_NETWORK, + [TOMOYO_MAC_NETWORK_UNIX_STREAM_LISTEN] = + TOMOYO_MAC_CATEGORY_NETWORK, + [TOMOYO_MAC_NETWORK_UNIX_STREAM_CONNECT] = + TOMOYO_MAC_CATEGORY_NETWORK, + [TOMOYO_MAC_NETWORK_UNIX_DGRAM_BIND] = + TOMOYO_MAC_CATEGORY_NETWORK, + [TOMOYO_MAC_NETWORK_UNIX_DGRAM_SEND] = + TOMOYO_MAC_CATEGORY_NETWORK, + [TOMOYO_MAC_NETWORK_UNIX_SEQPACKET_BIND] = + TOMOYO_MAC_CATEGORY_NETWORK, + [TOMOYO_MAC_NETWORK_UNIX_SEQPACKET_LISTEN] = + TOMOYO_MAC_CATEGORY_NETWORK, + [TOMOYO_MAC_NETWORK_UNIX_SEQPACKET_CONNECT] = + TOMOYO_MAC_CATEGORY_NETWORK, /* CONFIG::misc group */ [TOMOYO_MAC_ENVIRON] = TOMOYO_MAC_CATEGORY_MISC, }; From 1f067a682a9bd252107ac6f6946b7332fde42344 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Sat, 10 Sep 2011 15:24:56 +0900 Subject: [PATCH 60/95] TOMOYO: Allow controlling generation of access granted logs for per an entry basis. Add per-entry flag which controls generation of grant logs because Xen and KVM issues ioctl requests so frequently. For example, file ioctl /dev/null 0x5401 grant_log=no will suppress /sys/kernel/security/tomoyo/audit even if preference says grant_log=yes . Signed-off-by: Tetsuo Handa Signed-off-by: James Morris --- security/tomoyo/audit.c | 7 ++++++- security/tomoyo/common.c | 4 ++++ security/tomoyo/common.h | 12 ++++++++++++ security/tomoyo/condition.c | 15 +++++++++++++++ security/tomoyo/domain.c | 1 + 5 files changed, 38 insertions(+), 1 deletion(-) diff --git a/security/tomoyo/audit.c b/security/tomoyo/audit.c index 5dbb1f7617c0..075c3a6d1649 100644 --- a/security/tomoyo/audit.c +++ b/security/tomoyo/audit.c @@ -313,6 +313,7 @@ static unsigned int tomoyo_log_count; */ static bool tomoyo_get_audit(const struct tomoyo_policy_namespace *ns, const u8 profile, const u8 index, + const struct tomoyo_acl_info *matched_acl, const bool is_granted) { u8 mode; @@ -324,6 +325,9 @@ static bool tomoyo_get_audit(const struct tomoyo_policy_namespace *ns, p = tomoyo_profile(ns, profile); if (tomoyo_log_count >= p->pref[TOMOYO_PREF_MAX_AUDIT_LOG]) return false; + if (is_granted && matched_acl && matched_acl->cond && + matched_acl->cond->grant_log != TOMOYO_GRANTLOG_AUTO) + return matched_acl->cond->grant_log == TOMOYO_GRANTLOG_YES; mode = p->config[index]; if (mode == TOMOYO_CONFIG_USE_DEFAULT) mode = p->config[category]; @@ -350,7 +354,8 @@ void tomoyo_write_log2(struct tomoyo_request_info *r, int len, const char *fmt, char *buf; struct tomoyo_log *entry; bool quota_exceeded = false; - if (!tomoyo_get_audit(r->domain->ns, r->profile, r->type, r->granted)) + if (!tomoyo_get_audit(r->domain->ns, r->profile, r->type, + r->matched_acl, r->granted)) goto out; buf = tomoyo_init_log(r, len, fmt, args); if (!buf) diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c index 85d915587a71..2704c384bf1e 100644 --- a/security/tomoyo/common.c +++ b/security/tomoyo/common.c @@ -1272,6 +1272,10 @@ static bool tomoyo_print_condition(struct tomoyo_io_buffer *head, head->r.cond_step++; /* fall through */ case 3: + if (cond->grant_log != TOMOYO_GRANTLOG_AUTO) + tomoyo_io_printf(head, " grant_log=%s", + tomoyo_yesno(cond->grant_log == + TOMOYO_GRANTLOG_YES)); tomoyo_set_lf(head); return true; } diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h index d1c758e7f92b..435b3d869fc5 100644 --- a/security/tomoyo/common.h +++ b/security/tomoyo/common.h @@ -179,6 +179,16 @@ enum tomoyo_domain_info_flags_index { TOMOYO_MAX_DOMAIN_INFO_FLAGS }; +/* Index numbers for audit type. */ +enum tomoyo_grant_log { + /* Follow profile's configuration. */ + TOMOYO_GRANTLOG_AUTO, + /* Do not generate grant log. */ + TOMOYO_GRANTLOG_NO, + /* Generate grant_log. */ + TOMOYO_GRANTLOG_YES, +}; + /* Index numbers for group entries. */ enum tomoyo_group_id { TOMOYO_PATH_GROUP, @@ -471,6 +481,7 @@ struct tomoyo_request_info { int need_dev; } mount; } param; + struct tomoyo_acl_info *matched_acl; u8 param_type; bool granted; u8 retry; @@ -635,6 +646,7 @@ struct tomoyo_condition { u16 names_count; /* Number of "struct tomoyo_name_union names". */ u16 argc; /* Number of "struct tomoyo_argv". */ u16 envc; /* Number of "struct tomoyo_envp". */ + u8 grant_log; /* One of values in "enum tomoyo_grant_log". */ /* * struct tomoyo_condition_element condition[condc]; * struct tomoyo_number_union values[numbers_count]; diff --git a/security/tomoyo/condition.c b/security/tomoyo/condition.c index 8a05f71eaf67..3a05eb3e2a64 100644 --- a/security/tomoyo/condition.c +++ b/security/tomoyo/condition.c @@ -348,6 +348,7 @@ static inline bool tomoyo_same_condition(const struct tomoyo_condition *a, a->numbers_count == b->numbers_count && a->names_count == b->names_count && a->argc == b->argc && a->envc == b->envc && + a->grant_log == b->grant_log && !memcmp(a + 1, b + 1, a->size - sizeof(*a)); } @@ -486,6 +487,20 @@ rerun: goto out; dprintk(KERN_WARNING "%u: <%s>%s=<%s>\n", __LINE__, left_word, is_not ? "!" : "", right_word); + if (!strcmp(left_word, "grant_log")) { + if (entry) { + if (is_not || + entry->grant_log != TOMOYO_GRANTLOG_AUTO) + goto out; + else if (!strcmp(right_word, "yes")) + entry->grant_log = TOMOYO_GRANTLOG_YES; + else if (!strcmp(right_word, "no")) + entry->grant_log = TOMOYO_GRANTLOG_NO; + else + goto out; + } + continue; + } if (!strncmp(left_word, "exec.argv[", 10)) { if (!argv) { e.argc++; diff --git a/security/tomoyo/domain.c b/security/tomoyo/domain.c index 5931fb1c04d5..498fea732f48 100644 --- a/security/tomoyo/domain.c +++ b/security/tomoyo/domain.c @@ -157,6 +157,7 @@ retry: continue; if (!tomoyo_condition(r, ptr->cond)) continue; + r->matched_acl = ptr; r->granted = true; return; } From 731d37aa70c7b9de3be6bf2c8287366223bf5ce5 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Sat, 10 Sep 2011 15:25:58 +0900 Subject: [PATCH 61/95] TOMOYO: Allow domain transition without execve(). To be able to split permissions for Apache's CGI programs which are executed without execve(), add special domain transition which is performed by writing a TOMOYO's domainname to /sys/kernel/security/tomoyo/self_domain interface. This is an API for TOMOYO-aware userland applications. However, since I expect TOMOYO and other LSM modules to run in parallel, this patch does not use /proc/self/attr/ interface in order to avoid conflicts with other LSM modules when it became possible to run multiple LSM modules in parallel. Signed-off-by: Tetsuo Handa Signed-off-by: James Morris --- security/tomoyo/common.c | 75 +++++++++++++------- security/tomoyo/common.h | 16 ++++- security/tomoyo/securityfs_if.c | 122 +++++++++++++++++++++++++++++++- security/tomoyo/util.c | 25 +++++++ 4 files changed, 210 insertions(+), 28 deletions(-) diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c index 2704c384bf1e..1fd0fc1059ba 100644 --- a/security/tomoyo/common.c +++ b/security/tomoyo/common.c @@ -1010,6 +1010,48 @@ static bool tomoyo_select_domain(struct tomoyo_io_buffer *head, return true; } +/** + * tomoyo_same_task_acl - Check for duplicated "struct tomoyo_task_acl" entry. + * + * @a: Pointer to "struct tomoyo_acl_info". + * @b: Pointer to "struct tomoyo_acl_info". + * + * Returns true if @a == @b, false otherwise. + */ +static bool tomoyo_same_task_acl(const struct tomoyo_acl_info *a, + const struct tomoyo_acl_info *b) +{ + const struct tomoyo_task_acl *p1 = container_of(a, typeof(*p1), head); + const struct tomoyo_task_acl *p2 = container_of(b, typeof(*p2), head); + return p1->domainname == p2->domainname; +} + +/** + * tomoyo_write_task - Update task related list. + * + * @param: Pointer to "struct tomoyo_acl_param". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_write_task(struct tomoyo_acl_param *param) +{ + int error = -EINVAL; + if (tomoyo_str_starts(¶m->data, "manual_domain_transition ")) { + struct tomoyo_task_acl e = { + .head.type = TOMOYO_TYPE_MANUAL_TASK_ACL, + .domainname = tomoyo_get_domainname(param), + }; + if (e.domainname) + error = tomoyo_update_domain(&e.head, sizeof(e), param, + tomoyo_same_task_acl, + NULL); + tomoyo_put_name(e.domainname); + } + return error; +} + /** * tomoyo_delete_domain - Delete a domain. * @@ -1068,11 +1110,12 @@ static int tomoyo_write_domain2(struct tomoyo_policy_namespace *ns, static const struct { const char *keyword; int (*write) (struct tomoyo_acl_param *); - } tomoyo_callback[4] = { + } tomoyo_callback[5] = { { "file ", tomoyo_write_file }, { "network inet ", tomoyo_write_inet_network }, { "network unix ", tomoyo_write_unix_network }, { "misc ", tomoyo_write_misc }, + { "task ", tomoyo_write_task }, }; u8 i; @@ -1343,6 +1386,12 @@ static bool tomoyo_print_entry(struct tomoyo_io_buffer *head, if (first) return true; tomoyo_print_name_union(head, &ptr->name); + } else if (acl_type == TOMOYO_TYPE_MANUAL_TASK_ACL) { + struct tomoyo_task_acl *ptr = + container_of(acl, typeof(*ptr), head); + tomoyo_set_group(head, "task "); + tomoyo_set_string(head, "manual_domain_transition "); + tomoyo_set_string(head, ptr->domainname->name); } else if (head->r.print_transition_related_only) { return true; } else if (acl_type == TOMOYO_TYPE_PATH2_ACL) { @@ -2178,26 +2227,6 @@ static void tomoyo_read_version(struct tomoyo_io_buffer *head) } } -/** - * tomoyo_read_self_domain - Get the current process's domainname. - * - * @head: Pointer to "struct tomoyo_io_buffer". - * - * Returns the current process's domainname. - */ -static void tomoyo_read_self_domain(struct tomoyo_io_buffer *head) -{ - if (!head->r.eof) { - /* - * tomoyo_domain()->domainname != NULL - * because every process belongs to a domain and - * the domain's name cannot be NULL. - */ - tomoyo_io_printf(head, "%s", tomoyo_domain()->domainname->name); - head->r.eof = true; - } -} - /* String table for /sys/kernel/security/tomoyo/stat interface. */ static const char * const tomoyo_policy_headers[TOMOYO_MAX_POLICY_STAT] = { [TOMOYO_STAT_POLICY_UPDATES] = "update:", @@ -2328,10 +2357,6 @@ int tomoyo_open_control(const u8 type, struct file *file) head->poll = tomoyo_poll_log; head->read = tomoyo_read_log; break; - case TOMOYO_SELFDOMAIN: - /* /sys/kernel/security/tomoyo/self_domain */ - head->read = tomoyo_read_self_domain; - break; case TOMOYO_PROCESS_STATUS: /* /sys/kernel/security/tomoyo/.process_status */ head->write = tomoyo_write_pid; diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h index 435b3d869fc5..af82683df7ff 100644 --- a/security/tomoyo/common.h +++ b/security/tomoyo/common.h @@ -227,6 +227,7 @@ enum tomoyo_acl_entry_type_index { TOMOYO_TYPE_INET_ACL, TOMOYO_TYPE_UNIX_ACL, TOMOYO_TYPE_ENV_ACL, + TOMOYO_TYPE_MANUAL_TASK_ACL, }; /* Index numbers for access controls with one pathname. */ @@ -295,7 +296,6 @@ enum tomoyo_securityfs_interface_index { TOMOYO_EXCEPTIONPOLICY, TOMOYO_PROCESS_STATUS, TOMOYO_STAT, - TOMOYO_SELFDOMAIN, TOMOYO_AUDIT, TOMOYO_VERSION, TOMOYO_PROFILE, @@ -480,6 +480,9 @@ struct tomoyo_request_info { unsigned long flags; int need_dev; } mount; + struct { + const struct tomoyo_path_info *domainname; + } task; } param; struct tomoyo_acl_info *matched_acl; u8 param_type; @@ -679,6 +682,15 @@ struct tomoyo_domain_info { atomic_t users; /* Number of referring credentials. */ }; +/* + * Structure for "task manual_domain_transition" directive. + */ +struct tomoyo_task_acl { + struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_MANUAL_TASK_ACL */ + /* Pointer to domainname. */ + const struct tomoyo_path_info *domainname; +}; + /* * Structure for "file execute", "file read", "file write", "file append", * "file unlink", "file getattr", "file rmdir", "file truncate", @@ -935,6 +947,8 @@ const char *tomoyo_get_exe(void); const char *tomoyo_yesno(const unsigned int value); const struct tomoyo_path_info *tomoyo_compare_name_union (const struct tomoyo_path_info *name, const struct tomoyo_name_union *ptr); +const struct tomoyo_path_info *tomoyo_get_domainname +(struct tomoyo_acl_param *param); const struct tomoyo_path_info *tomoyo_get_name(const char *name); const struct tomoyo_path_info *tomoyo_path_matches_group (const struct tomoyo_path_info *pathname, const struct tomoyo_group *group); diff --git a/security/tomoyo/securityfs_if.c b/security/tomoyo/securityfs_if.c index a49c3bfd4dd5..d08296a4882b 100644 --- a/security/tomoyo/securityfs_if.c +++ b/security/tomoyo/securityfs_if.c @@ -7,6 +7,124 @@ #include #include "common.h" +/** + * tomoyo_check_task_acl - Check permission for task operation. + * + * @r: Pointer to "struct tomoyo_request_info". + * @ptr: Pointer to "struct tomoyo_acl_info". + * + * Returns true if granted, false otherwise. + */ +static bool tomoyo_check_task_acl(struct tomoyo_request_info *r, + const struct tomoyo_acl_info *ptr) +{ + const struct tomoyo_task_acl *acl = container_of(ptr, typeof(*acl), + head); + return !tomoyo_pathcmp(r->param.task.domainname, acl->domainname); +} + +/** + * tomoyo_write_self - write() for /sys/kernel/security/tomoyo/self_domain interface. + * + * @file: Pointer to "struct file". + * @buf: Domainname to transit to. + * @count: Size of @buf. + * @ppos: Unused. + * + * Returns @count on success, negative value otherwise. + * + * If domain transition was permitted but the domain transition failed, this + * function returns error rather than terminating current thread with SIGKILL. + */ +static ssize_t tomoyo_write_self(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + char *data; + int error; + if (!count || count >= TOMOYO_EXEC_TMPSIZE - 10) + return -ENOMEM; + data = kzalloc(count + 1, GFP_NOFS); + if (!data) + return -ENOMEM; + if (copy_from_user(data, buf, count)) { + error = -EFAULT; + goto out; + } + tomoyo_normalize_line(data); + if (tomoyo_correct_domain(data)) { + const int idx = tomoyo_read_lock(); + struct tomoyo_path_info name; + struct tomoyo_request_info r; + name.name = data; + tomoyo_fill_path_info(&name); + /* Check "task manual_domain_transition" permission. */ + tomoyo_init_request_info(&r, NULL, TOMOYO_MAC_FILE_EXECUTE); + r.param_type = TOMOYO_TYPE_MANUAL_TASK_ACL; + r.param.task.domainname = &name; + tomoyo_check_acl(&r, tomoyo_check_task_acl); + if (!r.granted) + error = -EPERM; + else { + struct tomoyo_domain_info *new_domain = + tomoyo_assign_domain(data, true); + if (!new_domain) { + error = -ENOENT; + } else { + struct cred *cred = prepare_creds(); + if (!cred) { + error = -ENOMEM; + } else { + struct tomoyo_domain_info *old_domain = + cred->security; + cred->security = new_domain; + atomic_inc(&new_domain->users); + atomic_dec(&old_domain->users); + commit_creds(cred); + error = 0; + } + } + } + tomoyo_read_unlock(idx); + } else + error = -EINVAL; +out: + kfree(data); + return error ? error : count; +} + +/** + * tomoyo_read_self - read() for /sys/kernel/security/tomoyo/self_domain interface. + * + * @file: Pointer to "struct file". + * @buf: Domainname which current thread belongs to. + * @count: Size of @buf. + * @ppos: Bytes read by now. + * + * Returns read size on success, negative value otherwise. + */ +static ssize_t tomoyo_read_self(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const char *domain = tomoyo_domain()->domainname->name; + loff_t len = strlen(domain); + loff_t pos = *ppos; + if (pos >= len || !count) + return 0; + len -= pos; + if (count < len) + len = count; + if (copy_to_user(buf, domain + pos, len)) + return -EFAULT; + *ppos += len; + return len; +} + +/* Operations for /sys/kernel/security/tomoyo/self_domain interface. */ +static const struct file_operations tomoyo_self_operations = { + .write = tomoyo_write_self, + .read = tomoyo_read_self, +}; + /** * tomoyo_open - open() for /sys/kernel/security/tomoyo/ interface. * @@ -135,8 +253,6 @@ static int __init tomoyo_initerface_init(void) TOMOYO_EXCEPTIONPOLICY); tomoyo_create_entry("audit", 0400, tomoyo_dir, TOMOYO_AUDIT); - tomoyo_create_entry("self_domain", 0400, tomoyo_dir, - TOMOYO_SELFDOMAIN); tomoyo_create_entry(".process_status", 0600, tomoyo_dir, TOMOYO_PROCESS_STATUS); tomoyo_create_entry("stat", 0644, tomoyo_dir, @@ -147,6 +263,8 @@ static int __init tomoyo_initerface_init(void) TOMOYO_MANAGER); tomoyo_create_entry("version", 0400, tomoyo_dir, TOMOYO_VERSION); + securityfs_create_file("self_domain", 0666, tomoyo_dir, NULL, + &tomoyo_self_operations); return 0; } diff --git a/security/tomoyo/util.c b/security/tomoyo/util.c index a1c3d9ccebfa..50e9b4c73ceb 100644 --- a/security/tomoyo/util.c +++ b/security/tomoyo/util.c @@ -158,6 +158,31 @@ char *tomoyo_read_token(struct tomoyo_acl_param *param) return pos; } +/** + * tomoyo_get_domainname - Read a domainname from a line. + * + * @param: Pointer to "struct tomoyo_acl_param". + * + * Returns a domainname on success, NULL otherwise. + */ +const struct tomoyo_path_info *tomoyo_get_domainname +(struct tomoyo_acl_param *param) +{ + char *start = param->data; + char *pos = start; + while (*pos) { + if (*pos++ != ' ' || *pos++ == '/') + continue; + pos -= 2; + *pos++ = '\0'; + break; + } + param->data = pos; + if (tomoyo_correct_domain(start)) + return tomoyo_get_name(start); + return NULL; +} + /** * tomoyo_parse_ulong - Parse an "unsigned long" value. * From a8f7640963ada66c412314c3559c11ff6946c1a5 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Sat, 10 Sep 2011 15:27:12 +0900 Subject: [PATCH 62/95] TOMOYO: Avoid race when retrying "file execute" permission check. There was a race window that the pathname which is subjected to "file execute" permission check when retrying via supervisor's decision because the pathname was recalculated upon retry. Though, there is an inevitable race window even without supervisor, for we have to calculate the symbolic link's pathname from "struct linux_binprm"->filename rather than from "struct linux_binprm"->file because we cannot back calculate the symbolic link's pathname from the dereferenced pathname. Signed-off-by: Tetsuo Handa Signed-off-by: James Morris --- security/tomoyo/domain.c | 56 ++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/security/tomoyo/domain.c b/security/tomoyo/domain.c index 498fea732f48..a1fc6b5f6125 100644 --- a/security/tomoyo/domain.c +++ b/security/tomoyo/domain.c @@ -664,9 +664,9 @@ int tomoyo_find_next_domain(struct linux_binprm *bprm) struct tomoyo_domain_info *domain = NULL; const char *original_name = bprm->filename; int retval = -ENOMEM; - bool need_kfree = false; bool reject_on_transition_failure = false; - struct tomoyo_path_info rn = { }; /* real name */ + const struct tomoyo_path_info *candidate; + struct tomoyo_path_info exename; struct tomoyo_execve *ee = kzalloc(sizeof(*ee), GFP_NOFS); if (!ee) @@ -682,40 +682,33 @@ int tomoyo_find_next_domain(struct linux_binprm *bprm) ee->bprm = bprm; ee->r.obj = &ee->obj; ee->obj.path1 = bprm->file->f_path; - retry: - if (need_kfree) { - kfree(rn.name); - need_kfree = false; - } /* Get symlink's pathname of program. */ retval = -ENOENT; - rn.name = tomoyo_realpath_nofollow(original_name); - if (!rn.name) + exename.name = tomoyo_realpath_nofollow(original_name); + if (!exename.name) goto out; - tomoyo_fill_path_info(&rn); - need_kfree = true; - + tomoyo_fill_path_info(&exename); +retry: /* Check 'aggregator' directive. */ { struct tomoyo_aggregator *ptr; struct list_head *list = &old_domain->ns->policy_list[TOMOYO_ID_AGGREGATOR]; /* Check 'aggregator' directive. */ + candidate = &exename; list_for_each_entry_rcu(ptr, list, head.list) { if (ptr->head.is_deleted || - !tomoyo_path_matches_pattern(&rn, + !tomoyo_path_matches_pattern(&exename, ptr->original_name)) continue; - kfree(rn.name); - need_kfree = false; - /* This is OK because it is read only. */ - rn = *ptr->aggregated_name; + candidate = ptr->aggregated_name; break; } } /* Check execute permission. */ - retval = tomoyo_path_permission(&ee->r, TOMOYO_TYPE_EXECUTE, &rn); + retval = tomoyo_path_permission(&ee->r, TOMOYO_TYPE_EXECUTE, + candidate); if (retval == TOMOYO_RETRY_REQUEST) goto retry; if (retval < 0) @@ -726,20 +719,16 @@ int tomoyo_find_next_domain(struct linux_binprm *bprm) * wildcard) rather than the pathname passed to execve() * (which never contains wildcard). */ - if (ee->r.param.path.matched_path) { - if (need_kfree) - kfree(rn.name); - need_kfree = false; - /* This is OK because it is read only. */ - rn = *ee->r.param.path.matched_path; - } + if (ee->r.param.path.matched_path) + candidate = ee->r.param.path.matched_path; /* Calculate domain to transit to. */ switch (tomoyo_transition_type(old_domain->ns, old_domain->domainname, - &rn)) { + candidate)) { case TOMOYO_TRANSITION_CONTROL_RESET: /* Transit to the root of specified namespace. */ - snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "<%s>", rn.name); + snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "<%s>", + candidate->name); /* * Make do_execve() fail if domain transition across namespaces * has failed. @@ -749,7 +738,7 @@ int tomoyo_find_next_domain(struct linux_binprm *bprm) case TOMOYO_TRANSITION_CONTROL_INITIALIZE: /* Transit to the child of current namespace's root. */ snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "%s %s", - old_domain->ns->name, rn.name); + old_domain->ns->name, candidate->name); break; case TOMOYO_TRANSITION_CONTROL_KEEP: /* Keep current domain. */ @@ -765,11 +754,11 @@ int tomoyo_find_next_domain(struct linux_binprm *bprm) * before /sbin/init. */ domain = old_domain; - } else { - /* Normal domain transition. */ - snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "%s %s", - old_domain->domainname->name, rn.name); + break; } + /* Normal domain transition. */ + snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "%s %s", + old_domain->domainname->name, candidate->name); break; } if (!domain) @@ -799,8 +788,7 @@ int tomoyo_find_next_domain(struct linux_binprm *bprm) /* Update reference count on "struct tomoyo_domain_info". */ atomic_inc(&domain->users); bprm->cred->security = domain; - if (need_kfree) - kfree(rn.name); + kfree(exename.name); if (!retval) { ee->r.domain = domain; retval = tomoyo_environ(ee); From 61cf45d0199041df1a8ba334b6bf4a3a13b7f904 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Wed, 14 Sep 2011 15:06:00 -0400 Subject: [PATCH 63/95] encrypted-keys: create encrypted-keys directory Move all files associated with encrypted keys to keys/encrypted-keys. Signed-off-by: Mimi Zohar --- security/keys/Makefile | 2 +- security/keys/encrypted-keys/Makefile | 5 +++++ security/keys/{ => encrypted-keys}/ecryptfs_format.c | 0 security/keys/{ => encrypted-keys}/ecryptfs_format.h | 0 security/keys/{ => encrypted-keys}/encrypted.c | 0 security/keys/{ => encrypted-keys}/encrypted.h | 0 6 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 security/keys/encrypted-keys/Makefile rename security/keys/{ => encrypted-keys}/ecryptfs_format.c (100%) rename security/keys/{ => encrypted-keys}/ecryptfs_format.h (100%) rename security/keys/{ => encrypted-keys}/encrypted.c (100%) rename security/keys/{ => encrypted-keys}/encrypted.h (100%) diff --git a/security/keys/Makefile b/security/keys/Makefile index b34cc6ee6900..a56f1ffdc64d 100644 --- a/security/keys/Makefile +++ b/security/keys/Makefile @@ -14,7 +14,7 @@ obj-y := \ user_defined.o obj-$(CONFIG_TRUSTED_KEYS) += trusted.o -obj-$(CONFIG_ENCRYPTED_KEYS) += ecryptfs_format.o encrypted.o +obj-$(CONFIG_ENCRYPTED_KEYS) += encrypted-keys/ obj-$(CONFIG_KEYS_COMPAT) += compat.o obj-$(CONFIG_PROC_FS) += proc.o obj-$(CONFIG_SYSCTL) += sysctl.o diff --git a/security/keys/encrypted-keys/Makefile b/security/keys/encrypted-keys/Makefile new file mode 100644 index 000000000000..cbd3f8de37bb --- /dev/null +++ b/security/keys/encrypted-keys/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for encrypted keys +# + +obj-$(CONFIG_ENCRYPTED_KEYS) += encrypted.o ecryptfs_format.o diff --git a/security/keys/ecryptfs_format.c b/security/keys/encrypted-keys/ecryptfs_format.c similarity index 100% rename from security/keys/ecryptfs_format.c rename to security/keys/encrypted-keys/ecryptfs_format.c diff --git a/security/keys/ecryptfs_format.h b/security/keys/encrypted-keys/ecryptfs_format.h similarity index 100% rename from security/keys/ecryptfs_format.h rename to security/keys/encrypted-keys/ecryptfs_format.h diff --git a/security/keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c similarity index 100% rename from security/keys/encrypted.c rename to security/keys/encrypted-keys/encrypted.c diff --git a/security/keys/encrypted.h b/security/keys/encrypted-keys/encrypted.h similarity index 100% rename from security/keys/encrypted.h rename to security/keys/encrypted-keys/encrypted.h From 982e617a313b57abee3bcfa53381c356d00fd64a Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Sat, 27 Aug 2011 22:21:26 -0400 Subject: [PATCH 64/95] encrypted-keys: remove trusted-keys dependency Encrypted keys are decrypted/encrypted using either a trusted-key or, for those systems without a TPM, a user-defined key. This patch removes the trusted-keys and TCG_TPM dependencies. Signed-off-by: Mimi Zohar --- security/Kconfig | 4 +- security/keys/encrypted-keys/Makefile | 1 + security/keys/encrypted-keys/encrypted.c | 35 ++++----------- security/keys/encrypted-keys/encrypted.h | 11 +++++ .../keys/encrypted-keys/masterkey_trusted.c | 44 +++++++++++++++++++ 5 files changed, 67 insertions(+), 28 deletions(-) create mode 100644 security/keys/encrypted-keys/masterkey_trusted.c diff --git a/security/Kconfig b/security/Kconfig index 22847a889081..51bd5a0b69ae 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -38,7 +38,9 @@ config TRUSTED_KEYS config ENCRYPTED_KEYS tristate "ENCRYPTED KEYS" - depends on KEYS && TRUSTED_KEYS + depends on KEYS + select CRYPTO + select CRYPTO_HMAC select CRYPTO_AES select CRYPTO_CBC select CRYPTO_SHA256 diff --git a/security/keys/encrypted-keys/Makefile b/security/keys/encrypted-keys/Makefile index cbd3f8de37bb..6bc7a86d1027 100644 --- a/security/keys/encrypted-keys/Makefile +++ b/security/keys/encrypted-keys/Makefile @@ -3,3 +3,4 @@ # obj-$(CONFIG_ENCRYPTED_KEYS) += encrypted.o ecryptfs_format.o +obj-$(CONFIG_TRUSTED_KEYS) += masterkey_trusted.o diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c index e7eca9ec4c65..3f577954b85a 100644 --- a/security/keys/encrypted-keys/encrypted.c +++ b/security/keys/encrypted-keys/encrypted.c @@ -298,31 +298,6 @@ out: return ascii_buf; } -/* - * request_trusted_key - request the trusted key - * - * Trusted keys are sealed to PCRs and other metadata. Although userspace - * manages both trusted/encrypted key-types, like the encrypted key type - * data, trusted key type data is not visible decrypted from userspace. - */ -static struct key *request_trusted_key(const char *trusted_desc, - u8 **master_key, size_t *master_keylen) -{ - struct trusted_key_payload *tpayload; - struct key *tkey; - - tkey = request_key(&key_type_trusted, trusted_desc, NULL); - if (IS_ERR(tkey)) - goto error; - - down_read(&tkey->sem); - tpayload = rcu_dereference(tkey->payload.data); - *master_key = tpayload->key; - *master_keylen = tpayload->key_len; -error: - return tkey; -} - /* * request_user_key - request the user key * @@ -469,8 +444,14 @@ static struct key *request_master_key(struct encrypted_key_payload *epayload, goto out; if (IS_ERR(mkey)) { - pr_info("encrypted_key: key %s not found", - epayload->master_desc); + int ret = PTR_ERR(epayload); + + if (ret == -ENOTSUPP) + pr_info("encrypted_key: key %s not supported", + epayload->master_desc); + else + pr_info("encrypted_key: key %s not found", + epayload->master_desc); goto out; } diff --git a/security/keys/encrypted-keys/encrypted.h b/security/keys/encrypted-keys/encrypted.h index cef5e2f2b7d1..b6ade8945250 100644 --- a/security/keys/encrypted-keys/encrypted.h +++ b/security/keys/encrypted-keys/encrypted.h @@ -2,6 +2,17 @@ #define __ENCRYPTED_KEY_H #define ENCRYPTED_DEBUG 0 +#ifdef CONFIG_TRUSTED_KEYS +extern struct key *request_trusted_key(const char *trusted_desc, + u8 **master_key, size_t *master_keylen); +#else +static inline struct key *request_trusted_key(const char *trusted_desc, + u8 **master_key, + size_t *master_keylen) +{ + return ERR_PTR(-EOPNOTSUPP); +} +#endif #if ENCRYPTED_DEBUG static inline void dump_master_key(const u8 *master_key, size_t master_keylen) diff --git a/security/keys/encrypted-keys/masterkey_trusted.c b/security/keys/encrypted-keys/masterkey_trusted.c new file mode 100644 index 000000000000..a5da5128891b --- /dev/null +++ b/security/keys/encrypted-keys/masterkey_trusted.c @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2010 IBM Corporation + * Copyright (C) 2010 Politecnico di Torino, Italy + * TORSEC group -- http://security.polito.it + * + * Authors: + * Mimi Zohar + * Roberto Sassu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * See Documentation/security/keys-trusted-encrypted.txt + */ + +#include +#include +#include + +/* + * request_trusted_key - request the trusted key + * + * Trusted keys are sealed to PCRs and other metadata. Although userspace + * manages both trusted/encrypted key-types, like the encrypted key type + * data, trusted key type data is not visible decrypted from userspace. + */ +struct key *request_trusted_key(const char *trusted_desc, + u8 **master_key, size_t *master_keylen) +{ + struct trusted_key_payload *tpayload; + struct key *tkey; + + tkey = request_key(&key_type_trusted, trusted_desc, NULL); + if (IS_ERR(tkey)) + goto error; + + down_read(&tkey->sem); + tpayload = rcu_dereference(tkey->payload.data); + *master_key = tpayload->key; + *master_keylen = tpayload->key_len; +error: + return tkey; +} From 1d714057ef8f6348eba7b28ace6d307513e57cef Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Sun, 28 Aug 2011 08:57:11 -0400 Subject: [PATCH 65/95] evm: remove TCG_TPM dependency All tristates selected by EVM(boolean) are forced to be builtin, except in the TCG_TPM(tristate) dependency case. Arnaud Lacombe summarizes the Kconfig bug as, "So it would seem direct dependency state influence the state of reverse dependencies.." For a detailed explanation, refer to Arnaud Lacombe's posting http://lkml.org/lkml/2011/8/23/498. With the "encrypted-keys: remove trusted-keys dependency" patch, EVM can now be built without a dependency on TCG_TPM. The trusted-keys dependency requires trusted-keys to either be builtin or not selected. This dependency will prevent the boolean/tristate mismatch from occuring. Reported-by: Stephen Rothwell , Randy Dunlap Signed-off-by: Mimi Zohar --- security/integrity/evm/Kconfig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/security/integrity/evm/Kconfig b/security/integrity/evm/Kconfig index 884617d4aad0..afbb59dd262d 100644 --- a/security/integrity/evm/Kconfig +++ b/security/integrity/evm/Kconfig @@ -1,11 +1,10 @@ config EVM boolean "EVM support" - depends on SECURITY && KEYS && TCG_TPM + depends on SECURITY && KEYS && (TRUSTED_KEYS=y || TRUSTED_KEYS=n) select CRYPTO_HMAC select CRYPTO_MD5 select CRYPTO_SHA1 select ENCRYPTED_KEYS - select TRUSTED_KEYS default n help EVM protects a file's security extended attributes against From fb88c2b6cbb1265a8bef60694699b37f5cd4ba76 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Mon, 15 Aug 2011 10:13:18 -0400 Subject: [PATCH 66/95] evm: fix security/security_old_init_security return code security_inode_init_security previously returned -EOPNOTSUPP, for S_PRIVATE inodes, and relied on the callers to change it to 0. As the callers do not change the return code anymore, return 0, intead of -EOPNOTSUPP. Signed-off-by: Mimi Zohar --- security/security.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/security.c b/security/security.c index 9ebda054a333..c1d69875db6c 100644 --- a/security/security.c +++ b/security/security.c @@ -348,7 +348,7 @@ int security_inode_init_security(struct inode *inode, struct inode *dir, int ret; if (unlikely(IS_PRIVATE(inode))) - return -EOPNOTSUPP; + return 0; memset(new_xattrs, 0, sizeof new_xattrs); if (!initxattrs) @@ -381,7 +381,7 @@ int security_old_inode_init_security(struct inode *inode, struct inode *dir, void **value, size_t *len) { if (unlikely(IS_PRIVATE(inode))) - return -EOPNOTSUPP; + return 0; return security_ops->inode_init_security(inode, dir, qstr, name, value, len); } From a924ce0b35875ef9512135b46a32f4150fd700b2 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Thu, 11 Aug 2011 01:22:30 -0400 Subject: [PATCH 67/95] evm: limit verifying current security.evm integrity evm_protect_xattr unnecessarily validates the current security.evm integrity, before updating non-evm protected extended attributes and other file metadata. This patch limits validating the current security.evm integrity to evm protected metadata. Signed-off-by: Mimi Zohar --- security/integrity/evm/evm_main.c | 58 +++++++++++++------------------ 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index f0127e536f84..7d4247535f9e 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -158,21 +158,6 @@ enum integrity_status evm_verifyxattr(struct dentry *dentry, } EXPORT_SYMBOL_GPL(evm_verifyxattr); -/* - * evm_protect_xattr - protect the EVM extended attribute - * - * Prevent security.evm from being modified or removed. - */ -static int evm_protect_xattr(struct dentry *dentry, const char *xattr_name, - const void *xattr_value, size_t xattr_value_len) -{ - if (strcmp(xattr_name, XATTR_NAME_EVM) == 0) { - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - } - return 0; -} - /* * evm_verify_current_integrity - verify the dentry's metadata integrity * @dentry: pointer to the affected dentry @@ -189,6 +174,26 @@ static enum integrity_status evm_verify_current_integrity(struct dentry *dentry) return evm_verify_hmac(dentry, NULL, NULL, 0, NULL); } +/* + * evm_protect_xattr - protect the EVM extended attribute + * + * Prevent security.evm from being modified or removed. + */ +static int evm_protect_xattr(struct dentry *dentry, const char *xattr_name, + const void *xattr_value, size_t xattr_value_len) +{ + enum integrity_status evm_status; + + if (strcmp(xattr_name, XATTR_NAME_EVM) == 0) { + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + } else if (!evm_protected_xattr(xattr_name)) + return 0; + + evm_status = evm_verify_current_integrity(dentry); + return evm_status == INTEGRITY_PASS ? 0 : -EPERM; +} + /** * evm_inode_setxattr - protect the EVM extended attribute * @dentry: pointer to the affected dentry @@ -202,16 +207,8 @@ static enum integrity_status evm_verify_current_integrity(struct dentry *dentry) int evm_inode_setxattr(struct dentry *dentry, const char *xattr_name, const void *xattr_value, size_t xattr_value_len) { - - enum integrity_status evm_status; - int ret; - - ret = evm_protect_xattr(dentry, xattr_name, xattr_value, - xattr_value_len); - if (ret) - return ret; - evm_status = evm_verify_current_integrity(dentry); - return evm_status == INTEGRITY_PASS ? 0 : -EPERM; + return evm_protect_xattr(dentry, xattr_name, xattr_value, + xattr_value_len); } /** @@ -224,14 +221,7 @@ int evm_inode_setxattr(struct dentry *dentry, const char *xattr_name, */ int evm_inode_removexattr(struct dentry *dentry, const char *xattr_name) { - enum integrity_status evm_status; - int ret; - - ret = evm_protect_xattr(dentry, xattr_name, NULL, 0); - if (ret) - return ret; - evm_status = evm_verify_current_integrity(dentry); - return evm_status == INTEGRITY_PASS ? 0 : -EPERM; + return evm_protect_xattr(dentry, xattr_name, NULL, 0); } /** @@ -286,7 +276,7 @@ int evm_inode_setattr(struct dentry *dentry, struct iattr *attr) unsigned int ia_valid = attr->ia_valid; enum integrity_status evm_status; - if (ia_valid & ~(ATTR_MODE | ATTR_UID | ATTR_GID)) + if (!(ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID))) return 0; evm_status = evm_verify_current_integrity(dentry); return evm_status == INTEGRITY_PASS ? 0 : -EPERM; From bf6d0f5dcda17df3cc5577e203d0f8ea1c2ad6aa Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Thu, 18 Aug 2011 18:07:44 -0400 Subject: [PATCH 68/95] evm: posix acls modify i_mode The posix xattr acls are 'system' prefixed, which normally would not affect security.evm. An interesting side affect of writing posix xattr acls is their modifying of the i_mode, which is included in security.evm. This patch updates security.evm when posix xattr acls are written. Signed-off-by: Mimi Zohar --- include/linux/evm.h | 8 ++++++++ include/linux/xattr.h | 5 +++++ security/integrity/evm/Makefile | 1 + security/integrity/evm/evm_main.c | 24 +++++++++++++++++++----- security/integrity/evm/evm_posix_acl.c | 26 ++++++++++++++++++++++++++ 5 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 security/integrity/evm/evm_posix_acl.c diff --git a/include/linux/evm.h b/include/linux/evm.h index ea603c9e775d..9fc13a760928 100644 --- a/include/linux/evm.h +++ b/include/linux/evm.h @@ -33,6 +33,14 @@ extern void evm_inode_post_removexattr(struct dentry *dentry, extern int evm_inode_init_security(struct inode *inode, const struct xattr *xattr_array, struct xattr *evm); +#ifdef CONFIG_FS_POSIX_ACL +extern int posix_xattr_acl(const char *xattrname); +#else +static inline int posix_xattr_acl(const char *xattrname) +{ + return 0; +} +#endif #else #ifdef CONFIG_INTEGRITY static inline enum integrity_status evm_verifyxattr(struct dentry *dentry, diff --git a/include/linux/xattr.h b/include/linux/xattr.h index b20cb965c322..e5d122031542 100644 --- a/include/linux/xattr.h +++ b/include/linux/xattr.h @@ -52,6 +52,11 @@ #define XATTR_CAPS_SUFFIX "capability" #define XATTR_NAME_CAPS XATTR_SECURITY_PREFIX XATTR_CAPS_SUFFIX +#define XATTR_POSIX_ACL_ACCESS "posix_acl_access" +#define XATTR_NAME_POSIX_ACL_ACCESS XATTR_SYSTEM_PREFIX XATTR_POSIX_ACL_ACCESS +#define XATTR_POSIX_ACL_DEFAULT "posix_acl_default" +#define XATTR_NAME_POSIX_ACL_DEFAULT XATTR_SYSTEM_PREFIX XATTR_POSIX_ACL_DEFAULT + #ifdef __KERNEL__ #include diff --git a/security/integrity/evm/Makefile b/security/integrity/evm/Makefile index 0787d262b9e3..7393c415a066 100644 --- a/security/integrity/evm/Makefile +++ b/security/integrity/evm/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_EVM) += evm.o evm-y := evm_main.o evm_crypto.o evm_secfs.o +evm-$(CONFIG_FS_POSIX_ACL) += evm_posix_acl.o diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index 7d4247535f9e..73c008d047c7 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -177,7 +177,14 @@ static enum integrity_status evm_verify_current_integrity(struct dentry *dentry) /* * evm_protect_xattr - protect the EVM extended attribute * - * Prevent security.evm from being modified or removed. + * Prevent security.evm from being modified or removed without the + * necessary permissions or when the existing value is invalid. + * + * The posix xattr acls are 'system' prefixed, which normally would not + * affect security.evm. An interesting side affect of writing posix xattr + * acls is their modifying of the i_mode, which is included in security.evm. + * For posix xattr acls only, permit security.evm, even if it currently + * doesn't exist, to be updated. */ static int evm_protect_xattr(struct dentry *dentry, const char *xattr_name, const void *xattr_value, size_t xattr_value_len) @@ -187,9 +194,15 @@ static int evm_protect_xattr(struct dentry *dentry, const char *xattr_name, if (strcmp(xattr_name, XATTR_NAME_EVM) == 0) { if (!capable(CAP_SYS_ADMIN)) return -EPERM; - } else if (!evm_protected_xattr(xattr_name)) - return 0; - + } else if (!evm_protected_xattr(xattr_name)) { + if (!posix_xattr_acl(xattr_name)) + return 0; + evm_status = evm_verify_current_integrity(dentry); + if ((evm_status == INTEGRITY_PASS) || + (evm_status == INTEGRITY_NOLABEL)) + return 0; + return -EPERM; + } evm_status = evm_verify_current_integrity(dentry); return evm_status == INTEGRITY_PASS ? 0 : -EPERM; } @@ -240,7 +253,8 @@ int evm_inode_removexattr(struct dentry *dentry, const char *xattr_name) void evm_inode_post_setxattr(struct dentry *dentry, const char *xattr_name, const void *xattr_value, size_t xattr_value_len) { - if (!evm_initialized || !evm_protected_xattr(xattr_name)) + if (!evm_initialized || (!evm_protected_xattr(xattr_name) + && !posix_xattr_acl(xattr_name))) return; evm_update_evmxattr(dentry, xattr_name, xattr_value, xattr_value_len); diff --git a/security/integrity/evm/evm_posix_acl.c b/security/integrity/evm/evm_posix_acl.c new file mode 100644 index 000000000000..b1753e98bf9a --- /dev/null +++ b/security/integrity/evm/evm_posix_acl.c @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2011 IBM Corporation + * + * Author: + * Mimi Zohar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + */ + +#include +#include + +int posix_xattr_acl(char *xattr) +{ + int xattr_len = strlen(xattr); + + if ((strlen(XATTR_NAME_POSIX_ACL_ACCESS) == xattr_len) + && (strncmp(XATTR_NAME_POSIX_ACL_ACCESS, xattr, xattr_len) == 0)) + return 1; + if ((strlen(XATTR_NAME_POSIX_ACL_DEFAULT) == xattr_len) + && (strncmp(XATTR_NAME_POSIX_ACL_DEFAULT, xattr, xattr_len) == 0)) + return 1; + return 0; +} From 566be59ab86c0e030b980645a580d683a015a483 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Mon, 22 Aug 2011 09:14:18 -0400 Subject: [PATCH 69/95] evm: permit mode bits to be updated Before permitting 'security.evm' to be updated, 'security.evm' must exist and be valid. In the case that there are no existing EVM protected xattrs, it is safe for posix acls to update the mode bits. To differentiate between no 'security.evm' xattr and no xattrs used to calculate 'security.evm', this patch defines INTEGRITY_NOXATTR. Signed-off-by: Mimi Zohar --- include/linux/integrity.h | 1 + security/integrity/evm/evm_main.c | 30 +++++++++++++----------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/include/linux/integrity.h b/include/linux/integrity.h index 968443385678..a0c41256cb92 100644 --- a/include/linux/integrity.h +++ b/include/linux/integrity.h @@ -16,6 +16,7 @@ enum integrity_status { INTEGRITY_PASS = 0, INTEGRITY_FAIL, INTEGRITY_NOLABEL, + INTEGRITY_NOXATTRS, INTEGRITY_UNKNOWN, }; diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index 73c008d047c7..92d3d99a9f7b 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -66,7 +66,7 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, struct integrity_iint_cache *iint) { struct evm_ima_xattr_data xattr_data; - enum integrity_status evm_status; + enum integrity_status evm_status = INTEGRITY_PASS; int rc; if (iint && iint->evm_status == INTEGRITY_PASS) @@ -76,25 +76,18 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, rc = evm_calc_hmac(dentry, xattr_name, xattr_value, xattr_value_len, xattr_data.digest); - if (rc < 0) - goto err_out; + if (rc < 0) { + evm_status = (rc == -ENODATA) + ? INTEGRITY_NOXATTRS : INTEGRITY_FAIL; + goto out; + } xattr_data.type = EVM_XATTR_HMAC; rc = vfs_xattr_cmp(dentry, XATTR_NAME_EVM, (u8 *)&xattr_data, sizeof xattr_data, GFP_NOFS); if (rc < 0) - goto err_out; - evm_status = INTEGRITY_PASS; - goto out; - -err_out: - switch (rc) { - case -ENODATA: /* file not labelled */ - evm_status = INTEGRITY_NOLABEL; - break; - default: - evm_status = INTEGRITY_FAIL; - } + evm_status = (rc == -ENODATA) + ? INTEGRITY_NOLABEL : INTEGRITY_FAIL; out: if (iint) iint->evm_status = evm_status; @@ -199,7 +192,7 @@ static int evm_protect_xattr(struct dentry *dentry, const char *xattr_name, return 0; evm_status = evm_verify_current_integrity(dentry); if ((evm_status == INTEGRITY_PASS) || - (evm_status == INTEGRITY_NOLABEL)) + (evm_status == INTEGRITY_NOXATTRS)) return 0; return -EPERM; } @@ -293,7 +286,10 @@ int evm_inode_setattr(struct dentry *dentry, struct iattr *attr) if (!(ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID))) return 0; evm_status = evm_verify_current_integrity(dentry); - return evm_status == INTEGRITY_PASS ? 0 : -EPERM; + if ((evm_status == INTEGRITY_PASS) || + (evm_status == INTEGRITY_NOXATTRS)) + return 0; + return -EPERM; } /** From fb788d8b981fa55603873416882f8dcf835e7924 Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Mon, 15 Aug 2011 15:30:11 +0300 Subject: [PATCH 70/95] evm: clean verification status When allocating from slab, initialization is done the first time in init_once() and subsequently on free. Because evm_status was not re-initialized on free, evm_verify_hmac() skipped verifications. This patch re-initializes evm_status. Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar --- security/integrity/iint.c | 1 + 1 file changed, 1 insertion(+) diff --git a/security/integrity/iint.c b/security/integrity/iint.c index 0a23e075e1d2..399641c3e846 100644 --- a/security/integrity/iint.c +++ b/security/integrity/iint.c @@ -74,6 +74,7 @@ static void iint_free(struct integrity_iint_cache *iint) { iint->version = 0; iint->flags = 0UL; + iint->evm_status = INTEGRITY_UNKNOWN; kmem_cache_free(iint_cache, iint); } From 843d183cdd816549b73e6bd3ae07f64adddf714b Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Wed, 14 Sep 2011 17:03:19 +0900 Subject: [PATCH 71/95] TOMOYO: Bump version. Tell userland tools that this is TOMOYO 2.5. Signed-off-by: Tetsuo Handa Signed-off-by: James Morris --- MAINTAINERS | 2 +- security/tomoyo/Makefile | 2 +- security/tomoyo/common.c | 12 ++++++------ security/tomoyo/common.h | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 02a60b8e503b..a790022055ca 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6443,7 +6443,7 @@ L: tomoyo-users-en@lists.sourceforge.jp (subscribers-only, for users in English) L: tomoyo-dev@lists.sourceforge.jp (subscribers-only, for developers in Japanese) L: tomoyo-users@lists.sourceforge.jp (subscribers-only, for users in Japanese) W: http://tomoyo.sourceforge.jp/ -T: quilt http://svn.sourceforge.jp/svnroot/tomoyo/trunk/2.4.x/tomoyo-lsm/patches/ +T: quilt http://svn.sourceforge.jp/svnroot/tomoyo/trunk/2.5.x/tomoyo-lsm/patches/ S: Maintained F: security/tomoyo/ diff --git a/security/tomoyo/Makefile b/security/tomoyo/Makefile index fc2a8ce40301..56a0c7be409e 100644 --- a/security/tomoyo/Makefile +++ b/security/tomoyo/Makefile @@ -27,7 +27,7 @@ $(obj)/policy/stat.conf: @touch $@ $(obj)/builtin-policy.h: $(obj)/policy/profile.conf $(obj)/policy/exception_policy.conf $(obj)/policy/domain_policy.conf $(obj)/policy/manager.conf $(obj)/policy/stat.conf - @echo Generating built-in policy for TOMOYO 2.4.x. + @echo Generating built-in policy for TOMOYO 2.5.x. @echo "static char tomoyo_builtin_profile[] __initdata =" > $@.tmp @sed -e 's/\\/\\\\/g' -e 's/\"/\\"/g' -e 's/\(.*\)/"\1\\n"/' < $(obj)/policy/profile.conf >> $@.tmp @echo "\"\";" >> $@.tmp diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c index 1fd0fc1059ba..084018351b4f 100644 --- a/security/tomoyo/common.c +++ b/security/tomoyo/common.c @@ -345,7 +345,7 @@ void tomoyo_init_policy_namespace(struct tomoyo_policy_namespace *ns) INIT_LIST_HEAD(&ns->group_list[idx]); for (idx = 0; idx < TOMOYO_MAX_POLICY; idx++) INIT_LIST_HEAD(&ns->policy_list[idx]); - ns->profile_version = 20100903; + ns->profile_version = 20110903; tomoyo_namespace_enabled = !list_empty(&tomoyo_namespace_list); list_add_tail_rcu(&ns->namespace_list, &tomoyo_namespace_list); } @@ -2222,7 +2222,7 @@ static int tomoyo_write_answer(struct tomoyo_io_buffer *head) static void tomoyo_read_version(struct tomoyo_io_buffer *head) { if (!head->r.eof) { - tomoyo_io_printf(head, "2.4.0"); + tomoyo_io_printf(head, "2.5.0"); head->r.eof = true; } } @@ -2694,11 +2694,11 @@ void tomoyo_check_profile(void) struct tomoyo_domain_info *domain; const int idx = tomoyo_read_lock(); tomoyo_policy_loaded = true; - printk(KERN_INFO "TOMOYO: 2.4.0\n"); + printk(KERN_INFO "TOMOYO: 2.5.0\n"); list_for_each_entry_rcu(domain, &tomoyo_domain_list, list) { const u8 profile = domain->profile; const struct tomoyo_policy_namespace *ns = domain->ns; - if (ns->profile_version != 20100903) + if (ns->profile_version != 20110903) printk(KERN_ERR "Profile version %u is not supported.\n", ns->profile_version); @@ -2709,9 +2709,9 @@ void tomoyo_check_profile(void) else continue; printk(KERN_ERR - "Userland tools for TOMOYO 2.4 must be installed and " + "Userland tools for TOMOYO 2.5 must be installed and " "policy must be initialized.\n"); - printk(KERN_ERR "Please see http://tomoyo.sourceforge.jp/2.4/ " + printk(KERN_ERR "Please see http://tomoyo.sourceforge.jp/2.5/ " "for more information.\n"); panic("STOP!"); } diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h index af82683df7ff..471c9f9afc18 100644 --- a/security/tomoyo/common.h +++ b/security/tomoyo/common.h @@ -3,7 +3,7 @@ * * Header file for TOMOYO. * - * Copyright (C) 2005-2010 NTT DATA CORPORATION + * Copyright (C) 2005-2011 NTT DATA CORPORATION */ #ifndef _SECURITY_TOMOYO_COMMON_H @@ -901,7 +901,7 @@ struct tomoyo_policy_namespace { struct list_head acl_group[TOMOYO_MAX_ACL_GROUPS]; /* List for connecting to tomoyo_namespace_list list. */ struct list_head namespace_list; - /* Profile version. Currently only 20100903 is defined. */ + /* Profile version. Currently only 20110903 is defined. */ unsigned int profile_version; /* Name of this namespace (e.g. "", "" ). */ const char *name; From cc100551b4d92f47abebfa7c7918b2be71263b4a Mon Sep 17 00:00:00 2001 From: Stephen Rothwell Date: Thu, 15 Sep 2011 17:07:15 +1000 Subject: [PATCH 72/95] encrypted-keys: IS_ERR need include/err.h Fixes this build error: security/keys/encrypted-keys/masterkey_trusted.c: In function 'request_trusted_key': security/keys/encrypted-keys/masterkey_trusted.c:35:2: error: implicit declaration of function 'IS_ERR' Signed-off-by: Stephen Rothwell Signed-off-by: Mimi Zohar --- security/keys/encrypted-keys/masterkey_trusted.c | 1 + 1 file changed, 1 insertion(+) diff --git a/security/keys/encrypted-keys/masterkey_trusted.c b/security/keys/encrypted-keys/masterkey_trusted.c index a5da5128891b..df87272e3f51 100644 --- a/security/keys/encrypted-keys/masterkey_trusted.c +++ b/security/keys/encrypted-keys/masterkey_trusted.c @@ -16,6 +16,7 @@ #include #include +#include #include /* From 6bce98edc3365a8f780ff3944ac7992544c194fe Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Fri, 16 Sep 2011 22:54:25 +0900 Subject: [PATCH 73/95] TOMOYO: Allow specifying domain transition preference. I got an opinion that it is difficult to use exception policy's domain transition control directives because they need to match the pathname specified to "file execute" directives. For example, if "file execute /bin/\*\-ls\-cat" is given, corresponding domain transition control directive needs to be like "no_keep_domain /bin/\*\-ls\-cat from any". If we can specify like below, it will become more convenient. file execute /bin/ls keep exec.realpath="/bin/ls" exec.argv[0]="ls" file execute /bin/cat keep exec.realpath="/bin/cat" exec.argv[0]="cat" file execute /bin/\*\-ls\-cat child file execute /usr/sbin/httpd exec.realpath="/usr/sbin/httpd" exec.argv[0]="/usr/sbin/httpd" In above examples, "keep" works as if keep_domain is specified, "child" works as if "no_reset_domain" and "no_initialize_domain" and "no_keep_domain" are specified, "" causes domain transition to domain upon successful execve() operation. Moreover, we can also allow transition to different domains based on conditions like below example. /usr/sbin/sshd file execute /bin/bash /usr/sbin/sshd //batch-session exec.argc=2 exec.argv[1]="-c" file execute /bin/bash /usr/sbin/sshd //root-session task.uid=0 file execute /bin/bash /usr/sbin/sshd //nonroot-session task.uid!=0 Signed-off-by: Tetsuo Handa Signed-off-by: James Morris --- security/tomoyo/common.c | 4 +++ security/tomoyo/common.h | 4 +++ security/tomoyo/condition.c | 50 +++++++++++++++++++++++++++++++--- security/tomoyo/domain.c | 53 ++++++++++++++++++++++++++++++++++--- security/tomoyo/file.c | 38 +++++++++++++++++++++----- 5 files changed, 137 insertions(+), 12 deletions(-) diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c index 084018351b4f..0994948f3edc 100644 --- a/security/tomoyo/common.c +++ b/security/tomoyo/common.c @@ -1203,6 +1203,10 @@ static bool tomoyo_print_condition(struct tomoyo_io_buffer *head, case 0: head->r.cond_index = 0; head->r.cond_step++; + if (cond->transit) { + tomoyo_set_space(head); + tomoyo_set_string(head, cond->transit->name); + } /* fall through */ case 1: { diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h index 471c9f9afc18..a2bc33fc60b6 100644 --- a/security/tomoyo/common.h +++ b/security/tomoyo/common.h @@ -615,6 +615,7 @@ struct tomoyo_execve { struct tomoyo_request_info r; struct tomoyo_obj_info obj; struct linux_binprm *bprm; + const struct tomoyo_path_info *transition; /* For dumping argv[] and envp[]. */ struct tomoyo_page_dump dump; /* For temporary use. */ @@ -650,6 +651,7 @@ struct tomoyo_condition { u16 argc; /* Number of "struct tomoyo_argv". */ u16 envc; /* Number of "struct tomoyo_envp". */ u8 grant_log; /* One of values in "enum tomoyo_grant_log". */ + const struct tomoyo_path_info *transit; /* Maybe NULL. */ /* * struct tomoyo_condition_element condition[condc]; * struct tomoyo_number_union values[numbers_count]; @@ -956,6 +958,8 @@ int tomoyo_check_open_permission(struct tomoyo_domain_info *domain, struct path *path, const int flag); int tomoyo_close_control(struct tomoyo_io_buffer *head); int tomoyo_env_perm(struct tomoyo_request_info *r, const char *env); +int tomoyo_execute_permission(struct tomoyo_request_info *r, + const struct tomoyo_path_info *filename); int tomoyo_find_next_domain(struct linux_binprm *bprm); int tomoyo_get_mode(const struct tomoyo_policy_namespace *ns, const u8 profile, const u8 index); diff --git a/security/tomoyo/condition.c b/security/tomoyo/condition.c index 3a05eb3e2a64..b854959c0fd4 100644 --- a/security/tomoyo/condition.c +++ b/security/tomoyo/condition.c @@ -348,7 +348,7 @@ static inline bool tomoyo_same_condition(const struct tomoyo_condition *a, a->numbers_count == b->numbers_count && a->names_count == b->names_count && a->argc == b->argc && a->envc == b->envc && - a->grant_log == b->grant_log && + a->grant_log == b->grant_log && a->transit == b->transit && !memcmp(a + 1, b + 1, a->size - sizeof(*a)); } @@ -428,6 +428,46 @@ out: return entry; } +/** + * tomoyo_get_transit_preference - Parse domain transition preference for execve(). + * + * @param: Pointer to "struct tomoyo_acl_param". + * @e: Pointer to "struct tomoyo_condition". + * + * Returns the condition string part. + */ +static char *tomoyo_get_transit_preference(struct tomoyo_acl_param *param, + struct tomoyo_condition *e) +{ + char * const pos = param->data; + bool flag; + if (*pos == '<') { + e->transit = tomoyo_get_domainname(param); + goto done; + } + { + char *cp = strchr(pos, ' '); + if (cp) + *cp = '\0'; + flag = tomoyo_correct_path(pos) || !strcmp(pos, "keep") || + !strcmp(pos, "initialize") || !strcmp(pos, "reset") || + !strcmp(pos, "child") || !strcmp(pos, "parent"); + if (cp) + *cp = ' '; + } + if (!flag) + return pos; + e->transit = tomoyo_get_name(tomoyo_read_token(param)); +done: + if (e->transit) + return param->data; + /* + * Return a bad read-only condition string that will let + * tomoyo_get_condition() return NULL. + */ + return "/"; +} + /** * tomoyo_get_condition - Parse condition part. * @@ -444,7 +484,8 @@ struct tomoyo_condition *tomoyo_get_condition(struct tomoyo_acl_param *param) struct tomoyo_argv *argv = NULL; struct tomoyo_envp *envp = NULL; struct tomoyo_condition e = { }; - char * const start_of_string = param->data; + char * const start_of_string = + tomoyo_get_transit_preference(param, &e); char * const end_of_string = start_of_string + strlen(start_of_string); char *pos; rerun: @@ -608,8 +649,9 @@ store_value: + e.envc * sizeof(struct tomoyo_envp); entry = kzalloc(e.size, GFP_NOFS); if (!entry) - return NULL; + goto out2; *entry = e; + e.transit = NULL; condp = (struct tomoyo_condition_element *) (entry + 1); numbers_p = (struct tomoyo_number_union *) (condp + e.condc); names_p = (struct tomoyo_name_union *) (numbers_p + e.numbers_count); @@ -636,6 +678,8 @@ out: tomoyo_del_condition(&entry->head.list); kfree(entry); } +out2: + tomoyo_put_name(e.transit); return NULL; } diff --git a/security/tomoyo/domain.c b/security/tomoyo/domain.c index a1fc6b5f6125..860390ee1fbe 100644 --- a/security/tomoyo/domain.c +++ b/security/tomoyo/domain.c @@ -102,6 +102,15 @@ int tomoyo_update_domain(struct tomoyo_acl_info *new_entry, const int size, new_entry->cond = tomoyo_get_condition(param); if (!new_entry->cond) return -EINVAL; + /* + * Domain transition preference is allowed for only + * "file execute" entries. + */ + if (new_entry->cond->transit && + !(new_entry->type == TOMOYO_TYPE_PATH_ACL && + container_of(new_entry, struct tomoyo_path_acl, head) + ->perm == 1 << TOMOYO_TYPE_EXECUTE)) + goto out; } if (mutex_lock_interruptible(&tomoyo_policy_lock)) goto out; @@ -707,8 +716,7 @@ retry: } /* Check execute permission. */ - retval = tomoyo_path_permission(&ee->r, TOMOYO_TYPE_EXECUTE, - candidate); + retval = tomoyo_execute_permission(&ee->r, candidate); if (retval == TOMOYO_RETRY_REQUEST) goto retry; if (retval < 0) @@ -722,10 +730,45 @@ retry: if (ee->r.param.path.matched_path) candidate = ee->r.param.path.matched_path; - /* Calculate domain to transit to. */ + /* + * Check for domain transition preference if "file execute" matched. + * If preference is given, make do_execve() fail if domain transition + * has failed, for domain transition preference should be used with + * destination domain defined. + */ + if (ee->transition) { + const char *domainname = ee->transition->name; + reject_on_transition_failure = true; + if (!strcmp(domainname, "keep")) + goto force_keep_domain; + if (!strcmp(domainname, "child")) + goto force_child_domain; + if (!strcmp(domainname, "reset")) + goto force_reset_domain; + if (!strcmp(domainname, "initialize")) + goto force_initialize_domain; + if (!strcmp(domainname, "parent")) { + char *cp; + strncpy(ee->tmp, old_domain->domainname->name, + TOMOYO_EXEC_TMPSIZE - 1); + cp = strrchr(ee->tmp, ' '); + if (cp) + *cp = '\0'; + } else if (*domainname == '<') + strncpy(ee->tmp, domainname, TOMOYO_EXEC_TMPSIZE - 1); + else + snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "%s %s", + old_domain->domainname->name, domainname); + goto force_jump_domain; + } + /* + * No domain transition preference specified. + * Calculate domain to transit to. + */ switch (tomoyo_transition_type(old_domain->ns, old_domain->domainname, candidate)) { case TOMOYO_TRANSITION_CONTROL_RESET: +force_reset_domain: /* Transit to the root of specified namespace. */ snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "<%s>", candidate->name); @@ -736,11 +779,13 @@ retry: reject_on_transition_failure = true; break; case TOMOYO_TRANSITION_CONTROL_INITIALIZE: +force_initialize_domain: /* Transit to the child of current namespace's root. */ snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "%s %s", old_domain->ns->name, candidate->name); break; case TOMOYO_TRANSITION_CONTROL_KEEP: +force_keep_domain: /* Keep current domain. */ domain = old_domain; break; @@ -756,11 +801,13 @@ retry: domain = old_domain; break; } +force_child_domain: /* Normal domain transition. */ snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "%s %s", old_domain->domainname->name, candidate->name); break; } +force_jump_domain: if (!domain) domain = tomoyo_assign_domain(ee->tmp, true); if (domain) diff --git a/security/tomoyo/file.c b/security/tomoyo/file.c index 743c35f5084a..b280c1bd652d 100644 --- a/security/tomoyo/file.c +++ b/security/tomoyo/file.c @@ -570,15 +570,41 @@ int tomoyo_path_permission(struct tomoyo_request_info *r, u8 operation, do { tomoyo_check_acl(r, tomoyo_check_path_acl); error = tomoyo_audit_path_log(r); - /* - * Do not retry for execute request, for alias may have - * changed. - */ - } while (error == TOMOYO_RETRY_REQUEST && - operation != TOMOYO_TYPE_EXECUTE); + } while (error == TOMOYO_RETRY_REQUEST); return error; } +/** + * tomoyo_execute_permission - Check permission for execute operation. + * + * @r: Pointer to "struct tomoyo_request_info". + * @filename: Filename to check. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_execute_permission(struct tomoyo_request_info *r, + const struct tomoyo_path_info *filename) +{ + /* + * Unlike other permission checks, this check is done regardless of + * profile mode settings in order to check for domain transition + * preference. + */ + r->type = TOMOYO_MAC_FILE_EXECUTE; + r->mode = tomoyo_get_mode(r->domain->ns, r->profile, r->type); + r->param_type = TOMOYO_TYPE_PATH_ACL; + r->param.path.filename = filename; + r->param.path.operation = TOMOYO_TYPE_EXECUTE; + tomoyo_check_acl(r, tomoyo_check_path_acl); + r->ee->transition = r->matched_acl && r->matched_acl->cond ? + r->matched_acl->cond->transit : NULL; + if (r->mode != TOMOYO_CONFIG_DISABLED) + return tomoyo_audit_path_log(r); + return 0; +} + /** * tomoyo_same_path_number_acl - Check for duplicated "struct tomoyo_path_number_acl" entry. * From b78049831ffed65f0b4e61f69df14f3ab17922cb Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Tue, 20 Sep 2011 11:23:49 -0400 Subject: [PATCH 74/95] lib: add error checking to hex2bin hex2bin converts a hexadecimal string to its binary representation. The original version of hex2bin did not do any error checking. This patch adds error checking and returns the result. Changelog v1: - removed unpack_hex_byte() - changed return code from boolean to int Changelog: - use the new unpack_hex_byte() - add __must_check compiler option (Andy Shevchenko's suggestion) - change function API to return error checking result (based on Tetsuo Handa's initial patch) Signed-off-by: Mimi Zohar Acked-by: Andy Shevchenko --- include/linux/kernel.h | 2 +- lib/hexdump.c | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/include/linux/kernel.h b/include/linux/kernel.h index 46ac9a50528d..8eefcf7e95eb 100644 --- a/include/linux/kernel.h +++ b/include/linux/kernel.h @@ -382,7 +382,7 @@ static inline char *pack_hex_byte(char *buf, u8 byte) } extern int hex_to_bin(char ch); -extern void hex2bin(u8 *dst, const char *src, size_t count); +extern int __must_check hex2bin(u8 *dst, const char *src, size_t count); /* * General tracing related utility functions - trace_printk(), diff --git a/lib/hexdump.c b/lib/hexdump.c index f5fe6ba7a3ab..51d5ae210244 100644 --- a/lib/hexdump.c +++ b/lib/hexdump.c @@ -38,14 +38,21 @@ EXPORT_SYMBOL(hex_to_bin); * @dst: binary result * @src: ascii hexadecimal string * @count: result length + * + * Return 0 on success, -1 in case of bad input. */ -void hex2bin(u8 *dst, const char *src, size_t count) +int hex2bin(u8 *dst, const char *src, size_t count) { while (count--) { - *dst = hex_to_bin(*src++) << 4; - *dst += hex_to_bin(*src++); - dst++; + int hi = hex_to_bin(*src++); + int lo = hex_to_bin(*src++); + + if ((hi < 0) || (lo < 0)) + return -1; + + *dst++ = (hi << 4) | lo; } + return 0; } EXPORT_SYMBOL(hex2bin); From 2684bf7f29cfb13ef2c60f3b3a53ee47d0db7022 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Tue, 20 Sep 2011 11:23:52 -0400 Subject: [PATCH 75/95] trusted-keys: check hex2bin result For each hex2bin call in trusted keys, check that the ascii hex string is valid. On failure, return -EINVAL. Changelog v1: - hex2bin now returns an int Signed-off-by: Mimi Zohar Acked-by: Andy Shevchenko --- security/keys/trusted.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/security/keys/trusted.c b/security/keys/trusted.c index 0c33e2ea1f3c..0964fc236946 100644 --- a/security/keys/trusted.c +++ b/security/keys/trusted.c @@ -779,7 +779,10 @@ static int getoptions(char *c, struct trusted_key_payload *pay, opt->pcrinfo_len = strlen(args[0].from) / 2; if (opt->pcrinfo_len > MAX_PCRINFO_SIZE) return -EINVAL; - hex2bin(opt->pcrinfo, args[0].from, opt->pcrinfo_len); + res = hex2bin(opt->pcrinfo, args[0].from, + opt->pcrinfo_len); + if (res < 0) + return -EINVAL; break; case Opt_keyhandle: res = strict_strtoul(args[0].from, 16, &handle); @@ -791,12 +794,18 @@ static int getoptions(char *c, struct trusted_key_payload *pay, case Opt_keyauth: if (strlen(args[0].from) != 2 * SHA1_DIGEST_SIZE) return -EINVAL; - hex2bin(opt->keyauth, args[0].from, SHA1_DIGEST_SIZE); + res = hex2bin(opt->keyauth, args[0].from, + SHA1_DIGEST_SIZE); + if (res < 0) + return -EINVAL; break; case Opt_blobauth: if (strlen(args[0].from) != 2 * SHA1_DIGEST_SIZE) return -EINVAL; - hex2bin(opt->blobauth, args[0].from, SHA1_DIGEST_SIZE); + res = hex2bin(opt->blobauth, args[0].from, + SHA1_DIGEST_SIZE); + if (res < 0) + return -EINVAL; break; case Opt_migratable: if (*args[0].from == '0') @@ -860,7 +869,9 @@ static int datablob_parse(char *datablob, struct trusted_key_payload *p, p->blob_len = strlen(c) / 2; if (p->blob_len > MAX_BLOB_SIZE) return -EINVAL; - hex2bin(p->blob, c, p->blob_len); + ret = hex2bin(p->blob, c, p->blob_len); + if (ret < 0) + return -EINVAL; ret = getoptions(datablob, p, o); if (ret < 0) return ret; From 2b3ff6319e2312656fbefe0209bef02d58b6836a Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Tue, 20 Sep 2011 11:23:55 -0400 Subject: [PATCH 76/95] encrypted-keys: check hex2bin result For each hex2bin call in encrypted keys, check that the ascii hex string is valid. On failure, return -EINVAL. Changelog v1: - hex2bin now returns an int Signed-off-by: Mimi Zohar Acked-by: Andy Shevchenko --- security/keys/encrypted-keys/encrypted.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c index 3f577954b85a..f33804c1b4c8 100644 --- a/security/keys/encrypted-keys/encrypted.c +++ b/security/keys/encrypted-keys/encrypted.c @@ -667,11 +667,19 @@ static int encrypted_key_decrypt(struct encrypted_key_payload *epayload, return -EINVAL; hex_encoded_data = hex_encoded_iv + (2 * ivsize) + 2; - hex2bin(epayload->iv, hex_encoded_iv, ivsize); - hex2bin(epayload->encrypted_data, hex_encoded_data, encrypted_datalen); + ret = hex2bin(epayload->iv, hex_encoded_iv, ivsize); + if (ret < 0) + return -EINVAL; + ret = hex2bin(epayload->encrypted_data, hex_encoded_data, + encrypted_datalen); + if (ret < 0) + return -EINVAL; hmac = epayload->format + epayload->datablob_len; - hex2bin(hmac, hex_encoded_data + (encrypted_datalen * 2), HASH_SIZE); + ret = hex2bin(hmac, hex_encoded_data + (encrypted_datalen * 2), + HASH_SIZE); + if (ret < 0) + return -EINVAL; mkey = request_master_key(epayload, &master_key, &master_keylen); if (IS_ERR(mkey)) From 8c35ad20270de91d0f3bfe521daa3b7983ee8db7 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Fri, 16 Sep 2011 08:50:30 -0400 Subject: [PATCH 77/95] target: check hex2bin result Now that hex2bin does error checking, on error add debugging error msg. Changelog v1 (update): - fixed definition of 'ret' - hex2bin now returns an int Signed-off-by: Mimi Zohar Acked-by: Andy Shevchenko --- drivers/target/target_core_fabric_lib.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/target/target_core_fabric_lib.c b/drivers/target/target_core_fabric_lib.c index c4ea3a9a555b..39f021b855ef 100644 --- a/drivers/target/target_core_fabric_lib.c +++ b/drivers/target/target_core_fabric_lib.c @@ -63,6 +63,7 @@ u32 sas_get_pr_transport_id( unsigned char *buf) { unsigned char *ptr; + int ret; /* * Set PROTOCOL IDENTIFIER to 6h for SAS @@ -74,7 +75,9 @@ u32 sas_get_pr_transport_id( */ ptr = &se_nacl->initiatorname[4]; /* Skip over 'naa. prefix */ - hex2bin(&buf[4], ptr, 8); + ret = hex2bin(&buf[4], ptr, 8); + if (ret < 0) + pr_debug("sas transport_id: invalid hex string\n"); /* * The SAS Transport ID is a hardcoded 24-byte length @@ -156,8 +159,9 @@ u32 fc_get_pr_transport_id( unsigned char *buf) { unsigned char *ptr; - int i; + int i, ret; u32 off = 8; + /* * PROTOCOL IDENTIFIER is 0h for FCP-2 * @@ -174,7 +178,9 @@ u32 fc_get_pr_transport_id( i++; continue; } - hex2bin(&buf[off++], &ptr[i], 1); + ret = hex2bin(&buf[off++], &ptr[i], 1); + if (ret < 0) + pr_debug("fc transport_id: invalid hex string\n"); i += 2; } /* From 778c4a4d60d932c1df6d270dcbc88365823c3963 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Sun, 25 Sep 2011 17:49:09 +0900 Subject: [PATCH 78/95] TOMOYO: Fix make namespacecheck warnings. Commit efe836ab "TOMOYO: Add built-in policy support." introduced tomoyo_load_builtin_policy() but was by error called from nowhere. Commit b22b8b9f "TOMOYO: Rename meminfo to stat and show more statistics." introduced tomoyo_update_stat() but was by error not called from tomoyo_assign_domain(). Also, mark tomoyo_io_printf() and tomoyo_path_permission() static functions, as reported by "make namespacecheck". Signed-off-by: Tetsuo Handa Signed-off-by: James Morris --- security/tomoyo/common.c | 6 +++++- security/tomoyo/common.h | 4 ---- security/tomoyo/domain.c | 1 + security/tomoyo/file.c | 4 ++-- security/tomoyo/securityfs_if.c | 1 + 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c index 0994948f3edc..2e2802060eef 100644 --- a/security/tomoyo/common.c +++ b/security/tomoyo/common.c @@ -262,13 +262,17 @@ static void tomoyo_set_string(struct tomoyo_io_buffer *head, const char *string) WARN_ON(1); } +static void tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt, + ...) __printf(2, 3); + /** * tomoyo_io_printf - printf() to "struct tomoyo_io_buffer" structure. * * @head: Pointer to "struct tomoyo_io_buffer". * @fmt: The printf()'s format string, followed by parameters. */ -void tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt, ...) +static void tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt, + ...) { va_list args; size_t len; diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h index a2bc33fc60b6..1a19ad3e67ea 100644 --- a/security/tomoyo/common.h +++ b/security/tomoyo/common.h @@ -978,8 +978,6 @@ int tomoyo_path_number_perm(const u8 operation, struct path *path, unsigned long number); int tomoyo_path_perm(const u8 operation, struct path *path, const char *target); -int tomoyo_path_permission(struct tomoyo_request_info *r, u8 operation, - const struct tomoyo_path_info *filename); int tomoyo_poll_control(struct file *file, poll_table *wait); int tomoyo_poll_log(struct file *file, poll_table *wait); int tomoyo_socket_bind_permission(struct socket *sock, struct sockaddr *addr, @@ -1041,8 +1039,6 @@ void tomoyo_del_condition(struct list_head *element); void tomoyo_fill_path_info(struct tomoyo_path_info *ptr); void tomoyo_get_attributes(struct tomoyo_obj_info *obj); void tomoyo_init_policy_namespace(struct tomoyo_policy_namespace *ns); -void tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt, ...) - __printf(2, 3); void tomoyo_load_policy(const char *filename); void tomoyo_memory_free(void *ptr); void tomoyo_normalize_line(unsigned char *buffer); diff --git a/security/tomoyo/domain.c b/security/tomoyo/domain.c index 860390ee1fbe..70acf7aebbda 100644 --- a/security/tomoyo/domain.c +++ b/security/tomoyo/domain.c @@ -567,6 +567,7 @@ out: tomoyo_write_log(&r, "use_profile %u\n", entry->profile); tomoyo_write_log(&r, "use_group %u\n", entry->group); + tomoyo_update_stat(TOMOYO_STAT_POLICY_UPDATES); } } return entry; diff --git a/security/tomoyo/file.c b/security/tomoyo/file.c index b280c1bd652d..400390790745 100644 --- a/security/tomoyo/file.c +++ b/security/tomoyo/file.c @@ -555,8 +555,8 @@ static int tomoyo_update_path2_acl(const u8 perm, * * Caller holds tomoyo_read_lock(). */ -int tomoyo_path_permission(struct tomoyo_request_info *r, u8 operation, - const struct tomoyo_path_info *filename) +static int tomoyo_path_permission(struct tomoyo_request_info *r, u8 operation, + const struct tomoyo_path_info *filename) { int error; diff --git a/security/tomoyo/securityfs_if.c b/security/tomoyo/securityfs_if.c index d08296a4882b..2672ac4f3beb 100644 --- a/security/tomoyo/securityfs_if.c +++ b/security/tomoyo/securityfs_if.c @@ -265,6 +265,7 @@ static int __init tomoyo_initerface_init(void) TOMOYO_VERSION); securityfs_create_file("self_domain", 0666, tomoyo_dir, NULL, &tomoyo_self_operations); + tomoyo_load_builtin_policy(); return 0; } From f9732ea145886786a6f8b0493bc2239e70cbacdb Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Sun, 25 Sep 2011 17:50:23 +0900 Subject: [PATCH 79/95] TOMOYO: Simplify garbage collector. When TOMOYO started using garbage collector at commit 847b173e "TOMOYO: Add garbage collector.", we waited for close() before kfree(). Thus, elements to be kfree()d were queued up using tomoyo_gc_list list. But it turned out that tomoyo_element_linked_by_gc() tends to choke garbage collector when certain pattern of entries are queued. Since garbage collector is no longer waiting for close() since commit 2e503bbb "TOMOYO: Fix lockdep warning.", we can remove tomoyo_gc_list list and tomoyo_element_linked_by_gc() by doing sequential processing. Signed-off-by: Tetsuo Handa Signed-off-by: James Morris --- security/tomoyo/common.h | 7 +- security/tomoyo/condition.c | 8 +- security/tomoyo/domain.c | 4 + security/tomoyo/gc.c | 480 +++++++++++++----------------------- security/tomoyo/memory.c | 6 +- 5 files changed, 186 insertions(+), 319 deletions(-) diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h index 1a19ad3e67ea..a0212fbf60fb 100644 --- a/security/tomoyo/common.h +++ b/security/tomoyo/common.h @@ -52,6 +52,9 @@ #define TOMOYO_EXEC_TMPSIZE 4096 +/* Garbage collector is trying to kfree() this element. */ +#define TOMOYO_GC_IN_PROGRESS -1 + /* Profile number is an integer between 0 and 255. */ #define TOMOYO_MAX_PROFILES 256 @@ -398,7 +401,7 @@ enum tomoyo_pref_index { /* Common header for holding ACL entries. */ struct tomoyo_acl_head { struct list_head list; - bool is_deleted; + s8 is_deleted; /* true or false or TOMOYO_GC_IN_PROGRESS */ } __packed; /* Common header for shared entries. */ @@ -665,7 +668,7 @@ struct tomoyo_condition { struct tomoyo_acl_info { struct list_head list; struct tomoyo_condition *cond; /* Maybe NULL. */ - bool is_deleted; + s8 is_deleted; /* true or false or TOMOYO_GC_IN_PROGRESS */ u8 type; /* One of values in "enum tomoyo_acl_entry_type_index". */ } __packed; diff --git a/security/tomoyo/condition.c b/security/tomoyo/condition.c index b854959c0fd4..986330b8c73e 100644 --- a/security/tomoyo/condition.c +++ b/security/tomoyo/condition.c @@ -400,8 +400,9 @@ static struct tomoyo_condition *tomoyo_commit_condition found = true; goto out; } - list_for_each_entry_rcu(ptr, &tomoyo_condition_list, head.list) { - if (!tomoyo_same_condition(ptr, entry)) + list_for_each_entry(ptr, &tomoyo_condition_list, head.list) { + if (!tomoyo_same_condition(ptr, entry) || + atomic_read(&ptr->head.users) == TOMOYO_GC_IN_PROGRESS) continue; /* Same entry found. Share this entry. */ atomic_inc(&ptr->head.users); @@ -411,8 +412,7 @@ static struct tomoyo_condition *tomoyo_commit_condition if (!found) { if (tomoyo_memory_ok(entry)) { atomic_set(&entry->head.users, 1); - list_add_rcu(&entry->head.list, - &tomoyo_condition_list); + list_add(&entry->head.list, &tomoyo_condition_list); } else { found = true; ptr = NULL; diff --git a/security/tomoyo/domain.c b/security/tomoyo/domain.c index 70acf7aebbda..da16dfeed728 100644 --- a/security/tomoyo/domain.c +++ b/security/tomoyo/domain.c @@ -39,6 +39,8 @@ int tomoyo_update_policy(struct tomoyo_acl_head *new_entry, const int size, if (mutex_lock_interruptible(&tomoyo_policy_lock)) return -ENOMEM; list_for_each_entry_rcu(entry, list, list) { + if (entry->is_deleted == TOMOYO_GC_IN_PROGRESS) + continue; if (!check_duplicate(entry, new_entry)) continue; entry->is_deleted = param->is_delete; @@ -115,6 +117,8 @@ int tomoyo_update_domain(struct tomoyo_acl_info *new_entry, const int size, if (mutex_lock_interruptible(&tomoyo_policy_lock)) goto out; list_for_each_entry_rcu(entry, list, list) { + if (entry->is_deleted == TOMOYO_GC_IN_PROGRESS) + continue; if (!tomoyo_same_acl_head(entry, new_entry) || !check_duplicate(entry, new_entry)) continue; diff --git a/security/tomoyo/gc.c b/security/tomoyo/gc.c index 7747ceb9a221..f2295c65f1e4 100644 --- a/security/tomoyo/gc.c +++ b/security/tomoyo/gc.c @@ -13,35 +13,6 @@ static LIST_HEAD(tomoyo_io_buffer_list); /* Lock for protecting tomoyo_io_buffer_list. */ static DEFINE_SPINLOCK(tomoyo_io_buffer_list_lock); -/* Size of an element. */ -static const u8 tomoyo_element_size[TOMOYO_MAX_POLICY] = { - [TOMOYO_ID_GROUP] = sizeof(struct tomoyo_group), - [TOMOYO_ID_ADDRESS_GROUP] = sizeof(struct tomoyo_address_group), - [TOMOYO_ID_PATH_GROUP] = sizeof(struct tomoyo_path_group), - [TOMOYO_ID_NUMBER_GROUP] = sizeof(struct tomoyo_number_group), - [TOMOYO_ID_AGGREGATOR] = sizeof(struct tomoyo_aggregator), - [TOMOYO_ID_TRANSITION_CONTROL] = - sizeof(struct tomoyo_transition_control), - [TOMOYO_ID_MANAGER] = sizeof(struct tomoyo_manager), - /* [TOMOYO_ID_CONDITION] = "struct tomoyo_condition"->size, */ - /* [TOMOYO_ID_NAME] = "struct tomoyo_name"->size, */ - /* [TOMOYO_ID_ACL] = - tomoyo_acl_size["struct tomoyo_acl_info"->type], */ - [TOMOYO_ID_DOMAIN] = sizeof(struct tomoyo_domain_info), -}; - -/* Size of a domain ACL element. */ -static const u8 tomoyo_acl_size[] = { - [TOMOYO_TYPE_PATH_ACL] = sizeof(struct tomoyo_path_acl), - [TOMOYO_TYPE_PATH2_ACL] = sizeof(struct tomoyo_path2_acl), - [TOMOYO_TYPE_PATH_NUMBER_ACL] = sizeof(struct tomoyo_path_number_acl), - [TOMOYO_TYPE_MKDEV_ACL] = sizeof(struct tomoyo_mkdev_acl), - [TOMOYO_TYPE_MOUNT_ACL] = sizeof(struct tomoyo_mount_acl), - [TOMOYO_TYPE_INET_ACL] = sizeof(struct tomoyo_inet_acl), - [TOMOYO_TYPE_UNIX_ACL] = sizeof(struct tomoyo_unix_acl), - [TOMOYO_TYPE_ENV_ACL] = sizeof(struct tomoyo_env_acl), -}; - /** * tomoyo_struct_used_by_io_buffer - Check whether the list element is used by /sys/kernel/security/tomoyo/ users or not. * @@ -59,15 +30,11 @@ static bool tomoyo_struct_used_by_io_buffer(const struct list_head *element) list_for_each_entry(head, &tomoyo_io_buffer_list, list) { head->users++; spin_unlock(&tomoyo_io_buffer_list_lock); - if (mutex_lock_interruptible(&head->io_sem)) { - in_use = true; - goto out; - } + mutex_lock(&head->io_sem); if (head->r.domain == element || head->r.group == element || head->r.acl == element || &head->w.domain->list == element) in_use = true; mutex_unlock(&head->io_sem); -out: spin_lock(&tomoyo_io_buffer_list_lock); head->users--; if (in_use) @@ -81,15 +48,14 @@ out: * tomoyo_name_used_by_io_buffer - Check whether the string is used by /sys/kernel/security/tomoyo/ users or not. * * @string: String to check. - * @size: Memory allocated for @string . * * Returns true if @string is used by /sys/kernel/security/tomoyo/ users, * false otherwise. */ -static bool tomoyo_name_used_by_io_buffer(const char *string, - const size_t size) +static bool tomoyo_name_used_by_io_buffer(const char *string) { struct tomoyo_io_buffer *head; + const size_t size = strlen(string) + 1; bool in_use = false; spin_lock(&tomoyo_io_buffer_list_lock); @@ -97,10 +63,7 @@ static bool tomoyo_name_used_by_io_buffer(const char *string, int i; head->users++; spin_unlock(&tomoyo_io_buffer_list_lock); - if (mutex_lock_interruptible(&head->io_sem)) { - in_use = true; - goto out; - } + mutex_lock(&head->io_sem); for (i = 0; i < TOMOYO_MAX_IO_READ_QUEUE; i++) { const char *w = head->r.w[i]; if (w < string || w > string + size) @@ -109,7 +72,6 @@ static bool tomoyo_name_used_by_io_buffer(const char *string, break; } mutex_unlock(&head->io_sem); -out: spin_lock(&tomoyo_io_buffer_list_lock); head->users--; if (in_use) @@ -119,84 +81,6 @@ out: return in_use; } -/* Structure for garbage collection. */ -struct tomoyo_gc { - struct list_head list; - enum tomoyo_policy_id type; - size_t size; - struct list_head *element; -}; -/* List of entries to be deleted. */ -static LIST_HEAD(tomoyo_gc_list); -/* Length of tomoyo_gc_list. */ -static int tomoyo_gc_list_len; - -/** - * tomoyo_add_to_gc - Add an entry to to be deleted list. - * - * @type: One of values in "enum tomoyo_policy_id". - * @element: Pointer to "struct list_head". - * - * Returns true on success, false otherwise. - * - * Caller holds tomoyo_policy_lock mutex. - * - * Adding an entry needs kmalloc(). Thus, if we try to add thousands of - * entries at once, it will take too long time. Thus, do not add more than 128 - * entries per a scan. But to be able to handle worst case where all entries - * are in-use, we accept one more entry per a scan. - * - * If we use singly linked list using "struct list_head"->prev (which is - * LIST_POISON2), we can avoid kmalloc(). - */ -static bool tomoyo_add_to_gc(const int type, struct list_head *element) -{ - struct tomoyo_gc *entry = kzalloc(sizeof(*entry), GFP_ATOMIC); - if (!entry) - return false; - entry->type = type; - if (type == TOMOYO_ID_ACL) - entry->size = tomoyo_acl_size[ - container_of(element, - typeof(struct tomoyo_acl_info), - list)->type]; - else if (type == TOMOYO_ID_NAME) - entry->size = strlen(container_of(element, - typeof(struct tomoyo_name), - head.list)->entry.name) + 1; - else if (type == TOMOYO_ID_CONDITION) - entry->size = - container_of(element, typeof(struct tomoyo_condition), - head.list)->size; - else - entry->size = tomoyo_element_size[type]; - entry->element = element; - list_add(&entry->list, &tomoyo_gc_list); - list_del_rcu(element); - return tomoyo_gc_list_len++ < 128; -} - -/** - * tomoyo_element_linked_by_gc - Validate next element of an entry. - * - * @element: Pointer to an element. - * @size: Size of @element in byte. - * - * Returns true if @element is linked by other elements in the garbage - * collector's queue, false otherwise. - */ -static bool tomoyo_element_linked_by_gc(const u8 *element, const size_t size) -{ - struct tomoyo_gc *p; - list_for_each_entry(p, &tomoyo_gc_list, list) { - const u8 *ptr = (const u8 *) p->element->next; - if (ptr < element || element + size < ptr) - continue; - return true; - } - return false; -} - /** * tomoyo_del_transition_control - Delete members in "struct tomoyo_transition_control". * @@ -204,7 +88,7 @@ static bool tomoyo_element_linked_by_gc(const u8 *element, const size_t size) * * Returns nothing. */ -static void tomoyo_del_transition_control(struct list_head *element) +static inline void tomoyo_del_transition_control(struct list_head *element) { struct tomoyo_transition_control *ptr = container_of(element, typeof(*ptr), head.list); @@ -219,7 +103,7 @@ static void tomoyo_del_transition_control(struct list_head *element) * * Returns nothing. */ -static void tomoyo_del_aggregator(struct list_head *element) +static inline void tomoyo_del_aggregator(struct list_head *element) { struct tomoyo_aggregator *ptr = container_of(element, typeof(*ptr), head.list); @@ -234,7 +118,7 @@ static void tomoyo_del_aggregator(struct list_head *element) * * Returns nothing. */ -static void tomoyo_del_manager(struct list_head *element) +static inline void tomoyo_del_manager(struct list_head *element) { struct tomoyo_manager *ptr = container_of(element, typeof(*ptr), head.list); @@ -330,44 +214,24 @@ static void tomoyo_del_acl(struct list_head *element) * * @element: Pointer to "struct list_head". * - * Returns true if deleted, false otherwise. + * Returns nothing. */ -static bool tomoyo_del_domain(struct list_head *element) +static inline void tomoyo_del_domain(struct list_head *element) { struct tomoyo_domain_info *domain = container_of(element, typeof(*domain), list); struct tomoyo_acl_info *acl; struct tomoyo_acl_info *tmp; /* - * Since we don't protect whole execve() operation using SRCU, - * we need to recheck domain->users at this point. - * - * (1) Reader starts SRCU section upon execve(). - * (2) Reader traverses tomoyo_domain_list and finds this domain. - * (3) Writer marks this domain as deleted. - * (4) Garbage collector removes this domain from tomoyo_domain_list - * because this domain is marked as deleted and used by nobody. - * (5) Reader saves reference to this domain into - * "struct linux_binprm"->cred->security . - * (6) Reader finishes SRCU section, although execve() operation has - * not finished yet. - * (7) Garbage collector waits for SRCU synchronization. - * (8) Garbage collector kfree() this domain because this domain is - * used by nobody. - * (9) Reader finishes execve() operation and restores this domain from - * "struct linux_binprm"->cred->security. - * - * By updating domain->users at (5), we can solve this race problem - * by rechecking domain->users at (8). + * Since this domain is referenced from neither + * "struct tomoyo_io_buffer" nor "struct cred"->security, we can delete + * elements without checking for is_deleted flag. */ - if (atomic_read(&domain->users)) - return false; list_for_each_entry_safe(acl, tmp, &domain->acl_info_list, list) { tomoyo_del_acl(&acl->list); tomoyo_memory_free(acl); } tomoyo_put_name(domain->domainname); - return true; } /** @@ -416,10 +280,9 @@ void tomoyo_del_condition(struct list_head *element) * * Returns nothing. */ -static void tomoyo_del_name(struct list_head *element) +static inline void tomoyo_del_name(struct list_head *element) { - const struct tomoyo_name *ptr = - container_of(element, typeof(*ptr), head.list); + /* Nothing to do. */ } /** @@ -429,7 +292,7 @@ static void tomoyo_del_name(struct list_head *element) * * Returns nothing. */ -static void tomoyo_del_path_group(struct list_head *element) +static inline void tomoyo_del_path_group(struct list_head *element) { struct tomoyo_path_group *member = container_of(element, typeof(*member), head.list); @@ -443,7 +306,7 @@ static void tomoyo_del_path_group(struct list_head *element) * * Returns nothing. */ -static void tomoyo_del_group(struct list_head *element) +static inline void tomoyo_del_group(struct list_head *element) { struct tomoyo_group *group = container_of(element, typeof(*group), head.list); @@ -469,10 +332,109 @@ static inline void tomoyo_del_address_group(struct list_head *element) * * Returns nothing. */ -static void tomoyo_del_number_group(struct list_head *element) +static inline void tomoyo_del_number_group(struct list_head *element) { - struct tomoyo_number_group *member = - container_of(element, typeof(*member), head.list); + /* Nothing to do. */ +} + +/** + * tomoyo_try_to_gc - Try to kfree() an entry. + * + * @type: One of values in "enum tomoyo_policy_id". + * @element: Pointer to "struct list_head". + * + * Returns nothing. + * + * Caller holds tomoyo_policy_lock mutex. + */ +static void tomoyo_try_to_gc(const enum tomoyo_policy_id type, + struct list_head *element) +{ + /* + * __list_del_entry() guarantees that the list element became no longer + * reachable from the list which the element was originally on (e.g. + * tomoyo_domain_list). Also, synchronize_srcu() guarantees that the + * list element became no longer referenced by syscall users. + */ + __list_del_entry(element); + mutex_unlock(&tomoyo_policy_lock); + synchronize_srcu(&tomoyo_ss); + /* + * However, there are two users which may still be using the list + * element. We need to defer until both users forget this element. + * + * Don't kfree() until "struct tomoyo_io_buffer"->r.{domain,group,acl} + * and "struct tomoyo_io_buffer"->w.domain forget this element. + */ + if (tomoyo_struct_used_by_io_buffer(element)) + goto reinject; + switch (type) { + case TOMOYO_ID_TRANSITION_CONTROL: + tomoyo_del_transition_control(element); + break; + case TOMOYO_ID_MANAGER: + tomoyo_del_manager(element); + break; + case TOMOYO_ID_AGGREGATOR: + tomoyo_del_aggregator(element); + break; + case TOMOYO_ID_GROUP: + tomoyo_del_group(element); + break; + case TOMOYO_ID_PATH_GROUP: + tomoyo_del_path_group(element); + break; + case TOMOYO_ID_ADDRESS_GROUP: + tomoyo_del_address_group(element); + break; + case TOMOYO_ID_NUMBER_GROUP: + tomoyo_del_number_group(element); + break; + case TOMOYO_ID_CONDITION: + tomoyo_del_condition(element); + break; + case TOMOYO_ID_NAME: + /* + * Don't kfree() until all "struct tomoyo_io_buffer"->r.w[] + * forget this element. + */ + if (tomoyo_name_used_by_io_buffer + (container_of(element, typeof(struct tomoyo_name), + head.list)->entry.name)) + goto reinject; + tomoyo_del_name(element); + break; + case TOMOYO_ID_ACL: + tomoyo_del_acl(element); + break; + case TOMOYO_ID_DOMAIN: + /* + * Don't kfree() until all "struct cred"->security forget this + * element. + */ + if (atomic_read(&container_of + (element, typeof(struct tomoyo_domain_info), + list)->users)) + goto reinject; + tomoyo_del_domain(element); + break; + case TOMOYO_MAX_POLICY: + break; + } + mutex_lock(&tomoyo_policy_lock); + tomoyo_memory_free(element); + return; +reinject: + /* + * We can safely reinject this element here bacause + * (1) Appending list elements and removing list elements are protected + * by tomoyo_policy_lock mutex. + * (2) Only this function removes list elements and this function is + * exclusively executed by tomoyo_gc_mutex mutex. + * are true. + */ + mutex_lock(&tomoyo_policy_lock); + list_add_rcu(element, element->prev); } /** @@ -481,19 +443,19 @@ static void tomoyo_del_number_group(struct list_head *element) * @id: One of values in "enum tomoyo_policy_id". * @member_list: Pointer to "struct list_head". * - * Returns true if some elements are deleted, false otherwise. + * Returns nothing. */ -static bool tomoyo_collect_member(const enum tomoyo_policy_id id, +static void tomoyo_collect_member(const enum tomoyo_policy_id id, struct list_head *member_list) { struct tomoyo_acl_head *member; - list_for_each_entry(member, member_list, list) { + struct tomoyo_acl_head *tmp; + list_for_each_entry_safe(member, tmp, member_list, list) { if (!member->is_deleted) continue; - if (!tomoyo_add_to_gc(id, &member->list)) - return false; + member->is_deleted = TOMOYO_GC_IN_PROGRESS; + tomoyo_try_to_gc(id, &member->list); } - return true; } /** @@ -501,22 +463,22 @@ static bool tomoyo_collect_member(const enum tomoyo_policy_id id, * * @list: Pointer to "struct list_head". * - * Returns true if some elements are deleted, false otherwise. + * Returns nothing. */ -static bool tomoyo_collect_acl(struct list_head *list) +static void tomoyo_collect_acl(struct list_head *list) { struct tomoyo_acl_info *acl; - list_for_each_entry(acl, list, list) { + struct tomoyo_acl_info *tmp; + list_for_each_entry_safe(acl, tmp, list, list) { if (!acl->is_deleted) continue; - if (!tomoyo_add_to_gc(TOMOYO_ID_ACL, &acl->list)) - return false; + acl->is_deleted = TOMOYO_GC_IN_PROGRESS; + tomoyo_try_to_gc(TOMOYO_ID_ACL, &acl->list); } - return true; } /** - * tomoyo_collect_entry - Scan lists for deleted elements. + * tomoyo_collect_entry - Try to kfree() deleted elements. * * Returns nothing. */ @@ -525,36 +487,40 @@ static void tomoyo_collect_entry(void) int i; enum tomoyo_policy_id id; struct tomoyo_policy_namespace *ns; - int idx; - if (mutex_lock_interruptible(&tomoyo_policy_lock)) - return; - idx = tomoyo_read_lock(); + mutex_lock(&tomoyo_policy_lock); { struct tomoyo_domain_info *domain; - list_for_each_entry_rcu(domain, &tomoyo_domain_list, list) { - if (!tomoyo_collect_acl(&domain->acl_info_list)) - goto unlock; + struct tomoyo_domain_info *tmp; + list_for_each_entry_safe(domain, tmp, &tomoyo_domain_list, + list) { + tomoyo_collect_acl(&domain->acl_info_list); if (!domain->is_deleted || atomic_read(&domain->users)) continue; - /* - * Nobody is referring this domain. But somebody may - * refer this domain after successful execve(). - * We recheck domain->users after SRCU synchronization. - */ - if (!tomoyo_add_to_gc(TOMOYO_ID_DOMAIN, &domain->list)) - goto unlock; + tomoyo_try_to_gc(TOMOYO_ID_DOMAIN, &domain->list); } } - list_for_each_entry_rcu(ns, &tomoyo_namespace_list, namespace_list) { + list_for_each_entry(ns, &tomoyo_namespace_list, namespace_list) { for (id = 0; id < TOMOYO_MAX_POLICY; id++) - if (!tomoyo_collect_member(id, &ns->policy_list[id])) - goto unlock; + tomoyo_collect_member(id, &ns->policy_list[id]); for (i = 0; i < TOMOYO_MAX_ACL_GROUPS; i++) - if (!tomoyo_collect_acl(&ns->acl_group[i])) - goto unlock; + tomoyo_collect_acl(&ns->acl_group[i]); + } + { + struct tomoyo_shared_acl_head *ptr; + struct tomoyo_shared_acl_head *tmp; + list_for_each_entry_safe(ptr, tmp, &tomoyo_condition_list, + list) { + if (atomic_read(&ptr->users) > 0) + continue; + atomic_set(&ptr->users, TOMOYO_GC_IN_PROGRESS); + tomoyo_try_to_gc(TOMOYO_ID_CONDITION, &ptr->list); + } + } + list_for_each_entry(ns, &tomoyo_namespace_list, namespace_list) { for (i = 0; i < TOMOYO_MAX_GROUP; i++) { struct list_head *list = &ns->group_list[i]; struct tomoyo_group *group; + struct tomoyo_group *tmp; switch (i) { case 0: id = TOMOYO_ID_PATH_GROUP; @@ -566,139 +532,37 @@ static void tomoyo_collect_entry(void) id = TOMOYO_ID_ADDRESS_GROUP; break; } - list_for_each_entry(group, list, head.list) { - if (!tomoyo_collect_member - (id, &group->member_list)) - goto unlock; + list_for_each_entry_safe(group, tmp, list, head.list) { + tomoyo_collect_member(id, &group->member_list); if (!list_empty(&group->member_list) || - atomic_read(&group->head.users)) + atomic_read(&group->head.users) > 0) continue; - if (!tomoyo_add_to_gc(TOMOYO_ID_GROUP, - &group->head.list)) - goto unlock; + atomic_set(&group->head.users, + TOMOYO_GC_IN_PROGRESS); + tomoyo_try_to_gc(TOMOYO_ID_GROUP, + &group->head.list); } } } - id = TOMOYO_ID_CONDITION; - for (i = 0; i < TOMOYO_MAX_HASH + 1; i++) { - struct list_head *list = !i ? - &tomoyo_condition_list : &tomoyo_name_list[i - 1]; + for (i = 0; i < TOMOYO_MAX_HASH; i++) { + struct list_head *list = &tomoyo_name_list[i]; struct tomoyo_shared_acl_head *ptr; - list_for_each_entry(ptr, list, list) { - if (atomic_read(&ptr->users)) + struct tomoyo_shared_acl_head *tmp; + list_for_each_entry_safe(ptr, tmp, list, list) { + if (atomic_read(&ptr->users) > 0) continue; - if (!tomoyo_add_to_gc(id, &ptr->list)) - goto unlock; + atomic_set(&ptr->users, TOMOYO_GC_IN_PROGRESS); + tomoyo_try_to_gc(TOMOYO_ID_NAME, &ptr->list); } - id = TOMOYO_ID_NAME; } -unlock: - tomoyo_read_unlock(idx); mutex_unlock(&tomoyo_policy_lock); } -/** - * tomoyo_kfree_entry - Delete entries in tomoyo_gc_list. - * - * Returns true if some entries were kfree()d, false otherwise. - */ -static bool tomoyo_kfree_entry(void) -{ - struct tomoyo_gc *p; - struct tomoyo_gc *tmp; - bool result = false; - - list_for_each_entry_safe(p, tmp, &tomoyo_gc_list, list) { - struct list_head *element = p->element; - - /* - * list_del_rcu() in tomoyo_add_to_gc() guarantees that the - * list element became no longer reachable from the list which - * the element was originally on (e.g. tomoyo_domain_list). - * Also, synchronize_srcu() in tomoyo_gc_thread() guarantees - * that the list element became no longer referenced by syscall - * users. - * - * However, there are three users which may still be using the - * list element. We need to defer until all of these users - * forget the list element. - * - * Firstly, defer until "struct tomoyo_io_buffer"->r.{domain, - * group,acl} and "struct tomoyo_io_buffer"->w.domain forget - * the list element. - */ - if (tomoyo_struct_used_by_io_buffer(element)) - continue; - /* - * Secondly, defer until all other elements in the - * tomoyo_gc_list list forget the list element. - */ - if (tomoyo_element_linked_by_gc((const u8 *) element, p->size)) - continue; - switch (p->type) { - case TOMOYO_ID_TRANSITION_CONTROL: - tomoyo_del_transition_control(element); - break; - case TOMOYO_ID_AGGREGATOR: - tomoyo_del_aggregator(element); - break; - case TOMOYO_ID_MANAGER: - tomoyo_del_manager(element); - break; - case TOMOYO_ID_CONDITION: - tomoyo_del_condition(element); - break; - case TOMOYO_ID_NAME: - /* - * Thirdly, defer until all "struct tomoyo_io_buffer" - * ->r.w[] forget the list element. - */ - if (tomoyo_name_used_by_io_buffer( - container_of(element, typeof(struct tomoyo_name), - head.list)->entry.name, p->size)) - continue; - tomoyo_del_name(element); - break; - case TOMOYO_ID_ACL: - tomoyo_del_acl(element); - break; - case TOMOYO_ID_DOMAIN: - if (!tomoyo_del_domain(element)) - continue; - break; - case TOMOYO_ID_PATH_GROUP: - tomoyo_del_path_group(element); - break; - case TOMOYO_ID_ADDRESS_GROUP: - tomoyo_del_address_group(element); - break; - case TOMOYO_ID_GROUP: - tomoyo_del_group(element); - break; - case TOMOYO_ID_NUMBER_GROUP: - tomoyo_del_number_group(element); - break; - case TOMOYO_MAX_POLICY: - break; - } - tomoyo_memory_free(element); - list_del(&p->list); - kfree(p); - tomoyo_gc_list_len--; - result = true; - } - return result; -} - /** * tomoyo_gc_thread - Garbage collector thread function. * * @unused: Unused. * - * In case OOM-killer choose this thread for termination, we create this thread - * as a short live thread whenever /sys/kernel/security/tomoyo/ interface was - * close()d. - * * Returns 0. */ static int tomoyo_gc_thread(void *unused) @@ -707,13 +571,7 @@ static int tomoyo_gc_thread(void *unused) static DEFINE_MUTEX(tomoyo_gc_mutex); if (!mutex_trylock(&tomoyo_gc_mutex)) goto out; - - do { - tomoyo_collect_entry(); - if (list_empty(&tomoyo_gc_list)) - break; - synchronize_srcu(&tomoyo_ss); - } while (tomoyo_kfree_entry()); + tomoyo_collect_entry(); { struct tomoyo_io_buffer *head; struct tomoyo_io_buffer *tmp; diff --git a/security/tomoyo/memory.c b/security/tomoyo/memory.c index 7a56051146c2..277b9ade4408 100644 --- a/security/tomoyo/memory.c +++ b/security/tomoyo/memory.c @@ -123,7 +123,8 @@ struct tomoyo_group *tomoyo_get_group(struct tomoyo_acl_param *param, goto out; list = ¶m->ns->group_list[idx]; list_for_each_entry(group, list, head.list) { - if (e.group_name != group->group_name) + if (e.group_name != group->group_name || + atomic_read(&group->head.users) == TOMOYO_GC_IN_PROGRESS) continue; atomic_inc(&group->head.users); found = true; @@ -175,7 +176,8 @@ const struct tomoyo_path_info *tomoyo_get_name(const char *name) if (mutex_lock_interruptible(&tomoyo_policy_lock)) return NULL; list_for_each_entry(ptr, head, head.list) { - if (hash != ptr->entry.hash || strcmp(name, ptr->entry.name)) + if (hash != ptr->entry.hash || strcmp(name, ptr->entry.name) || + atomic_read(&ptr->head.users) == TOMOYO_GC_IN_PROGRESS) continue; atomic_inc(&ptr->head.users); goto out; From a427fd14d3edf6396c4b9638dbc8e2972afaa05b Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Sun, 25 Sep 2011 17:51:06 +0900 Subject: [PATCH 80/95] TOMOYO: Remove tomoyo_policy_memory_lock spinlock. tomoyo_policy_lock mutex already protects it. Signed-off-by: Tetsuo Handa Signed-off-by: James Morris --- security/tomoyo/common.h | 1 - security/tomoyo/gc.c | 20 +++++++++++++++++++- security/tomoyo/memory.c | 33 ++++++++------------------------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h index a0212fbf60fb..ed311d7a8ce0 100644 --- a/security/tomoyo/common.h +++ b/security/tomoyo/common.h @@ -1043,7 +1043,6 @@ void tomoyo_fill_path_info(struct tomoyo_path_info *ptr); void tomoyo_get_attributes(struct tomoyo_obj_info *obj); void tomoyo_init_policy_namespace(struct tomoyo_policy_namespace *ns); void tomoyo_load_policy(const char *filename); -void tomoyo_memory_free(void *ptr); void tomoyo_normalize_line(unsigned char *buffer); void tomoyo_notify_gc(struct tomoyo_io_buffer *head, const bool is_register); void tomoyo_print_ip(char *buf, const unsigned int size, diff --git a/security/tomoyo/gc.c b/security/tomoyo/gc.c index f2295c65f1e4..c3214b32dbfb 100644 --- a/security/tomoyo/gc.c +++ b/security/tomoyo/gc.c @@ -8,6 +8,21 @@ #include #include +/** + * tomoyo_memory_free - Free memory for elements. + * + * @ptr: Pointer to allocated memory. + * + * Returns nothing. + * + * Caller holds tomoyo_policy_lock mutex. + */ +static inline void tomoyo_memory_free(void *ptr) +{ + tomoyo_memory_used[TOMOYO_MEMORY_POLICY] -= ksize(ptr); + kfree(ptr); +} + /* The list for "struct tomoyo_io_buffer". */ static LIST_HEAD(tomoyo_io_buffer_list); /* Lock for protecting tomoyo_io_buffer_list. */ @@ -215,6 +230,8 @@ static void tomoyo_del_acl(struct list_head *element) * @element: Pointer to "struct list_head". * * Returns nothing. + * + * Caller holds tomoyo_policy_lock mutex. */ static inline void tomoyo_del_domain(struct list_head *element) { @@ -416,12 +433,13 @@ static void tomoyo_try_to_gc(const enum tomoyo_policy_id type, (element, typeof(struct tomoyo_domain_info), list)->users)) goto reinject; - tomoyo_del_domain(element); break; case TOMOYO_MAX_POLICY: break; } mutex_lock(&tomoyo_policy_lock); + if (type == TOMOYO_ID_DOMAIN) + tomoyo_del_domain(element); tomoyo_memory_free(element); return; reinject: diff --git a/security/tomoyo/memory.c b/security/tomoyo/memory.c index 277b9ade4408..0e995716cc25 100644 --- a/security/tomoyo/memory.c +++ b/security/tomoyo/memory.c @@ -27,8 +27,6 @@ void tomoyo_warn_oom(const char *function) panic("MAC Initialization failed.\n"); } -/* Lock for protecting tomoyo_memory_used. */ -static DEFINE_SPINLOCK(tomoyo_policy_memory_lock); /* Memoy currently used by policy/audit log/query. */ unsigned int tomoyo_memory_used[TOMOYO_MAX_MEMORY_STAT]; /* Memory quota for "policy"/"audit log"/"query". */ @@ -42,22 +40,19 @@ unsigned int tomoyo_memory_quota[TOMOYO_MAX_MEMORY_STAT]; * Returns true on success, false otherwise. * * Returns true if @ptr is not NULL and quota not exceeded, false otherwise. + * + * Caller holds tomoyo_policy_lock mutex. */ bool tomoyo_memory_ok(void *ptr) { if (ptr) { const size_t s = ksize(ptr); - bool result; - spin_lock(&tomoyo_policy_memory_lock); tomoyo_memory_used[TOMOYO_MEMORY_POLICY] += s; - result = !tomoyo_memory_quota[TOMOYO_MEMORY_POLICY] || - tomoyo_memory_used[TOMOYO_MEMORY_POLICY] <= - tomoyo_memory_quota[TOMOYO_MEMORY_POLICY]; - if (!result) - tomoyo_memory_used[TOMOYO_MEMORY_POLICY] -= s; - spin_unlock(&tomoyo_policy_memory_lock); - if (result) + if (!tomoyo_memory_quota[TOMOYO_MEMORY_POLICY] || + tomoyo_memory_used[TOMOYO_MEMORY_POLICY] <= + tomoyo_memory_quota[TOMOYO_MEMORY_POLICY]) return true; + tomoyo_memory_used[TOMOYO_MEMORY_POLICY] -= s; } tomoyo_warn_oom(__func__); return false; @@ -71,6 +66,8 @@ bool tomoyo_memory_ok(void *ptr) * * Returns pointer to allocated memory on success, NULL otherwise. * @data is zero-cleared on success. + * + * Caller holds tomoyo_policy_lock mutex. */ void *tomoyo_commit_ok(void *data, const unsigned int size) { @@ -84,20 +81,6 @@ void *tomoyo_commit_ok(void *data, const unsigned int size) return NULL; } -/** - * tomoyo_memory_free - Free memory for elements. - * - * @ptr: Pointer to allocated memory. - */ -void tomoyo_memory_free(void *ptr) -{ - size_t s = ksize(ptr); - spin_lock(&tomoyo_policy_memory_lock); - tomoyo_memory_used[TOMOYO_MEMORY_POLICY] -= s; - spin_unlock(&tomoyo_policy_memory_lock); - kfree(ptr); -} - /** * tomoyo_get_group - Allocate memory for "struct tomoyo_path_group"/"struct tomoyo_number_group". * From e00fb3f7af111d1b3252f7d622213d2e22be65f5 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Tue, 27 Sep 2011 11:48:53 +0900 Subject: [PATCH 81/95] TOMOYO: Fix domain transition failure warning. Commit bd03a3e4 "TOMOYO: Add policy namespace support." introduced policy namespace. But as of /sbin/modprobe is executed from initramfs/initrd, profiles for target domain's namespace is not defined because /sbin/tomoyo-init is not yet called. Reported-by: Jamie Nguyen Signed-off-by: Tetsuo Handa Signed-off-by: James Morris --- security/tomoyo/domain.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/security/tomoyo/domain.c b/security/tomoyo/domain.c index da16dfeed728..9027ac1534af 100644 --- a/security/tomoyo/domain.c +++ b/security/tomoyo/domain.c @@ -515,7 +515,8 @@ struct tomoyo_domain_info *tomoyo_assign_domain(const char *domainname, * that domain. Do not perform domain transition if * profile for that domain is not yet created. */ - if (!entry->ns->profile_ptr[entry->profile]) + if (tomoyo_policy_loaded && + !entry->ns->profile_ptr[entry->profile]) return NULL; } return entry; From e2b8b25a6795488eba7bb757706b3ac725c31fac Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Tue, 11 Oct 2011 14:05:08 +0900 Subject: [PATCH 82/95] TOMOYO: Remove redundant tasklist_lock. rcu_read_lock() is sufficient for calling find_task_by_pid_ns()/find_task_by_vpid(). Signed-off-by: Tetsuo Handa Signed-off-by: James Morris --- security/tomoyo/common.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c index 2e2802060eef..365f3bddee79 100644 --- a/security/tomoyo/common.c +++ b/security/tomoyo/common.c @@ -984,14 +984,12 @@ static bool tomoyo_select_domain(struct tomoyo_io_buffer *head, (global_pid = true, sscanf(data, "global-pid=%u", &pid) == 1)) { struct task_struct *p; rcu_read_lock(); - read_lock(&tasklist_lock); if (global_pid) p = find_task_by_pid_ns(pid, &init_pid_ns); else p = find_task_by_vpid(pid); if (p) domain = tomoyo_real_domain(p); - read_unlock(&tasklist_lock); rcu_read_unlock(); } else if (!strncmp(data, "domain=", 7)) { if (tomoyo_domain_def(data + 7)) @@ -1664,14 +1662,12 @@ static void tomoyo_read_pid(struct tomoyo_io_buffer *head) global_pid = true; pid = (unsigned int) simple_strtoul(buf, NULL, 10); rcu_read_lock(); - read_lock(&tasklist_lock); if (global_pid) p = find_task_by_pid_ns(pid, &init_pid_ns); else p = find_task_by_vpid(pid); if (p) domain = tomoyo_real_domain(p); - read_unlock(&tasklist_lock); rcu_read_unlock(); if (!domain) return; From 545a7260343bbaf11c7f1a4b8c3d9660bb9266e5 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Tue, 11 Oct 2011 14:06:41 +0900 Subject: [PATCH 83/95] TOMOYO: Fix quota and garbage collector. Commit 059d84db "TOMOYO: Add socket operation restriction support" and commit 731d37aa "TOMOYO: Allow domain transition without execve()." forgot to update tomoyo_domain_quota_is_ok() and tomoyo_del_acl() which results in incorrect quota counting and memory leak. Signed-off-by: Tetsuo Handa Signed-off-by: James Morris --- security/tomoyo/gc.c | 7 +++++++ security/tomoyo/util.c | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/security/tomoyo/gc.c b/security/tomoyo/gc.c index c3214b32dbfb..986a6a756868 100644 --- a/security/tomoyo/gc.c +++ b/security/tomoyo/gc.c @@ -221,6 +221,13 @@ static void tomoyo_del_acl(struct list_head *element) tomoyo_put_name_union(&entry->name); } break; + case TOMOYO_TYPE_MANUAL_TASK_ACL: + { + struct tomoyo_task_acl *entry = + container_of(acl, typeof(*entry), head); + tomoyo_put_name(entry->domainname); + } + break; } } diff --git a/security/tomoyo/util.c b/security/tomoyo/util.c index 50e9b4c73ceb..4a9b4b2eb755 100644 --- a/security/tomoyo/util.c +++ b/security/tomoyo/util.c @@ -1057,6 +1057,17 @@ bool tomoyo_domain_quota_is_ok(struct tomoyo_request_info *r) perm = container_of(ptr, struct tomoyo_mkdev_acl, head)->perm; break; + case TOMOYO_TYPE_INET_ACL: + perm = container_of(ptr, struct tomoyo_inet_acl, + head)->perm; + break; + case TOMOYO_TYPE_UNIX_ACL: + perm = container_of(ptr, struct tomoyo_unix_acl, + head)->perm; + break; + case TOMOYO_TYPE_MANUAL_TASK_ACL: + perm = 0; + break; default: perm = 1; } From 828716c28fe4aa232ea280ea8ed6fb103eefb6ac Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Thu, 8 Sep 2011 10:12:01 +0300 Subject: [PATCH 84/95] Smack: check permissions from user space (v2) Adds a new file into SmackFS called 'access'. Wanted Smack permission is written into /smack/access. After that result can be read from the opened file. If access applies result contains 1 and otherwise 0. File access is protected from race conditions by using simple_transaction_get()/set() API. Fixes from the previous version: - Removed smack.h changes, refactoring left-over from previous version. - Removed #include , refactoring left-over from previous version. Signed-off-by: Jarkko Sakkinen Signed-off-by: Casey Schaufler --- security/smack/smackfs.c | 180 +++++++++++++++++++++++++-------------- 1 file changed, 117 insertions(+), 63 deletions(-) diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index f93460156dce..f4c28eeba1b1 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -44,6 +44,7 @@ enum smk_inos { SMK_ONLYCAP = 9, /* the only "capable" label */ SMK_LOGGING = 10, /* logging */ SMK_LOAD_SELF = 11, /* task specific rules */ + SMK_ACCESSES = 12, /* access policy */ }; /* @@ -175,6 +176,81 @@ static int smk_set_access(struct smack_rule *srp, struct list_head *rule_list, return found; } +/** + * smk_parse_rule - parse subject, object and access type + * @data: string to be parsed whose size is SMK_LOADLEN + * @rule: parsed entities are stored in here + */ +static int smk_parse_rule(const char *data, struct smack_rule *rule) +{ + rule->smk_subject = smk_import(data, 0); + if (rule->smk_subject == NULL) + return -1; + + rule->smk_object = smk_import(data + SMK_LABELLEN, 0); + if (rule->smk_object == NULL) + return -1; + + rule->smk_access = 0; + + switch (data[SMK_LABELLEN + SMK_LABELLEN]) { + case '-': + break; + case 'r': + case 'R': + rule->smk_access |= MAY_READ; + break; + default: + return -1; + } + + switch (data[SMK_LABELLEN + SMK_LABELLEN + 1]) { + case '-': + break; + case 'w': + case 'W': + rule->smk_access |= MAY_WRITE; + break; + default: + return -1; + } + + switch (data[SMK_LABELLEN + SMK_LABELLEN + 2]) { + case '-': + break; + case 'x': + case 'X': + rule->smk_access |= MAY_EXEC; + break; + default: + return -1; + } + + switch (data[SMK_LABELLEN + SMK_LABELLEN + 3]) { + case '-': + break; + case 'a': + case 'A': + rule->smk_access |= MAY_APPEND; + break; + default: + return -1; + } + + switch (data[SMK_LABELLEN + SMK_LABELLEN + 4]) { + case '-': + break; + case 't': + case 'T': + rule->smk_access |= MAY_TRANSMUTE; + break; + default: + return -1; + } + + return 0; +} + /** * smk_write_load_list - write() for any /smack/load * @file: file pointer, not actually used @@ -234,71 +310,9 @@ static ssize_t smk_write_load_list(struct file *file, const char __user *buf, goto out; } - rule->smk_subject = smk_import(data, 0); - if (rule->smk_subject == NULL) + if (smk_parse_rule(data, rule)) goto out_free_rule; - rule->smk_object = smk_import(data + SMK_LABELLEN, 0); - if (rule->smk_object == NULL) - goto out_free_rule; - - rule->smk_access = 0; - - switch (data[SMK_LABELLEN + SMK_LABELLEN]) { - case '-': - break; - case 'r': - case 'R': - rule->smk_access |= MAY_READ; - break; - default: - goto out_free_rule; - } - - switch (data[SMK_LABELLEN + SMK_LABELLEN + 1]) { - case '-': - break; - case 'w': - case 'W': - rule->smk_access |= MAY_WRITE; - break; - default: - goto out_free_rule; - } - - switch (data[SMK_LABELLEN + SMK_LABELLEN + 2]) { - case '-': - break; - case 'x': - case 'X': - rule->smk_access |= MAY_EXEC; - break; - default: - goto out_free_rule; - } - - switch (data[SMK_LABELLEN + SMK_LABELLEN + 3]) { - case '-': - break; - case 'a': - case 'A': - rule->smk_access |= MAY_APPEND; - break; - default: - goto out_free_rule; - } - - switch (data[SMK_LABELLEN + SMK_LABELLEN + 4]) { - case '-': - break; - case 't': - case 'T': - rule->smk_access |= MAY_TRANSMUTE; - break; - default: - goto out_free_rule; - } - rc = count; /* * smk_set_access returns true if there was already a rule @@ -1425,6 +1439,44 @@ static const struct file_operations smk_load_self_ops = { .write = smk_write_load_self, .release = seq_release, }; + +/** + * smk_write_access - handle access check transaction + * @file: file pointer + * @buf: data from user space + * @count: bytes sent + * @ppos: where to start - must be 0 + */ +static ssize_t smk_write_access(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct smack_rule rule; + char *data; + + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + + data = simple_transaction_get(file, buf, count); + if (IS_ERR(data)) + return PTR_ERR(data); + + if (count < SMK_LOADLEN || smk_parse_rule(data, &rule)) + return -EINVAL; + + data[0] = smk_access(rule.smk_subject, rule.smk_object, + rule.smk_access, NULL) == 0; + + simple_transaction_set(file, 1); + return SMK_LOADLEN; +} + +static const struct file_operations smk_access_ops = { + .write = smk_write_access, + .read = simple_transaction_read, + .release = simple_transaction_release, + .llseek = generic_file_llseek, +}; + /** * smk_fill_super - fill the /smackfs superblock * @sb: the empty superblock @@ -1459,6 +1511,8 @@ static int smk_fill_super(struct super_block *sb, void *data, int silent) "logging", &smk_logging_ops, S_IRUGO|S_IWUSR}, [SMK_LOAD_SELF] = { "load-self", &smk_load_self_ops, S_IRUGO|S_IWUGO}, + [SMK_ACCESSES] = { + "access", &smk_access_ops, S_IRUGO|S_IWUSR}, /* last one */ {""} }; From 272cd7a8c67dd40a31ecff76a503bbb84707f757 Mon Sep 17 00:00:00 2001 From: Casey Schaufler Date: Tue, 20 Sep 2011 12:24:36 -0700 Subject: [PATCH 85/95] Smack: Rule list lookup performance This patch is targeted for the smack-next tree. Smack access checks suffer from two significant performance issues. In cases where there are large numbers of rules the search of the single list of rules is wasteful. Comparing the string values of the smack labels is less efficient than a numeric comparison would. These changes take advantage of the Smack label list, which maintains the mapping of Smack labels to secids and optional CIPSO labels. Because the labels are kept perpetually, an access check can be done strictly based on the address of the label in the list without ever looking at the label itself. Rather than keeping one global list of rules the rules with a particular subject label can be based off of that label list entry. The access check need never look at entries that do not use the current subject label. This requires that packets coming off the network with CIPSO direct Smack labels that have never been seen before be treated carefully. The only case where they could be delivered is where the receiving socket has an IPIN star label, so that case is explicitly addressed. On a system with 39,800 rules (200 labels in all permutations) a system with this patch runs an access speed test in 5% of the time of the old version. That should be a best case improvement. If all of the rules are associated with the same subject label and all of the accesses are for processes with that label (unlikely) the improvement is about 30%. Signed-off-by: Casey Schaufler --- security/smack/smack.h | 18 +++-- security/smack/smack_access.c | 113 +++++++++++++++--------------- security/smack/smack_lsm.c | 126 +++++++++++++++++++++------------- security/smack/smackfs.c | 84 ++++++++++++++++++++--- 4 files changed, 220 insertions(+), 121 deletions(-) diff --git a/security/smack/smack.h b/security/smack/smack.h index 2b6c6a516123..174d3be9aaee 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -41,9 +41,9 @@ struct superblock_smack { }; struct socket_smack { - char *smk_out; /* outbound label */ - char *smk_in; /* inbound label */ - char smk_packet[SMK_LABELLEN]; /* TCP peer label */ + char *smk_out; /* outbound label */ + char *smk_in; /* inbound label */ + char *smk_packet; /* TCP peer label */ }; /* @@ -116,13 +116,19 @@ struct smk_netlbladdr { * If there is a cipso value associated with the label it * gets stored here, too. This will most likely be rare as * the cipso direct mapping in used internally. + * + * Keep the access rules for this subject label here so that + * the entire set of rules does not need to be examined every + * time. */ struct smack_known { struct list_head list; char smk_known[SMK_LABELLEN]; u32 smk_secid; struct smack_cipso *smk_cipso; - spinlock_t smk_cipsolock; /* for changing cipso map */ + spinlock_t smk_cipsolock; /* for changing cipso map */ + struct list_head smk_rules; /* access rules */ + struct mutex smk_rules_lock; /* lock for the rules */ }; /* @@ -201,10 +207,11 @@ int smk_access_entry(char *, char *, struct list_head *); int smk_access(char *, char *, int, struct smk_audit_info *); int smk_curacc(char *, u32, struct smk_audit_info *); int smack_to_cipso(const char *, struct smack_cipso *); -void smack_from_cipso(u32, char *, char *); +char *smack_from_cipso(u32, char *); char *smack_from_secid(const u32); char *smk_import(const char *, int); struct smack_known *smk_import_entry(const char *, int); +struct smack_known *smk_find_entry(const char *); u32 smack_to_secid(const char *); /* @@ -223,7 +230,6 @@ extern struct smack_known smack_known_star; extern struct smack_known smack_known_web; extern struct list_head smack_known_list; -extern struct list_head smack_rule_list; extern struct list_head smk_netlbladdr_list; extern struct security_operations smack_ops; diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c index 9637e107f7ea..a885f628f56e 100644 --- a/security/smack/smack_access.c +++ b/security/smack/smack_access.c @@ -77,14 +77,19 @@ int log_policy = SMACK_AUDIT_DENIED; * entry is found returns -ENOENT. * * NOTE: - * Even though Smack labels are usually shared on smack_list - * labels that come in off the network can't be imported - * and added to the list for locking reasons. * - * Therefore, it is necessary to check the contents of the labels, - * not just the pointer values. Of course, in most cases the labels - * will be on the list, so checking the pointers may be a worthwhile - * optimization. + * Earlier versions of this function allowed for labels that + * were not on the label list. This was done to allow for + * labels to come over the network that had never been seen + * before on this host. Unless the receiving socket has the + * star label this will always result in a failure check. The + * star labeled socket case is now handled in the networking + * hooks so there is no case where the label is not on the + * label list. Checking to see if the address of two labels + * is the same is now a reliable test. + * + * Do the object check first because that is more + * likely to differ. */ int smk_access_entry(char *subject_label, char *object_label, struct list_head *rule_list) @@ -93,13 +98,10 @@ int smk_access_entry(char *subject_label, char *object_label, struct smack_rule *srp; list_for_each_entry_rcu(srp, rule_list, list) { - if (srp->smk_subject == subject_label || - strcmp(srp->smk_subject, subject_label) == 0) { - if (srp->smk_object == object_label || - strcmp(srp->smk_object, object_label) == 0) { - may = srp->smk_access; - break; - } + if (srp->smk_object == object_label && + srp->smk_subject == subject_label) { + may = srp->smk_access; + break; } } @@ -117,18 +119,12 @@ int smk_access_entry(char *subject_label, char *object_label, * access rule list and returns 0 if the access is permitted, * non zero otherwise. * - * Even though Smack labels are usually shared on smack_list - * labels that come in off the network can't be imported - * and added to the list for locking reasons. - * - * Therefore, it is necessary to check the contents of the labels, - * not just the pointer values. Of course, in most cases the labels - * will be on the list, so checking the pointers may be a worthwhile - * optimization. + * Smack labels are shared on smack_list */ int smk_access(char *subject_label, char *object_label, int request, struct smk_audit_info *a) { + struct smack_known *skp; int may = MAY_NOT; int rc = 0; @@ -137,8 +133,7 @@ int smk_access(char *subject_label, char *object_label, int request, * * A star subject can't access any object. */ - if (subject_label == smack_known_star.smk_known || - strcmp(subject_label, smack_known_star.smk_known) == 0) { + if (subject_label == smack_known_star.smk_known) { rc = -EACCES; goto out_audit; } @@ -148,33 +143,27 @@ int smk_access(char *subject_label, char *object_label, int request, * An internet subject can access any object. */ if (object_label == smack_known_web.smk_known || - subject_label == smack_known_web.smk_known || - strcmp(object_label, smack_known_web.smk_known) == 0 || - strcmp(subject_label, smack_known_web.smk_known) == 0) + subject_label == smack_known_web.smk_known) goto out_audit; /* * A star object can be accessed by any subject. */ - if (object_label == smack_known_star.smk_known || - strcmp(object_label, smack_known_star.smk_known) == 0) + if (object_label == smack_known_star.smk_known) goto out_audit; /* * An object can be accessed in any way by a subject * with the same label. */ - if (subject_label == object_label || - strcmp(subject_label, object_label) == 0) + if (subject_label == object_label) goto out_audit; /* * A hat subject can read any object. * A floor object can be read by any subject. */ if ((request & MAY_ANYREAD) == request) { - if (object_label == smack_known_floor.smk_known || - strcmp(object_label, smack_known_floor.smk_known) == 0) + if (object_label == smack_known_floor.smk_known) goto out_audit; - if (subject_label == smack_known_hat.smk_known || - strcmp(subject_label, smack_known_hat.smk_known) == 0) + if (subject_label == smack_known_hat.smk_known) goto out_audit; } /* @@ -184,8 +173,9 @@ int smk_access(char *subject_label, char *object_label, int request, * good. A negative response from smk_access_entry() * indicates there is no entry for this pair. */ + skp = smk_find_entry(subject_label); rcu_read_lock(); - may = smk_access_entry(subject_label, object_label, &smack_rule_list); + may = smk_access_entry(subject_label, object_label, &skp->smk_rules); rcu_read_unlock(); if (may > 0 && (request & may) == request) @@ -343,6 +333,25 @@ void smack_log(char *subject_label, char *object_label, int request, static DEFINE_MUTEX(smack_known_lock); +/** + * smk_find_entry - find a label on the list, return the list entry + * @string: a text string that might be a Smack label + * + * Returns a pointer to the entry in the label list that + * matches the passed string. + */ +struct smack_known *smk_find_entry(const char *string) +{ + struct smack_known *skp; + + list_for_each_entry_rcu(skp, &smack_known_list, list) { + if (strncmp(skp->smk_known, string, SMK_MAXLEN) == 0) + return skp; + } + + return NULL; +} + /** * smk_import_entry - import a label, return the list entry * @string: a text string that might be a Smack label @@ -378,21 +387,17 @@ struct smack_known *smk_import_entry(const char *string, int len) mutex_lock(&smack_known_lock); - found = 0; - list_for_each_entry_rcu(skp, &smack_known_list, list) { - if (strncmp(skp->smk_known, smack, SMK_MAXLEN) == 0) { - found = 1; - break; - } - } + skp = smk_find_entry(smack); - if (found == 0) { + if (skp == NULL) { skp = kzalloc(sizeof(struct smack_known), GFP_KERNEL); if (skp != NULL) { strncpy(skp->smk_known, smack, SMK_MAXLEN); skp->smk_secid = smack_next_secid++; skp->smk_cipso = NULL; + INIT_LIST_HEAD(&skp->smk_rules); spin_lock_init(&skp->smk_cipsolock); + mutex_init(&skp->smk_rules_lock); /* * Make sure that the entry is actually * filled before putting it on the list. @@ -480,19 +485,12 @@ u32 smack_to_secid(const char *smack) * smack_from_cipso - find the Smack label associated with a CIPSO option * @level: Bell & LaPadula level from the network * @cp: Bell & LaPadula categories from the network - * @result: where to put the Smack value * * This is a simple lookup in the label table. * - * This is an odd duck as far as smack handling goes in that - * it sends back a copy of the smack label rather than a pointer - * to the master list. This is done because it is possible for - * a foreign host to send a smack label that is new to this - * machine and hence not on the list. That would not be an - * issue except that adding an entry to the master list can't - * be done at that point. + * Return the matching label from the label list or NULL. */ -void smack_from_cipso(u32 level, char *cp, char *result) +char *smack_from_cipso(u32 level, char *cp) { struct smack_known *kp; char *final = NULL; @@ -509,12 +507,13 @@ void smack_from_cipso(u32 level, char *cp, char *result) final = kp->smk_known; spin_unlock_bh(&kp->smk_cipsolock); + + if (final != NULL) + break; } rcu_read_unlock(); - if (final == NULL) - final = smack_known_huh.smk_known; - strncpy(result, final, SMK_MAXLEN); - return; + + return final; } /** diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index b9c5e149903b..fb915163f967 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -516,6 +516,8 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir, const struct qstr *qstr, char **name, void **value, size_t *len) { + struct smack_known *skp; + char *csp = smk_of_current(); char *isp = smk_of_inode(inode); char *dsp = smk_of_inode(dir); int may; @@ -527,8 +529,9 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir, } if (value) { + skp = smk_find_entry(csp); rcu_read_lock(); - may = smk_access_entry(smk_of_current(), dsp, &smack_rule_list); + may = smk_access_entry(csp, dsp, &skp->smk_rules); rcu_read_unlock(); /* @@ -1138,6 +1141,7 @@ static int smack_file_mmap(struct file *file, unsigned long flags, unsigned long addr, unsigned long addr_only) { + struct smack_known *skp; struct smack_rule *srp; struct task_smack *tsp; char *sp; @@ -1170,6 +1174,7 @@ static int smack_file_mmap(struct file *file, tsp = current_security(); sp = smk_of_current(); + skp = smk_find_entry(sp); rc = 0; rcu_read_lock(); @@ -1177,15 +1182,8 @@ static int smack_file_mmap(struct file *file, * For each Smack rule associated with the subject * label verify that the SMACK64MMAP also has access * to that rule's object label. - * - * Because neither of the labels comes - * from the networking code it is sufficient - * to compare pointers. */ - list_for_each_entry_rcu(srp, &smack_rule_list, list) { - if (srp->smk_subject != sp) - continue; - + list_for_each_entry_rcu(srp, &skp->smk_rules, list) { osmack = srp->smk_object; /* * Matching labels always allows access. @@ -1214,7 +1212,8 @@ static int smack_file_mmap(struct file *file, * If there isn't one a SMACK64MMAP subject * can't have as much access as current. */ - mmay = smk_access_entry(msmack, osmack, &smack_rule_list); + skp = smk_find_entry(msmack); + mmay = smk_access_entry(msmack, osmack, &skp->smk_rules); if (mmay == -ENOENT) { rc = -EACCES; break; @@ -1711,7 +1710,7 @@ static int smack_sk_alloc_security(struct sock *sk, int family, gfp_t gfp_flags) ssp->smk_in = csp; ssp->smk_out = csp; - ssp->smk_packet[0] = '\0'; + ssp->smk_packet = NULL; sk->sk_security = ssp; @@ -2813,16 +2812,17 @@ static int smack_socket_sendmsg(struct socket *sock, struct msghdr *msg, return smack_netlabel_send(sock->sk, sip); } - /** * smack_from_secattr - Convert a netlabel attr.mls.lvl/attr.mls.cat pair to smack * @sap: netlabel secattr - * @sip: where to put the result + * @ssp: socket security information * - * Copies a smack label into sip + * Returns a pointer to a Smack label found on the label list. */ -static void smack_from_secattr(struct netlbl_lsm_secattr *sap, char *sip) +static char *smack_from_secattr(struct netlbl_lsm_secattr *sap, + struct socket_smack *ssp) { + struct smack_known *skp; char smack[SMK_LABELLEN]; char *sp; int pcat; @@ -2852,15 +2852,43 @@ static void smack_from_secattr(struct netlbl_lsm_secattr *sap, char *sip) * we are already done. WeeHee. */ if (sap->attr.mls.lvl == smack_cipso_direct) { - memcpy(sip, smack, SMK_MAXLEN); - return; + /* + * The label sent is usually on the label list. + * + * If it is not we may still want to allow the + * delivery. + * + * If the recipient is accepting all packets + * because it is using the star ("*") label + * for SMACK64IPIN provide the web ("@") label + * so that a directed response will succeed. + * This is not very correct from a MAC point + * of view, but gets around the problem that + * locking prevents adding the newly discovered + * label to the list. + * The case where the recipient is not using + * the star label should obviously fail. + * The easy way to do this is to provide the + * star label as the subject label. + */ + skp = smk_find_entry(smack); + if (skp != NULL) + return skp->smk_known; + if (ssp != NULL && + ssp->smk_in == smack_known_star.smk_known) + return smack_known_web.smk_known; + return smack_known_star.smk_known; } /* * Look it up in the supplied table if it is not * a direct mapping. */ - smack_from_cipso(sap->attr.mls.lvl, smack, sip); - return; + sp = smack_from_cipso(sap->attr.mls.lvl, smack); + if (sp != NULL) + return sp; + if (ssp != NULL && ssp->smk_in == smack_known_star.smk_known) + return smack_known_web.smk_known; + return smack_known_star.smk_known; } if ((sap->flags & NETLBL_SECATTR_SECID) != 0) { /* @@ -2875,16 +2903,14 @@ static void smack_from_secattr(struct netlbl_lsm_secattr *sap, char *sip) * secid is from a fallback. */ BUG_ON(sp == NULL); - strncpy(sip, sp, SMK_MAXLEN); - return; + return sp; } /* * Without guidance regarding the smack value * for the packet fall back on the network * ambient value. */ - strncpy(sip, smack_net_ambient, SMK_MAXLEN); - return; + return smack_net_ambient; } /** @@ -2898,7 +2924,6 @@ static int smack_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) { struct netlbl_lsm_secattr secattr; struct socket_smack *ssp = sk->sk_security; - char smack[SMK_LABELLEN]; char *csp; int rc; struct smk_audit_info ad; @@ -2911,10 +2936,9 @@ static int smack_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) netlbl_secattr_init(&secattr); rc = netlbl_skbuff_getattr(skb, sk->sk_family, &secattr); - if (rc == 0) { - smack_from_secattr(&secattr, smack); - csp = smack; - } else + if (rc == 0) + csp = smack_from_secattr(&secattr, ssp); + else csp = smack_net_ambient; netlbl_secattr_destroy(&secattr); @@ -2951,15 +2975,19 @@ static int smack_socket_getpeersec_stream(struct socket *sock, int __user *optlen, unsigned len) { struct socket_smack *ssp; - int slen; + char *rcp = ""; + int slen = 1; int rc = 0; ssp = sock->sk->sk_security; - slen = strlen(ssp->smk_packet) + 1; + if (ssp->smk_packet != NULL) { + rcp = ssp->smk_packet; + slen = strlen(rcp) + 1; + } if (slen > len) rc = -ERANGE; - else if (copy_to_user(optval, ssp->smk_packet, slen) != 0) + else if (copy_to_user(optval, rcp, slen) != 0) rc = -EFAULT; if (put_user(slen, optlen) != 0) @@ -2982,8 +3010,8 @@ static int smack_socket_getpeersec_dgram(struct socket *sock, { struct netlbl_lsm_secattr secattr; - struct socket_smack *sp; - char smack[SMK_LABELLEN]; + struct socket_smack *ssp = NULL; + char *sp; int family = PF_UNSPEC; u32 s = 0; /* 0 is the invalid secid */ int rc; @@ -2998,17 +3026,19 @@ static int smack_socket_getpeersec_dgram(struct socket *sock, family = sock->sk->sk_family; if (family == PF_UNIX) { - sp = sock->sk->sk_security; - s = smack_to_secid(sp->smk_out); + ssp = sock->sk->sk_security; + s = smack_to_secid(ssp->smk_out); } else if (family == PF_INET || family == PF_INET6) { /* * Translate what netlabel gave us. */ + if (sock != NULL && sock->sk != NULL) + ssp = sock->sk->sk_security; netlbl_secattr_init(&secattr); rc = netlbl_skbuff_getattr(skb, family, &secattr); if (rc == 0) { - smack_from_secattr(&secattr, smack); - s = smack_to_secid(smack); + sp = smack_from_secattr(&secattr, ssp); + s = smack_to_secid(sp); } netlbl_secattr_destroy(&secattr); } @@ -3056,7 +3086,7 @@ static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb, struct netlbl_lsm_secattr secattr; struct sockaddr_in addr; struct iphdr *hdr; - char smack[SMK_LABELLEN]; + char *sp; int rc; struct smk_audit_info ad; @@ -3067,9 +3097,9 @@ static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb, netlbl_secattr_init(&secattr); rc = netlbl_skbuff_getattr(skb, family, &secattr); if (rc == 0) - smack_from_secattr(&secattr, smack); + sp = smack_from_secattr(&secattr, ssp); else - strncpy(smack, smack_known_huh.smk_known, SMK_MAXLEN); + sp = smack_known_huh.smk_known; netlbl_secattr_destroy(&secattr); #ifdef CONFIG_AUDIT @@ -3082,7 +3112,7 @@ static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb, * Receiving a packet requires that the other end be able to write * here. Read access is not required. */ - rc = smk_access(smack, ssp->smk_in, MAY_WRITE, &ad); + rc = smk_access(sp, ssp->smk_in, MAY_WRITE, &ad); if (rc != 0) return rc; @@ -3090,7 +3120,7 @@ static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb, * Save the peer's label in the request_sock so we can later setup * smk_packet in the child socket so that SO_PEERCRED can report it. */ - req->peer_secid = smack_to_secid(smack); + req->peer_secid = smack_to_secid(sp); /* * We need to decide if we want to label the incoming connection here @@ -3103,7 +3133,7 @@ static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb, if (smack_host_label(&addr) == NULL) { rcu_read_unlock(); netlbl_secattr_init(&secattr); - smack_to_secattr(smack, &secattr); + smack_to_secattr(sp, &secattr); rc = netlbl_req_setattr(req, &secattr); netlbl_secattr_destroy(&secattr); } else { @@ -3125,13 +3155,11 @@ static void smack_inet_csk_clone(struct sock *sk, const struct request_sock *req) { struct socket_smack *ssp = sk->sk_security; - char *smack; - if (req->peer_secid != 0) { - smack = smack_from_secid(req->peer_secid); - strncpy(ssp->smk_packet, smack, SMK_MAXLEN); - } else - ssp->smk_packet[0] = '\0'; + if (req->peer_secid != 0) + ssp->smk_packet = smack_from_secid(req->peer_secid); + else + ssp->smk_packet = NULL; } /* diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index f4c28eeba1b1..76e520be1b5d 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -86,6 +86,16 @@ char *smack_onlycap; */ LIST_HEAD(smk_netlbladdr_list); + +/* + * Rule lists are maintained for each label. + * This master list is just for reading /smack/load. + */ +struct smack_master_list { + struct list_head list; + struct smack_rule *smk_rule; +}; + LIST_HEAD(smack_rule_list); static int smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT; @@ -93,7 +103,10 @@ static int smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT; const char *smack_cipso_option = SMACK_CIPSO_OPTION; +#define SEQ_READ_FINISHED ((loff_t)-1) +/* #define SEQ_READ_FINISHED 1 +*/ /* * Values for parsing cipso rules @@ -160,9 +173,13 @@ static int smk_set_access(struct smack_rule *srp, struct list_head *rule_list, mutex_lock(rule_lock); + /* + * Because the object label is less likely to match + * than the subject label check it first + */ list_for_each_entry_rcu(sp, rule_list, list) { - if (sp->smk_subject == srp->smk_subject && - sp->smk_object == srp->smk_object) { + if (sp->smk_object == srp->smk_object && + sp->smk_subject == srp->smk_subject) { found = 1; sp->smk_access = srp->smk_access; break; @@ -273,9 +290,12 @@ static ssize_t smk_write_load_list(struct file *file, const char __user *buf, struct list_head *rule_list, struct mutex *rule_lock) { + struct smack_master_list *smlp; + struct smack_known *skp; struct smack_rule *rule; char *data; int rc = -EINVAL; + int load = 0; /* * No partial writes. @@ -313,13 +333,27 @@ static ssize_t smk_write_load_list(struct file *file, const char __user *buf, if (smk_parse_rule(data, rule)) goto out_free_rule; + if (rule_list == NULL) { + load = 1; + skp = smk_find_entry(rule->smk_subject); + rule_list = &skp->smk_rules; + rule_lock = &skp->smk_rules_lock; + } + rc = count; /* * smk_set_access returns true if there was already a rule * for the subject/object pair, and false if it was new. */ - if (!smk_set_access(rule, rule_list, rule_lock)) + if (!smk_set_access(rule, rule_list, rule_lock)) { + smlp = kzalloc(sizeof(*smlp), GFP_KERNEL); + if (smlp != NULL) { + smlp->smk_rule = rule; + list_add_rcu(&smlp->list, &smack_rule_list); + } else + rc = -ENOMEM; goto out; + } out_free_rule: kfree(rule); @@ -335,11 +369,24 @@ out: static void *load_seq_start(struct seq_file *s, loff_t *pos) { - if (*pos == SEQ_READ_FINISHED) + struct list_head *list; + + /* + * This is 0 the first time through. + */ + if (s->index == 0) + s->private = &smack_rule_list; + + if (s->private == NULL) return NULL; - if (list_empty(&smack_rule_list)) + + list = s->private; + if (list_empty(list)) return NULL; - return smack_rule_list.next; + + if (s->index == 0) + return list->next; + return list; } static void *load_seq_next(struct seq_file *s, void *v, loff_t *pos) @@ -347,17 +394,19 @@ static void *load_seq_next(struct seq_file *s, void *v, loff_t *pos) struct list_head *list = v; if (list_is_last(list, &smack_rule_list)) { - *pos = SEQ_READ_FINISHED; + s->private = NULL; return NULL; } + s->private = list->next; return list->next; } static int load_seq_show(struct seq_file *s, void *v) { struct list_head *list = v; - struct smack_rule *srp = - list_entry(list, struct smack_rule, list); + struct smack_master_list *smlp = + list_entry(list, struct smack_master_list, list); + struct smack_rule *srp = smlp->smk_rule; seq_printf(s, "%s %s", (char *)srp->smk_subject, (char *)srp->smk_object); @@ -426,8 +475,11 @@ static ssize_t smk_write_load(struct file *file, const char __user *buf, if (!capable(CAP_MAC_ADMIN)) return -EPERM; +/* return smk_write_load_list(file, buf, count, ppos, &smack_rule_list, &smack_list_lock); +*/ + return smk_write_load_list(file, buf, count, ppos, NULL, NULL); } static const struct file_operations smk_load_ops = { @@ -1588,6 +1640,20 @@ static int __init init_smk_fs(void) smk_cipso_doi(); smk_unlbl_ambient(NULL); + mutex_init(&smack_known_floor.smk_rules_lock); + mutex_init(&smack_known_hat.smk_rules_lock); + mutex_init(&smack_known_huh.smk_rules_lock); + mutex_init(&smack_known_invalid.smk_rules_lock); + mutex_init(&smack_known_star.smk_rules_lock); + mutex_init(&smack_known_web.smk_rules_lock); + + INIT_LIST_HEAD(&smack_known_floor.smk_rules); + INIT_LIST_HEAD(&smack_known_hat.smk_rules); + INIT_LIST_HEAD(&smack_known_huh.smk_rules); + INIT_LIST_HEAD(&smack_known_invalid.smk_rules); + INIT_LIST_HEAD(&smack_known_star.smk_rules); + INIT_LIST_HEAD(&smack_known_web.smk_rules); + return err; } From 531f1d453ed8a8acee4015bd64e7bcc2eab939e4 Mon Sep 17 00:00:00 2001 From: Casey Schaufler Date: Mon, 19 Sep 2011 12:41:42 -0700 Subject: [PATCH 86/95] Smack: Repair processing of fcntl Al Viro pointed out that the processing of fcntl done by Smack appeared poorly designed. He was right. There are three things that required change. Most obviously, the list of commands that really imply writing is limited to those involving file locking and signal handling. The initialization if the file security blob was incomplete, requiring use of a heretofore unused LSM hook. Finally, the audit information coming from a helper masked the identity of the LSM hook. This patch corrects all three of these defects. This is targeted for the smack-next tree pending comments. Signed-off-by: Casey Schaufler --- security/smack/smack_lsm.c | 67 ++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index fb915163f967..2e71c3f445fc 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -1091,36 +1091,31 @@ static int smack_file_lock(struct file *file, unsigned int cmd) * @cmd: what action to check * @arg: unused * + * Generally these operations are harmless. + * File locking operations present an obvious mechanism + * for passing information, so they require write access. + * * Returns 0 if current has access, error code otherwise */ static int smack_file_fcntl(struct file *file, unsigned int cmd, unsigned long arg) { struct smk_audit_info ad; - int rc; + int rc = 0; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); - smk_ad_setfield_u_fs_path(&ad, file->f_path); switch (cmd) { - case F_DUPFD: - case F_GETFD: - case F_GETFL: case F_GETLK: - case F_GETOWN: - case F_GETSIG: - rc = smk_curacc(file->f_security, MAY_READ, &ad); - break; - case F_SETFD: - case F_SETFL: case F_SETLK: case F_SETLKW: case F_SETOWN: case F_SETSIG: + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); + smk_ad_setfield_u_fs_path(&ad, file->f_path); rc = smk_curacc(file->f_security, MAY_WRITE, &ad); break; default: - rc = smk_curacc(file->f_security, MAY_READWRITE, &ad); + break; } return rc; @@ -1314,6 +1309,24 @@ static int smack_file_receive(struct file *file) return smk_curacc(file->f_security, may, &ad); } +/** + * smack_dentry_open - Smack dentry open processing + * @file: the object + * @cred: unused + * + * Set the security blob in the file structure. + * + * Returns 0 + */ +static int smack_dentry_open(struct file *file, const struct cred *cred) +{ + struct inode_smack *isp = file->f_path.dentry->d_inode->i_security; + + file->f_security = isp->smk_inode; + + return 0; +} + /* * Task hooks */ @@ -1454,15 +1467,17 @@ static int smack_kernel_create_files_as(struct cred *new, /** * smk_curacc_on_task - helper to log task related access * @p: the task object - * @access : the access requested + * @access: the access requested + * @caller: name of the calling function for audit * * Return 0 if access is permitted */ -static int smk_curacc_on_task(struct task_struct *p, int access) +static int smk_curacc_on_task(struct task_struct *p, int access, + const char *caller) { struct smk_audit_info ad; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_TASK); + smk_ad_init(&ad, caller, LSM_AUDIT_DATA_TASK); smk_ad_setfield_u_tsk(&ad, p); return smk_curacc(smk_of_task(task_security(p)), access, &ad); } @@ -1476,7 +1491,7 @@ static int smk_curacc_on_task(struct task_struct *p, int access) */ static int smack_task_setpgid(struct task_struct *p, pid_t pgid) { - return smk_curacc_on_task(p, MAY_WRITE); + return smk_curacc_on_task(p, MAY_WRITE, __func__); } /** @@ -1487,7 +1502,7 @@ static int smack_task_setpgid(struct task_struct *p, pid_t pgid) */ static int smack_task_getpgid(struct task_struct *p) { - return smk_curacc_on_task(p, MAY_READ); + return smk_curacc_on_task(p, MAY_READ, __func__); } /** @@ -1498,7 +1513,7 @@ static int smack_task_getpgid(struct task_struct *p) */ static int smack_task_getsid(struct task_struct *p) { - return smk_curacc_on_task(p, MAY_READ); + return smk_curacc_on_task(p, MAY_READ, __func__); } /** @@ -1526,7 +1541,7 @@ static int smack_task_setnice(struct task_struct *p, int nice) rc = cap_task_setnice(p, nice); if (rc == 0) - rc = smk_curacc_on_task(p, MAY_WRITE); + rc = smk_curacc_on_task(p, MAY_WRITE, __func__); return rc; } @@ -1543,7 +1558,7 @@ static int smack_task_setioprio(struct task_struct *p, int ioprio) rc = cap_task_setioprio(p, ioprio); if (rc == 0) - rc = smk_curacc_on_task(p, MAY_WRITE); + rc = smk_curacc_on_task(p, MAY_WRITE, __func__); return rc; } @@ -1555,7 +1570,7 @@ static int smack_task_setioprio(struct task_struct *p, int ioprio) */ static int smack_task_getioprio(struct task_struct *p) { - return smk_curacc_on_task(p, MAY_READ); + return smk_curacc_on_task(p, MAY_READ, __func__); } /** @@ -1572,7 +1587,7 @@ static int smack_task_setscheduler(struct task_struct *p) rc = cap_task_setscheduler(p); if (rc == 0) - rc = smk_curacc_on_task(p, MAY_WRITE); + rc = smk_curacc_on_task(p, MAY_WRITE, __func__); return rc; } @@ -1584,7 +1599,7 @@ static int smack_task_setscheduler(struct task_struct *p) */ static int smack_task_getscheduler(struct task_struct *p) { - return smk_curacc_on_task(p, MAY_READ); + return smk_curacc_on_task(p, MAY_READ, __func__); } /** @@ -1595,7 +1610,7 @@ static int smack_task_getscheduler(struct task_struct *p) */ static int smack_task_movememory(struct task_struct *p) { - return smk_curacc_on_task(p, MAY_WRITE); + return smk_curacc_on_task(p, MAY_WRITE, __func__); } /** @@ -3468,6 +3483,8 @@ struct security_operations smack_ops = { .file_send_sigiotask = smack_file_send_sigiotask, .file_receive = smack_file_receive, + .dentry_open = smack_dentry_open, + .cred_alloc_blank = smack_cred_alloc_blank, .cred_free = smack_cred_free, .cred_prepare = smack_cred_prepare, From ce8a432197d9892689eb4896f690b9fe6b3de598 Mon Sep 17 00:00:00 2001 From: Casey Schaufler Date: Thu, 29 Sep 2011 18:21:01 -0700 Subject: [PATCH 87/95] Smack: Clean up comments There are a number of comments in the Smack code that are either malformed or include code. This patch cleans them up. Signed-off-by: Casey Schaufler --- security/smack/smack.h | 5 ++--- security/smack/smack_lsm.c | 10 ++++++++-- security/smack/smackfs.c | 7 ------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/security/smack/smack.h b/security/smack/smack.h index 174d3be9aaee..9da2b2dfdefb 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -156,7 +156,6 @@ struct smack_known { /* * smackfs magic number - * smackfs macic number */ #define SMACK_MAGIC 0x43415d53 /* "SMAC" */ @@ -182,9 +181,9 @@ struct smack_known { #define MAY_NOT 0 /* - * Number of access types used by Smack (rwxa) + * Number of access types used by Smack (rwxat) */ -#define SMK_NUM_ACCESS_TYPE 4 +#define SMK_NUM_ACCESS_TYPE 5 /* * Smack audit data; is empty if CONFIG_AUDIT not set diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 2e71c3f445fc..6a8226541327 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -441,6 +441,12 @@ static int smack_sb_umount(struct vfsmount *mnt, int flags) * BPRM hooks */ +/** + * smack_bprm_set_creds - set creds for exec + * @bprm: the exec information + * + * Returns 0 if it gets a blob, -ENOMEM otherwise + */ static int smack_bprm_set_creds(struct linux_binprm *bprm) { struct task_smack *tsp = bprm->cred->security; @@ -844,7 +850,7 @@ static void smack_inode_post_setxattr(struct dentry *dentry, const char *name, return; } -/* +/** * smack_inode_getxattr - Smack check on getxattr * @dentry: the object * @name: unused @@ -861,7 +867,7 @@ static int smack_inode_getxattr(struct dentry *dentry, const char *name) return smk_curacc(smk_of_inode(dentry->d_inode), MAY_READ, &ad); } -/* +/** * smack_inode_removexattr - Smack check on removexattr * @dentry: the object * @name: name of the attribute diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index 76e520be1b5d..54f6e18dea2f 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -104,9 +104,6 @@ const char *smack_cipso_option = SMACK_CIPSO_OPTION; #define SEQ_READ_FINISHED ((loff_t)-1) -/* -#define SEQ_READ_FINISHED 1 -*/ /* * Values for parsing cipso rules @@ -475,10 +472,6 @@ static ssize_t smk_write_load(struct file *file, const char __user *buf, if (!capable(CAP_MAC_ADMIN)) return -EPERM; -/* - return smk_write_load_list(file, buf, count, ppos, &smack_rule_list, - &smack_list_lock); -*/ return smk_write_load_list(file, buf, count, ppos, NULL, NULL); } From 975d5e55c2e78b755bd0b92b71db1c241c5a2665 Mon Sep 17 00:00:00 2001 From: Casey Schaufler Date: Mon, 26 Sep 2011 14:43:39 -0700 Subject: [PATCH 88/95] Smack: Provide information for UDS getsockopt(SO_PEERCRED) This patch is targeted for the smack-next tree. This patch takes advantage of the recent changes for performance and points the packet labels on UDS connect at the output label of the far side. This makes getsockopt(...SO_PEERCRED...) function properly. Without this change the getsockopt does not provide any information. Signed-off-by: Casey Schaufler --- security/smack/smack_lsm.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 6a8226541327..dab8af17ef38 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -2773,6 +2773,7 @@ static int smack_unix_stream_connect(struct sock *sock, { struct socket_smack *ssp = sock->sk_security; struct socket_smack *osp = other->sk_security; + struct socket_smack *nsp = newsk->sk_security; struct smk_audit_info ad; int rc = 0; @@ -2782,6 +2783,14 @@ static int smack_unix_stream_connect(struct sock *sock, if (!capable(CAP_MAC_OVERRIDE)) rc = smk_access(ssp->smk_out, osp->smk_in, MAY_WRITE, &ad); + /* + * Cross reference the peer labels for SO_PEERSEC. + */ + if (rc == 0) { + nsp->smk_packet = ssp->smk_out; + ssp->smk_packet = osp->smk_out; + } + return rc; } From 84088ba239293abb24260c6c36d86e8775b6707f Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Fri, 7 Oct 2011 09:27:53 +0300 Subject: [PATCH 89/95] Smack: domain transition protections (v3) Protections for domain transition: - BPRM unsafe flags - Secureexec - Clear unsafe personality bits. - Clear parent death signal Signed-off-by: Jarkko Sakkinen --- security/smack/smack_lsm.c | 55 +++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index dab8af17ef38..d55b991268d5 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -5,12 +5,13 @@ * * Authors: * Casey Schaufler - * Jarkko Sakkinen + * Jarkko Sakkinen * * Copyright (C) 2007 Casey Schaufler * Copyright (C) 2009 Hewlett-Packard Development Company, L.P. * Paul Moore * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2011 Intel Corporation. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -449,9 +450,9 @@ static int smack_sb_umount(struct vfsmount *mnt, int flags) */ static int smack_bprm_set_creds(struct linux_binprm *bprm) { - struct task_smack *tsp = bprm->cred->security; + struct inode *inode = bprm->file->f_path.dentry->d_inode; + struct task_smack *bsp = bprm->cred->security; struct inode_smack *isp; - struct dentry *dp; int rc; rc = cap_bprm_set_creds(bprm); @@ -461,22 +462,50 @@ static int smack_bprm_set_creds(struct linux_binprm *bprm) if (bprm->cred_prepared) return 0; - if (bprm->file == NULL || bprm->file->f_dentry == NULL) + isp = inode->i_security; + if (isp->smk_task == NULL || isp->smk_task == bsp->smk_task) return 0; - dp = bprm->file->f_dentry; + if (bprm->unsafe) + return -EPERM; - if (dp->d_inode == NULL) - return 0; - - isp = dp->d_inode->i_security; - - if (isp->smk_task != NULL) - tsp->smk_task = isp->smk_task; + bsp->smk_task = isp->smk_task; + bprm->per_clear |= PER_CLEAR_ON_SETID; return 0; } +/** + * smack_bprm_committing_creds - Prepare to install the new credentials + * from bprm. + * + * @bprm: binprm for exec + */ +static void smack_bprm_committing_creds(struct linux_binprm *bprm) +{ + struct task_smack *bsp = bprm->cred->security; + + if (bsp->smk_task != bsp->smk_forked) + current->pdeath_signal = 0; +} + +/** + * smack_bprm_secureexec - Return the decision to use secureexec. + * @bprm: binprm for exec + * + * Returns 0 on success. + */ +static int smack_bprm_secureexec(struct linux_binprm *bprm) +{ + struct task_smack *tsp = current_security(); + int ret = cap_bprm_secureexec(bprm); + + if (!ret && (tsp->smk_task != tsp->smk_forked)) + ret = 1; + + return ret; +} + /* * Inode hooks */ @@ -3467,6 +3496,8 @@ struct security_operations smack_ops = { .sb_umount = smack_sb_umount, .bprm_set_creds = smack_bprm_set_creds, + .bprm_committing_creds = smack_bprm_committing_creds, + .bprm_secureexec = smack_bprm_secureexec, .inode_alloc_security = smack_inode_alloc_security, .inode_free_security = smack_inode_free_security, From f8859d98c1d1e73393285fb9dd57007839956247 Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Mon, 10 Oct 2011 14:29:28 +0300 Subject: [PATCH 90/95] Smack: fix for /smack/access output, use string instead of byte Small fix for the output of access SmackFS file. Use string is instead of byte. Makes it easier to extend API if it is needed. Signed-off-by: Jarkko Sakkinen --- security/smack/smackfs.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index 54f6e18dea2f..5498c4a2d1ae 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -1497,6 +1497,7 @@ static ssize_t smk_write_access(struct file *file, const char __user *buf, { struct smack_rule rule; char *data; + int res; if (!capable(CAP_MAC_ADMIN)) return -EPERM; @@ -1508,8 +1509,10 @@ static ssize_t smk_write_access(struct file *file, const char __user *buf, if (count < SMK_LOADLEN || smk_parse_rule(data, &rule)) return -EINVAL; - data[0] = smk_access(rule.smk_subject, rule.smk_object, - rule.smk_access, NULL) == 0; + res = smk_access(rule.smk_subject, rule.smk_object, rule.smk_access, + NULL); + data[0] = res == 0 ? '1' : '0'; + data[1] = '\0'; simple_transaction_set(file, 1); return SMK_LOADLEN; From 16014d87509e26d6ed6935adbbf437a571fb5870 Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Fri, 14 Oct 2011 13:16:24 +0300 Subject: [PATCH 91/95] Smack: compilation fix On some build configurations PER_CLEAR_ON_SETID symbol was not found when compiling smack_lsm.c. This patch fixes the issue by explicitly doing #include . Signed-off-by: Jarkko Sakkinen Signed-off-by: Casey Schaufler --- security/smack/smack_lsm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index d55b991268d5..7db62b48eb42 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -35,6 +35,7 @@ #include #include #include +#include #include "smack.h" #define task_security(task) (task_cred_xxx((task), security)) From d86b2b61d4dea614d6f319772a90a8f98b55ed67 Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Tue, 18 Oct 2011 14:34:28 +0300 Subject: [PATCH 92/95] Smack: fix: invalid length set for the result of /smack/access Forgot to update simple_transaction_set() to take terminator character into account. Signed-off-by: Jarkko Sakkinen Signed-off-by: Casey Schaufler --- security/smack/smackfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index 5498c4a2d1ae..381eecffe516 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -1514,7 +1514,7 @@ static ssize_t smk_write_access(struct file *file, const char __user *buf, data[0] = res == 0 ? '1' : '0'; data[1] = '\0'; - simple_transaction_set(file, 1); + simple_transaction_set(file, 2); return SMK_LOADLEN; } From 6afcb3b7393f5aa388a0d077c490ed411ab3cd27 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Sun, 16 Oct 2011 09:43:46 +0900 Subject: [PATCH 93/95] TOMOYO: Fix unused kernel config option. CONFIG_SECURITY_TOMOYO_MAX_{ACCEPT_ENTRY,AUDIT_LOG} introduced by commit 0e4ae0e0 "TOMOYO: Make several options configurable." were by error not used. Reported-by: Paul Bolle Signed-off-by: Tetsuo Handa Signed-off-by: James Morris --- security/tomoyo/common.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c index 365f3bddee79..96b7233a0df6 100644 --- a/security/tomoyo/common.c +++ b/security/tomoyo/common.c @@ -502,8 +502,10 @@ static struct tomoyo_profile *tomoyo_assign_profile TOMOYO_CONFIG_WANT_REJECT_LOG; memset(ptr->config, TOMOYO_CONFIG_USE_DEFAULT, sizeof(ptr->config)); - ptr->pref[TOMOYO_PREF_MAX_AUDIT_LOG] = 1024; - ptr->pref[TOMOYO_PREF_MAX_LEARNING_ENTRY] = 2048; + ptr->pref[TOMOYO_PREF_MAX_AUDIT_LOG] = + CONFIG_SECURITY_TOMOYO_MAX_AUDIT_LOG; + ptr->pref[TOMOYO_PREF_MAX_LEARNING_ENTRY] = + CONFIG_SECURITY_TOMOYO_MAX_ACCEPT_ENTRY; mb(); /* Avoid out-of-order execution. */ ns->profile_ptr[profile] = ptr; entry = NULL; From 0e94ae17c857b3835a2b8ea46ce44b5da4e2cc5d Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Tue, 18 Oct 2011 21:21:36 +0300 Subject: [PATCH 94/95] Smack: allow to access /smack/access as normal user Allow query access as a normal user removing the need for CAP_MAC_ADMIN. Give RW access to /smack/access for UGO. Do not import smack labels in access check. Signed-off-by: Jarkko Sakkinen Signed-off-by: Casey Schaufler --- security/smack/smack.h | 1 + security/smack/smack_access.c | 27 ++++++++++++++------- security/smack/smackfs.c | 45 +++++++++++++++++++++++------------ 3 files changed, 50 insertions(+), 23 deletions(-) diff --git a/security/smack/smack.h b/security/smack/smack.h index 9da2b2dfdefb..2ad00657b801 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -208,6 +208,7 @@ int smk_curacc(char *, u32, struct smk_audit_info *); int smack_to_cipso(const char *, struct smack_cipso *); char *smack_from_cipso(u32, char *); char *smack_from_secid(const u32); +void smk_parse_smack(const char *string, int len, char *smack); char *smk_import(const char *, int); struct smack_known *smk_import_entry(const char *, int); struct smack_known *smk_find_entry(const char *); diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c index a885f628f56e..cc7cb6edba08 100644 --- a/security/smack/smack_access.c +++ b/security/smack/smack_access.c @@ -353,17 +353,13 @@ struct smack_known *smk_find_entry(const char *string) } /** - * smk_import_entry - import a label, return the list entry - * @string: a text string that might be a Smack label + * smk_parse_smack - parse smack label from a text string + * @string: a text string that might contain a Smack label * @len: the maximum size, or zero if it is NULL terminated. - * - * Returns a pointer to the entry in the label list that - * matches the passed string, adding it if necessary. + * @smack: parsed smack label, or NULL if parse error */ -struct smack_known *smk_import_entry(const char *string, int len) +void smk_parse_smack(const char *string, int len, char *smack) { - struct smack_known *skp; - char smack[SMK_LABELLEN]; int found; int i; @@ -381,7 +377,22 @@ struct smack_known *smk_import_entry(const char *string, int len) } else smack[i] = string[i]; } +} +/** + * smk_import_entry - import a label, return the list entry + * @string: a text string that might be a Smack label + * @len: the maximum size, or zero if it is NULL terminated. + * + * Returns a pointer to the entry in the label list that + * matches the passed string, adding it if necessary. + */ +struct smack_known *smk_import_entry(const char *string, int len) +{ + struct smack_known *skp; + char smack[SMK_LABELLEN]; + + smk_parse_smack(string, len, smack); if (smack[0] == '\0') return NULL; diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index 381eecffe516..6aceef518a41 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -191,19 +191,37 @@ static int smk_set_access(struct smack_rule *srp, struct list_head *rule_list, } /** - * smk_parse_rule - parse subject, object and access type + * smk_parse_rule - parse Smack rule from load string * @data: string to be parsed whose size is SMK_LOADLEN - * @rule: parsed entities are stored in here + * @rule: Smack rule + * @import: if non-zero, import labels */ -static int smk_parse_rule(const char *data, struct smack_rule *rule) +static int smk_parse_rule(const char *data, struct smack_rule *rule, int import) { - rule->smk_subject = smk_import(data, 0); - if (rule->smk_subject == NULL) - return -1; + char smack[SMK_LABELLEN]; + struct smack_known *skp; - rule->smk_object = smk_import(data + SMK_LABELLEN, 0); - if (rule->smk_object == NULL) - return -1; + if (import) { + rule->smk_subject = smk_import(data, 0); + if (rule->smk_subject == NULL) + return -1; + + rule->smk_object = smk_import(data + SMK_LABELLEN, 0); + if (rule->smk_object == NULL) + return -1; + } else { + smk_parse_smack(data, 0, smack); + skp = smk_find_entry(smack); + if (skp == NULL) + return -1; + rule->smk_subject = skp->smk_known; + + smk_parse_smack(data + SMK_LABELLEN, 0, smack); + skp = smk_find_entry(smack); + if (skp == NULL) + return -1; + rule->smk_object = skp->smk_known; + } rule->smk_access = 0; @@ -327,7 +345,7 @@ static ssize_t smk_write_load_list(struct file *file, const char __user *buf, goto out; } - if (smk_parse_rule(data, rule)) + if (smk_parse_rule(data, rule, 1)) goto out_free_rule; if (rule_list == NULL) { @@ -1499,14 +1517,11 @@ static ssize_t smk_write_access(struct file *file, const char __user *buf, char *data; int res; - if (!capable(CAP_MAC_ADMIN)) - return -EPERM; - data = simple_transaction_get(file, buf, count); if (IS_ERR(data)) return PTR_ERR(data); - if (count < SMK_LOADLEN || smk_parse_rule(data, &rule)) + if (count < SMK_LOADLEN || smk_parse_rule(data, &rule, 0)) return -EINVAL; res = smk_access(rule.smk_subject, rule.smk_object, rule.smk_access, @@ -1560,7 +1575,7 @@ static int smk_fill_super(struct super_block *sb, void *data, int silent) [SMK_LOAD_SELF] = { "load-self", &smk_load_self_ops, S_IRUGO|S_IWUGO}, [SMK_ACCESSES] = { - "access", &smk_access_ops, S_IRUGO|S_IWUSR}, + "access", &smk_access_ops, S_IRUGO|S_IWUGO}, /* last one */ {""} }; From e0b057b406a33501a656dc8d67ea945d7bcdad61 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Fri, 21 Oct 2011 12:37:13 +0900 Subject: [PATCH 95/95] TOMOYO: Fix incomplete read after seek. Commit f23571e8 "TOMOYO: Copy directly to userspace buffer." introduced tomoyo_flush() that flushes data to be read as soon as possible. tomoyo_select_domain() (which is called by write()) enqueues data which meant to be read by next read(), but previous read()'s read buffer's size was not cleared. As a result, since 2.6.36, sequence like char *cp = "select global-pid=1\n"; read(fd, buf1, sizeof(buf1)); write(fd, cp, strlen(cp)); read(fd, buf2, sizeof(buf2)); causes enqueued data to be flushed to buf1 rather than buf2. Fix this bug by clearing read buffer's size upon write() request. Signed-off-by: Tetsuo Handa Signed-off-by: James Morris --- security/tomoyo/common.c | 1 + 1 file changed, 1 insertion(+) diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c index 96b7233a0df6..d41900de8a69 100644 --- a/security/tomoyo/common.c +++ b/security/tomoyo/common.c @@ -2591,6 +2591,7 @@ ssize_t tomoyo_write_control(struct tomoyo_io_buffer *head, return -EFAULT; if (mutex_lock_interruptible(&head->io_sem)) return -EINTR; + head->read_user_buf_avail = 0; idx = tomoyo_read_lock(); /* Read a line and dispatch it to the policy handler. */ while (avail_len > 0) {