From 0d90a7ec48c704025307b129413bc62451b20ab3 Mon Sep 17 00:00:00 2001 From: "David P. Quigley" Date: Fri, 16 Jan 2009 09:22:02 -0500 Subject: SELinux: Condense super block security structure flags and cleanup necessary code. The super block security structure currently has three fields for what are essentially flags. The flags field is used for mount options while two other char fields are used for initialization and proc flags. These latter two fields are essentially bit fields since the only used values are 0 and 1. These fields have been collapsed into the flags field and new bit masks have been added for them. The code is also fixed to work with these new flags. Signed-off-by: David P. Quigley Acked-by: Eric Paris Signed-off-by: James Morris --- security/selinux/hooks.c | 38 ++++++++++++++++++------------------- security/selinux/include/objsec.h | 2 -- security/selinux/include/security.h | 6 ++++++ 3 files changed, 24 insertions(+), 22 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 00815973d41..473adc5f4f9 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -431,7 +431,7 @@ static int sb_finish_set_opts(struct super_block *sb) } } - sbsec->initialized = 1; + sbsec->flags |= SE_SBINITIALIZED; if (sbsec->behavior > ARRAY_SIZE(labeling_behaviors)) printk(KERN_ERR "SELinux: initialized (dev %s, type %s), unknown behavior\n", @@ -487,17 +487,13 @@ static int selinux_get_mnt_opts(const struct super_block *sb, security_init_mnt_opts(opts); - if (!sbsec->initialized) + if (!(sbsec->flags & SE_SBINITIALIZED)) return -EINVAL; if (!ss_initialized) return -EINVAL; - /* - * if we ever use sbsec flags for anything other than tracking mount - * settings this is going to need a mask - */ - tmp = sbsec->flags; + tmp = sbsec->flags & SE_MNTMASK; /* count the number of mount options for this sb */ for (i = 0; i < 8; i++) { if (tmp & 0x01) @@ -562,8 +558,10 @@ out_free: static int bad_option(struct superblock_security_struct *sbsec, char flag, u32 old_sid, u32 new_sid) { + char mnt_flags = sbsec->flags & SE_MNTMASK; + /* check if the old mount command had the same options */ - if (sbsec->initialized) + if (sbsec->flags & SE_SBINITIALIZED) if (!(sbsec->flags & flag) || (old_sid != new_sid)) return 1; @@ -571,8 +569,8 @@ static int bad_option(struct superblock_security_struct *sbsec, char flag, /* check if we were passed the same options twice, * aka someone passed context=a,context=b */ - if (!sbsec->initialized) - if (sbsec->flags & flag) + if (!(sbsec->flags & SE_SBINITIALIZED)) + if (mnt_flags & flag) return 1; return 0; } @@ -626,7 +624,7 @@ static int selinux_set_mnt_opts(struct super_block *sb, * this sb does not set any security options. (The first options * will be used for both mounts) */ - if (sbsec->initialized && (sb->s_type->fs_flags & FS_BINARY_MOUNTDATA) + if ((sbsec->flags & SE_SBINITIALIZED) && (sb->s_type->fs_flags & FS_BINARY_MOUNTDATA) && (num_opts == 0)) goto out; @@ -690,19 +688,19 @@ static int selinux_set_mnt_opts(struct super_block *sb, } } - if (sbsec->initialized) { + if (sbsec->flags & SE_SBINITIALIZED) { /* previously mounted with options, but not on this attempt? */ - if (sbsec->flags && !num_opts) + if ((sbsec->flags & SE_MNTMASK) && !num_opts) goto out_double_mount; rc = 0; goto out; } if (strcmp(sb->s_type->name, "proc") == 0) - sbsec->proc = 1; + sbsec->flags |= SE_SBPROC; /* Determine the labeling behavior to use for this filesystem type. */ - rc = security_fs_use(sbsec->proc ? "proc" : sb->s_type->name, &sbsec->behavior, &sbsec->sid); + rc = security_fs_use((sbsec->flags & SE_SBPROC) ? "proc" : sb->s_type->name, &sbsec->behavior, &sbsec->sid); if (rc) { printk(KERN_WARNING "%s: security_fs_use(%s) returned %d\n", __func__, sb->s_type->name, rc); @@ -806,10 +804,10 @@ static void selinux_sb_clone_mnt_opts(const struct super_block *oldsb, } /* how can we clone if the old one wasn't set up?? */ - BUG_ON(!oldsbsec->initialized); + BUG_ON(!(oldsbsec->flags & SE_SBINITIALIZED)); /* if fs is reusing a sb, just let its options stand... */ - if (newsbsec->initialized) + if (newsbsec->flags & SE_SBINITIALIZED) return; mutex_lock(&newsbsec->lock); @@ -1209,7 +1207,7 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent goto out_unlock; sbsec = inode->i_sb->s_security; - if (!sbsec->initialized) { + if (!(sbsec->flags & SE_SBINITIALIZED)) { /* Defer initialization until selinux_complete_init, after the initial policy is loaded and the security server is ready to handle calls. */ @@ -1326,7 +1324,7 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent /* Default to the fs superblock SID. */ isec->sid = sbsec->sid; - if (sbsec->proc && !S_ISLNK(inode->i_mode)) { + if ((sbsec->flags & SE_SBPROC) && !S_ISLNK(inode->i_mode)) { struct proc_inode *proci = PROC_I(inode); if (proci->pde) { isec->sclass = inode_mode_to_security_class(inode->i_mode); @@ -2585,7 +2583,7 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir, } /* Possibly defer initialization to selinux_complete_init. */ - if (sbsec->initialized) { + if (sbsec->flags & SE_SBINITIALIZED) { struct inode_security_struct *isec = inode->i_security; isec->sclass = inode_mode_to_security_class(inode->i_mode); isec->sid = newsid; diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index 3cc45168f67..c4e062336ef 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -60,9 +60,7 @@ struct superblock_security_struct { u32 def_sid; /* default SID for labeling */ u32 mntpoint_sid; /* SECURITY_FS_USE_MNTPOINT context for files */ unsigned int behavior; /* labeling behavior */ - unsigned char initialized; /* initialization flag */ unsigned char flags; /* which mount options were specified */ - unsigned char proc; /* proc fs */ struct mutex lock; struct list_head isec_head; spinlock_t isec_lock; diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index 72447370bc9..ff4e19ccd8f 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -37,10 +37,16 @@ #define POLICYDB_VERSION_MAX POLICYDB_VERSION_BOUNDARY #endif +/* Mask for just the mount related flags */ +#define SE_MNTMASK 0x0f +/* Super block security struct flags for mount options */ #define CONTEXT_MNT 0x01 #define FSCONTEXT_MNT 0x02 #define ROOTCONTEXT_MNT 0x04 #define DEFCONTEXT_MNT 0x08 +/* Non-mount related flags */ +#define SE_SBINITIALIZED 0x10 +#define SE_SBPROC 0x20 #define CONTEXT_STR "context=" #define FSCONTEXT_STR "fscontext=" -- cgit v1.2.3 From 11689d47f0957121920c9ec646eb5d838755853a Mon Sep 17 00:00:00 2001 From: "David P. Quigley" Date: Fri, 16 Jan 2009 09:22:03 -0500 Subject: SELinux: Add new security mount option to indicate security label support. There is no easy way to tell if a file system supports SELinux security labeling. Because of this a new flag is being added to the super block security structure to indicate that the particular super block supports labeling. This flag is set for file systems using the xattr, task, and transition labeling methods unless that behavior is overridden by context mounts. Signed-off-by: David P. Quigley Acked-by: Eric Paris Signed-off-by: James Morris --- security/selinux/hooks.c | 39 ++++++++++++++++++++++++++++++++----- security/selinux/include/security.h | 2 ++ 2 files changed, 36 insertions(+), 5 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 473adc5f4f9..1a9768a8b64 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -89,7 +89,7 @@ #define XATTR_SELINUX_SUFFIX "selinux" #define XATTR_NAME_SELINUX XATTR_SECURITY_PREFIX XATTR_SELINUX_SUFFIX -#define NUM_SEL_MNT_OPTS 4 +#define NUM_SEL_MNT_OPTS 5 extern unsigned int policydb_loaded_version; extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm); @@ -353,6 +353,7 @@ enum { Opt_fscontext = 2, Opt_defcontext = 3, Opt_rootcontext = 4, + Opt_labelsupport = 5, }; static const match_table_t tokens = { @@ -360,6 +361,7 @@ static const match_table_t tokens = { {Opt_fscontext, FSCONTEXT_STR "%s"}, {Opt_defcontext, DEFCONTEXT_STR "%s"}, {Opt_rootcontext, ROOTCONTEXT_STR "%s"}, + {Opt_labelsupport, LABELSUPP_STR}, {Opt_error, NULL}, }; @@ -431,7 +433,7 @@ static int sb_finish_set_opts(struct super_block *sb) } } - sbsec->flags |= SE_SBINITIALIZED; + sbsec->flags |= (SE_SBINITIALIZED | SE_SBLABELSUPP); if (sbsec->behavior > ARRAY_SIZE(labeling_behaviors)) printk(KERN_ERR "SELinux: initialized (dev %s, type %s), unknown behavior\n", @@ -441,6 +443,12 @@ static int sb_finish_set_opts(struct super_block *sb) sb->s_id, sb->s_type->name, labeling_behaviors[sbsec->behavior-1]); + if (sbsec->behavior == SECURITY_FS_USE_GENFS || + sbsec->behavior == SECURITY_FS_USE_MNTPOINT || + sbsec->behavior == SECURITY_FS_USE_NONE || + sbsec->behavior > ARRAY_SIZE(labeling_behaviors)) + sbsec->flags &= ~SE_SBLABELSUPP; + /* Initialize the root inode. */ rc = inode_doinit_with_dentry(root_inode, root); @@ -500,6 +508,9 @@ static int selinux_get_mnt_opts(const struct super_block *sb, opts->num_mnt_opts++; tmp >>= 1; } + /* Check if the Label support flag is set */ + if (sbsec->flags & SE_SBLABELSUPP) + opts->num_mnt_opts++; opts->mnt_opts = kcalloc(opts->num_mnt_opts, sizeof(char *), GFP_ATOMIC); if (!opts->mnt_opts) { @@ -545,6 +556,10 @@ static int selinux_get_mnt_opts(const struct super_block *sb, opts->mnt_opts[i] = context; opts->mnt_opts_flags[i++] = ROOTCONTEXT_MNT; } + if (sbsec->flags & SE_SBLABELSUPP) { + opts->mnt_opts[i] = NULL; + opts->mnt_opts_flags[i++] = SE_SBLABELSUPP; + } BUG_ON(i != opts->num_mnt_opts); @@ -635,6 +650,9 @@ static int selinux_set_mnt_opts(struct super_block *sb, */ for (i = 0; i < num_opts; i++) { u32 sid; + + if (flags[i] == SE_SBLABELSUPP) + continue; rc = security_context_to_sid(mount_options[i], strlen(mount_options[i]), &sid); if (rc) { @@ -915,7 +933,8 @@ static int selinux_parse_opts_str(char *options, goto out_err; } break; - + case Opt_labelsupport: + break; default: rc = -EINVAL; printk(KERN_WARNING "SELinux: unknown mount option\n"); @@ -997,7 +1016,12 @@ static void selinux_write_opts(struct seq_file *m, char *prefix; for (i = 0; i < opts->num_mnt_opts; i++) { - char *has_comma = strchr(opts->mnt_opts[i], ','); + char *has_comma; + + if (opts->mnt_opts[i]) + has_comma = strchr(opts->mnt_opts[i], ','); + else + has_comma = NULL; switch (opts->mnt_opts_flags[i]) { case CONTEXT_MNT: @@ -1012,6 +1036,10 @@ static void selinux_write_opts(struct seq_file *m, case DEFCONTEXT_MNT: prefix = DEFCONTEXT_STR; break; + case SE_SBLABELSUPP: + seq_putc(m, ','); + seq_puts(m, LABELSUPP_STR); + continue; default: BUG(); }; @@ -2398,7 +2426,8 @@ static inline int selinux_option(char *option, int len) return (match_prefix(CONTEXT_STR, sizeof(CONTEXT_STR)-1, option, len) || match_prefix(FSCONTEXT_STR, sizeof(FSCONTEXT_STR)-1, option, len) || match_prefix(DEFCONTEXT_STR, sizeof(DEFCONTEXT_STR)-1, option, len) || - match_prefix(ROOTCONTEXT_STR, sizeof(ROOTCONTEXT_STR)-1, option, len)); + match_prefix(ROOTCONTEXT_STR, sizeof(ROOTCONTEXT_STR)-1, option, len) || + match_prefix(LABELSUPP_STR, sizeof(LABELSUPP_STR)-1, option, len)); } static inline void take_option(char **to, char *from, int *first, int len) diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index ff4e19ccd8f..e1d9db77998 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -47,11 +47,13 @@ /* Non-mount related flags */ #define SE_SBINITIALIZED 0x10 #define SE_SBPROC 0x20 +#define SE_SBLABELSUPP 0x40 #define CONTEXT_STR "context=" #define FSCONTEXT_STR "fscontext=" #define ROOTCONTEXT_STR "rootcontext=" #define DEFCONTEXT_STR "defcontext=" +#define LABELSUPP_STR "seclabel" struct netlbl_lsm_secattr; -- cgit v1.2.3 From cd89596f0ccfa3ccb8a81ce47782231cf7ea7296 Mon Sep 17 00:00:00 2001 From: "David P. Quigley" Date: Fri, 16 Jan 2009 09:22:04 -0500 Subject: SELinux: Unify context mount and genfs behavior Context mounts and genfs labeled file systems behave differently with respect to setting file system labels. This patch brings genfs labeled file systems in line with context mounts in that setxattr calls to them should return EOPNOTSUPP and fscreate calls will be ignored. Signed-off-by: David P. Quigley Acked-by: Eric Paris Signed-off-by: James Morris --- security/selinux/hooks.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 1a9768a8b64..3bb4942e39c 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -1613,7 +1613,7 @@ static int may_create(struct inode *dir, if (rc) return rc; - if (!newsid || sbsec->behavior == SECURITY_FS_USE_MNTPOINT) { + if (!newsid || !(sbsec->flags & SE_SBLABELSUPP)) { rc = security_transition_sid(sid, dsec->sid, tclass, &newsid); if (rc) return rc; @@ -2597,7 +2597,7 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir, sid = tsec->sid; newsid = tsec->create_sid; - if (!newsid || sbsec->behavior == SECURITY_FS_USE_MNTPOINT) { + if (!newsid || !(sbsec->flags & SE_SBLABELSUPP)) { rc = security_transition_sid(sid, dsec->sid, inode_mode_to_security_class(inode->i_mode), &newsid); @@ -2619,7 +2619,7 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir, isec->initialized = 1; } - if (!ss_initialized || sbsec->behavior == SECURITY_FS_USE_MNTPOINT) + if (!ss_initialized || !(sbsec->flags & SE_SBLABELSUPP)) return -EOPNOTSUPP; if (name) { @@ -2796,7 +2796,7 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name, return selinux_inode_setotherxattr(dentry, name); sbsec = inode->i_sb->s_security; - if (sbsec->behavior == SECURITY_FS_USE_MNTPOINT) + if (!(sbsec->flags & SE_SBLABELSUPP)) return -EOPNOTSUPP; if (!is_owner_or_cap(inode)) -- cgit v1.2.3 From bc05595845f58c065adc0763a678187647ec040f Mon Sep 17 00:00:00 2001 From: James Morris Date: Thu, 29 Jan 2009 11:28:33 +1100 Subject: selinux: remove unused bprm_check_security hook Remove unused bprm_check_security hook from SELinux. This currently calls into the capabilities hook, which is a noop. Acked-by: Eric Paris Acked-by: Serge Hallyn Signed-off-by: James Morris --- security/selinux/hooks.c | 6 ------ 1 file changed, 6 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 3bb4942e39c..8251c6ba36c 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2182,11 +2182,6 @@ static int selinux_bprm_set_creds(struct linux_binprm *bprm) return 0; } -static int selinux_bprm_check_security(struct linux_binprm *bprm) -{ - return secondary_ops->bprm_check_security(bprm); -} - static int selinux_bprm_secureexec(struct linux_binprm *bprm) { const struct cred *cred = current_cred(); @@ -5608,7 +5603,6 @@ static struct security_operations selinux_ops = { .netlink_recv = selinux_netlink_recv, .bprm_set_creds = selinux_bprm_set_creds, - .bprm_check_security = selinux_bprm_check_security, .bprm_committing_creds = selinux_bprm_committing_creds, .bprm_committed_creds = selinux_bprm_committed_creds, .bprm_secureexec = selinux_bprm_secureexec, -- cgit v1.2.3 From 2ec5dbe23d68bddc043a85d1226bfc499a724b1c Mon Sep 17 00:00:00 2001 From: James Morris Date: Thu, 29 Jan 2009 11:46:14 +1100 Subject: selinux: remove secondary ops call to bprm_committing_creds Remove secondary ops call to bprm_committing_creds, which is a noop in capabilities. Acked-by: Serge Hallyn Acked-by: Eric Paris Signed-off-by: James Morris --- security/selinux/hooks.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 8251c6ba36c..fc01ffa0b69 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2311,8 +2311,6 @@ static void selinux_bprm_committing_creds(struct linux_binprm *bprm) struct rlimit *rlim, *initrlim; int rc, i; - secondary_ops->bprm_committing_creds(bprm); - new_tsec = bprm->cred->security; if (new_tsec->sid == new_tsec->osid) return; -- cgit v1.2.3 From 5565b0b865f672e3d7e31936ad1d40710ab7bfc4 Mon Sep 17 00:00:00 2001 From: James Morris Date: Thu, 29 Jan 2009 11:47:49 +1100 Subject: selinux: remove secondary ops call to bprm_committed_creds Remove secondary ops call to bprm_committed_creds, which is a noop in capabilities. Acked-by: Serge Hallyn Acked-by: Eric Paris Signed-off-by: James Morris --- security/selinux/hooks.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index fc01ffa0b69..516058ff063 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2356,8 +2356,6 @@ static void selinux_bprm_committed_creds(struct linux_binprm *bprm) int rc, i; unsigned long flags; - secondary_ops->bprm_committed_creds(bprm); - osid = tsec->osid; sid = tsec->sid; -- cgit v1.2.3 From ef935b9136eeaa203f75bf0b4d6e398c29f44d27 Mon Sep 17 00:00:00 2001 From: James Morris Date: Thu, 29 Jan 2009 11:51:11 +1100 Subject: selinux: remove secondary ops call to sb_mount Remove secondary ops call to sb_mount, which is a noop in capabilities. Acked-by: Serge Hallyn Acked-by: Eric Paris Signed-off-by: James Morris --- security/selinux/hooks.c | 5 ----- 1 file changed, 5 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 516058ff063..bdd483096b3 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2531,11 +2531,6 @@ static int selinux_mount(char *dev_name, void *data) { const struct cred *cred = current_cred(); - int rc; - - rc = secondary_ops->sb_mount(dev_name, path, type, flags, data); - if (rc) - return rc; if (flags & MS_REMOUNT) return superblock_has_perm(cred, path->mnt->mnt_sb, -- cgit v1.2.3 From 97422ab9ef45118cb7418d799dc69040f17108ce Mon Sep 17 00:00:00 2001 From: James Morris Date: Thu, 29 Jan 2009 11:55:02 +1100 Subject: selinux: remove secondary ops call to sb_umount Remove secondary ops call to sb_umount, which is a noop in capabilities. Acked-by: Serge Hallyn Acked-by: Eric Paris Signed-off-by: James Morris --- security/selinux/hooks.c | 5 ----- 1 file changed, 5 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index bdd483096b3..42aa8de5f59 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2543,11 +2543,6 @@ static int selinux_mount(char *dev_name, static int selinux_umount(struct vfsmount *mnt, int flags) { const struct cred *cred = current_cred(); - int rc; - - rc = secondary_ops->sb_umount(mnt, flags); - if (rc) - return rc; return superblock_has_perm(cred, mnt->mnt_sb, FILESYSTEM__UNMOUNT, NULL); -- cgit v1.2.3 From efdfac437607e4acfed66c383091a376525eaec4 Mon Sep 17 00:00:00 2001 From: James Morris Date: Thu, 29 Jan 2009 11:57:34 +1100 Subject: selinux: remove secondary ops call to inode_link Remove secondary ops call to inode_link, which is a noop in capabilities. Acked-by: Serge Hallyn Acked-by: Eric Paris Signed-off-by: James Morris --- security/selinux/hooks.c | 5 ----- 1 file changed, 5 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 42aa8de5f59..da0e523157d 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2630,11 +2630,6 @@ static int selinux_inode_create(struct inode *dir, struct dentry *dentry, int ma static int selinux_inode_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) { - int rc; - - rc = secondary_ops->inode_link(old_dentry, dir, new_dentry); - if (rc) - return rc; return may_link(dir, old_dentry, MAY_LINK); } -- cgit v1.2.3 From e4737250b751b4e0e802adae9a4d3ae0227b580b Mon Sep 17 00:00:00 2001 From: James Morris Date: Thu, 29 Jan 2009 12:00:08 +1100 Subject: selinux: remove secondary ops call to inode_unlink Remove secondary ops call to inode_unlink, which is a noop in capabilities. Acked-by: Serge Hallyn Acked-by: Eric Paris Signed-off-by: James Morris --- security/selinux/hooks.c | 5 ----- 1 file changed, 5 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index da0e523157d..ec834dc0b41 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2635,11 +2635,6 @@ static int selinux_inode_link(struct dentry *old_dentry, struct inode *dir, stru static int selinux_inode_unlink(struct inode *dir, struct dentry *dentry) { - int rc; - - rc = secondary_ops->inode_unlink(dir, dentry); - if (rc) - return rc; return may_link(dir, dentry, MAY_UNLINK); } -- cgit v1.2.3 From dd4907a6d4e038dc65839fcd4030ebefe2f5f439 Mon Sep 17 00:00:00 2001 From: James Morris Date: Thu, 29 Jan 2009 12:08:34 +1100 Subject: selinux: remove secondary ops call to inode_mknod Remove secondary ops call to inode_mknod, which is a noop in capabilities. Acked-by: Serge Hallyn Acked-by: Eric Paris Signed-off-by: James Morris --- security/selinux/hooks.c | 6 ------ 1 file changed, 6 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index ec834dc0b41..03621928f1b 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2655,12 +2655,6 @@ static int selinux_inode_rmdir(struct inode *dir, struct dentry *dentry) static int selinux_inode_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev) { - int rc; - - rc = secondary_ops->inode_mknod(dir, dentry, mode, dev); - if (rc) - return rc; - return may_create(dir, dentry, inode_mode_to_security_class(mode)); } -- cgit v1.2.3 From f51115b9ab5b9cfd0b7be1cce75afbf3ffbcdd87 Mon Sep 17 00:00:00 2001 From: James Morris Date: Thu, 29 Jan 2009 12:10:56 +1100 Subject: selinux: remove secondary ops call to inode_follow_link Remove secondary ops call to inode_follow_link, which is a noop in capabilities. Acked-by: Serge Hallyn Signed-off-by: James Morris --- security/selinux/hooks.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 03621928f1b..67291a385c7 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2674,11 +2674,7 @@ static int selinux_inode_readlink(struct dentry *dentry) static int selinux_inode_follow_link(struct dentry *dentry, struct nameidata *nameidata) { const struct cred *cred = current_cred(); - int rc; - rc = secondary_ops->inode_follow_link(dentry, nameidata); - if (rc) - return rc; return dentry_has_perm(cred, NULL, dentry, FILE__READ); } -- cgit v1.2.3 From 188fbcca9dd02f15dcf45cfc51ce0dd6c13993f6 Mon Sep 17 00:00:00 2001 From: James Morris Date: Thu, 29 Jan 2009 12:14:03 +1100 Subject: selinux: remove secondary ops call to inode_permission Remove secondary ops call to inode_permission, which is a noop in capabilities. Acked-by: Serge Hallyn Acked-by: Eric Paris Signed-off-by: James Morris --- security/selinux/hooks.c | 5 ----- 1 file changed, 5 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 67291a385c7..7e90c9e5865 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2681,11 +2681,6 @@ static int selinux_inode_follow_link(struct dentry *dentry, struct nameidata *na static int selinux_inode_permission(struct inode *inode, int mask) { const struct cred *cred = current_cred(); - int rc; - - rc = secondary_ops->inode_permission(inode, mask); - if (rc) - return rc; if (!mask) { /* No permission to check. Existence test. */ -- cgit v1.2.3 From 438add6b32d9295db6e3ecd4d9e137086ec5b5d9 Mon Sep 17 00:00:00 2001 From: James Morris Date: Thu, 29 Jan 2009 12:15:59 +1100 Subject: selinux: remove secondary ops call to inode_setattr Remove secondary ops call to inode_setattr, which is a noop in capabilities. Acked-by: Serge Hallyn Acked-by: Eric Paris Signed-off-by: James Morris --- security/selinux/hooks.c | 5 ----- 1 file changed, 5 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 7e90c9e5865..08b506846a1 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2694,11 +2694,6 @@ static int selinux_inode_permission(struct inode *inode, int mask) static int selinux_inode_setattr(struct dentry *dentry, struct iattr *iattr) { const struct cred *cred = current_cred(); - int rc; - - rc = secondary_ops->inode_setattr(dentry, iattr); - if (rc) - return rc; if (iattr->ia_valid & ATTR_FORCE) return 0; -- cgit v1.2.3 From d541bbee6902d5ffb8a03d63ac8f4b1364c2ff93 Mon Sep 17 00:00:00 2001 From: James Morris Date: Thu, 29 Jan 2009 12:19:51 +1100 Subject: selinux: remove secondary ops call to file_mprotect Remove secondary ops call to file_mprotect, which is a noop in capabilities. Acked-by: Serge Hallyn Acked-by: Eric Paris Signed-off-by: James Morris --- security/selinux/hooks.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 08b506846a1..2c98071fba8 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3056,18 +3056,13 @@ static int selinux_file_mprotect(struct vm_area_struct *vma, unsigned long prot) { const struct cred *cred = current_cred(); - int rc; - - rc = secondary_ops->file_mprotect(vma, reqprot, prot); - if (rc) - return rc; if (selinux_checkreqprot) prot = reqprot; #ifndef CONFIG_PPC32 if ((prot & PROT_EXEC) && !(vma->vm_flags & VM_EXEC)) { - rc = 0; + int rc = 0; if (vma->vm_start >= vma->vm_mm->start_brk && vma->vm_end <= vma->vm_mm->brk) { rc = cred_has_perm(cred, cred, PROCESS__EXECHEAP); -- cgit v1.2.3 From af294e41d0c95a291cc821a1b43ec2cd13976a8b Mon Sep 17 00:00:00 2001 From: James Morris Date: Thu, 29 Jan 2009 12:23:36 +1100 Subject: selinux: remove secondary ops call to task_create Remove secondary ops call to task_create, which is a noop in capabilities. Acked-by: Serge Hallyn Acked-by: Eric Paris Signed-off-by: James Morris --- security/selinux/hooks.c | 6 ------ 1 file changed, 6 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 2c98071fba8..72c1e5cd26d 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3212,12 +3212,6 @@ static int selinux_dentry_open(struct file *file, const struct cred *cred) static int selinux_task_create(unsigned long clone_flags) { - int rc; - - rc = secondary_ops->task_create(clone_flags); - if (rc) - return rc; - return current_has_perm(current, PROCESS__FORK); } -- cgit v1.2.3 From ca5143d3ff3c7a4e1c2c8bdcf0f53aea227a7722 Mon Sep 17 00:00:00 2001 From: James Morris Date: Thu, 29 Jan 2009 12:26:14 +1100 Subject: selinux: remove unused cred_commit hook Remove unused cred_commit hook from SELinux. This currently calls into the capabilities hook, which is a noop. Acked-by: Serge Hallyn Acked-by: Eric Paris Signed-off-by: James Morris --- security/selinux/hooks.c | 9 --------- 1 file changed, 9 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 72c1e5cd26d..afccada1f26 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3244,14 +3244,6 @@ static int selinux_cred_prepare(struct cred *new, const struct cred *old, return 0; } -/* - * commit new credentials - */ -static void selinux_cred_commit(struct cred *new, const struct cred *old) -{ - secondary_ops->cred_commit(new, old); -} - /* * set the security data for a kernel service * - all the creation contexts are set to unlabelled @@ -5610,7 +5602,6 @@ static struct security_operations selinux_ops = { .task_create = selinux_task_create, .cred_free = selinux_cred_free, .cred_prepare = selinux_cred_prepare, - .cred_commit = selinux_cred_commit, .kernel_act_as = selinux_kernel_act_as, .kernel_create_files_as = selinux_kernel_create_files_as, .task_setuid = selinux_task_setuid, -- cgit v1.2.3 From ef76e748faa823a738d632ee4c8ed9adaabc8a40 Mon Sep 17 00:00:00 2001 From: James Morris Date: Thu, 29 Jan 2009 12:30:28 +1100 Subject: selinux: remove secondary ops call to task_setrlimit Remove secondary ops call to task_setrlimit, which is a noop in capabilities. Acked-by: Serge Hallyn Acked-by: Eric Paris Signed-off-by: James Morris --- security/selinux/hooks.c | 5 ----- 1 file changed, 5 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index afccada1f26..3aaa63cc5c7 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3367,11 +3367,6 @@ static int selinux_task_getioprio(struct task_struct *p) static int selinux_task_setrlimit(unsigned int resource, struct rlimit *new_rlim) { struct rlimit *old_rlim = current->signal->rlim + resource; - int rc; - - rc = secondary_ops->task_setrlimit(resource, new_rlim); - if (rc) - return rc; /* Control the ability to change the hard limit (whether lowering or raising it), so that the hard limit can -- cgit v1.2.3 From 2cbbd19812b7636c1c37bcf50c403e7af5278d73 Mon Sep 17 00:00:00 2001 From: James Morris Date: Thu, 29 Jan 2009 12:32:50 +1100 Subject: selinux: remove secondary ops call to task_kill Remove secondary ops call to task_kill, which is a noop in capabilities. Acked-by: Serge Hallyn Acked-by: Eric Paris Signed-off-by: James Morris --- security/selinux/hooks.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 3aaa63cc5c7..0bd36a17587 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3405,10 +3405,6 @@ static int selinux_task_kill(struct task_struct *p, struct siginfo *info, u32 perm; int rc; - rc = secondary_ops->task_kill(p, info, sig, secid); - if (rc) - return rc; - if (!sig) perm = PROCESS__SIGNULL; /* null signal; existence test */ else -- cgit v1.2.3 From 5c4054ccfafb6a446e9b65c524af1741656c6c60 Mon Sep 17 00:00:00 2001 From: James Morris Date: Thu, 29 Jan 2009 12:34:53 +1100 Subject: selinux: remove secondary ops call to unix_stream_connect Remove secondary ops call to unix_stream_connect, which is a noop in capabilities. Acked-by: Serge Hallyn Acked-by: Eric Paris Signed-off-by: James Morris --- security/selinux/hooks.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 0bd36a17587..25198e9896f 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3997,10 +3997,6 @@ static int selinux_socket_unix_stream_connect(struct socket *sock, struct avc_audit_data ad; int err; - err = secondary_ops->unix_stream_connect(sock, other, newsk); - if (err) - return err; - isec = SOCK_INODE(sock)->i_security; other_isec = SOCK_INODE(other)->i_security; -- cgit v1.2.3 From 95c14904b6f6f8a35365f0c58d530c85b4fb96b4 Mon Sep 17 00:00:00 2001 From: James Morris Date: Thu, 29 Jan 2009 12:37:58 +1100 Subject: selinux: remove secondary ops call to shm_shmat Remove secondary ops call to shm_shmat, which is a noop in capabilities. Acked-by: Serge Hallyn Acked-by: Eric Paris Signed-off-by: James Morris --- security/selinux/hooks.c | 5 ----- 1 file changed, 5 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 25198e9896f..d9604794a4d 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -5113,11 +5113,6 @@ static int selinux_shm_shmat(struct shmid_kernel *shp, char __user *shmaddr, int shmflg) { u32 perms; - int rc; - - rc = secondary_ops->shm_shmat(shp, shmaddr, shmflg); - if (rc) - return rc; if (shmflg & SHM_RDONLY) perms = SHM__READ; -- cgit v1.2.3 From 5626d3e86141390c8efc7bcb929b6a4b58b00480 Mon Sep 17 00:00:00 2001 From: James Morris Date: Fri, 30 Jan 2009 10:05:06 +1100 Subject: selinux: remove hooks which simply defer to capabilities Remove SELinux hooks which do nothing except defer to the capabilites hooks (or in one case, replicates the function). Signed-off-by: James Morris Acked-by: Stephen Smalley --- security/selinux/hooks.c | 68 +++++++----------------------------------------- 1 file changed, 10 insertions(+), 58 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index d9604794a4d..a69d6f8970c 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -1892,6 +1892,16 @@ static int selinux_capset(struct cred *new, const struct cred *old, return cred_has_perm(old, new, PROCESS__SETCAP); } +/* + * (This comment used to live with the selinux_task_setuid hook, + * which was removed). + * + * Since setuid only affects the current process, and since the SELinux + * controls are not based on the Linux identity attributes, SELinux does not + * need to control this operation. However, SELinux does control the use of + * the CAP_SETUID and CAP_SETGID capabilities using the capable hook. + */ + static int selinux_capable(struct task_struct *tsk, const struct cred *cred, int cap, int audit) { @@ -2909,16 +2919,6 @@ static int selinux_inode_listsecurity(struct inode *inode, char *buffer, size_t return len; } -static int selinux_inode_need_killpriv(struct dentry *dentry) -{ - return secondary_ops->inode_need_killpriv(dentry); -} - -static int selinux_inode_killpriv(struct dentry *dentry) -{ - return secondary_ops->inode_killpriv(dentry); -} - static void selinux_inode_getsecid(const struct inode *inode, u32 *secid) { struct inode_security_struct *isec = inode->i_security; @@ -3288,29 +3288,6 @@ static int selinux_kernel_create_files_as(struct cred *new, struct inode *inode) return 0; } -static int selinux_task_setuid(uid_t id0, uid_t id1, uid_t id2, int flags) -{ - /* Since setuid only affects the current process, and - since the SELinux controls are not based on the Linux - identity attributes, SELinux does not need to control - this operation. However, SELinux does control the use - of the CAP_SETUID and CAP_SETGID capabilities using the - capable hook. */ - return 0; -} - -static int selinux_task_fix_setuid(struct cred *new, const struct cred *old, - int flags) -{ - return secondary_ops->task_fix_setuid(new, old, flags); -} - -static int selinux_task_setgid(gid_t id0, gid_t id1, gid_t id2, int flags) -{ - /* See the comment for setuid above. */ - return 0; -} - static int selinux_task_setpgid(struct task_struct *p, pid_t pgid) { return current_has_perm(p, PROCESS__SETPGID); @@ -3331,12 +3308,6 @@ static void selinux_task_getsecid(struct task_struct *p, u32 *secid) *secid = task_sid(p); } -static int selinux_task_setgroups(struct group_info *group_info) -{ - /* See the comment for setuid above. */ - return 0; -} - static int selinux_task_setnice(struct task_struct *p, int nice) { int rc; @@ -3417,18 +3388,6 @@ static int selinux_task_kill(struct task_struct *p, struct siginfo *info, return rc; } -static int selinux_task_prctl(int option, - unsigned long arg2, - unsigned long arg3, - unsigned long arg4, - unsigned long arg5) -{ - /* The current prctl operations do not appear to require - any SELinux controls since they merely observe or modify - the state of the current process. */ - return secondary_ops->task_prctl(option, arg2, arg3, arg4, arg5); -} - static int selinux_task_wait(struct task_struct *p) { return task_has_perm(p, current, PROCESS__SIGCHLD); @@ -5563,8 +5522,6 @@ static struct security_operations selinux_ops = { .inode_getsecurity = selinux_inode_getsecurity, .inode_setsecurity = selinux_inode_setsecurity, .inode_listsecurity = selinux_inode_listsecurity, - .inode_need_killpriv = selinux_inode_need_killpriv, - .inode_killpriv = selinux_inode_killpriv, .inode_getsecid = selinux_inode_getsecid, .file_permission = selinux_file_permission, @@ -5586,14 +5543,10 @@ static struct security_operations selinux_ops = { .cred_prepare = selinux_cred_prepare, .kernel_act_as = selinux_kernel_act_as, .kernel_create_files_as = selinux_kernel_create_files_as, - .task_setuid = selinux_task_setuid, - .task_fix_setuid = selinux_task_fix_setuid, - .task_setgid = selinux_task_setgid, .task_setpgid = selinux_task_setpgid, .task_getpgid = selinux_task_getpgid, .task_getsid = selinux_task_getsid, .task_getsecid = selinux_task_getsecid, - .task_setgroups = selinux_task_setgroups, .task_setnice = selinux_task_setnice, .task_setioprio = selinux_task_setioprio, .task_getioprio = selinux_task_getioprio, @@ -5603,7 +5556,6 @@ static struct security_operations selinux_ops = { .task_movememory = selinux_task_movememory, .task_kill = selinux_task_kill, .task_wait = selinux_task_wait, - .task_prctl = selinux_task_prctl, .task_to_inode = selinux_task_to_inode, .ipc_permission = selinux_ipc_permission, -- cgit v1.2.3 From faa3aad75a959f55e7783f4dc7840253c7506571 Mon Sep 17 00:00:00 2001 From: "Serge E. Hallyn" Date: Mon, 2 Feb 2009 15:07:33 -0800 Subject: securityfs: fix long-broken securityfs_create_file comment If there is an error creating a file through securityfs_create_file, NULL is not returned, rather the error is propagated. Signed-off-by: Serge E. Hallyn Signed-off-by: James Morris --- security/inode.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'security') diff --git a/security/inode.c b/security/inode.c index efea5a60546..b41e708147a 100644 --- a/security/inode.c +++ b/security/inode.c @@ -205,12 +205,11 @@ static int create_by_name(const char *name, mode_t mode, * This function returns a pointer to a dentry if it succeeds. This * pointer must be passed to the securityfs_remove() function when the file is * to be removed (no automatic cleanup happens if your module is unloaded, - * you are responsible here). If an error occurs, %NULL is returned. + * you are responsible here). If an error occurs, the function will return + * the erorr value (via ERR_PTR). * * If securityfs is not enabled in the kernel, the value %-ENODEV is - * returned. It is not wise to check for this value, but rather, check for - * %NULL or !%NULL instead as to eliminate the need for #ifdef in the calling - * code. + * returned. */ struct dentry *securityfs_create_file(const char *name, mode_t mode, struct dentry *parent, void *data, -- cgit v1.2.3 From 3323eec921efd815178a23107ab63588c605c0b2 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Wed, 4 Feb 2009 09:06:58 -0500 Subject: integrity: IMA as an integrity service provider IMA provides hardware (TPM) based measurement and attestation for file measurements. As the Trusted Computing (TPM) model requires, IMA measures all files before they are accessed in any way (on the integrity_bprm_check, integrity_path_check and integrity_file_mmap hooks), and commits the measurements to the TPM. Once added to the TPM, measurements can not be removed. In addition, IMA maintains a list of these file measurements, which can be used to validate the aggregate value stored in the TPM. The TPM can sign these measurements, and thus the system can prove, to itself and to a third party, the system's integrity in a way that cannot be circumvented by malicious or compromised software. - alloc ima_template_entry before calling ima_store_template() - log ima_add_boot_aggregate() failure - removed unused IMA_TEMPLATE_NAME_LEN - replaced hard coded string length with #define name Signed-off-by: Mimi Zohar Signed-off-by: James Morris --- security/Kconfig | 5 +- security/Makefile | 4 + security/integrity/ima/Kconfig | 49 +++++++ security/integrity/ima/Makefile | 9 ++ security/integrity/ima/ima.h | 135 +++++++++++++++++ security/integrity/ima/ima_api.c | 190 ++++++++++++++++++++++++ security/integrity/ima/ima_audit.c | 78 ++++++++++ security/integrity/ima/ima_crypto.c | 140 ++++++++++++++++++ security/integrity/ima/ima_iint.c | 185 ++++++++++++++++++++++++ security/integrity/ima/ima_init.c | 90 ++++++++++++ security/integrity/ima/ima_main.c | 280 ++++++++++++++++++++++++++++++++++++ security/integrity/ima/ima_policy.c | 126 ++++++++++++++++ security/integrity/ima/ima_queue.c | 140 ++++++++++++++++++ 13 files changed, 1430 insertions(+), 1 deletion(-) create mode 100644 security/integrity/ima/Kconfig create mode 100644 security/integrity/ima/Makefile create mode 100644 security/integrity/ima/ima.h create mode 100644 security/integrity/ima/ima_api.c create mode 100644 security/integrity/ima/ima_audit.c create mode 100644 security/integrity/ima/ima_crypto.c create mode 100644 security/integrity/ima/ima_iint.c create mode 100644 security/integrity/ima/ima_init.c create mode 100644 security/integrity/ima/ima_main.c create mode 100644 security/integrity/ima/ima_policy.c create mode 100644 security/integrity/ima/ima_queue.c (limited to 'security') diff --git a/security/Kconfig b/security/Kconfig index d9f47ce7e20..a79b23f73d0 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -55,7 +55,8 @@ config SECURITYFS bool "Enable the securityfs filesystem" help This will build the securityfs filesystem. It is currently used by - the TPM bios character driver. It is not used by SELinux or SMACK. + the TPM bios character driver and IMA, an integrity provider. It is + not used by SELinux or SMACK. If you are unsure how to answer this question, answer N. @@ -126,5 +127,7 @@ config SECURITY_DEFAULT_MMAP_MIN_ADDR source security/selinux/Kconfig source security/smack/Kconfig +source security/integrity/ima/Kconfig + endmenu diff --git a/security/Makefile b/security/Makefile index c05c127fff9..595536cbffb 100644 --- a/security/Makefile +++ b/security/Makefile @@ -17,3 +17,7 @@ obj-$(CONFIG_SECURITY_SELINUX) += selinux/built-in.o obj-$(CONFIG_SECURITY_SMACK) += smack/built-in.o obj-$(CONFIG_SECURITY_ROOTPLUG) += root_plug.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 diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig new file mode 100644 index 00000000000..2a761c8ac99 --- /dev/null +++ b/security/integrity/ima/Kconfig @@ -0,0 +1,49 @@ +# IBM Integrity Measurement Architecture +# +config IMA + bool "Integrity Measurement Architecture(IMA)" + depends on ACPI + select SECURITYFS + select CRYPTO + select CRYPTO_HMAC + select CRYPTO_MD5 + select CRYPTO_SHA1 + select TCG_TPM + select TCG_TIS + help + The Trusted Computing Group(TCG) runtime Integrity + Measurement Architecture(IMA) maintains a list of hash + values of executables and other sensitive system files, + as they are read or executed. If an attacker manages + to change the contents of an important system file + being measured, we can tell. + + If your system has a TPM chip, then IMA also maintains + an aggregate integrity value over this list inside the + TPM hardware, so that the TPM can prove to a third party + whether or not critical system files have been modified. + Read + to learn more about IMA. + If unsure, say N. + +config IMA_MEASURE_PCR_IDX + int + depends on IMA + range 8 14 + default 10 + help + IMA_MEASURE_PCR_IDX determines the TPM PCR register index + that IMA uses to maintain the integrity aggregate of the + measurement list. If unsure, use the default 10. + +config IMA_AUDIT + bool + depends on IMA + default y + help + This option adds a kernel parameter 'ima_audit', which + allows informational auditing messages to be enabled + at boot. If this option is selected, informational integrity + auditing messages can be enabled with 'ima_audit=1' on + the kernel command line. + diff --git a/security/integrity/ima/Makefile b/security/integrity/ima/Makefile new file mode 100644 index 00000000000..9d6bf973b9b --- /dev/null +++ b/security/integrity/ima/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for building Trusted Computing Group's(TCG) runtime Integrity +# Measurement Architecture(IMA). +# + +obj-$(CONFIG_IMA) += ima.o + +ima-y := ima_queue.o ima_init.o ima_main.o ima_crypto.o ima_api.o \ + ima_policy.o ima_iint.o ima_audit.o diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h new file mode 100644 index 00000000000..bfa72ed41b9 --- /dev/null +++ b/security/integrity/ima/ima.h @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * + * Authors: + * Reiner Sailer + * 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.h + * internal Integrity Measurement Architecture (IMA) definitions + */ + +#ifndef __LINUX_IMA_H +#define __LINUX_IMA_H + +#include +#include +#include +#include +#include +#include + +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_EVENT_NAME_LEN_MAX 255 + +#define IMA_HASH_BITS 9 +#define IMA_MEASURE_HTABLE_SIZE (1 << IMA_HASH_BITS) + +/* set during initialization */ +extern int ima_initialized; +extern int ima_used_chip; +extern char *ima_hash; + +/* IMA inode template definition */ +struct ima_template_data { + u8 digest[IMA_DIGEST_SIZE]; /* sha1/md5 measurement hash */ + char file_name[IMA_EVENT_NAME_LEN_MAX + 1]; /* name + \0 */ +}; + +struct ima_template_entry { + u8 digest[IMA_DIGEST_SIZE]; /* sha1 or md5 measurement hash */ + char *template_name; + int template_len; + struct ima_template_data template; +}; + +struct ima_queue_entry { + struct hlist_node hnext; /* place in hash collision list */ + struct list_head later; /* place in ima_measurements list */ + struct ima_template_entry *entry; +}; +extern struct list_head ima_measurements; /* list of all measurements */ + +/* declarations */ +void integrity_audit_msg(int audit_msgno, struct inode *inode, + const unsigned char *fname, const char *op, + const char *cause, int result, int info); + +/* Internal IMA function definitions */ +void ima_iintcache_init(void); +int ima_init(void); +int ima_add_template_entry(struct ima_template_entry *entry, int violation, + const char *op, struct inode *inode); +int ima_calc_hash(struct file *file, char *digest); +int ima_calc_template_hash(int template_len, void *template, char *digest); +int ima_calc_boot_aggregate(char *digest); +void ima_add_violation(struct inode *inode, const unsigned char *filename, + const char *op, const char *cause); + +/* + * used to protect h_table and sha_table + */ +extern spinlock_t ima_queue_lock; + +struct ima_h_table { + atomic_long_t len; /* number of stored measurements in the list */ + atomic_long_t violations; + struct hlist_head queue[IMA_MEASURE_HTABLE_SIZE]; +}; +extern struct ima_h_table ima_htable; + +static inline unsigned long ima_hash_key(u8 *digest) +{ + return hash_long(*digest, IMA_HASH_BITS); +} + +/* iint cache flags */ +#define IMA_MEASURED 1 + +/* integrity data associated with an inode */ +struct ima_iint_cache { + u64 version; /* track inode changes */ + unsigned long flags; + u8 digest[IMA_DIGEST_SIZE]; + struct mutex mutex; /* protects: version, flags, digest */ + long readcount; /* measured files readcount */ + long writecount; /* measured files writecount */ + struct kref refcount; /* ima_iint_cache reference count */ + struct rcu_head rcu; +}; + +/* LIM API function definitions */ +int ima_must_measure(struct ima_iint_cache *iint, 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, + const unsigned char *filename); +int ima_store_template(struct ima_template_entry *entry, int violation, + struct inode *inode); + +/* radix 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_get(struct inode *inode); +struct ima_iint_cache *ima_iint_find_insert_get(struct inode *inode); +void ima_iint_delete(struct inode *inode); +void iint_free(struct kref *kref); +void iint_rcu_free(struct rcu_head *rcu); + +/* IMA policy related functions */ +enum ima_hooks { PATH_CHECK = 1, FILE_MMAP, BPRM_CHECK }; + +int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask); +void ima_init_policy(void); +void ima_update_policy(void); +#endif diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c new file mode 100644 index 00000000000..a148a25804f --- /dev/null +++ b/security/integrity/ima/ima_api.c @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2008 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. + * + * File: ima_api.c + * Implements must_measure, collect_measurement, store_measurement, + * and store_template. + */ +#include + +#include "ima.h" +static char *IMA_TEMPLATE_NAME = "ima"; + +/* + * ima_store_template - store ima template measurements + * + * Calculate the hash of a template entry, add the template entry + * to an ordered list of measurement entries maintained inside the kernel, + * and also update the aggregate integrity value (maintained inside the + * configured TPM PCR) over the hashes of the current list of measurement + * entries. + * + * Applications retrieve the current kernel-held measurement list through + * the securityfs entries in /sys/kernel/security/ima. The signed aggregate + * TPM PCR (called quote) can be retrieved using a TPM user space library + * and is used to validate the measurement list. + * + * Returns 0 on success, error code otherwise + */ +int ima_store_template(struct ima_template_entry *entry, + int violation, struct inode *inode) +{ + const char *op = "add_template_measure"; + const char *audit_cause = "hashing_error"; + int result; + + memset(entry->digest, 0, sizeof(entry->digest)); + entry->template_name = IMA_TEMPLATE_NAME; + entry->template_len = sizeof(entry->template); + + if (!violation) { + result = ima_calc_template_hash(entry->template_len, + &entry->template, + entry->digest); + if (result < 0) { + integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, + entry->template_name, op, + audit_cause, result, 0); + return result; + } + } + result = ima_add_template_entry(entry, violation, op, inode); + return result; +} + +/* + * ima_add_violation - add violation to measurement list. + * + * Violations are flagged in the measurement list with zero hash values. + * By extending the PCR with 0xFF's instead of with zeroes, the PCR + * value is invalidated. + */ +void ima_add_violation(struct inode *inode, const unsigned char *filename, + const char *op, const char *cause) +{ + struct ima_template_entry *entry; + int violation = 1; + int result; + + /* can overflow, only indicator */ + atomic_long_inc(&ima_htable.violations); + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) { + result = -ENOMEM; + goto err_out; + } + memset(&entry->template, 0, sizeof(entry->template)); + strncpy(entry->template.file_name, filename, IMA_EVENT_NAME_LEN_MAX); + result = ima_store_template(entry, violation, inode); + if (result < 0) + kfree(entry); +err_out: + integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename, + op, cause, result, 0); +} + +/** + * ima_must_measure - measure decision based on policy. + * @inode: pointer to inode to measure + * @mask: contains the permission mask (MAY_READ, MAY_WRITE, MAY_EXECUTE) + * @function: calling function (PATH_CHECK, BPRM_CHECK, FILE_MMAP) + * + * The policy is defined in terms of keypairs: + * subj=, obj=, type=, func=, mask=, fsmagic= + * subj,obj, and type: are LSM specific. + * func: PATH_CHECK | BPRM_CHECK | FILE_MMAP + * mask: contains the permission mask + * fsmagic: hex value + * + * Must be called with iint->mutex held. + * + * Return 0 to measure. Return 1 if already measured. + * For matching a DONT_MEASURE policy, no policy, or other + * error, return an error code. +*/ +int ima_must_measure(struct ima_iint_cache *iint, struct inode *inode, + int mask, int function) +{ + int must_measure; + + if (iint->flags & IMA_MEASURED) + return 1; + + must_measure = ima_match_policy(inode, function, mask); + return must_measure ? 0 : -EACCES; +} + +/* + * ima_collect_measurement - collect file measurement + * + * Calculate the file hash, if it doesn't already exist, + * storing the measurement and i_version in the iint. + * + * Must be called with iint->mutex held. + * + * Return 0 on success, error code otherwise + */ +int ima_collect_measurement(struct ima_iint_cache *iint, struct file *file) +{ + int result = -EEXIST; + + if (!(iint->flags & IMA_MEASURED)) { + u64 i_version = file->f_dentry->d_inode->i_version; + + memset(iint->digest, 0, IMA_DIGEST_SIZE); + result = ima_calc_hash(file, iint->digest); + if (!result) + iint->version = i_version; + } + return result; +} + +/* + * ima_store_measurement - store file measurement + * + * Create an "ima" template and then store the template by calling + * ima_store_template. + * + * We only get here if the inode has not already been measured, + * but the measurement could already exist: + * - multiple copies of the same file on either the same or + * different filesystems. + * - the inode was previously flushed as well as the iint info, + * containing the hashing info. + * + * Must be called with iint->mutex held. + */ +void ima_store_measurement(struct ima_iint_cache *iint, struct file *file, + const unsigned char *filename) +{ + const char *op = "add_template_measure"; + const char *audit_cause = "ENOMEM"; + int result = -ENOMEM; + struct inode *inode = file->f_dentry->d_inode; + struct ima_template_entry *entry; + int violation = 0; + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) { + integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename, + op, audit_cause, result, 0); + return; + } + memset(&entry->template, 0, sizeof(entry->template)); + memcpy(entry->template.digest, iint->digest, IMA_DIGEST_SIZE); + strncpy(entry->template.file_name, filename, IMA_EVENT_NAME_LEN_MAX); + + result = ima_store_template(entry, violation, inode); + if (!result) + iint->flags |= IMA_MEASURED; + else + kfree(entry); +} diff --git a/security/integrity/ima/ima_audit.c b/security/integrity/ima/ima_audit.c new file mode 100644 index 00000000000..8a0f1e23ccf --- /dev/null +++ b/security/integrity/ima/ima_audit.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2008 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. + * + * File: integrity_audit.c + * Audit calls for the integrity subsystem + */ + +#include +#include +#include "ima.h" + +static int ima_audit; + +#ifdef CONFIG_IMA_AUDIT + +/* ima_audit_setup - enable informational auditing messages */ +static int __init ima_audit_setup(char *str) +{ + unsigned long audit; + int rc; + char *op; + + rc = strict_strtoul(str, 0, &audit); + if (rc || audit > 1) + printk(KERN_INFO "ima: invalid ima_audit value\n"); + else + ima_audit = audit; + op = ima_audit ? "ima_audit_enabled" : "ima_audit_not_enabled"; + integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, NULL, NULL, op, 0, 0); + return 1; +} +__setup("ima_audit=", ima_audit_setup); +#endif + +void integrity_audit_msg(int audit_msgno, struct inode *inode, + const unsigned char *fname, const char *op, + const char *cause, int result, int audit_info) +{ + struct audit_buffer *ab; + + if (!ima_audit && audit_info == 1) /* Skip informational messages */ + return; + + ab = audit_log_start(current->audit_context, GFP_KERNEL, audit_msgno); + audit_log_format(ab, "integrity: pid=%d uid=%u auid=%u", + current->pid, current->cred->uid, + audit_get_loginuid(current)); + audit_log_task_context(ab); + switch (audit_msgno) { + case AUDIT_INTEGRITY_DATA: + case AUDIT_INTEGRITY_METADATA: + case AUDIT_INTEGRITY_PCR: + audit_log_format(ab, " op=%s cause=%s", op, cause); + break; + case AUDIT_INTEGRITY_HASH: + audit_log_format(ab, " op=%s hash=%s", op, cause); + break; + case AUDIT_INTEGRITY_STATUS: + default: + audit_log_format(ab, " op=%s", op); + } + audit_log_format(ab, " comm="); + audit_log_untrustedstring(ab, current->comm); + if (fname) { + audit_log_format(ab, " name="); + audit_log_untrustedstring(ab, fname); + } + if (inode) + audit_log_format(ab, " dev=%s ino=%lu", + inode->i_sb->s_id, inode->i_ino); + audit_log_format(ab, " res=%d", result); + audit_log_end(ab); +} diff --git a/security/integrity/ima/ima_crypto.c b/security/integrity/ima/ima_crypto.c new file mode 100644 index 00000000000..c2a46e40999 --- /dev/null +++ b/security/integrity/ima/ima_crypto.c @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2005,2006,2007,2008 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: ima_crypto.c + * Calculates md5/sha1 file hash, template hash, boot-aggreate hash + */ + +#include +#include +#include +#include +#include +#include "ima.h" + +static int init_desc(struct hash_desc *desc) +{ + int rc; + + desc->tfm = crypto_alloc_hash(ima_hash, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(desc->tfm)) { + pr_info("failed to load %s transform: %ld\n", + ima_hash, PTR_ERR(desc->tfm)); + rc = PTR_ERR(desc->tfm); + return rc; + } + desc->flags = 0; + rc = crypto_hash_init(desc); + if (rc) + crypto_free_hash(desc->tfm); + return rc; +} + +/* + * Calculate the MD5/SHA1 file digest + */ +int ima_calc_hash(struct file *file, char *digest) +{ + struct hash_desc desc; + struct scatterlist sg[1]; + loff_t i_size; + char *rbuf; + int rc, offset = 0; + + rc = init_desc(&desc); + if (rc != 0) + return rc; + + rbuf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!rbuf) { + rc = -ENOMEM; + goto out; + } + i_size = i_size_read(file->f_dentry->d_inode); + while (offset < i_size) { + int rbuf_len; + + rbuf_len = kernel_read(file, offset, rbuf, PAGE_SIZE); + if (rbuf_len < 0) { + rc = rbuf_len; + break; + } + offset += rbuf_len; + sg_set_buf(sg, rbuf, rbuf_len); + + rc = crypto_hash_update(&desc, sg, rbuf_len); + if (rc) + break; + } + kfree(rbuf); + if (!rc) + rc = crypto_hash_final(&desc, digest); +out: + crypto_free_hash(desc.tfm); + return rc; +} + +/* + * Calculate the hash of a given template + */ +int ima_calc_template_hash(int template_len, void *template, char *digest) +{ + struct hash_desc desc; + struct scatterlist sg[1]; + int rc; + + rc = init_desc(&desc); + if (rc != 0) + return rc; + + sg_set_buf(sg, template, template_len); + rc = crypto_hash_update(&desc, sg, template_len); + if (!rc) + rc = crypto_hash_final(&desc, digest); + crypto_free_hash(desc.tfm); + return rc; +} + +static void ima_pcrread(int idx, u8 *pcr) +{ + if (!ima_used_chip) + return; + + if (tpm_pcr_read(TPM_ANY_NUM, idx, pcr) != 0) + pr_err("Error Communicating to TPM chip\n"); +} + +/* + * Calculate the boot aggregate hash + */ +int ima_calc_boot_aggregate(char *digest) +{ + struct hash_desc desc; + struct scatterlist sg; + u8 pcr_i[IMA_DIGEST_SIZE]; + int rc, i; + + rc = init_desc(&desc); + if (rc != 0) + return rc; + + /* cumulative sha1 over tpm registers 0-7 */ + for (i = TPM_PCR0; i < TPM_PCR8; i++) { + ima_pcrread(i, pcr_i); + /* now accumulate with current aggregate */ + sg_init_one(&sg, pcr_i, IMA_DIGEST_SIZE); + rc = crypto_hash_update(&desc, &sg, IMA_DIGEST_SIZE); + } + if (!rc) + crypto_hash_final(&desc, digest); + crypto_free_hash(desc.tfm); + return rc; +} diff --git a/security/integrity/ima/ima_iint.c b/security/integrity/ima/ima_iint.c new file mode 100644 index 00000000000..750db3c993a --- /dev/null +++ b/security/integrity/ima/ima_iint.c @@ -0,0 +1,185 @@ +/* + * 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 radix tree. + */ +#include +#include +#include +#include "ima.h" + +#define ima_iint_delete ima_inode_free + +RADIX_TREE(ima_iint_store, GFP_ATOMIC); +DEFINE_SPINLOCK(ima_iint_lock); + +static struct kmem_cache *iint_cache __read_mostly; + +/* ima_iint_find_get - return the iint associated with an inode + * + * ima_iint_find_get gets a reference to the iint. Caller must + * remember to put the iint reference. + */ +struct ima_iint_cache *ima_iint_find_get(struct inode *inode) +{ + struct ima_iint_cache *iint; + + rcu_read_lock(); + iint = radix_tree_lookup(&ima_iint_store, (unsigned long)inode); + if (!iint) + goto out; + kref_get(&iint->refcount); +out: + rcu_read_unlock(); + return iint; +} + +/* Allocate memory for the iint associated with the inode + * from the iint_cache slab, initialize the iint, and + * insert it into the radix tree. + * + * On success return a pointer to the iint; on failure return NULL. + */ +struct ima_iint_cache *ima_iint_insert(struct inode *inode) +{ + struct ima_iint_cache *iint = NULL; + int rc = 0; + + if (!ima_initialized) + return iint; + iint = kmem_cache_alloc(iint_cache, GFP_KERNEL); + if (!iint) + return iint; + + rc = radix_tree_preload(GFP_KERNEL); + if (rc < 0) + goto out; + + spin_lock(&ima_iint_lock); + rc = radix_tree_insert(&ima_iint_store, (unsigned long)inode, iint); + spin_unlock(&ima_iint_lock); +out: + if (rc < 0) { + kmem_cache_free(iint_cache, iint); + if (rc == -EEXIST) { + iint = radix_tree_lookup(&ima_iint_store, + (unsigned long)inode); + } else + iint = NULL; + } + radix_tree_preload_end(); + return iint; +} + +/** + * ima_inode_alloc - allocate an iint associated with an inode + * @inode: pointer to the inode + * + * Return 0 on success, 1 on failure. + */ +int ima_inode_alloc(struct inode *inode) +{ + struct ima_iint_cache *iint; + + if (!ima_initialized) + return 0; + + iint = ima_iint_insert(inode); + if (!iint) + return 1; + return 0; +} + +/* ima_iint_find_insert_get - get the iint associated with an inode + * + * Most insertions are done at inode_alloc, except those allocated + * before late_initcall. When the iint does not exist, allocate it, + * initialize and insert it, and increment the iint refcount. + * + * (Can't initialize at security_initcall before any inodes are + * allocated, got to wait at least until proc_init.) + * + * Return the iint. + */ +struct ima_iint_cache *ima_iint_find_insert_get(struct inode *inode) +{ + struct ima_iint_cache *iint = NULL; + + iint = ima_iint_find_get(inode); + if (iint) + return iint; + + iint = ima_iint_insert(inode); + if (iint) + kref_get(&iint->refcount); + + return iint; +} + +/* iint_free - called when the iint refcount goes to zero */ +void iint_free(struct kref *kref) +{ + struct ima_iint_cache *iint = container_of(kref, struct ima_iint_cache, + refcount); + iint->version = 0; + iint->flags = 0UL; + kref_set(&iint->refcount, 1); + kmem_cache_free(iint_cache, iint); +} + +void iint_rcu_free(struct rcu_head *rcu_head) +{ + struct ima_iint_cache *iint = container_of(rcu_head, + struct ima_iint_cache, rcu); + kref_put(&iint->refcount, iint_free); +} + +/** + * ima_iint_delete - called on integrity_inode_free + * @inode: pointer to the inode + * + * Free the integrity information(iint) associated with an inode. + */ +void ima_iint_delete(struct inode *inode) +{ + struct ima_iint_cache *iint; + + if (!ima_initialized) + return; + spin_lock(&ima_iint_lock); + iint = radix_tree_delete(&ima_iint_store, (unsigned long)inode); + spin_unlock(&ima_iint_lock); + if (iint) + call_rcu(&iint->rcu, iint_rcu_free); +} + +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); + iint->readcount = 0; + iint->writecount = 0; + kref_set(&iint->refcount, 1); +} + +void ima_iintcache_init(void) +{ + iint_cache = + kmem_cache_create("iint_cache", sizeof(struct ima_iint_cache), 0, + SLAB_PANIC, init_once); +} diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c new file mode 100644 index 00000000000..e0f02e328d7 --- /dev/null +++ b/security/integrity/ima/ima_init.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * + * Authors: + * Reiner Sailer + * Leendert van Doorn + * 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_init.c + * initialization and cleanup functions + */ +#include +#include +#include +#include "ima.h" + +/* name for boot aggregate entry */ +static char *boot_aggregate_name = "boot_aggregate"; +int ima_used_chip; + +/* Add the boot aggregate to the IMA measurement list and extend + * the PCR register. + * + * Calculate the boot aggregate, a SHA1 over tpm registers 0-7, + * assuming a TPM chip exists, and zeroes if the TPM chip does not + * exist. Add the boot aggregate measurement to the measurement + * list and extend the PCR register. + * + * If a tpm chip does not exist, indicate the core root of trust is + * not hardware based by invalidating the aggregate PCR value. + * (The aggregate PCR value is invalidated by adding one value to + * the measurement list and extending the aggregate PCR value with + * a different value.) Violations add a zero entry to the measurement + * list and extend the aggregate PCR value with ff...ff's. + */ +static void ima_add_boot_aggregate(void) +{ + struct ima_template_entry *entry; + const char *op = "add_boot_aggregate"; + const char *audit_cause = "ENOMEM"; + int result = -ENOMEM; + int violation = 1; + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + goto err_out; + + memset(&entry->template, 0, sizeof(entry->template)); + strncpy(entry->template.file_name, boot_aggregate_name, + IMA_EVENT_NAME_LEN_MAX); + if (ima_used_chip) { + violation = 0; + result = ima_calc_boot_aggregate(entry->template.digest); + if (result < 0) { + audit_cause = "hashing_error"; + kfree(entry); + goto err_out; + } + } + result = ima_store_template(entry, violation, NULL); + if (result < 0) + kfree(entry); + return; +err_out: + integrity_audit_msg(AUDIT_INTEGRITY_PCR, NULL, boot_aggregate_name, op, + audit_cause, result, 0); +} + +int ima_init(void) +{ + u8 pcr_i[IMA_DIGEST_SIZE]; + int rc; + + ima_used_chip = 0; + rc = tpm_pcr_read(TPM_ANY_NUM, 0, pcr_i); + if (rc == 0) + ima_used_chip = 1; + + if (!ima_used_chip) + pr_info("No TPM chip found, activating TPM-bypass!\n"); + + ima_add_boot_aggregate(); /* boot aggregate must be first entry */ + ima_init_policy(); + return 0; +} diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c new file mode 100644 index 00000000000..53cee4c512c --- /dev/null +++ b/security/integrity/ima/ima_main.c @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * + * Authors: + * Reiner Sailer + * Serge Hallyn + * Kylene Hall + * 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_main.c + * implements the IMA hooks: ima_bprm_check, ima_file_mmap, + * and ima_path_check. + */ +#include +#include +#include +#include +#include + +#include "ima.h" + +int ima_initialized; + +char *ima_hash = "sha1"; +static int __init hash_setup(char *str) +{ + const char *op = "hash_setup"; + const char *hash = "sha1"; + int result = 0; + int audit_info = 0; + + if (strncmp(str, "md5", 3) == 0) { + hash = "md5"; + ima_hash = str; + } else if (strncmp(str, "sha1", 4) != 0) { + hash = "invalid_hash_type"; + result = 1; + } + integrity_audit_msg(AUDIT_INTEGRITY_HASH, NULL, NULL, op, hash, + result, audit_info); + return 1; +} +__setup("ima_hash=", hash_setup); + +/** + * ima_file_free - called on __fput() + * @file: pointer to file structure being freed + * + * Flag files that changed, based on i_version; + * and decrement the iint readcount/writecount. + */ +void ima_file_free(struct file *file) +{ + struct inode *inode = file->f_dentry->d_inode; + struct ima_iint_cache *iint; + + if (!ima_initialized || !S_ISREG(inode->i_mode)) + return; + iint = ima_iint_find_get(inode); + if (!iint) + return; + + mutex_lock(&iint->mutex); + if ((file->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ) + iint->readcount--; + + if (file->f_mode & FMODE_WRITE) { + iint->writecount--; + if (iint->writecount == 0) { + if (iint->version != inode->i_version) + iint->flags &= ~IMA_MEASURED; + } + } + mutex_unlock(&iint->mutex); + kref_put(&iint->refcount, iint_free); +} + +/* ima_read_write_check - reflect possible reading/writing errors in the PCR. + * + * When opening a file for read, if the file is already open for write, + * the file could change, resulting in a file measurement error. + * + * Opening a file for write, if the file is already open for read, results + * in a time of measure, time of use (ToMToU) error. + * + * In either case invalidate the PCR. + */ +enum iint_pcr_error { TOMTOU, OPEN_WRITERS }; +static void ima_read_write_check(enum iint_pcr_error error, + struct ima_iint_cache *iint, + struct inode *inode, + const unsigned char *filename) +{ + switch (error) { + case TOMTOU: + if (iint->readcount > 0) + ima_add_violation(inode, filename, "invalid_pcr", + "ToMToU"); + break; + case OPEN_WRITERS: + if (iint->writecount > 0) + ima_add_violation(inode, filename, "invalid_pcr", + "open_writers"); + break; + } +} + +static int get_path_measurement(struct ima_iint_cache *iint, struct file *file, + const unsigned char *filename) +{ + int rc = 0; + + if (IS_ERR(file)) { + pr_info("%s dentry_open failed\n", filename); + return rc; + } + iint->readcount++; + + rc = ima_collect_measurement(iint, file); + if (!rc) + ima_store_measurement(iint, file, filename); + return rc; +} + +/** + * ima_path_check - based on policy, collect/store measurement. + * @path: contains a pointer to the path to be measured + * @mask: contains MAY_READ, MAY_WRITE or MAY_EXECUTE + * + * Measure the file being open for readonly, based on the + * ima_must_measure() policy decision. + * + * Keep read/write counters for all files, but only + * invalidate the PCR for measured files: + * - Opening a file for write when already open for read, + * results in a time of measure, time of use (ToMToU) error. + * - Opening a file for read when already open for write, + * could result in a file measurement error. + * + * Return 0 on success, an error code on failure. + * (Based on the results of appraise_measurement().) + */ +int ima_path_check(struct path *path, int mask) +{ + struct inode *inode = path->dentry->d_inode; + struct ima_iint_cache *iint; + struct file *file = NULL; + int rc; + + if (!ima_initialized || !S_ISREG(inode->i_mode)) + return 0; + iint = ima_iint_find_insert_get(inode); + if (!iint) + return 0; + + mutex_lock(&iint->mutex); + if ((mask & MAY_WRITE) || (mask == 0)) + iint->writecount++; + else if (mask & (MAY_READ | MAY_EXEC)) + iint->readcount++; + + rc = ima_must_measure(iint, inode, MAY_READ, PATH_CHECK); + if (rc < 0) + goto out; + + if ((mask & MAY_WRITE) || (mask == 0)) + ima_read_write_check(TOMTOU, iint, inode, + path->dentry->d_name.name); + + if ((mask & (MAY_WRITE | MAY_READ | MAY_EXEC)) != MAY_READ) + goto out; + + ima_read_write_check(OPEN_WRITERS, iint, inode, + path->dentry->d_name.name); + if (!(iint->flags & IMA_MEASURED)) { + struct dentry *dentry = dget(path->dentry); + struct vfsmount *mnt = mntget(path->mnt); + + file = dentry_open(dentry, mnt, O_RDONLY, current->cred); + rc = get_path_measurement(iint, file, dentry->d_name.name); + } +out: + mutex_unlock(&iint->mutex); + if (file) + fput(file); + kref_put(&iint->refcount, iint_free); + return 0; +} + +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; + int rc; + + if (!ima_initialized || !S_ISREG(inode->i_mode)) + return 0; + iint = ima_iint_find_insert_get(inode); + if (!iint) + return -ENOMEM; + + mutex_lock(&iint->mutex); + rc = ima_must_measure(iint, inode, mask, function); + if (rc != 0) + goto out; + + rc = ima_collect_measurement(iint, file); + if (!rc) + ima_store_measurement(iint, file, filename); +out: + mutex_unlock(&iint->mutex); + kref_put(&iint->refcount, iint_free); + return rc; +} + +/** + * ima_file_mmap - based on policy, collect/store measurement. + * @file: pointer to the file to be measured (May be NULL) + * @prot: contains the protection that will be applied by the kernel. + * + * Measure files being mmapped executable based on the ima_must_measure() + * policy decision. + * + * Return 0 on success, an error code on failure. + * (Based on the results of appraise_measurement().) + */ +int ima_file_mmap(struct file *file, unsigned long prot) +{ + int rc; + + if (!file) + return 0; + if (prot & PROT_EXEC) + rc = process_measurement(file, file->f_dentry->d_name.name, + MAY_EXEC, FILE_MMAP); + return 0; +} + +/** + * ima_bprm_check - based on policy, collect/store measurement. + * @bprm: contains the linux_binprm structure + * + * The OS protects against an executable file, already open for write, + * from being executed in deny_write_access() and an executable file, + * already open for execute, from being modified in get_write_access(). + * So we can be certain that what we verify and measure here is actually + * what is being executed. + * + * Return 0 on success, an error code on failure. + * (Based on the results of appraise_measurement().) + */ +int ima_bprm_check(struct linux_binprm *bprm) +{ + int rc; + + rc = process_measurement(bprm->file, bprm->filename, + MAY_EXEC, BPRM_CHECK); + return 0; +} + +static int __init init_ima(void) +{ + int error; + + ima_iintcache_init(); + error = ima_init(); + ima_initialized = 1; + return error; +} + +late_initcall(init_ima); /* Start IMA after the TPM is available */ + +MODULE_DESCRIPTION("Integrity Measurement Architecture"); +MODULE_LICENSE("GPL"); diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c new file mode 100644 index 00000000000..7c3d1ffb147 --- /dev/null +++ b/security/integrity/ima/ima_policy.c @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2008 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. + * + * ima_policy.c + * - initialize default measure policy rules + * + */ +#include +#include +#include +#include +#include + +#include "ima.h" + +/* flags definitions */ +#define IMA_FUNC 0x0001 +#define IMA_MASK 0x0002 +#define IMA_FSMAGIC 0x0004 +#define IMA_UID 0x0008 + +enum ima_action { DONT_MEASURE, MEASURE }; + +struct ima_measure_rule_entry { + struct list_head list; + enum ima_action action; + unsigned int flags; + enum ima_hooks func; + int mask; + unsigned long fsmagic; + uid_t uid; +}; + +static struct ima_measure_rule_entry default_rules[] = { + {.action = DONT_MEASURE,.fsmagic = PROC_SUPER_MAGIC, + .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE,.fsmagic = SYSFS_MAGIC,.flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE,.fsmagic = DEBUGFS_MAGIC,.flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE,.fsmagic = TMPFS_MAGIC,.flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE,.fsmagic = SECURITYFS_MAGIC, + .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE,.fsmagic = 0xF97CFF8C,.flags = IMA_FSMAGIC}, + {.action = MEASURE,.func = FILE_MMAP,.mask = MAY_EXEC, + .flags = IMA_FUNC | IMA_MASK}, + {.action = MEASURE,.func = BPRM_CHECK,.mask = MAY_EXEC, + .flags = IMA_FUNC | IMA_MASK}, + {.action = MEASURE,.func = PATH_CHECK,.mask = MAY_READ,.uid = 0, + .flags = IMA_FUNC | IMA_MASK | IMA_UID} +}; + +static LIST_HEAD(measure_default_rules); +static struct list_head *ima_measure; + +/** + * ima_match_rules - determine whether an inode matches the measure rule. + * @rule: a pointer to a rule + * @inode: a pointer to an inode + * @func: LIM hook identifier + * @mask: requested action (MAY_READ | MAY_WRITE | MAY_APPEND | MAY_EXEC) + * + * Returns true on rule match, false on failure. + */ +static bool ima_match_rules(struct ima_measure_rule_entry *rule, + struct inode *inode, enum ima_hooks func, int mask) +{ + struct task_struct *tsk = current; + + if ((rule->flags & IMA_FUNC) && rule->func != func) + return false; + if ((rule->flags & IMA_MASK) && rule->mask != mask) + return false; + if ((rule->flags & IMA_FSMAGIC) + && rule->fsmagic != inode->i_sb->s_magic) + return false; + if ((rule->flags & IMA_UID) && rule->uid != tsk->cred->uid) + return false; + return true; +} + +/** + * ima_match_policy - decision based on LSM and other conditions + * @inode: pointer to an inode for which the policy decision is being made + * @func: IMA hook identifier + * @mask: requested action (MAY_READ | MAY_WRITE | MAY_APPEND | MAY_EXEC) + * + * Measure decision based on func/mask/fsmagic and LSM(subj/obj/type) + * conditions. + * + * (There is no need for locking when walking the policy list, + * as elements in the list are never deleted, nor does the list + * change.) + */ +int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask) +{ + struct ima_measure_rule_entry *entry; + + list_for_each_entry(entry, ima_measure, list) { + bool rc; + + rc = ima_match_rules(entry, inode, func, mask); + if (rc) + return entry->action; + } + return 0; +} + +/** + * ima_init_policy - initialize the default measure rules. + * + * (Could use the default_rules directly, but in policy patch + * ima_measure points to either the measure_default_rules or the + * the new measure_policy_rules.) + */ +void ima_init_policy(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(default_rules); i++) + list_add_tail(&default_rules[i].list, &measure_default_rules); + ima_measure = &measure_default_rules; +} diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c new file mode 100644 index 00000000000..7ec94314ac0 --- /dev/null +++ b/security/integrity/ima/ima_queue.c @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * + * Authors: + * Serge Hallyn + * Reiner Sailer + * 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_queue.c + * Implements queues that store template measurements and + * maintains aggregate over the stored measurements + * in the pre-configured TPM PCR (if available). + * The measurement list is append-only. No entry is + * ever removed or changed during the boot-cycle. + */ +#include +#include +#include "ima.h" + +LIST_HEAD(ima_measurements); /* list of all measurements */ + +/* key: inode (before secure-hashing a file) */ +struct ima_h_table ima_htable = { + .len = ATOMIC_LONG_INIT(0), + .violations = ATOMIC_LONG_INIT(0), + .queue[0 ... IMA_MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT +}; + +/* mutex protects atomicity of extending measurement list + * and extending the TPM PCR aggregate. Since tpm_extend can take + * long (and the tpm driver uses a mutex), we can't use the spinlock. + */ +static DEFINE_MUTEX(ima_extend_list_mutex); + +/* lookup up the digest value in the hash table, and return the entry */ +static struct ima_queue_entry *ima_lookup_digest_entry(u8 *digest_value) +{ + struct ima_queue_entry *qe, *ret = NULL; + unsigned int key; + struct hlist_node *pos; + int rc; + + key = ima_hash_key(digest_value); + rcu_read_lock(); + hlist_for_each_entry_rcu(qe, pos, &ima_htable.queue[key], hnext) { + rc = memcmp(qe->entry->digest, digest_value, IMA_DIGEST_SIZE); + if (rc == 0) { + ret = qe; + break; + } + } + rcu_read_unlock(); + return ret; +} + +/* ima_add_template_entry helper function: + * - Add template entry to measurement list and hash table. + * + * (Called with ima_extend_list_mutex held.) + */ +static int ima_add_digest_entry(struct ima_template_entry *entry) +{ + struct ima_queue_entry *qe; + unsigned int key; + + qe = kmalloc(sizeof(*qe), GFP_KERNEL); + if (qe == NULL) { + pr_err("OUT OF MEMORY ERROR creating queue entry.\n"); + return -ENOMEM; + } + qe->entry = entry; + + INIT_LIST_HEAD(&qe->later); + list_add_tail_rcu(&qe->later, &ima_measurements); + + atomic_long_inc(&ima_htable.len); + key = ima_hash_key(entry->digest); + hlist_add_head_rcu(&qe->hnext, &ima_htable.queue[key]); + return 0; +} + +static int ima_pcr_extend(const u8 *hash) +{ + int result = 0; + + if (!ima_used_chip) + return result; + + result = tpm_pcr_extend(TPM_ANY_NUM, CONFIG_IMA_MEASURE_PCR_IDX, hash); + if (result != 0) + pr_err("Error Communicating to TPM chip\n"); + return result; +} + +/* Add template entry to the measurement list and hash table, + * and extend the pcr. + */ +int ima_add_template_entry(struct ima_template_entry *entry, int violation, + const char *op, struct inode *inode) +{ + u8 digest[IMA_DIGEST_SIZE]; + const char *audit_cause = "hash_added"; + int audit_info = 1; + int result = 0; + + mutex_lock(&ima_extend_list_mutex); + if (!violation) { + memcpy(digest, entry->digest, sizeof digest); + if (ima_lookup_digest_entry(digest)) { + audit_cause = "hash_exists"; + goto out; + } + } + + result = ima_add_digest_entry(entry); + if (result < 0) { + audit_cause = "ENOMEM"; + audit_info = 0; + goto out; + } + + if (violation) /* invalidate pcr */ + memset(digest, 0xff, sizeof digest); + + result = ima_pcr_extend(digest); + if (result != 0) { + audit_cause = "TPM error"; + audit_info = 0; + } +out: + mutex_unlock(&ima_extend_list_mutex); + integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, entry->template_name, + op, audit_cause, result, audit_info); + return result; +} -- cgit v1.2.3 From bab739378758a1e2b2d7ddcee7bc06cf4c591c3c Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Wed, 4 Feb 2009 09:06:59 -0500 Subject: integrity: IMA display Make the measurement lists available through securityfs. - removed test for NULL return code from securityfs_create_file/dir Signed-off-by: Mimi Zohar Acked-by: Serge Hallyn Signed-off-by: James Morris --- security/integrity/ima/Makefile | 2 +- security/integrity/ima/ima.h | 5 + security/integrity/ima/ima_fs.c | 296 ++++++++++++++++++++++++++++++++++++++ security/integrity/ima/ima_init.c | 8 +- security/integrity/ima/ima_main.c | 5 + 5 files changed, 314 insertions(+), 2 deletions(-) create mode 100644 security/integrity/ima/ima_fs.c (limited to 'security') diff --git a/security/integrity/ima/Makefile b/security/integrity/ima/Makefile index 9d6bf973b9b..787c4cb916c 100644 --- a/security/integrity/ima/Makefile +++ b/security/integrity/ima/Makefile @@ -5,5 +5,5 @@ obj-$(CONFIG_IMA) += ima.o -ima-y := ima_queue.o ima_init.o ima_main.o ima_crypto.o ima_api.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 diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index bfa72ed41b9..9c280cc7300 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -67,6 +67,9 @@ void integrity_audit_msg(int audit_msgno, struct inode *inode, /* Internal IMA function definitions */ void ima_iintcache_init(void); int ima_init(void); +void ima_cleanup(void); +int ima_fs_init(void); +void ima_fs_cleanup(void); int ima_add_template_entry(struct ima_template_entry *entry, int violation, const char *op, struct inode *inode); int ima_calc_hash(struct file *file, char *digest); @@ -115,6 +118,8 @@ void ima_store_measurement(struct ima_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); /* radix tree calls to lookup, insert, delete * integrity data associated with an inode. diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c new file mode 100644 index 00000000000..4f25be768b5 --- /dev/null +++ b/security/integrity/ima/ima_fs.c @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * + * Authors: + * Kylene Hall + * Reiner Sailer + * 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_fs.c + * implemenents security file system for reporting + * current measurement list and IMA statistics + */ +#include +#include +#include +#include + +#include "ima.h" + +#define TMPBUFLEN 12 +static ssize_t ima_show_htable_value(char __user *buf, size_t count, + loff_t *ppos, atomic_long_t *val) +{ + char tmpbuf[TMPBUFLEN]; + ssize_t len; + + len = scnprintf(tmpbuf, TMPBUFLEN, "%li\n", atomic_long_read(val)); + return simple_read_from_buffer(buf, count, ppos, tmpbuf, len); +} + +static ssize_t ima_show_htable_violations(struct file *filp, + char __user *buf, + size_t count, loff_t *ppos) +{ + return ima_show_htable_value(buf, count, ppos, &ima_htable.violations); +} + +static struct file_operations ima_htable_violations_ops = { + .read = ima_show_htable_violations +}; + +static ssize_t ima_show_measurements_count(struct file *filp, + char __user *buf, + size_t count, loff_t *ppos) +{ + return ima_show_htable_value(buf, count, ppos, &ima_htable.len); + +} + +static struct file_operations ima_measurements_count_ops = { + .read = ima_show_measurements_count +}; + +/* returns pointer to hlist_node */ +static void *ima_measurements_start(struct seq_file *m, loff_t *pos) +{ + loff_t l = *pos; + struct ima_queue_entry *qe; + + /* we need a lock since pos could point beyond last element */ + rcu_read_lock(); + list_for_each_entry_rcu(qe, &ima_measurements, later) { + if (!l--) { + rcu_read_unlock(); + return qe; + } + } + rcu_read_unlock(); + return NULL; +} + +static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos) +{ + struct ima_queue_entry *qe = v; + + /* lock protects when reading beyond last element + * against concurrent list-extension + */ + rcu_read_lock(); + qe = list_entry(rcu_dereference(qe->later.next), + struct ima_queue_entry, later); + rcu_read_unlock(); + (*pos)++; + + return (&qe->later == &ima_measurements) ? NULL : qe; +} + +static void ima_measurements_stop(struct seq_file *m, void *v) +{ +} + +static void ima_putc(struct seq_file *m, void *data, int datalen) +{ + while (datalen--) + seq_putc(m, *(char *)data++); +} + +/* print format: + * 32bit-le=pcr# + * char[20]=template digest + * 32bit-le=template name size + * char[n]=template name + * eventdata[n]=template specific data + */ +static int ima_measurements_show(struct seq_file *m, void *v) +{ + /* the list never shrinks, so we don't need a lock here */ + struct ima_queue_entry *qe = v; + struct ima_template_entry *e; + int namelen; + u32 pcr = CONFIG_IMA_MEASURE_PCR_IDX; + + /* get entry */ + e = qe->entry; + if (e == NULL) + return -1; + + /* + * 1st: PCRIndex + * PCR used is always the same (config option) in + * little-endian format + */ + ima_putc(m, &pcr, sizeof pcr); + + /* 2nd: template digest */ + ima_putc(m, e->digest, IMA_DIGEST_SIZE); + + /* 3rd: template name size */ + namelen = strlen(e->template_name); + ima_putc(m, &namelen, sizeof namelen); + + /* 4th: template name */ + ima_putc(m, e->template_name, namelen); + + /* 5th: template specific data */ + ima_template_show(m, (struct ima_template_data *)&e->template, + IMA_SHOW_BINARY); + return 0; +} + +static struct seq_operations ima_measurments_seqops = { + .start = ima_measurements_start, + .next = ima_measurements_next, + .stop = ima_measurements_stop, + .show = ima_measurements_show +}; + +static int ima_measurements_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &ima_measurments_seqops); +} + +static struct file_operations ima_measurements_ops = { + .open = ima_measurements_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static void ima_print_digest(struct seq_file *m, u8 *digest) +{ + int i; + + for (i = 0; i < IMA_DIGEST_SIZE; i++) + seq_printf(m, "%02x", *(digest + i)); +} + +void ima_template_show(struct seq_file *m, void *e, enum ima_show_type show) +{ + struct ima_template_data *entry = e; + int namelen; + + switch (show) { + case IMA_SHOW_ASCII: + ima_print_digest(m, entry->digest); + seq_printf(m, " %s\n", entry->file_name); + break; + case IMA_SHOW_BINARY: + ima_putc(m, entry->digest, IMA_DIGEST_SIZE); + + namelen = strlen(entry->file_name); + ima_putc(m, &namelen, sizeof namelen); + ima_putc(m, entry->file_name, namelen); + default: + break; + } +} + +/* print in ascii */ +static int ima_ascii_measurements_show(struct seq_file *m, void *v) +{ + /* the list never shrinks, so we don't need a lock here */ + struct ima_queue_entry *qe = v; + struct ima_template_entry *e; + + /* get entry */ + e = qe->entry; + if (e == NULL) + return -1; + + /* 1st: PCR used (config option) */ + seq_printf(m, "%2d ", CONFIG_IMA_MEASURE_PCR_IDX); + + /* 2nd: SHA1 template hash */ + ima_print_digest(m, e->digest); + + /* 3th: template name */ + seq_printf(m, " %s ", e->template_name); + + /* 4th: template specific data */ + ima_template_show(m, (struct ima_template_data *)&e->template, + IMA_SHOW_ASCII); + return 0; +} + +static struct seq_operations ima_ascii_measurements_seqops = { + .start = ima_measurements_start, + .next = ima_measurements_next, + .stop = ima_measurements_stop, + .show = ima_ascii_measurements_show +}; + +static int ima_ascii_measurements_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &ima_ascii_measurements_seqops); +} + +static struct file_operations ima_ascii_measurements_ops = { + .open = ima_ascii_measurements_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static struct dentry *ima_dir; +static struct dentry *binary_runtime_measurements; +static struct dentry *ascii_runtime_measurements; +static struct dentry *runtime_measurements_count; +static struct dentry *violations; + +int ima_fs_init(void) +{ + ima_dir = securityfs_create_dir("ima", NULL); + if (IS_ERR(ima_dir)) + return -1; + + binary_runtime_measurements = + securityfs_create_file("binary_runtime_measurements", + S_IRUSR | S_IRGRP, ima_dir, NULL, + &ima_measurements_ops); + if (IS_ERR(binary_runtime_measurements)) + goto out; + + ascii_runtime_measurements = + securityfs_create_file("ascii_runtime_measurements", + S_IRUSR | S_IRGRP, ima_dir, NULL, + &ima_ascii_measurements_ops); + if (IS_ERR(ascii_runtime_measurements)) + goto out; + + runtime_measurements_count = + securityfs_create_file("runtime_measurements_count", + S_IRUSR | S_IRGRP, ima_dir, NULL, + &ima_measurements_count_ops); + if (IS_ERR(runtime_measurements_count)) + goto out; + + violations = + securityfs_create_file("violations", S_IRUSR | S_IRGRP, + ima_dir, NULL, &ima_htable_violations_ops); + if (IS_ERR(violations)) + goto out; + + return 0; + +out: + securityfs_remove(runtime_measurements_count); + securityfs_remove(ascii_runtime_measurements); + securityfs_remove(binary_runtime_measurements); + securityfs_remove(ima_dir); + return -1; +} + +void __exit ima_fs_cleanup(void) +{ + securityfs_remove(violations); + securityfs_remove(runtime_measurements_count); + securityfs_remove(ascii_runtime_measurements); + securityfs_remove(binary_runtime_measurements); + securityfs_remove(ima_dir); +} diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c index e0f02e328d7..cf227dbfac2 100644 --- a/security/integrity/ima/ima_init.c +++ b/security/integrity/ima/ima_init.c @@ -86,5 +86,11 @@ int ima_init(void) ima_add_boot_aggregate(); /* boot aggregate must be first entry */ ima_init_policy(); - return 0; + + return ima_fs_init(); +} + +void __exit ima_cleanup(void) +{ + ima_fs_cleanup(); } diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 53cee4c512c..871e356e8d6 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -274,6 +274,11 @@ static int __init init_ima(void) return error; } +static void __exit cleanup_ima(void) +{ + ima_cleanup(); +} + late_initcall(init_ima); /* Start IMA after the TPM is available */ MODULE_DESCRIPTION("Integrity Measurement Architecture"); -- cgit v1.2.3 From 4af4662fa4a9dc62289c580337ae2506339c4729 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Wed, 4 Feb 2009 09:07:00 -0500 Subject: integrity: IMA policy Support for a user loadable policy through securityfs with support for LSM specific policy data. - free invalid rule in ima_parse_add_rule() Signed-off-by: Mimi Zohar Acked-by: Serge Hallyn Signed-off-by: James Morris --- security/integrity/ima/Kconfig | 6 + security/integrity/ima/ima.h | 24 +++ security/integrity/ima/ima_fs.c | 67 ++++++++- security/integrity/ima/ima_policy.c | 293 +++++++++++++++++++++++++++++++++++- 4 files changed, 386 insertions(+), 4 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index 2a761c8ac99..3d2b6ee778a 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -47,3 +47,9 @@ config IMA_AUDIT auditing messages can be enabled with 'ima_audit=1' on the kernel command line. +config IMA_LSM_RULES + bool + depends on IMA && (SECURITY_SELINUX || SECURITY_SMACK) + default y + help + Disabling this option will disregard LSM based policy rules diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 9c280cc7300..42706b55492 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -137,4 +137,28 @@ enum ima_hooks { PATH_CHECK = 1, FILE_MMAP, BPRM_CHECK }; int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask); void ima_init_policy(void); void ima_update_policy(void); +int ima_parse_add_rule(char *); +void ima_delete_rules(void); + +/* LSM based policy rules require audit */ +#ifdef CONFIG_IMA_LSM_RULES + +#define security_filter_rule_init security_audit_rule_init +#define security_filter_rule_match security_audit_rule_match + +#else + +static inline int security_filter_rule_init(u32 field, u32 op, char *rulestr, + void **lsmrule) +{ + return -EINVAL; +} + +static inline int security_filter_rule_match(u32 secid, u32 field, u32 op, + void *lsmrule, + struct audit_context *actx) +{ + return -EINVAL; +} +#endif /* CONFIG_IMA_LSM_RULES */ #endif diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index 4f25be768b5..95ef1caa64b 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -19,9 +19,11 @@ #include #include #include +#include #include "ima.h" +static int valid_policy = 1; #define TMPBUFLEN 12 static ssize_t ima_show_htable_value(char __user *buf, size_t count, loff_t *ppos, atomic_long_t *val) @@ -237,11 +239,66 @@ static struct file_operations ima_ascii_measurements_ops = { .release = seq_release, }; +static ssize_t ima_write_policy(struct file *file, const char __user *buf, + size_t datalen, loff_t *ppos) +{ + char *data; + int rc; + + if (datalen >= PAGE_SIZE) + return -ENOMEM; + if (*ppos != 0) { + /* No partial writes. */ + return -EINVAL; + } + data = kmalloc(datalen + 1, GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (copy_from_user(data, buf, datalen)) { + kfree(data); + return -EFAULT; + } + *(data + datalen) = '\0'; + rc = ima_parse_add_rule(data); + if (rc < 0) { + datalen = -EINVAL; + valid_policy = 0; + } + + kfree(data); + return datalen; +} + static struct dentry *ima_dir; static struct dentry *binary_runtime_measurements; static struct dentry *ascii_runtime_measurements; static struct dentry *runtime_measurements_count; static struct dentry *violations; +static struct dentry *ima_policy; + +/* + * ima_release_policy - start using the new measure policy rules. + * + * Initially, ima_measure points to the default policy rules, now + * point to the new policy rules, and remove the securityfs policy file. + */ +static int ima_release_policy(struct inode *inode, struct file *file) +{ + if (!valid_policy) { + ima_delete_rules(); + return 0; + } + ima_update_policy(); + securityfs_remove(ima_policy); + ima_policy = NULL; + return 0; +} + +static struct file_operations ima_measure_policy_ops = { + .write = ima_write_policy, + .release = ima_release_policy +}; int ima_fs_init(void) { @@ -276,13 +333,20 @@ int ima_fs_init(void) if (IS_ERR(violations)) goto out; - return 0; + ima_policy = securityfs_create_file("policy", + S_IRUSR | S_IRGRP | S_IWUSR, + ima_dir, NULL, + &ima_measure_policy_ops); + if (IS_ERR(ima_policy)) + goto out; + return 0; out: securityfs_remove(runtime_measurements_count); securityfs_remove(ascii_runtime_measurements); securityfs_remove(binary_runtime_measurements); securityfs_remove(ima_dir); + securityfs_remove(ima_policy); return -1; } @@ -293,4 +357,5 @@ void __exit ima_fs_cleanup(void) securityfs_remove(ascii_runtime_measurements); securityfs_remove(binary_runtime_measurements); securityfs_remove(ima_dir); + securityfs_remove(ima_policy); } diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 7c3d1ffb147..bd453603e2c 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "ima.h" @@ -24,7 +25,12 @@ #define IMA_FSMAGIC 0x0004 #define IMA_UID 0x0008 -enum ima_action { DONT_MEASURE, MEASURE }; +enum ima_action { UNKNOWN = -1, DONT_MEASURE = 0, MEASURE }; + +#define MAX_LSM_RULES 6 +enum lsm_rule_types { LSM_OBJ_USER, LSM_OBJ_ROLE, LSM_OBJ_TYPE, + LSM_SUBJ_USER, LSM_SUBJ_ROLE, LSM_SUBJ_TYPE +}; struct ima_measure_rule_entry { struct list_head list; @@ -34,8 +40,15 @@ struct ima_measure_rule_entry { int mask; unsigned long fsmagic; uid_t uid; + struct { + void *rule; /* LSM file metadata specific */ + int type; /* audit type */ + } lsm[MAX_LSM_RULES]; }; +/* Without LSM specific knowledge, the default policy can only be + * written in terms of .action, .func, .mask, .fsmagic, and .uid + */ static struct ima_measure_rule_entry default_rules[] = { {.action = DONT_MEASURE,.fsmagic = PROC_SUPER_MAGIC, .flags = IMA_FSMAGIC}, @@ -54,8 +67,11 @@ static struct ima_measure_rule_entry default_rules[] = { }; static LIST_HEAD(measure_default_rules); +static LIST_HEAD(measure_policy_rules); static struct list_head *ima_measure; +static DEFINE_MUTEX(ima_measure_mutex); + /** * ima_match_rules - determine whether an inode matches the measure rule. * @rule: a pointer to a rule @@ -69,6 +85,7 @@ static bool ima_match_rules(struct ima_measure_rule_entry *rule, struct inode *inode, enum ima_hooks func, int mask) { struct task_struct *tsk = current; + int i; if ((rule->flags & IMA_FUNC) && rule->func != func) return false; @@ -79,6 +96,39 @@ static bool ima_match_rules(struct ima_measure_rule_entry *rule, return false; if ((rule->flags & IMA_UID) && rule->uid != tsk->cred->uid) return false; + for (i = 0; i < MAX_LSM_RULES; i++) { + int rc; + u32 osid, sid; + + if (!rule->lsm[i].rule) + continue; + + switch (i) { + case LSM_OBJ_USER: + case LSM_OBJ_ROLE: + case LSM_OBJ_TYPE: + security_inode_getsecid(inode, &osid); + rc = security_filter_rule_match(osid, + rule->lsm[i].type, + AUDIT_EQUAL, + rule->lsm[i].rule, + NULL); + break; + case LSM_SUBJ_USER: + case LSM_SUBJ_ROLE: + case LSM_SUBJ_TYPE: + security_task_getsecid(tsk, &sid); + rc = security_filter_rule_match(sid, + rule->lsm[i].type, + AUDIT_EQUAL, + rule->lsm[i].rule, + NULL); + default: + break; + } + if (!rc) + return false; + } return true; } @@ -112,9 +162,8 @@ int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask) /** * ima_init_policy - initialize the default measure rules. * - * (Could use the default_rules directly, but in policy patch * ima_measure points to either the measure_default_rules or the - * the new measure_policy_rules.) + * the new measure_policy_rules. */ void ima_init_policy(void) { @@ -124,3 +173,241 @@ void ima_init_policy(void) list_add_tail(&default_rules[i].list, &measure_default_rules); ima_measure = &measure_default_rules; } + +/** + * ima_update_policy - update default_rules with new measure rules + * + * Called on file .release to update the default rules with a complete new + * policy. Once updated, the policy is locked, no additional rules can be + * added to the policy. + */ +void ima_update_policy(void) +{ + const char *op = "policy_update"; + const char *cause = "already exists"; + int result = 1; + int audit_info = 0; + + if (ima_measure == &measure_default_rules) { + ima_measure = &measure_policy_rules; + cause = "complete"; + result = 0; + } + integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, + NULL, op, cause, result, audit_info); +} + +enum { + Opt_err = -1, + Opt_measure = 1, Opt_dont_measure, + Opt_obj_user, Opt_obj_role, Opt_obj_type, + Opt_subj_user, Opt_subj_role, Opt_subj_type, + Opt_func, Opt_mask, Opt_fsmagic, Opt_uid +}; + +static match_table_t policy_tokens = { + {Opt_measure, "measure"}, + {Opt_dont_measure, "dont_measure"}, + {Opt_obj_user, "obj_user=%s"}, + {Opt_obj_role, "obj_role=%s"}, + {Opt_obj_type, "obj_type=%s"}, + {Opt_subj_user, "subj_user=%s"}, + {Opt_subj_role, "subj_role=%s"}, + {Opt_subj_type, "subj_type=%s"}, + {Opt_func, "func=%s"}, + {Opt_mask, "mask=%s"}, + {Opt_fsmagic, "fsmagic=%s"}, + {Opt_uid, "uid=%s"}, + {Opt_err, NULL} +}; + +static int ima_lsm_rule_init(struct ima_measure_rule_entry *entry, + char *args, int lsm_rule, int audit_type) +{ + int result; + + entry->lsm[lsm_rule].type = audit_type; + result = security_filter_rule_init(entry->lsm[lsm_rule].type, + AUDIT_EQUAL, args, + &entry->lsm[lsm_rule].rule); + return result; +} + +static int ima_parse_rule(char *rule, struct ima_measure_rule_entry *entry) +{ + struct audit_buffer *ab; + char *p; + int result = 0; + + ab = audit_log_start(current->audit_context, GFP_KERNEL, + AUDIT_INTEGRITY_STATUS); + + entry->action = -1; + while ((p = strsep(&rule, " \n")) != NULL) { + substring_t args[MAX_OPT_ARGS]; + int token; + unsigned long lnum; + + if (result < 0) + break; + if (!*p) + continue; + token = match_token(p, policy_tokens, args); + switch (token) { + case Opt_measure: + audit_log_format(ab, "%s ", "measure"); + entry->action = MEASURE; + break; + case Opt_dont_measure: + audit_log_format(ab, "%s ", "dont_measure"); + entry->action = DONT_MEASURE; + break; + case Opt_func: + audit_log_format(ab, "func=%s ", args[0].from); + if (strcmp(args[0].from, "PATH_CHECK") == 0) + entry->func = PATH_CHECK; + else if (strcmp(args[0].from, "FILE_MMAP") == 0) + entry->func = FILE_MMAP; + else if (strcmp(args[0].from, "BPRM_CHECK") == 0) + entry->func = BPRM_CHECK; + else + result = -EINVAL; + if (!result) + entry->flags |= IMA_FUNC; + break; + case Opt_mask: + audit_log_format(ab, "mask=%s ", args[0].from); + if ((strcmp(args[0].from, "MAY_EXEC")) == 0) + entry->mask = MAY_EXEC; + else if (strcmp(args[0].from, "MAY_WRITE") == 0) + entry->mask = MAY_WRITE; + else if (strcmp(args[0].from, "MAY_READ") == 0) + entry->mask = MAY_READ; + else if (strcmp(args[0].from, "MAY_APPEND") == 0) + entry->mask = MAY_APPEND; + else + result = -EINVAL; + if (!result) + entry->flags |= IMA_MASK; + break; + case Opt_fsmagic: + audit_log_format(ab, "fsmagic=%s ", args[0].from); + result = strict_strtoul(args[0].from, 16, + &entry->fsmagic); + if (!result) + entry->flags |= IMA_FSMAGIC; + break; + case Opt_uid: + audit_log_format(ab, "uid=%s ", args[0].from); + result = strict_strtoul(args[0].from, 10, &lnum); + if (!result) { + entry->uid = (uid_t) lnum; + if (entry->uid != lnum) + result = -EINVAL; + else + entry->flags |= IMA_UID; + } + break; + case Opt_obj_user: + audit_log_format(ab, "obj_user=%s ", args[0].from); + result = ima_lsm_rule_init(entry, args[0].from, + LSM_OBJ_USER, + AUDIT_OBJ_USER); + break; + case Opt_obj_role: + audit_log_format(ab, "obj_role=%s ", args[0].from); + result = ima_lsm_rule_init(entry, args[0].from, + LSM_OBJ_ROLE, + AUDIT_OBJ_ROLE); + break; + case Opt_obj_type: + audit_log_format(ab, "obj_type=%s ", args[0].from); + result = ima_lsm_rule_init(entry, args[0].from, + LSM_OBJ_TYPE, + AUDIT_OBJ_TYPE); + break; + case Opt_subj_user: + audit_log_format(ab, "subj_user=%s ", args[0].from); + result = ima_lsm_rule_init(entry, args[0].from, + LSM_SUBJ_USER, + AUDIT_SUBJ_USER); + break; + case Opt_subj_role: + audit_log_format(ab, "subj_role=%s ", args[0].from); + result = ima_lsm_rule_init(entry, args[0].from, + LSM_SUBJ_ROLE, + AUDIT_SUBJ_ROLE); + break; + case Opt_subj_type: + audit_log_format(ab, "subj_type=%s ", args[0].from); + result = ima_lsm_rule_init(entry, args[0].from, + LSM_SUBJ_TYPE, + AUDIT_SUBJ_TYPE); + break; + case Opt_err: + printk(KERN_INFO "%s: unknown token: %s\n", + __FUNCTION__, p); + break; + } + } + if (entry->action == UNKNOWN) + result = -EINVAL; + + audit_log_format(ab, "res=%d", result); + audit_log_end(ab); + return result; +} + +/** + * ima_parse_add_rule - add a rule to measure_policy_rules + * @rule - ima measurement policy rule + * + * Uses a mutex to protect the policy list from multiple concurrent writers. + * Returns 0 on success, an error code on failure. + */ +int ima_parse_add_rule(char *rule) +{ + const char *op = "add_rule"; + struct ima_measure_rule_entry *entry; + int result = 0; + int audit_info = 0; + + /* Prevent installed policy from changing */ + if (ima_measure != &measure_default_rules) { + integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, + NULL, op, "already exists", + -EACCES, audit_info); + return -EACCES; + } + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) { + integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, + NULL, op, "-ENOMEM", -ENOMEM, audit_info); + return -ENOMEM; + } + + INIT_LIST_HEAD(&entry->list); + + result = ima_parse_rule(rule, entry); + if (!result) { + mutex_lock(&ima_measure_mutex); + list_add_tail(&entry->list, &measure_policy_rules); + mutex_unlock(&ima_measure_mutex); + } else + kfree(entry); + return result; +} + +/* ima_delete_rules called to cleanup invalid policy */ +void ima_delete_rules() +{ + struct ima_measure_rule_entry *entry, *tmp; + + mutex_lock(&ima_measure_mutex); + list_for_each_entry_safe(entry, tmp, &measure_policy_rules, list) { + list_del(&entry->list); + kfree(entry); + } + mutex_unlock(&ima_measure_mutex); +} -- cgit v1.2.3 From f4bd857bc8ed997c25ec06b56ef8064aafa6d4f3 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Wed, 4 Feb 2009 09:07:01 -0500 Subject: integrity: IMA policy open Sequentialize access to the policy file - permit multiple attempts to replace default policy with a valid policy Signed-off-by: Mimi Zohar Acked-by: Serge Hallyn Signed-off-by: James Morris --- security/integrity/ima/ima_fs.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'security') diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index 95ef1caa64b..573780c76f1 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -277,16 +277,30 @@ static struct dentry *runtime_measurements_count; static struct dentry *violations; static struct dentry *ima_policy; +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) +{ + if (atomic_dec_and_test(&policy_opencount)) + return 0; + return -EBUSY; +} + /* * ima_release_policy - start using the new measure policy rules. * * Initially, ima_measure points to the default policy rules, now - * point to the new policy rules, and remove the securityfs policy file. + * point to the new policy rules, and remove the securityfs policy file, + * assuming a valid policy. */ static int ima_release_policy(struct inode *inode, struct file *file) { if (!valid_policy) { ima_delete_rules(); + valid_policy = 1; + atomic_set(&policy_opencount, 1); return 0; } ima_update_policy(); @@ -296,6 +310,7 @@ static int ima_release_policy(struct inode *inode, struct file *file) } static struct file_operations ima_measure_policy_ops = { + .open = ima_open_policy, .write = ima_write_policy, .release = ima_release_policy }; -- cgit v1.2.3 From 1df9f0a73178718969ae47d813b8e7aab2cf073c Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Wed, 4 Feb 2009 09:07:02 -0500 Subject: Integrity: IMA file free imbalance The number of calls to ima_path_check()/ima_file_free() should be balanced. An extra call to fput(), indicates the file could have been accessed without first being measured. Although f_count is incremented/decremented in places other than fget/fput, like fget_light/fput_light and get_file, the current task must already hold a file refcnt. The call to __fput() is delayed until the refcnt becomes 0, resulting in ima_file_free() flagging any changes. - add hook to increment opencount for IPC shared memory(SYSV), shmat files, and /dev/zero - moved NULL iint test in opencount_get() Signed-off-by: Mimi Zohar Acked-by: Serge Hallyn Signed-off-by: James Morris --- security/integrity/ima/ima.h | 2 ++ security/integrity/ima/ima_iint.c | 17 ++++++++++++++++ security/integrity/ima/ima_main.c | 42 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) (limited to 'security') diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 42706b55492..e3c16a21a38 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -97,6 +97,7 @@ static inline unsigned long ima_hash_key(u8 *digest) /* iint cache flags */ #define IMA_MEASURED 1 +#define IMA_IINT_DUMP_STACK 512 /* integrity data associated with an inode */ struct ima_iint_cache { @@ -106,6 +107,7 @@ struct ima_iint_cache { struct mutex mutex; /* protects: version, flags, digest */ long readcount; /* measured files readcount */ long writecount; /* measured files writecount */ + long opencount; /* opens reference count */ struct kref refcount; /* ima_iint_cache reference count */ struct rcu_head rcu; }; diff --git a/security/integrity/ima/ima_iint.c b/security/integrity/ima/ima_iint.c index 750db3c993a..1f035e8d29c 100644 --- a/security/integrity/ima/ima_iint.c +++ b/security/integrity/ima/ima_iint.c @@ -126,6 +126,7 @@ struct ima_iint_cache *ima_iint_find_insert_get(struct inode *inode) return iint; } +EXPORT_SYMBOL_GPL(ima_iint_find_insert_get); /* iint_free - called when the iint refcount goes to zero */ void iint_free(struct kref *kref) @@ -134,6 +135,21 @@ void iint_free(struct kref *kref) refcount); iint->version = 0; iint->flags = 0UL; + if (iint->readcount != 0) { + printk(KERN_INFO "%s: readcount: %ld\n", __FUNCTION__, + iint->readcount); + iint->readcount = 0; + } + if (iint->writecount != 0) { + printk(KERN_INFO "%s: writecount: %ld\n", __FUNCTION__, + iint->writecount); + iint->writecount = 0; + } + if (iint->opencount != 0) { + printk(KERN_INFO "%s: opencount: %ld\n", __FUNCTION__, + iint->opencount); + iint->opencount = 0; + } kref_set(&iint->refcount, 1); kmem_cache_free(iint_cache, iint); } @@ -174,6 +190,7 @@ static void init_once(void *foo) mutex_init(&iint->mutex); iint->readcount = 0; iint->writecount = 0; + iint->opencount = 0; kref_set(&iint->refcount, 1); } diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 871e356e8d6..f4e7266f5ae 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -66,6 +66,19 @@ void ima_file_free(struct file *file) return; mutex_lock(&iint->mutex); + if (iint->opencount <= 0) { + printk(KERN_INFO + "%s: %s open/free imbalance (r:%ld w:%ld o:%ld f:%ld)\n", + __FUNCTION__, file->f_dentry->d_name.name, + iint->readcount, iint->writecount, + iint->opencount, atomic_long_read(&file->f_count)); + if (!(iint->flags & IMA_IINT_DUMP_STACK)) { + dump_stack(); + iint->flags |= IMA_IINT_DUMP_STACK; + } + } + iint->opencount--; + if ((file->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ) iint->readcount--; @@ -119,6 +132,7 @@ static int get_path_measurement(struct ima_iint_cache *iint, struct file *file, pr_info("%s dentry_open failed\n", filename); return rc; } + iint->opencount++; iint->readcount++; rc = ima_collect_measurement(iint, file); @@ -159,6 +173,7 @@ int ima_path_check(struct path *path, int mask) return 0; mutex_lock(&iint->mutex); + iint->opencount++; if ((mask & MAY_WRITE) || (mask == 0)) iint->writecount++; else if (mask & (MAY_READ | MAY_EXEC)) @@ -219,6 +234,21 @@ out: return rc; } +static void opencount_get(struct file *file) +{ + struct inode *inode = file->f_dentry->d_inode; + struct ima_iint_cache *iint; + + if (!ima_initialized || !S_ISREG(inode->i_mode)) + return; + iint = ima_iint_find_insert_get(inode); + if (!iint) + return; + mutex_lock(&iint->mutex); + iint->opencount++; + mutex_unlock(&iint->mutex); +} + /** * ima_file_mmap - based on policy, collect/store measurement. * @file: pointer to the file to be measured (May be NULL) @@ -242,6 +272,18 @@ int ima_file_mmap(struct file *file, unsigned long prot) return 0; } +/* + * ima_shm_check - IPC shm and shmat create/fput a file + * + * Maintain the opencount for these files to prevent unnecessary + * imbalance messages. + */ +void ima_shm_check(struct file *file) +{ + opencount_get(file); + return; +} + /** * ima_bprm_check - based on policy, collect/store measurement. * @bprm: contains the linux_binprm structure -- cgit v1.2.3 From 64c61d80a6e4c935a09ac5ff1d952967ca1268f8 Mon Sep 17 00:00:00 2001 From: James Morris Date: Thu, 5 Feb 2009 09:28:26 +1100 Subject: IMA: fix ima_delete_rules() definition Fix ima_delete_rules() definition so sparse doesn't complain. Signed-off-by: James Morris --- security/integrity/ima/ima_policy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'security') diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index bd453603e2c..23810e0bfc6 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -400,7 +400,7 @@ int ima_parse_add_rule(char *rule) } /* ima_delete_rules called to cleanup invalid policy */ -void ima_delete_rules() +void ima_delete_rules(void) { struct ima_measure_rule_entry *entry, *tmp; -- cgit v1.2.3 From 523979adfa0b79d4e3aa053220c37a9233294206 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Wed, 11 Feb 2009 11:12:28 -0500 Subject: integrity: audit update Based on discussions on linux-audit, as per Steve Grubb's request http://lkml.org/lkml/2009/2/6/269, the following changes were made: - forced audit result to be either 0 or 1. - made template names const - Added new stand-alone message type: AUDIT_INTEGRITY_RULE Signed-off-by: Mimi Zohar Acked-by: Steve Grubb Signed-off-by: James Morris --- security/integrity/ima/ima.h | 2 +- security/integrity/ima/ima_api.c | 2 +- security/integrity/ima/ima_audit.c | 21 ++++++++++++--------- security/integrity/ima/ima_fs.c | 2 +- security/integrity/ima/ima_init.c | 2 +- security/integrity/ima/ima_policy.c | 17 +++++++++-------- 6 files changed, 25 insertions(+), 21 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index e3c16a21a38..165eb5397ea 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -47,7 +47,7 @@ struct ima_template_data { struct ima_template_entry { u8 digest[IMA_DIGEST_SIZE]; /* sha1 or md5 measurement hash */ - char *template_name; + const char *template_name; int template_len; struct ima_template_data template; }; diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index a148a25804f..3cd58b60afd 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -15,7 +15,7 @@ #include #include "ima.h" -static char *IMA_TEMPLATE_NAME = "ima"; +static const char *IMA_TEMPLATE_NAME = "ima"; /* * ima_store_template - store ima template measurements diff --git a/security/integrity/ima/ima_audit.c b/security/integrity/ima/ima_audit.c index 8a0f1e23ccf..1e082bb987b 100644 --- a/security/integrity/ima/ima_audit.c +++ b/security/integrity/ima/ima_audit.c @@ -22,16 +22,18 @@ static int ima_audit; static int __init ima_audit_setup(char *str) { unsigned long audit; - int rc; - char *op; + int rc, result = 0; + char *op = "ima_audit"; + char *cause; rc = strict_strtoul(str, 0, &audit); if (rc || audit > 1) - printk(KERN_INFO "ima: invalid ima_audit value\n"); + result = 1; else ima_audit = audit; - op = ima_audit ? "ima_audit_enabled" : "ima_audit_not_enabled"; - integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, NULL, NULL, op, 0, 0); + cause = ima_audit ? "enabled" : "not_enabled"; + integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, NULL, + op, cause, result, 0); return 1; } __setup("ima_audit=", ima_audit_setup); @@ -47,20 +49,21 @@ void integrity_audit_msg(int audit_msgno, struct inode *inode, return; ab = audit_log_start(current->audit_context, GFP_KERNEL, audit_msgno); - audit_log_format(ab, "integrity: pid=%d uid=%u auid=%u", + audit_log_format(ab, "integrity: pid=%d uid=%u auid=%u ses=%u", current->pid, current->cred->uid, - audit_get_loginuid(current)); + audit_get_loginuid(current), + audit_get_sessionid(current)); audit_log_task_context(ab); switch (audit_msgno) { case AUDIT_INTEGRITY_DATA: case AUDIT_INTEGRITY_METADATA: case AUDIT_INTEGRITY_PCR: + case AUDIT_INTEGRITY_STATUS: audit_log_format(ab, " op=%s cause=%s", op, cause); break; case AUDIT_INTEGRITY_HASH: audit_log_format(ab, " op=%s hash=%s", op, cause); break; - case AUDIT_INTEGRITY_STATUS: default: audit_log_format(ab, " op=%s", op); } @@ -73,6 +76,6 @@ void integrity_audit_msg(int audit_msgno, struct inode *inode, if (inode) audit_log_format(ab, " dev=%s ino=%lu", inode->i_sb->s_id, inode->i_ino); - audit_log_format(ab, " res=%d", result); + audit_log_format(ab, " res=%d", !result ? 0 : 1); audit_log_end(ab); } diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index 573780c76f1..ffbe259700b 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -137,7 +137,7 @@ static int ima_measurements_show(struct seq_file *m, void *v) ima_putc(m, &namelen, sizeof namelen); /* 4th: template name */ - ima_putc(m, e->template_name, namelen); + ima_putc(m, (void *)e->template_name, namelen); /* 5th: template specific data */ ima_template_show(m, (struct ima_template_data *)&e->template, diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c index cf227dbfac2..0b0bb8c978c 100644 --- a/security/integrity/ima/ima_init.c +++ b/security/integrity/ima/ima_init.c @@ -20,7 +20,7 @@ #include "ima.h" /* name for boot aggregate entry */ -static char *boot_aggregate_name = "boot_aggregate"; +static const char *boot_aggregate_name = "boot_aggregate"; int ima_used_chip; /* Add the boot aggregate to the IMA measurement list and extend diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 23810e0bfc6..b5291ad5ef5 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -12,7 +12,6 @@ */ #include #include -#include #include #include #include @@ -239,8 +238,7 @@ static int ima_parse_rule(char *rule, struct ima_measure_rule_entry *entry) char *p; int result = 0; - ab = audit_log_start(current->audit_context, GFP_KERNEL, - AUDIT_INTEGRITY_STATUS); + ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_INTEGRITY_RULE); entry->action = -1; while ((p = strsep(&rule, " \n")) != NULL) { @@ -345,15 +343,14 @@ static int ima_parse_rule(char *rule, struct ima_measure_rule_entry *entry) AUDIT_SUBJ_TYPE); break; case Opt_err: - printk(KERN_INFO "%s: unknown token: %s\n", - __FUNCTION__, p); + audit_log_format(ab, "UNKNOWN=%s ", p); break; } } if (entry->action == UNKNOWN) result = -EINVAL; - audit_log_format(ab, "res=%d", result); + audit_log_format(ab, "res=%d", !result ? 0 : 1); audit_log_end(ab); return result; } @@ -367,7 +364,7 @@ static int ima_parse_rule(char *rule, struct ima_measure_rule_entry *entry) */ int ima_parse_add_rule(char *rule) { - const char *op = "add_rule"; + const char *op = "update_policy"; struct ima_measure_rule_entry *entry; int result = 0; int audit_info = 0; @@ -394,8 +391,12 @@ int ima_parse_add_rule(char *rule) mutex_lock(&ima_measure_mutex); list_add_tail(&entry->list, &measure_policy_rules); mutex_unlock(&ima_measure_mutex); - } else + } else { kfree(entry); + integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, + NULL, op, "invalid policy", result, + audit_info); + } return result; } -- cgit v1.2.3 From c73bd6d473ceb5d643d3afd7e75b7dc2e6918558 Mon Sep 17 00:00:00 2001 From: Kentaro Takeda Date: Thu, 5 Feb 2009 17:18:12 +0900 Subject: Memory and pathname management functions. TOMOYO Linux performs pathname based access control. To remove factors that make pathname based access control difficult (e.g. symbolic links, "..", "//" etc.), TOMOYO Linux derives realpath of requested pathname from "struct dentry" and "struct vfsmount". The maximum length of string data is limited to 4000 including trailing '\0'. Since TOMOYO Linux uses '\ooo' style representation for non ASCII printable characters, maybe TOMOYO Linux should be able to support 16336 (which means (NAME_MAX * (PATH_MAX / (NAME_MAX + 1)) * 4 + (PATH_MAX / (NAME_MAX + 1))) including trailing '\0'), but I think 4000 is enough for practical use. TOMOYO uses only 0x21 - 0x7E (as printable characters) and 0x20 (as word delimiter) and 0x0A (as line delimiter). 0x01 - 0x20 and 0x80 - 0xFF is handled in \ooo style representation. The reason to use \ooo is to guarantee that "%s" won't damage logs. Userland program can request open("/tmp/file granted.\nAccess /tmp/file ", O_WRONLY | O_CREAT, 0600) and logging such crazy pathname using "Access %s denied.\n" format will cause "fabrication of logs" like Access /tmp/file granted. Access /tmp/file denied. TOMOYO converts such characters to \ooo so that the logs will become Access /tmp/file\040granted.\012Access\040/tmp/file denied. and the administrator can read the logs safely using /bin/cat . Likewise, a crazy request like open("/tmp/\x01\x02\x03\x04\x05\x06\x07\x08\x09", O_WRONLY | O_CREAT, 0600) will be processed safely by converting to Access /tmp/\001\002\003\004\005\006\007\010\011 denied. Signed-off-by: Kentaro Takeda Signed-off-by: Tetsuo Handa Signed-off-by: Toshiharu Harada Signed-off-by: James Morris --- security/tomoyo/realpath.c | 487 +++++++++++++++++++++++++++++++++++++++++++++ security/tomoyo/realpath.h | 63 ++++++ 2 files changed, 550 insertions(+) create mode 100644 security/tomoyo/realpath.c create mode 100644 security/tomoyo/realpath.h (limited to 'security') diff --git a/security/tomoyo/realpath.c b/security/tomoyo/realpath.c new file mode 100644 index 00000000000..5fd48d23a21 --- /dev/null +++ b/security/tomoyo/realpath.c @@ -0,0 +1,487 @@ +/* + * security/tomoyo/realpath.c + * + * Get the canonicalized absolute pathnames. The basis for TOMOYO. + * + * Copyright (C) 2005-2009 NTT DATA CORPORATION + * + * Version: 2.2.0-pre 2009/02/01 + * + */ + +#include +#include +#include +#include "common.h" +#include "realpath.h" + +/** + * tomoyo_encode: Convert binary string to ascii string. + * + * @buffer: Buffer for ASCII string. + * @buflen: Size of @buffer. + * @str: Binary string. + * + * Returns 0 on success, -ENOMEM otherwise. + */ +int tomoyo_encode(char *buffer, int buflen, const char *str) +{ + while (1) { + const unsigned char c = *(unsigned char *) str++; + + if (tomoyo_is_valid(c)) { + if (--buflen <= 0) + break; + *buffer++ = (char) c; + if (c != '\\') + continue; + if (--buflen <= 0) + break; + *buffer++ = (char) c; + continue; + } + if (!c) { + if (--buflen <= 0) + break; + *buffer = '\0'; + return 0; + } + buflen -= 4; + if (buflen <= 0) + break; + *buffer++ = '\\'; + *buffer++ = (c >> 6) + '0'; + *buffer++ = ((c >> 3) & 7) + '0'; + *buffer++ = (c & 7) + '0'; + } + return -ENOMEM; +} + +/** + * tomoyo_realpath_from_path2 - Returns realpath(3) of the given dentry but ignores chroot'ed root. + * + * @path: Pointer to "struct path". + * @newname: Pointer to buffer to return value in. + * @newname_len: Size of @newname. + * + * Returns 0 on success, negative value otherwise. + * + * If dentry is a directory, trailing '/' is appended. + * Characters out of 0x20 < c < 0x7F range are converted to + * \ooo style octal string. + * Character \ is converted to \\ string. + */ +int tomoyo_realpath_from_path2(struct path *path, char *newname, + int newname_len) +{ + int error = -ENOMEM; + struct dentry *dentry = path->dentry; + char *sp; + + if (!dentry || !path->mnt || !newname || newname_len <= 2048) + return -EINVAL; + if (dentry->d_op && dentry->d_op->d_dname) { + /* For "socket:[\$]" and "pipe:[\$]". */ + static const int offset = 1536; + sp = dentry->d_op->d_dname(dentry, newname + offset, + newname_len - offset); + } else { + /* Taken from d_namespace_path(). */ + struct path root; + struct path ns_root = { }; + struct path tmp; + + read_lock(¤t->fs->lock); + root = current->fs->root; + path_get(&root); + read_unlock(¤t->fs->lock); + spin_lock(&vfsmount_lock); + if (root.mnt && root.mnt->mnt_ns) + ns_root.mnt = mntget(root.mnt->mnt_ns->root); + if (ns_root.mnt) + ns_root.dentry = dget(ns_root.mnt->mnt_root); + spin_unlock(&vfsmount_lock); + spin_lock(&dcache_lock); + tmp = ns_root; + sp = __d_path(path, &tmp, newname, newname_len); + spin_unlock(&dcache_lock); + path_put(&root); + path_put(&ns_root); + } + if (IS_ERR(sp)) + error = PTR_ERR(sp); + else + error = tomoyo_encode(newname, sp - newname, sp); + /* Append trailing '/' if dentry is a directory. */ + if (!error && dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode) + && *newname) { + sp = newname + strlen(newname); + if (*(sp - 1) != '/') { + if (sp < newname + newname_len - 4) { + *sp++ = '/'; + *sp = '\0'; + } else { + error = -ENOMEM; + } + } + } + if (error) + printk(KERN_WARNING "tomoyo_realpath: Pathname too long.\n"); + return error; +} + +/** + * tomoyo_realpath_from_path - Returns realpath(3) of the given pathname but ignores chroot'ed root. + * + * @path: Pointer to "struct path". + * + * Returns the realpath of the given @path on success, NULL otherwise. + * + * These functions use tomoyo_alloc(), so the caller must call tomoyo_free() + * if these functions didn't return NULL. + */ +char *tomoyo_realpath_from_path(struct path *path) +{ + char *buf = tomoyo_alloc(sizeof(struct tomoyo_page_buffer)); + + BUILD_BUG_ON(sizeof(struct tomoyo_page_buffer) + <= TOMOYO_MAX_PATHNAME_LEN - 1); + if (!buf) + return NULL; + if (tomoyo_realpath_from_path2(path, buf, + TOMOYO_MAX_PATHNAME_LEN - 1) == 0) + return buf; + tomoyo_free(buf); + return NULL; +} + +/** + * tomoyo_realpath - Get realpath of a pathname. + * + * @pathname: The pathname to solve. + * + * Returns the realpath of @pathname on success, NULL otherwise. + */ +char *tomoyo_realpath(const char *pathname) +{ + struct nameidata nd; + + if (pathname && path_lookup(pathname, LOOKUP_FOLLOW, &nd) == 0) { + char *buf = tomoyo_realpath_from_path(&nd.path); + path_put(&nd.path); + return buf; + } + return NULL; +} + +/** + * tomoyo_realpath_nofollow - Get realpath of a pathname. + * + * @pathname: The pathname to solve. + * + * Returns the realpath of @pathname on success, NULL otherwise. + */ +char *tomoyo_realpath_nofollow(const char *pathname) +{ + struct nameidata nd; + + if (pathname && path_lookup(pathname, 0, &nd) == 0) { + char *buf = tomoyo_realpath_from_path(&nd.path); + path_put(&nd.path); + return buf; + } + return NULL; +} + +/* Memory allocated for non-string data. */ +static unsigned int tomoyo_allocated_memory_for_elements; +/* Quota for holding non-string data. */ +static unsigned int tomoyo_quota_for_elements; + +/** + * tomoyo_alloc_element - Allocate permanent memory for structures. + * + * @size: Size in bytes. + * + * Returns pointer to allocated memory on success, NULL otherwise. + * + * Memory has to be zeroed. + * The RAM is chunked, so NEVER try to kfree() the returned pointer. + */ +void *tomoyo_alloc_element(const unsigned int size) +{ + static char *buf; + static DEFINE_MUTEX(lock); + static unsigned int buf_used_len = PATH_MAX; + char *ptr = NULL; + /*Assumes sizeof(void *) >= sizeof(long) is true. */ + const unsigned int word_aligned_size + = roundup(size, max(sizeof(void *), sizeof(long))); + if (word_aligned_size > PATH_MAX) + return NULL; + /***** EXCLUSIVE SECTION START *****/ + mutex_lock(&lock); + if (buf_used_len + word_aligned_size > PATH_MAX) { + if (!tomoyo_quota_for_elements || + tomoyo_allocated_memory_for_elements + + PATH_MAX <= tomoyo_quota_for_elements) + ptr = kzalloc(PATH_MAX, GFP_KERNEL); + if (!ptr) { + printk(KERN_WARNING "ERROR: Out of memory " + "for tomoyo_alloc_element().\n"); + if (!tomoyo_policy_loaded) + panic("MAC Initialization failed.\n"); + } else { + buf = ptr; + tomoyo_allocated_memory_for_elements += PATH_MAX; + buf_used_len = word_aligned_size; + ptr = buf; + } + } else if (word_aligned_size) { + int i; + ptr = buf + buf_used_len; + buf_used_len += word_aligned_size; + for (i = 0; i < word_aligned_size; i++) { + if (!ptr[i]) + continue; + printk(KERN_ERR "WARNING: Reserved memory was tainted! " + "The system might go wrong.\n"); + ptr[i] = '\0'; + } + } + mutex_unlock(&lock); + /***** EXCLUSIVE SECTION END *****/ + return ptr; +} + +/* Memory allocated for string data in bytes. */ +static unsigned int tomoyo_allocated_memory_for_savename; +/* Quota for holding string data in bytes. */ +static unsigned int tomoyo_quota_for_savename; + +/* + * TOMOYO uses this hash only when appending a string into the string + * table. Frequency of appending strings is very low. So we don't need + * large (e.g. 64k) hash size. 256 will be sufficient. + */ +#define TOMOYO_MAX_HASH 256 + +/* Structure for string data. */ +struct tomoyo_name_entry { + struct list_head list; + struct tomoyo_path_info entry; +}; + +/* Structure for available memory region. */ +struct tomoyo_free_memory_block_list { + struct list_head list; + char *ptr; /* Pointer to a free area. */ + int len; /* Length of the area. */ +}; + +/* + * The list for "struct tomoyo_name_entry". + * + * This list is updated only inside tomoyo_save_name(), thus + * no global mutex exists. + */ +static struct list_head tomoyo_name_list[TOMOYO_MAX_HASH]; + +/** + * tomoyo_save_name - Allocate permanent memory for string data. + * + * @name: The string to store into the permernent memory. + * + * Returns pointer to "struct tomoyo_path_info" on success, NULL otherwise. + * + * The RAM is shared, so NEVER try to modify or kfree() the returned name. + */ +const struct tomoyo_path_info *tomoyo_save_name(const char *name) +{ + static LIST_HEAD(fmb_list); + static DEFINE_MUTEX(lock); + struct tomoyo_name_entry *ptr; + unsigned int hash; + /* fmb contains available size in bytes. + fmb is removed from the fmb_list when fmb->len becomes 0. */ + struct tomoyo_free_memory_block_list *fmb; + int len; + char *cp; + + if (!name) + return NULL; + len = strlen(name) + 1; + if (len > TOMOYO_MAX_PATHNAME_LEN) { + printk(KERN_WARNING "ERROR: Name too long " + "for tomoyo_save_name().\n"); + return NULL; + } + hash = full_name_hash((const unsigned char *) name, len - 1); + /***** EXCLUSIVE SECTION START *****/ + mutex_lock(&lock); + list_for_each_entry(ptr, &tomoyo_name_list[hash % TOMOYO_MAX_HASH], + list) { + if (hash == ptr->entry.hash && !strcmp(name, ptr->entry.name)) + goto out; + } + list_for_each_entry(fmb, &fmb_list, list) { + if (len <= fmb->len) + goto ready; + } + if (!tomoyo_quota_for_savename || + tomoyo_allocated_memory_for_savename + PATH_MAX + <= tomoyo_quota_for_savename) + cp = kzalloc(PATH_MAX, GFP_KERNEL); + else + cp = NULL; + fmb = kzalloc(sizeof(*fmb), GFP_KERNEL); + if (!cp || !fmb) { + kfree(cp); + kfree(fmb); + printk(KERN_WARNING "ERROR: Out of memory " + "for tomoyo_save_name().\n"); + if (!tomoyo_policy_loaded) + panic("MAC Initialization failed.\n"); + ptr = NULL; + goto out; + } + tomoyo_allocated_memory_for_savename += PATH_MAX; + list_add(&fmb->list, &fmb_list); + fmb->ptr = cp; + fmb->len = PATH_MAX; + ready: + ptr = tomoyo_alloc_element(sizeof(*ptr)); + if (!ptr) + goto out; + ptr->entry.name = fmb->ptr; + memmove(fmb->ptr, name, len); + tomoyo_fill_path_info(&ptr->entry); + fmb->ptr += len; + fmb->len -= len; + list_add_tail(&ptr->list, &tomoyo_name_list[hash % TOMOYO_MAX_HASH]); + if (fmb->len == 0) { + list_del(&fmb->list); + kfree(fmb); + } + out: + mutex_unlock(&lock); + /***** EXCLUSIVE SECTION END *****/ + return ptr ? &ptr->entry : NULL; +} + +/** + * tomoyo_realpath_init - Initialize realpath related code. + * + * Returns 0. + */ +static int __init tomoyo_realpath_init(void) +{ + int i; + + BUILD_BUG_ON(TOMOYO_MAX_PATHNAME_LEN > PATH_MAX); + for (i = 0; i < TOMOYO_MAX_HASH; i++) + INIT_LIST_HEAD(&tomoyo_name_list[i]); + INIT_LIST_HEAD(&tomoyo_kernel_domain.acl_info_list); + tomoyo_kernel_domain.domainname = tomoyo_save_name(TOMOYO_ROOT_NAME); + list_add_tail(&tomoyo_kernel_domain.list, &tomoyo_domain_list); + down_read(&tomoyo_domain_list_lock); + if (tomoyo_find_domain(TOMOYO_ROOT_NAME) != &tomoyo_kernel_domain) + panic("Can't register tomoyo_kernel_domain"); + up_read(&tomoyo_domain_list_lock); + return 0; +} + +security_initcall(tomoyo_realpath_init); + +/* Memory allocated for temporary purpose. */ +static atomic_t tomoyo_dynamic_memory_size; + +/** + * tomoyo_alloc - Allocate memory for temporary purpose. + * + * @size: Size in bytes. + * + * Returns pointer to allocated memory on success, NULL otherwise. + */ +void *tomoyo_alloc(const size_t size) +{ + void *p = kzalloc(size, GFP_KERNEL); + if (p) + atomic_add(ksize(p), &tomoyo_dynamic_memory_size); + return p; +} + +/** + * tomoyo_free - Release memory allocated by tomoyo_alloc(). + * + * @p: Pointer returned by tomoyo_alloc(). May be NULL. + * + * Returns nothing. + */ +void tomoyo_free(const void *p) +{ + if (p) { + atomic_sub(ksize(p), &tomoyo_dynamic_memory_size); + kfree(p); + } +} + +/** + * tomoyo_read_memory_counter - Check for memory usage in bytes. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns memory usage. + */ +int tomoyo_read_memory_counter(struct tomoyo_io_buffer *head) +{ + if (!head->read_eof) { + const unsigned int shared + = tomoyo_allocated_memory_for_savename; + const unsigned int private + = tomoyo_allocated_memory_for_elements; + const unsigned int dynamic + = atomic_read(&tomoyo_dynamic_memory_size); + char buffer[64]; + + memset(buffer, 0, sizeof(buffer)); + if (tomoyo_quota_for_savename) + snprintf(buffer, sizeof(buffer) - 1, + " (Quota: %10u)", + tomoyo_quota_for_savename); + else + buffer[0] = '\0'; + tomoyo_io_printf(head, "Shared: %10u%s\n", shared, buffer); + if (tomoyo_quota_for_elements) + snprintf(buffer, sizeof(buffer) - 1, + " (Quota: %10u)", + tomoyo_quota_for_elements); + else + buffer[0] = '\0'; + tomoyo_io_printf(head, "Private: %10u%s\n", private, buffer); + tomoyo_io_printf(head, "Dynamic: %10u\n", dynamic); + tomoyo_io_printf(head, "Total: %10u\n", + shared + private + dynamic); + head->read_eof = true; + } + return 0; +} + +/** + * tomoyo_write_memory_quota - Set memory quota. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0. + */ +int tomoyo_write_memory_quota(struct tomoyo_io_buffer *head) +{ + char *data = head->write_buf; + unsigned int size; + + if (sscanf(data, "Shared: %u", &size) == 1) + tomoyo_quota_for_savename = size; + else if (sscanf(data, "Private: %u", &size) == 1) + tomoyo_quota_for_elements = size; + return 0; +} diff --git a/security/tomoyo/realpath.h b/security/tomoyo/realpath.h new file mode 100644 index 00000000000..0ea541fcb53 --- /dev/null +++ b/security/tomoyo/realpath.h @@ -0,0 +1,63 @@ +/* + * security/tomoyo/realpath.h + * + * Get the canonicalized absolute pathnames. The basis for TOMOYO. + * + * Copyright (C) 2005-2009 NTT DATA CORPORATION + * + * Version: 2.2.0-pre 2009/02/01 + * + */ + +#ifndef _SECURITY_TOMOYO_REALPATH_H +#define _SECURITY_TOMOYO_REALPATH_H + +struct path; +struct tomoyo_path_info; +struct tomoyo_io_buffer; + +/* Convert binary string to ascii string. */ +int tomoyo_encode(char *buffer, int buflen, const char *str); + +/* Returns realpath(3) of the given pathname but ignores chroot'ed root. */ +int tomoyo_realpath_from_path2(struct path *path, char *newname, + int newname_len); + +/* + * Returns realpath(3) of the given pathname but ignores chroot'ed root. + * These functions use tomoyo_alloc(), so the caller must call tomoyo_free() + * if these functions didn't return NULL. + */ +char *tomoyo_realpath(const char *pathname); +/* + * Same with tomoyo_realpath() except that it doesn't follow the final symlink. + */ +char *tomoyo_realpath_nofollow(const char *pathname); +/* Same with tomoyo_realpath() except that the pathname is already solved. */ +char *tomoyo_realpath_from_path(struct path *path); + +/* + * Allocate memory for ACL entry. + * The RAM is chunked, so NEVER try to kfree() the returned pointer. + */ +void *tomoyo_alloc_element(const unsigned int size); + +/* + * Keep the given name on the RAM. + * The RAM is shared, so NEVER try to modify or kfree() the returned name. + */ +const struct tomoyo_path_info *tomoyo_save_name(const char *name); + +/* Allocate memory for temporary use (e.g. permission checks). */ +void *tomoyo_alloc(const size_t size); + +/* Free memory allocated by tomoyo_alloc(). */ +void tomoyo_free(const void *p); + +/* Check for memory usage. */ +int tomoyo_read_memory_counter(struct tomoyo_io_buffer *head); + +/* Set memory quota. */ +int tomoyo_write_memory_quota(struct tomoyo_io_buffer *head); + +#endif /* !defined(_SECURITY_TOMOYO_REALPATH_H) */ -- cgit v1.2.3 From 9590837b89aaa4523209ac91c52db5ea0d9142fd Mon Sep 17 00:00:00 2001 From: Kentaro Takeda Date: Thu, 5 Feb 2009 17:18:13 +0900 Subject: Common functions for TOMOYO Linux. This file contains common functions (e.g. policy I/O, pattern matching). -------------------- About pattern matching -------------------- Since TOMOYO Linux is a name based access control, TOMOYO Linux seriously considers "safe" string representation. TOMOYO Linux's string manipulation functions make reviewers feel crazy, but there are reasons why TOMOYO Linux needs its own string manipulation functions. ----- Part 1 : preconditions ----- People definitely want to use wild card. To support pattern matching, we have to support wild card characters. In a typical Linux system, filenames are likely consists of only alphabets, numbers, and some characters (e.g. + - ~ . / ). But theoretically, the Linux kernel accepts all characters but NUL character (which is used as a terminator of a string). Some Linux systems can have filenames which contain * ? ** etc. Therefore, we have to somehow modify string so that we can distinguish wild card characters and normal characters. It might be possible for some application's configuration files to restrict acceptable characters. It is impossible for kernel to restrict acceptable characters. We can't accept approaches which will cause troubles for applications. ----- Part 2 : commonly used approaches ----- Text formatted strings separated by space character (0x20) and new line character (0x0A) is more preferable for users over array of NUL-terminated string. Thus, people use text formatted configuration files separated by space character and new line. We sometimes need to handle non-printable characters. Thus, people use \ character (0x5C) as escape character and represent non-printable characters using octal or hexadecimal format. At this point, we remind (at least) 3 approaches. (1) Shell glob style expression (2) POSIX regular expression (UNIX style regular expression) (3) Maverick wild card expression On the surface, (1) and (2) sound good choices. But they have a big pitfall. All meta-characters in (1) and (2) are legal characters for representing a pathname, and users easily write incorrect expression. What is worse, users unlikely notice incorrect expressions because characters used for regular pathnames unlikely contain meta-characters. This incorrect use of meta-characters in pathname representation reveals vulnerability (e.g. unexpected results) only when irregular pathname is specified. The authors of TOMOYO Linux think that approaches which adds some character for interpreting meta-characters as normal characters (i.e. (1) and (2)) are not suitable for security use. Therefore, the authors of TOMOYO Linux propose (3). ----- Part 3: consideration points ----- We need to solve encoding problem. A single character can be represented in several ways using encodings. For Japanese language, there are "ShiftJIS", "ISO-2022-JP", "EUC-JP", "UTF-8" and more. Some languages (e.g. Japanese language) supports multi-byte characters (where a single character is represented using several bytes). Some multi-byte characters may match the escape character. For Japanese language, some characters in "ShiftJIS" encoding match \ character, and bothering Web's CGI developers. It is important that the kernel string is not bothered by encoding problem. Linus said, "I really would expect that kernel strings don't have an encoding. They're just C strings: a NUL-terminated stream of bytes." http://lkml.org/lkml/2007/11/6/142 Yes. The kernel strings are just C strings. We are talking about how to store and carry "kernel strings" safely. If we store "kernel string" into policy file as-is, the "kernel string" will be interpreted differently depending on application's encoding settings. One application may interpret "kernel string" as "UTF-8", another application may interpret "kernel string" as "ShiftJIS". Therefore, we propose to represent strings using ASCII encoding. In this way, we are no longer bothered by encoding problems. We need to avoid information loss caused by display. It is difficult to input and display non-printable characters, but we have to be able to handle such characters because the kernel string is a C string. If we use only ASCII printable characters (from 0x21 to 0x7E) and space character (0x20) and new line character (0x0A), it is easy to input from keyboard and display on all terminals which is running Linux. Therefore, we propose to represent strings using only characters which value is one of "from 0x21 to 0x7E", "0x20", "0x0A". We need to consider ease of splitting strings from a line. If we use an approach which uses "\ " for representing a space character within a string, we have to count the string from the beginning to check whether this space character is accompanied with \ character or not. As a result, we cannot monotonically split a line using space character. If we use an approach which uses "\040" for representing a space character within a string, we can monotonically split a line using space character. If we use an approach which uses NUL character as a delimiter, we cannot use string manipulation functions for splitting strings from a line. Therefore, we propose that we represent space character as "\040". We need to avoid wrong designations (incorrect use of special characters). Not all users can understand and utilize POSIX's regular expressions correctly and perfectly. If a character acts as a wild card by default, the user will get unexpected result if that user didn't know the meaning of that character. Therefore, we propose that all characters but \ character act as a normal character and let the user add \ character to make a character act as a wild card. In this way, users needn't to know all wild card characters beforehand. They can learn when they encountered an unseen wild card character for their first time. ----- Part 4: supported wild card expressions ----- At this point, we have wild card expressions listed below. +-----------+--------------------------------------------------------------+ | Wild card | Meaning and example | +-----------+--------------------------------------------------------------+ | \* | More than or equals to 0 character other than '/'. | | | /var/log/samba/\* | +-----------+--------------------------------------------------------------+ | \@ | More than or equals to 0 character other than '/' or '.'. | | | /var/www/html/\@.html | +-----------+--------------------------------------------------------------+ | \? | 1 byte character other than '/'. | | | /tmp/mail.\?\?\?\?\?\? | +-----------+--------------------------------------------------------------+ | \$ | More than or equals to 1 decimal digit. | | | /proc/\$/cmdline | +-----------+--------------------------------------------------------------+ | \+ | 1 decimal digit. | | | /var/tmp/my_work.\+ | +-----------+--------------------------------------------------------------+ | \X | More than or equals to 1 hexadecimal digit. | | | /var/tmp/my-work.\X | +-----------+--------------------------------------------------------------+ | \x | 1 hexadecimal digit. | | | /tmp/my-work.\x | +-----------+--------------------------------------------------------------+ | \A | More than or equals to 1 alphabet character. | | | /var/log/my-work/\$-\A-\$.log | +-----------+--------------------------------------------------------------+ | \a | 1 alphabet character. | | | /home/users/\a/\*/public_html/\*.html | +-----------+--------------------------------------------------------------+ | \- | Pathname subtraction operator. | | | +---------------------+------------------------------------+ | | | | Example | Meaning | | | | +---------------------+------------------------------------+ | | | | /etc/\* | All files in /etc/ directory. | | | | +---------------------+------------------------------------+ | | | | /etc/\*\-\*shadow\* | /etc/\* other than /etc/\*shadow\* | | | | +---------------------+------------------------------------+ | | | | /\*\-proc\-sys/ | /\*/ other than /proc/ /sys/ | | | | +---------------------+------------------------------------+ | +-----------+--------------------------------------------------------------+ +----------------+---------------------------------------------------------+ | Representation | Meaning and example | +----------------+---------------------------------------------------------+ | \\ | backslash character itself. | +----------------+---------------------------------------------------------+ | \ooo | 1 byte character. | | | ooo is 001 <= ooo <= 040 || 177 <= ooo <= 377. | | | | | | \040 for space character. | | | \177 for del character. | | | | +----------------+---------------------------------------------------------+ ----- Part 5: Advantages ----- We can obtain extensibility. Since our proposed approach adds \ to a character to interpret as a wild card, we can introduce new wild card in future while maintaining backward compatibility. We can process monotonically. Since our proposed approach separates strings using a space character, we can split strings using existing string manipulation functions. We can reliably analyze access logs. It is guaranteed that a string doesn't contain space character (0x20) and new line character (0x0A). It is guaranteed that a string won't be converted by FTP and won't be damaged by a terminal's settings. It is guaranteed that a string won't be affected by encoding converters (except encodings which insert NUL character (e.g. UTF-16)). ----- Part 6: conclusion ----- TOMOYO Linux is using its own encoding with reasons described above. There is a disadvantage that we need to introduce a series of new string manipulation functions. But TOMOYO Linux's encoding is useful for all users (including audit and AppArmor) who want to perform pattern matching and safely exchange string information between the kernel and the userspace. -------------------- About policy interface -------------------- TOMOYO Linux creates the following files on securityfs (normally mounted on /sys/kernel/security) as interfaces between kernel and userspace. These files are for TOMOYO Linux management tools *only*, not for general programs. * profile * exception_policy * domain_policy * manager * meminfo * self_domain * version * .domain_status * .process_status ** /sys/kernel/security/tomoyo/profile ** This file is used to read or write profiles. "profile" means a running mode of process. A profile lists up functions and their modes in "$number-$variable=$value" format. The $number is profile number between 0 and 255. Each domain is assigned one profile. To assign profile to domains, use "ccs-setprofile" or "ccs-editpolicy" or "ccs-loadpolicy" commands. (Example) [root@tomoyo]# cat /sys/kernel/security/tomoyo/profile 0-COMMENT=-----Disabled Mode----- 0-MAC_FOR_FILE=disabled 0-MAX_ACCEPT_ENTRY=2048 0-TOMOYO_VERBOSE=disabled 1-COMMENT=-----Learning Mode----- 1-MAC_FOR_FILE=learning 1-MAX_ACCEPT_ENTRY=2048 1-TOMOYO_VERBOSE=disabled 2-COMMENT=-----Permissive Mode----- 2-MAC_FOR_FILE=permissive 2-MAX_ACCEPT_ENTRY=2048 2-TOMOYO_VERBOSE=enabled 3-COMMENT=-----Enforcing Mode----- 3-MAC_FOR_FILE=enforcing 3-MAX_ACCEPT_ENTRY=2048 3-TOMOYO_VERBOSE=enabled - MAC_FOR_FILE: Specifies access control level regarding file access requests. - MAX_ACCEPT_ENTRY: Limits the max number of ACL entries that are automatically appended during learning mode. Default is 2048. - TOMOYO_VERBOSE: Specifies whether to print domain policy violation messages or not. ** /sys/kernel/security/tomoyo/manager ** This file is used to read or append the list of programs or domains that can write to /sys/kernel/security/tomoyo interface. By default, only processes with both UID = 0 and EUID = 0 can modify policy via /sys/kernel/security/tomoyo interface. You can use keyword "manage_by_non_root" to allow policy modification by non root user. (Example) [root@tomoyo]# cat /sys/kernel/security/tomoyo/manager /usr/lib/ccs/loadpolicy /usr/lib/ccs/editpolicy /usr/lib/ccs/setlevel /usr/lib/ccs/setprofile /usr/lib/ccs/ld-watch /usr/lib/ccs/ccs-queryd ** /sys/kernel/security/tomoyo/exception_policy ** This file is used to read and write system global settings. Each line has a directive and operand pair. Directives are listed below. - initialize_domain: To initialize domain transition when specific program is executed, use initialize_domain directive. * initialize_domain "program" from "domain" * initialize_domain "program" from "the last program part of domain" * initialize_domain "program" If the part "from" and after is not given, the entry is applied to all domain. If the "domain" doesn't start with "", the entry is applied to all domain whose domainname ends with "the last program part of domain". This directive is intended to aggregate domain transitions for daemon program and program that are invoked by the kernel on demand, by transiting to different domain. - keep_domain To prevent domain transition when program is executed from specific domain, use keep_domain directive. * keep_domain "program" from "domain" * keep_domain "program" from "the last program part of domain" * keep_domain "domain" * keep_domain "the last program part of domain" If the part "from" and before is not given, this entry is applied to all program. If the "domain" doesn't start with "", the entry is applied to all domain whose domainname ends with "the last program part of domain". This directive is intended to reduce total number of domains and memory usage by suppressing unneeded domain transitions. To declare domain keepers, use keep_domain directive followed by domain definition. Any process that belongs to any domain declared with this directive, the process stays at the same domain unless any program registered with initialize_domain directive is executed. In order to control domain transition in detail, you can use no_keep_domain/no_initialize_domain keywrods. - alias: To allow executing programs using the name of symbolic links, use alias keyword followed by dereferenced pathname and reference pathname. For example, /sbin/pidof is a symbolic link to /sbin/killall5 . In normal case, if /sbin/pidof is executed, the domain is defined as if /sbin/killall5 is executed. By specifying "alias /sbin/killall5 /sbin/pidof", you can run /sbin/pidof in the domain for /sbin/pidof . (Example) alias /sbin/killall5 /sbin/pidof - allow_read: To grant unconditionally readable permissions, use allow_read keyword followed by canonicalized file. This keyword is intended to reduce size of domain policy by granting read access to library files such as GLIBC and locale files. Exception is, if ignore_global_allow_read keyword is given to a domain, entries specified by this keyword are ignored. (Example) allow_read /lib/libc-2.5.so - file_pattern: To declare pathname pattern, use file_pattern keyword followed by pathname pattern. The pathname pattern must be a canonicalized Pathname. This keyword is not applicable to neither granting execute permissions nor domain definitions. For example, canonicalized pathname that contains a process ID (i.e. /proc/PID/ files) needs to be grouped in order to make access control work well. (Example) file_pattern /proc/\$/cmdline - path_group To declare pathname group, use path_group keyword followed by name of the group and pathname pattern. For example, if you want to group all files under home directory, you can define path_group HOME-DIR-FILE /home/\*/\* path_group HOME-DIR-FILE /home/\*/\*/\* path_group HOME-DIR-FILE /home/\*/\*/\*/\* in the exception policy and use like allow_read @HOME-DIR-FILE to grant file access permission. - deny_rewrite: To deny overwriting already written contents of file (such as log files) by default, use deny_rewrite keyword followed by pathname pattern. Files whose pathname match the patterns are not permitted to open for writing without append mode or truncate unless the pathnames are explicitly granted using allow_rewrite keyword in domain policy. (Example) deny_rewrite /var/log/\* - aggregator To deal multiple programs as a single program, use aggregator keyword followed by name of original program and aggregated program. This keyword is intended to aggregate similar programs. For example, /usr/bin/tac and /bin/cat are similar. By specifying "aggregator /usr/bin/tac /bin/cat", you can run /usr/bin/tac in the domain for /bin/cat . For example, /usr/sbin/logrotate for Fedora Core 3 generates programs like /tmp/logrotate.\?\?\?\?\?\? and run them, but TOMOYO Linux doesn't allow using patterns for granting execute permission and defining domains. By specifying "aggregator /tmp/logrotate.\?\?\?\?\?\? /tmp/logrotate.tmp", you can run /tmp/logrotate.\?\?\?\?\?\? as if /tmp/logrotate.tmp is running. ** /sys/kernel/security/tomoyo/domain_policy ** This file contains definition of all domains and permissions that are granted to each domain. Lines from the next line to a domain definition ( any lines starting with "") to the previous line to the next domain definitions are interpreted as access permissions for that domain. ** /sys/kernel/security/tomoyo/meminfo ** This file is to show the total RAM used to keep policy in the kernel by TOMOYO Linux in bytes. (Example) [root@tomoyo]# cat /sys/kernel/security/tomoyo/meminfo Shared: 61440 Private: 69632 Dynamic: 768 Total: 131840 You can set memory quota by writing to this file. (Example) [root@tomoyo]# echo Shared: 2097152 > /sys/kernel/security/tomoyo/meminfo [root@tomoyo]# echo Private: 2097152 > /sys/kernel/security/tomoyo/meminfo ** /sys/kernel/security/tomoyo/self_domain ** This file is to show the name of domain the caller process belongs to. (Example) [root@etch]# cat /sys/kernel/security/tomoyo/self_domain /usr/sbin/sshd /bin/zsh /bin/cat ** /sys/kernel/security/tomoyo/version ** This file is used for getting TOMOYO Linux's version. (Example) [root@etch]# cat /sys/kernel/security/tomoyo/version 2.2.0-pre ** /sys/kernel/security/tomoyo/.domain_status ** This is a view (of a DBMS) that contains only profile number and domainnames of domain so that "ccs-setprofile" command can do line-oriented processing easily. ** /sys/kernel/security/tomoyo/.process_status ** This file is used by "ccs-ccstree" command to show "list of processes currently running" and "domains which each process belongs to" and "profile number which the domain is currently assigned" like "pstree" command. This file is writable by programs that aren't registered as policy manager. Signed-off-by: Kentaro Takeda Signed-off-by: Tetsuo Handa Signed-off-by: Toshiharu Harada Signed-off-by: James Morris --- security/tomoyo/common.c | 2202 ++++++++++++++++++++++++++++++++++++++++++++++ security/tomoyo/common.h | 359 ++++++++ 2 files changed, 2561 insertions(+) create mode 100644 security/tomoyo/common.c create mode 100644 security/tomoyo/common.h (limited to 'security') diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c new file mode 100644 index 00000000000..8bedfb1992e --- /dev/null +++ b/security/tomoyo/common.c @@ -0,0 +1,2202 @@ +/* + * security/tomoyo/common.c + * + * Common functions for TOMOYO. + * + * Copyright (C) 2005-2009 NTT DATA CORPORATION + * + * Version: 2.2.0-pre 2009/02/01 + * + */ + +#include +#include +#include +#include "realpath.h" +#include "common.h" +#include "tomoyo.h" + +/* Has loading policy done? */ +bool tomoyo_policy_loaded; + +/* String table for functionality that takes 4 modes. */ +static const char *tomoyo_mode_4[4] = { + "disabled", "learning", "permissive", "enforcing" +}; +/* String table for functionality that takes 2 modes. */ +static const char *tomoyo_mode_2[4] = { + "disabled", "enabled", "enabled", "enabled" +}; + +/* Table for profile. */ +static struct { + const char *keyword; + unsigned int current_value; + const unsigned int max_value; +} tomoyo_control_array[TOMOYO_MAX_CONTROL_INDEX] = { + [TOMOYO_MAC_FOR_FILE] = { "MAC_FOR_FILE", 0, 3 }, + [TOMOYO_MAX_ACCEPT_ENTRY] = { "MAX_ACCEPT_ENTRY", 2048, INT_MAX }, + [TOMOYO_VERBOSE] = { "TOMOYO_VERBOSE", 1, 1 }, +}; + +/* Profile table. Memory is allocated as needed. */ +static struct tomoyo_profile { + unsigned int value[TOMOYO_MAX_CONTROL_INDEX]; + const struct tomoyo_path_info *comment; +} *tomoyo_profile_ptr[TOMOYO_MAX_PROFILES]; + +/* Permit policy management by non-root user? */ +static bool tomoyo_manage_by_non_root; + +/* Utility functions. */ + +/* Open operation for /sys/kernel/security/tomoyo/ interface. */ +static int tomoyo_open_control(const u8 type, struct file *file); +/* Close /sys/kernel/security/tomoyo/ interface. */ +static int tomoyo_close_control(struct file *file); +/* Read operation for /sys/kernel/security/tomoyo/ interface. */ +static int tomoyo_read_control(struct file *file, char __user *buffer, + const int buffer_len); +/* Write operation for /sys/kernel/security/tomoyo/ interface. */ +static int tomoyo_write_control(struct file *file, const char __user *buffer, + const int buffer_len); + +/** + * tomoyo_is_byte_range - Check whether the string isa \ooo style octal value. + * + * @str: Pointer to the string. + * + * Returns true if @str is a \ooo style octal value, false otherwise. + * + * TOMOYO uses \ooo style representation for 0x01 - 0x20 and 0x7F - 0xFF. + * This function verifies that \ooo is in valid range. + */ +static inline bool tomoyo_is_byte_range(const char *str) +{ + return *str >= '0' && *str++ <= '3' && + *str >= '0' && *str++ <= '7' && + *str >= '0' && *str <= '7'; +} + +/** + * tomoyo_is_alphabet_char - Check whether the character is an alphabet. + * + * @c: The character to check. + * + * Returns true if @c is an alphabet character, false otherwise. + */ +static inline bool tomoyo_is_alphabet_char(const char c) +{ + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); +} + +/** + * tomoyo_make_byte - Make byte value from three octal characters. + * + * @c1: The first character. + * @c2: The second character. + * @c3: The third character. + * + * Returns byte value. + */ +static inline u8 tomoyo_make_byte(const u8 c1, const u8 c2, const u8 c3) +{ + return ((c1 - '0') << 6) + ((c2 - '0') << 3) + (c3 - '0'); +} + +/** + * tomoyo_str_starts - Check whether the given string starts with the given keyword. + * + * @src: Pointer to pointer to the string. + * @find: Pointer to the keyword. + * + * Returns true if @src starts with @find, false otherwise. + * + * The @src is updated to point the first character after the @find + * if @src starts with @find. + */ +static bool tomoyo_str_starts(char **src, const char *find) +{ + const int len = strlen(find); + char *tmp = *src; + + if (strncmp(tmp, find, len)) + return false; + tmp += len; + *src = tmp; + return true; +} + +/** + * tomoyo_normalize_line - Format string. + * + * @buffer: The line to normalize. + * + * Leading and trailing whitespaces are removed. + * Multiple whitespaces are packed into single space. + * + * Returns nothing. + */ +static void tomoyo_normalize_line(unsigned char *buffer) +{ + unsigned char *sp = buffer; + unsigned char *dp = buffer; + bool first = true; + + while (tomoyo_is_invalid(*sp)) + sp++; + while (*sp) { + if (!first) + *dp++ = ' '; + first = false; + while (tomoyo_is_valid(*sp)) + *dp++ = *sp++; + while (tomoyo_is_invalid(*sp)) + sp++; + } + *dp = '\0'; +} + +/** + * tomoyo_is_correct_path - Validate a pathname. + * @filename: The pathname to check. + * @start_type: Should the pathname start with '/'? + * 1 = must / -1 = must not / 0 = don't care + * @pattern_type: Can the pathname contain a wildcard? + * 1 = must / -1 = must not / 0 = don't care + * @end_type: Should the pathname end with '/'? + * 1 = must / -1 = must not / 0 = don't care + * @function: The name of function calling me. + * + * Check whether the given filename follows the naming rules. + * Returns true if @filename follows the naming rules, false otherwise. + */ +bool tomoyo_is_correct_path(const char *filename, const s8 start_type, + const s8 pattern_type, const s8 end_type, + const char *function) +{ + bool contains_pattern = false; + unsigned char c; + unsigned char d; + unsigned char e; + const char *original_filename = filename; + + if (!filename) + goto out; + c = *filename; + if (start_type == 1) { /* Must start with '/' */ + if (c != '/') + goto out; + } else if (start_type == -1) { /* Must not start with '/' */ + if (c == '/') + goto out; + } + if (c) + c = *(filename + strlen(filename) - 1); + if (end_type == 1) { /* Must end with '/' */ + if (c != '/') + goto out; + } else if (end_type == -1) { /* Must not end with '/' */ + if (c == '/') + goto out; + } + while ((c = *filename++) != '\0') { + if (c == '\\') { + switch ((c = *filename++)) { + case '\\': /* "\\" */ + continue; + case '$': /* "\$" */ + case '+': /* "\+" */ + case '?': /* "\?" */ + case '*': /* "\*" */ + case '@': /* "\@" */ + case 'x': /* "\x" */ + case 'X': /* "\X" */ + case 'a': /* "\a" */ + case 'A': /* "\A" */ + case '-': /* "\-" */ + if (pattern_type == -1) + break; /* Must not contain pattern */ + contains_pattern = true; + continue; + case '0': /* "\ooo" */ + case '1': + case '2': + case '3': + d = *filename++; + if (d < '0' || d > '7') + break; + e = *filename++; + if (e < '0' || e > '7') + break; + c = tomoyo_make_byte(c, d, e); + if (tomoyo_is_invalid(c)) + continue; /* pattern is not \000 */ + } + goto out; + } else if (tomoyo_is_invalid(c)) { + goto out; + } + } + if (pattern_type == 1) { /* Must contain pattern */ + if (!contains_pattern) + goto out; + } + return true; + out: + printk(KERN_DEBUG "%s: Invalid pathname '%s'\n", function, + original_filename); + return false; +} + +/** + * tomoyo_is_correct_domain - Check whether the given domainname follows the naming rules. + * @domainname: The domainname to check. + * @function: The name of function calling me. + * + * Returns true if @domainname follows the naming rules, false otherwise. + */ +bool tomoyo_is_correct_domain(const unsigned char *domainname, + const char *function) +{ + unsigned char c; + unsigned char d; + unsigned char e; + const char *org_domainname = domainname; + + if (!domainname || strncmp(domainname, TOMOYO_ROOT_NAME, + TOMOYO_ROOT_NAME_LEN)) + goto out; + domainname += TOMOYO_ROOT_NAME_LEN; + if (!*domainname) + return true; + do { + if (*domainname++ != ' ') + goto out; + if (*domainname++ != '/') + goto out; + while ((c = *domainname) != '\0' && c != ' ') { + domainname++; + if (c == '\\') { + c = *domainname++; + switch ((c)) { + case '\\': /* "\\" */ + continue; + case '0': /* "\ooo" */ + case '1': + case '2': + case '3': + d = *domainname++; + if (d < '0' || d > '7') + break; + e = *domainname++; + if (e < '0' || e > '7') + break; + c = tomoyo_make_byte(c, d, e); + if (tomoyo_is_invalid(c)) + /* pattern is not \000 */ + continue; + } + goto out; + } else if (tomoyo_is_invalid(c)) { + goto out; + } + } + } while (*domainname); + return true; + out: + printk(KERN_DEBUG "%s: Invalid domainname '%s'\n", function, + org_domainname); + return false; +} + +/** + * tomoyo_is_domain_def - Check whether the given token can be a domainname. + * + * @buffer: The token to check. + * + * Returns true if @buffer possibly be a domainname, false otherwise. + */ +bool tomoyo_is_domain_def(const unsigned char *buffer) +{ + return !strncmp(buffer, TOMOYO_ROOT_NAME, TOMOYO_ROOT_NAME_LEN); +} + +/** + * tomoyo_find_domain - Find a domain by the given name. + * + * @domainname: The domainname to find. + * + * Caller must call down_read(&tomoyo_domain_list_lock); or + * down_write(&tomoyo_domain_list_lock); . + * + * Returns pointer to "struct tomoyo_domain_info" if found, NULL otherwise. + */ +struct tomoyo_domain_info *tomoyo_find_domain(const char *domainname) +{ + struct tomoyo_domain_info *domain; + struct tomoyo_path_info name; + + name.name = domainname; + tomoyo_fill_path_info(&name); + list_for_each_entry(domain, &tomoyo_domain_list, list) { + if (!domain->is_deleted && + !tomoyo_pathcmp(&name, domain->domainname)) + return domain; + } + return NULL; +} + +/** + * tomoyo_path_depth - Evaluate the number of '/' in a string. + * + * @pathname: The string to evaluate. + * + * Returns path depth of the string. + * + * I score 2 for each of the '/' in the @pathname + * and score 1 if the @pathname ends with '/'. + */ +static int tomoyo_path_depth(const char *pathname) +{ + int i = 0; + + if (pathname) { + const char *ep = pathname + strlen(pathname); + if (pathname < ep--) { + if (*ep != '/') + i++; + while (pathname <= ep) + if (*ep-- == '/') + i += 2; + } + } + return i; +} + +/** + * tomoyo_const_part_length - Evaluate the initial length without a pattern in a token. + * + * @filename: The string to evaluate. + * + * Returns the initial length without a pattern in @filename. + */ +static int tomoyo_const_part_length(const char *filename) +{ + char c; + int len = 0; + + if (!filename) + return 0; + while ((c = *filename++) != '\0') { + if (c != '\\') { + len++; + continue; + } + c = *filename++; + switch (c) { + case '\\': /* "\\" */ + len += 2; + continue; + case '0': /* "\ooo" */ + case '1': + case '2': + case '3': + c = *filename++; + if (c < '0' || c > '7') + break; + c = *filename++; + if (c < '0' || c > '7') + break; + len += 4; + continue; + } + break; + } + return len; +} + +/** + * tomoyo_fill_path_info - Fill in "struct tomoyo_path_info" members. + * + * @ptr: Pointer to "struct tomoyo_path_info" to fill in. + * + * The caller sets "struct tomoyo_path_info"->name. + */ +void tomoyo_fill_path_info(struct tomoyo_path_info *ptr) +{ + const char *name = ptr->name; + const int len = strlen(name); + + ptr->total_len = len; + ptr->const_len = tomoyo_const_part_length(name); + ptr->is_dir = len && (name[len - 1] == '/'); + ptr->is_patterned = (ptr->const_len < len); + ptr->hash = full_name_hash(name, len); + ptr->depth = tomoyo_path_depth(name); +} + +/** + * tomoyo_file_matches_to_pattern2 - Pattern matching without '/' character + * and "\-" pattern. + * + * @filename: The start of string to check. + * @filename_end: The end of string to check. + * @pattern: The start of pattern to compare. + * @pattern_end: The end of pattern to compare. + * + * Returns true if @filename matches @pattern, false otherwise. + */ +static bool tomoyo_file_matches_to_pattern2(const char *filename, + const char *filename_end, + const char *pattern, + const char *pattern_end) +{ + while (filename < filename_end && pattern < pattern_end) { + char c; + if (*pattern != '\\') { + if (*filename++ != *pattern++) + return false; + continue; + } + c = *filename; + pattern++; + switch (*pattern) { + int i; + int j; + case '?': + if (c == '/') { + return false; + } else if (c == '\\') { + if (filename[1] == '\\') + filename++; + else if (tomoyo_is_byte_range(filename + 1)) + filename += 3; + else + return false; + } + break; + case '\\': + if (c != '\\') + return false; + if (*++filename != '\\') + return false; + break; + case '+': + if (!isdigit(c)) + return false; + break; + case 'x': + if (!isxdigit(c)) + return false; + break; + case 'a': + if (!tomoyo_is_alphabet_char(c)) + return false; + break; + case '0': + case '1': + case '2': + case '3': + if (c == '\\' && tomoyo_is_byte_range(filename + 1) + && strncmp(filename + 1, pattern, 3) == 0) { + filename += 3; + pattern += 2; + break; + } + return false; /* Not matched. */ + case '*': + case '@': + for (i = 0; i <= filename_end - filename; i++) { + if (tomoyo_file_matches_to_pattern2( + filename + i, filename_end, + pattern + 1, pattern_end)) + return true; + c = filename[i]; + if (c == '.' && *pattern == '@') + break; + if (c != '\\') + continue; + if (filename[i + 1] == '\\') + i++; + else if (tomoyo_is_byte_range(filename + i + 1)) + i += 3; + else + break; /* Bad pattern. */ + } + return false; /* Not matched. */ + default: + j = 0; + c = *pattern; + if (c == '$') { + while (isdigit(filename[j])) + j++; + } else if (c == 'X') { + while (isxdigit(filename[j])) + j++; + } else if (c == 'A') { + while (tomoyo_is_alphabet_char(filename[j])) + j++; + } + for (i = 1; i <= j; i++) { + if (tomoyo_file_matches_to_pattern2( + filename + i, filename_end, + pattern + 1, pattern_end)) + return true; + } + return false; /* Not matched or bad pattern. */ + } + filename++; + pattern++; + } + while (*pattern == '\\' && + (*(pattern + 1) == '*' || *(pattern + 1) == '@')) + pattern += 2; + return filename == filename_end && pattern == pattern_end; +} + +/** + * tomoyo_file_matches_to_pattern - Pattern matching without without '/' character. + * + * @filename: The start of string to check. + * @filename_end: The end of string to check. + * @pattern: The start of pattern to compare. + * @pattern_end: The end of pattern to compare. + * + * Returns true if @filename matches @pattern, false otherwise. + */ +static bool tomoyo_file_matches_to_pattern(const char *filename, + const char *filename_end, + const char *pattern, + const char *pattern_end) +{ + const char *pattern_start = pattern; + bool first = true; + bool result; + + while (pattern < pattern_end - 1) { + /* Split at "\-" pattern. */ + if (*pattern++ != '\\' || *pattern++ != '-') + continue; + result = tomoyo_file_matches_to_pattern2(filename, + filename_end, + pattern_start, + pattern - 2); + if (first) + result = !result; + if (result) + return false; + first = false; + pattern_start = pattern; + } + result = tomoyo_file_matches_to_pattern2(filename, filename_end, + pattern_start, pattern_end); + return first ? result : !result; +} + +/** + * tomoyo_path_matches_pattern - Check whether the given filename matches the given pattern. + * @filename: The filename to check. + * @pattern: The pattern to compare. + * + * Returns true if matches, false otherwise. + * + * The following patterns are available. + * \\ \ itself. + * \ooo Octal representation of a byte. + * \* More than or equals to 0 character other than '/'. + * \@ More than or equals to 0 character other than '/' or '.'. + * \? 1 byte character other than '/'. + * \$ More than or equals to 1 decimal digit. + * \+ 1 decimal digit. + * \X More than or equals to 1 hexadecimal digit. + * \x 1 hexadecimal digit. + * \A More than or equals to 1 alphabet character. + * \a 1 alphabet character. + * \- Subtraction operator. + */ +bool tomoyo_path_matches_pattern(const struct tomoyo_path_info *filename, + const struct tomoyo_path_info *pattern) +{ + /* + if (!filename || !pattern) + return false; + */ + const char *f = filename->name; + const char *p = pattern->name; + const int len = pattern->const_len; + + /* If @pattern doesn't contain pattern, I can use strcmp(). */ + if (!pattern->is_patterned) + return !tomoyo_pathcmp(filename, pattern); + /* Dont compare if the number of '/' differs. */ + if (filename->depth != pattern->depth) + return false; + /* Compare the initial length without patterns. */ + if (strncmp(f, p, len)) + return false; + f += len; + p += len; + /* Main loop. Compare each directory component. */ + while (*f && *p) { + const char *f_delimiter = strchr(f, '/'); + const char *p_delimiter = strchr(p, '/'); + if (!f_delimiter) + f_delimiter = f + strlen(f); + if (!p_delimiter) + p_delimiter = p + strlen(p); + if (!tomoyo_file_matches_to_pattern(f, f_delimiter, + p, p_delimiter)) + return false; + f = f_delimiter; + if (*f) + f++; + p = p_delimiter; + if (*p) + p++; + } + /* Ignore trailing "\*" and "\@" in @pattern. */ + while (*p == '\\' && + (*(p + 1) == '*' || *(p + 1) == '@')) + p += 2; + return !*f && !*p; +} + +/** + * tomoyo_io_printf - Transactional printf() to "struct tomoyo_io_buffer" structure. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @fmt: The printf()'s format string, followed by parameters. + * + * Returns true if output was written, false otherwise. + * + * The snprintf() will truncate, but tomoyo_io_printf() won't. + */ +bool tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt, ...) +{ + va_list args; + int len; + int pos = head->read_avail; + int size = head->readbuf_size - pos; + + if (size <= 0) + return false; + va_start(args, fmt); + len = vsnprintf(head->read_buf + pos, size, fmt, args); + va_end(args); + if (pos + len >= head->readbuf_size) + return false; + head->read_avail += len; + return true; +} + +/** + * tomoyo_get_exe - Get tomoyo_realpath() of current process. + * + * Returns the tomoyo_realpath() of current process on success, NULL otherwise. + * + * This function uses tomoyo_alloc(), so the caller must call tomoyo_free() + * if this function didn't return NULL. + */ +static const char *tomoyo_get_exe(void) +{ + struct mm_struct *mm = current->mm; + struct vm_area_struct *vma; + const char *cp = NULL; + + if (!mm) + return NULL; + down_read(&mm->mmap_sem); + for (vma = mm->mmap; vma; vma = vma->vm_next) { + if ((vma->vm_flags & VM_EXECUTABLE) && vma->vm_file) { + cp = tomoyo_realpath_from_path(&vma->vm_file->f_path); + break; + } + } + up_read(&mm->mmap_sem); + return cp; +} + +/** + * tomoyo_get_msg - Get warning message. + * + * @is_enforce: Is it enforcing mode? + * + * Returns "ERROR" or "WARNING". + */ +const char *tomoyo_get_msg(const bool is_enforce) +{ + if (is_enforce) + return "ERROR"; + else + return "WARNING"; +} + +/** + * tomoyo_check_flags - Check mode for specified functionality. + * + * @domain: Pointer to "struct tomoyo_domain_info". + * @index: The functionality to check mode. + * + * TOMOYO checks only process context. + * This code disables TOMOYO's enforcement in case the function is called from + * interrupt context. + */ +unsigned int tomoyo_check_flags(const struct tomoyo_domain_info *domain, + const u8 index) +{ + const u8 profile = domain->profile; + + if (WARN_ON(in_interrupt())) + return 0; + return tomoyo_policy_loaded && index < TOMOYO_MAX_CONTROL_INDEX +#if TOMOYO_MAX_PROFILES != 256 + && profile < TOMOYO_MAX_PROFILES +#endif + && tomoyo_profile_ptr[profile] ? + tomoyo_profile_ptr[profile]->value[index] : 0; +} + +/** + * tomoyo_verbose_mode - Check whether TOMOYO is verbose mode. + * + * @domain: Pointer to "struct tomoyo_domain_info". + * + * Returns true if domain policy violation warning should be printed to + * console. + */ +bool tomoyo_verbose_mode(const struct tomoyo_domain_info *domain) +{ + return tomoyo_check_flags(domain, TOMOYO_VERBOSE) != 0; +} + +/** + * tomoyo_domain_quota_is_ok - Check for domain's quota. + * + * @domain: Pointer to "struct tomoyo_domain_info". + * + * Returns true if the domain is not exceeded quota, false otherwise. + */ +bool tomoyo_domain_quota_is_ok(struct tomoyo_domain_info * const domain) +{ + unsigned int count = 0; + struct tomoyo_acl_info *ptr; + + if (!domain) + return true; + down_read(&tomoyo_domain_acl_info_list_lock); + list_for_each_entry(ptr, &domain->acl_info_list, list) { + if (ptr->type & TOMOYO_ACL_DELETED) + continue; + switch (tomoyo_acl_type2(ptr)) { + struct tomoyo_single_path_acl_record *acl1; + struct tomoyo_double_path_acl_record *acl2; + u16 perm; + case TOMOYO_TYPE_SINGLE_PATH_ACL: + acl1 = container_of(ptr, + struct tomoyo_single_path_acl_record, + head); + perm = acl1->perm; + if (perm & (1 << TOMOYO_TYPE_EXECUTE_ACL)) + count++; + if (perm & + ((1 << TOMOYO_TYPE_READ_ACL) | + (1 << TOMOYO_TYPE_WRITE_ACL))) + count++; + if (perm & (1 << TOMOYO_TYPE_CREATE_ACL)) + count++; + if (perm & (1 << TOMOYO_TYPE_UNLINK_ACL)) + count++; + if (perm & (1 << TOMOYO_TYPE_MKDIR_ACL)) + count++; + if (perm & (1 << TOMOYO_TYPE_RMDIR_ACL)) + count++; + if (perm & (1 << TOMOYO_TYPE_MKFIFO_ACL)) + count++; + if (perm & (1 << TOMOYO_TYPE_MKSOCK_ACL)) + count++; + if (perm & (1 << TOMOYO_TYPE_MKBLOCK_ACL)) + count++; + if (perm & (1 << TOMOYO_TYPE_MKCHAR_ACL)) + count++; + if (perm & (1 << TOMOYO_TYPE_TRUNCATE_ACL)) + count++; + if (perm & (1 << TOMOYO_TYPE_SYMLINK_ACL)) + count++; + if (perm & (1 << TOMOYO_TYPE_REWRITE_ACL)) + count++; + break; + case TOMOYO_TYPE_DOUBLE_PATH_ACL: + acl2 = container_of(ptr, + struct tomoyo_double_path_acl_record, + head); + perm = acl2->perm; + if (perm & (1 << TOMOYO_TYPE_LINK_ACL)) + count++; + if (perm & (1 << TOMOYO_TYPE_RENAME_ACL)) + count++; + break; + } + } + up_read(&tomoyo_domain_acl_info_list_lock); + if (count < tomoyo_check_flags(domain, TOMOYO_MAX_ACCEPT_ENTRY)) + return true; + if (!domain->quota_warned) { + domain->quota_warned = true; + printk(KERN_WARNING "TOMOYO-WARNING: " + "Domain '%s' has so many ACLs to hold. " + "Stopped learning mode.\n", domain->domainname->name); + } + return false; +} + +/** + * tomoyo_find_or_assign_new_profile - Create a new profile. + * + * @profile: Profile number to create. + * + * Returns pointer to "struct tomoyo_profile" on success, NULL otherwise. + */ +static struct tomoyo_profile *tomoyo_find_or_assign_new_profile(const unsigned + int profile) +{ + static DEFINE_MUTEX(lock); + struct tomoyo_profile *ptr = NULL; + int i; + + if (profile >= TOMOYO_MAX_PROFILES) + return NULL; + /***** EXCLUSIVE SECTION START *****/ + mutex_lock(&lock); + ptr = tomoyo_profile_ptr[profile]; + if (ptr) + goto ok; + ptr = tomoyo_alloc_element(sizeof(*ptr)); + if (!ptr) + goto ok; + for (i = 0; i < TOMOYO_MAX_CONTROL_INDEX; i++) + ptr->value[i] = tomoyo_control_array[i].current_value; + mb(); /* Avoid out-of-order execution. */ + tomoyo_profile_ptr[profile] = ptr; + ok: + mutex_unlock(&lock); + /***** EXCLUSIVE SECTION END *****/ + return ptr; +} + +/** + * tomoyo_write_profile - Write to profile table. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_write_profile(struct tomoyo_io_buffer *head) +{ + char *data = head->write_buf; + unsigned int i; + unsigned int value; + char *cp; + struct tomoyo_profile *profile; + unsigned long num; + + cp = strchr(data, '-'); + if (cp) + *cp = '\0'; + if (strict_strtoul(data, 10, &num)) + return -EINVAL; + if (cp) + data = cp + 1; + profile = tomoyo_find_or_assign_new_profile(num); + if (!profile) + return -EINVAL; + cp = strchr(data, '='); + if (!cp) + return -EINVAL; + *cp = '\0'; + if (!strcmp(data, "COMMENT")) { + profile->comment = tomoyo_save_name(cp + 1); + return 0; + } + for (i = 0; i < TOMOYO_MAX_CONTROL_INDEX; i++) { + if (strcmp(data, tomoyo_control_array[i].keyword)) + continue; + if (sscanf(cp + 1, "%u", &value) != 1) { + int j; + const char **modes; + switch (i) { + case TOMOYO_VERBOSE: + modes = tomoyo_mode_2; + break; + default: + modes = tomoyo_mode_4; + break; + } + for (j = 0; j < 4; j++) { + if (strcmp(cp + 1, modes[j])) + continue; + value = j; + break; + } + if (j == 4) + return -EINVAL; + } else if (value > tomoyo_control_array[i].max_value) { + value = tomoyo_control_array[i].max_value; + } + profile->value[i] = value; + return 0; + } + return -EINVAL; +} + +/** + * tomoyo_read_profile - Read from profile table. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0. + */ +static int tomoyo_read_profile(struct tomoyo_io_buffer *head) +{ + static const int total = TOMOYO_MAX_CONTROL_INDEX + 1; + int step; + + if (head->read_eof) + return 0; + for (step = head->read_step; step < TOMOYO_MAX_PROFILES * total; + step++) { + const u8 index = step / total; + u8 type = step % total; + const struct tomoyo_profile *profile + = tomoyo_profile_ptr[index]; + head->read_step = step; + if (!profile) + continue; + if (!type) { /* Print profile' comment tag. */ + if (!tomoyo_io_printf(head, "%u-COMMENT=%s\n", + index, profile->comment ? + profile->comment->name : "")) + break; + continue; + } + type--; + if (type < TOMOYO_MAX_CONTROL_INDEX) { + const unsigned int value = profile->value[type]; + const char **modes = NULL; + const char *keyword + = tomoyo_control_array[type].keyword; + switch (tomoyo_control_array[type].max_value) { + case 3: + modes = tomoyo_mode_4; + break; + case 1: + modes = tomoyo_mode_2; + break; + } + if (modes) { + if (!tomoyo_io_printf(head, "%u-%s=%s\n", index, + keyword, modes[value])) + break; + } else { + if (!tomoyo_io_printf(head, "%u-%s=%u\n", index, + keyword, value)) + break; + } + } + } + if (step == TOMOYO_MAX_PROFILES * total) + head->read_eof = true; + return 0; +} + +/* Structure for policy manager. */ +struct tomoyo_policy_manager_entry { + struct list_head list; + /* A path to program or a domainname. */ + const struct tomoyo_path_info *manager; + bool is_domain; /* True if manager is a domainname. */ + bool is_deleted; /* True if this entry is deleted. */ +}; + +/* The list for "struct tomoyo_policy_manager_entry". */ +static LIST_HEAD(tomoyo_policy_manager_list); +static DECLARE_RWSEM(tomoyo_policy_manager_list_lock); + +/** + * tomoyo_update_manager_entry - Add a manager entry. + * + * @manager: The path to manager or the domainnamme. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_update_manager_entry(const char *manager, + const bool is_delete) +{ + struct tomoyo_policy_manager_entry *new_entry; + struct tomoyo_policy_manager_entry *ptr; + const struct tomoyo_path_info *saved_manager; + int error = -ENOMEM; + bool is_domain = false; + + if (tomoyo_is_domain_def(manager)) { + if (!tomoyo_is_correct_domain(manager, __func__)) + return -EINVAL; + is_domain = true; + } else { + if (!tomoyo_is_correct_path(manager, 1, -1, -1, __func__)) + return -EINVAL; + } + saved_manager = tomoyo_save_name(manager); + if (!saved_manager) + return -ENOMEM; + /***** EXCLUSIVE SECTION START *****/ + down_write(&tomoyo_policy_manager_list_lock); + list_for_each_entry(ptr, &tomoyo_policy_manager_list, list) { + if (ptr->manager != saved_manager) + continue; + ptr->is_deleted = is_delete; + error = 0; + goto out; + } + if (is_delete) { + error = -ENOENT; + goto out; + } + new_entry = tomoyo_alloc_element(sizeof(*new_entry)); + if (!new_entry) + goto out; + new_entry->manager = saved_manager; + new_entry->is_domain = is_domain; + list_add_tail(&new_entry->list, &tomoyo_policy_manager_list); + error = 0; + out: + up_write(&tomoyo_policy_manager_list_lock); + /***** EXCLUSIVE SECTION END *****/ + return error; +} + +/** + * tomoyo_write_manager_policy - Write manager policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_write_manager_policy(struct tomoyo_io_buffer *head) +{ + char *data = head->write_buf; + bool is_delete = tomoyo_str_starts(&data, TOMOYO_KEYWORD_DELETE); + + if (!strcmp(data, "manage_by_non_root")) { + tomoyo_manage_by_non_root = !is_delete; + return 0; + } + return tomoyo_update_manager_entry(data, is_delete); +} + +/** + * tomoyo_read_manager_policy - Read manager policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0. + */ +static int tomoyo_read_manager_policy(struct tomoyo_io_buffer *head) +{ + struct list_head *pos; + bool done = true; + + if (head->read_eof) + return 0; + down_read(&tomoyo_policy_manager_list_lock); + list_for_each_cookie(pos, head->read_var2, + &tomoyo_policy_manager_list) { + struct tomoyo_policy_manager_entry *ptr; + ptr = list_entry(pos, struct tomoyo_policy_manager_entry, + list); + if (ptr->is_deleted) + continue; + if (!tomoyo_io_printf(head, "%s\n", ptr->manager->name)) { + done = false; + break; + } + } + up_read(&tomoyo_policy_manager_list_lock); + head->read_eof = done; + return 0; +} + +/** + * tomoyo_is_policy_manager - Check whether the current process is a policy manager. + * + * Returns true if the current process is permitted to modify policy + * via /sys/kernel/security/tomoyo/ interface. + */ +static bool tomoyo_is_policy_manager(void) +{ + struct tomoyo_policy_manager_entry *ptr; + const char *exe; + const struct task_struct *task = current; + const struct tomoyo_path_info *domainname = tomoyo_domain()->domainname; + bool found = false; + + if (!tomoyo_policy_loaded) + return true; + if (!tomoyo_manage_by_non_root && (task->cred->uid || task->cred->euid)) + return false; + down_read(&tomoyo_policy_manager_list_lock); + list_for_each_entry(ptr, &tomoyo_policy_manager_list, list) { + if (!ptr->is_deleted && ptr->is_domain + && !tomoyo_pathcmp(domainname, ptr->manager)) { + found = true; + break; + } + } + up_read(&tomoyo_policy_manager_list_lock); + if (found) + return true; + exe = tomoyo_get_exe(); + if (!exe) + return false; + down_read(&tomoyo_policy_manager_list_lock); + list_for_each_entry(ptr, &tomoyo_policy_manager_list, list) { + if (!ptr->is_deleted && !ptr->is_domain + && !strcmp(exe, ptr->manager->name)) { + found = true; + break; + } + } + up_read(&tomoyo_policy_manager_list_lock); + if (!found) { /* Reduce error messages. */ + static pid_t last_pid; + const pid_t pid = current->pid; + if (last_pid != pid) { + printk(KERN_WARNING "%s ( %s ) is not permitted to " + "update policies.\n", domainname->name, exe); + last_pid = pid; + } + } + tomoyo_free(exe); + return found; +} + +/** + * tomoyo_is_select_one - Parse select command. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @data: String to parse. + * + * Returns true on success, false otherwise. + */ +static bool tomoyo_is_select_one(struct tomoyo_io_buffer *head, + const char *data) +{ + unsigned int pid; + struct tomoyo_domain_info *domain = NULL; + + if (sscanf(data, "pid=%u", &pid) == 1) { + struct task_struct *p; + /***** CRITICAL SECTION START *****/ + read_lock(&tasklist_lock); + p = find_task_by_vpid(pid); + if (p) + domain = tomoyo_real_domain(p); + read_unlock(&tasklist_lock); + /***** CRITICAL SECTION END *****/ + } else if (!strncmp(data, "domain=", 7)) { + if (tomoyo_is_domain_def(data + 7)) { + down_read(&tomoyo_domain_list_lock); + domain = tomoyo_find_domain(data + 7); + up_read(&tomoyo_domain_list_lock); + } + } else + return false; + head->write_var1 = domain; + /* Accessing read_buf is safe because head->io_sem is held. */ + if (!head->read_buf) + return true; /* Do nothing if open(O_WRONLY). */ + head->read_avail = 0; + tomoyo_io_printf(head, "# select %s\n", data); + head->read_single_domain = true; + head->read_eof = !domain; + if (domain) { + struct tomoyo_domain_info *d; + head->read_var1 = NULL; + down_read(&tomoyo_domain_list_lock); + list_for_each_entry(d, &tomoyo_domain_list, list) { + if (d == domain) + break; + head->read_var1 = &d->list; + } + up_read(&tomoyo_domain_list_lock); + head->read_var2 = NULL; + head->read_bit = 0; + head->read_step = 0; + if (domain->is_deleted) + tomoyo_io_printf(head, "# This is a deleted domain.\n"); + } + return true; +} + +/** + * tomoyo_write_domain_policy - Write domain policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_write_domain_policy(struct tomoyo_io_buffer *head) +{ + char *data = head->write_buf; + struct tomoyo_domain_info *domain = head->write_var1; + bool is_delete = false; + bool is_select = false; + bool is_undelete = false; + unsigned int profile; + + if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_DELETE)) + is_delete = true; + else if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_SELECT)) + is_select = true; + else if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_UNDELETE)) + is_undelete = true; + if (is_select && tomoyo_is_select_one(head, data)) + return 0; + /* Don't allow updating policies by non manager programs. */ + if (!tomoyo_is_policy_manager()) + return -EPERM; + if (tomoyo_is_domain_def(data)) { + domain = NULL; + if (is_delete) + tomoyo_delete_domain(data); + else if (is_select) { + down_read(&tomoyo_domain_list_lock); + domain = tomoyo_find_domain(data); + up_read(&tomoyo_domain_list_lock); + } else if (is_undelete) + domain = tomoyo_undelete_domain(data); + else + domain = tomoyo_find_or_assign_new_domain(data, 0); + head->write_var1 = domain; + return 0; + } + if (!domain) + return -EINVAL; + + if (sscanf(data, TOMOYO_KEYWORD_USE_PROFILE "%u", &profile) == 1 + && profile < TOMOYO_MAX_PROFILES) { + if (tomoyo_profile_ptr[profile] || !tomoyo_policy_loaded) + domain->profile = (u8) profile; + return 0; + } + if (!strcmp(data, TOMOYO_KEYWORD_IGNORE_GLOBAL_ALLOW_READ)) { + tomoyo_set_domain_flag(domain, is_delete, + TOMOYO_DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ); + return 0; + } + return tomoyo_write_file_policy(data, domain, is_delete); +} + +/** + * tomoyo_print_single_path_acl - Print a single path ACL entry. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @ptr: Pointer to "struct tomoyo_single_path_acl_record". + * + * Returns true on success, false otherwise. + */ +static bool tomoyo_print_single_path_acl(struct tomoyo_io_buffer *head, + struct tomoyo_single_path_acl_record * + ptr) +{ + int pos; + u8 bit; + const char *atmark = ""; + const char *filename; + const u16 perm = ptr->perm; + + filename = ptr->filename->name; + for (bit = head->read_bit; bit < TOMOYO_MAX_SINGLE_PATH_OPERATION; + bit++) { + const char *msg; + if (!(perm & (1 << bit))) + continue; + /* Print "read/write" instead of "read" and "write". */ + if ((bit == TOMOYO_TYPE_READ_ACL || + bit == TOMOYO_TYPE_WRITE_ACL) + && (perm & (1 << TOMOYO_TYPE_READ_WRITE_ACL))) + continue; + msg = tomoyo_sp2keyword(bit); + pos = head->read_avail; + if (!tomoyo_io_printf(head, "allow_%s %s%s\n", msg, + atmark, filename)) + goto out; + } + head->read_bit = 0; + return true; + out: + head->read_bit = bit; + head->read_avail = pos; + return false; +} + +/** + * tomoyo_print_double_path_acl - Print a double path ACL entry. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @ptr: Pointer to "struct tomoyo_double_path_acl_record". + * + * Returns true on success, false otherwise. + */ +static bool tomoyo_print_double_path_acl(struct tomoyo_io_buffer *head, + struct tomoyo_double_path_acl_record * + ptr) +{ + int pos; + const char *atmark1 = ""; + const char *atmark2 = ""; + const char *filename1; + const char *filename2; + const u8 perm = ptr->perm; + u8 bit; + + filename1 = ptr->filename1->name; + filename2 = ptr->filename2->name; + for (bit = head->read_bit; bit < TOMOYO_MAX_DOUBLE_PATH_OPERATION; + bit++) { + const char *msg; + if (!(perm & (1 << bit))) + continue; + msg = tomoyo_dp2keyword(bit); + pos = head->read_avail; + if (!tomoyo_io_printf(head, "allow_%s %s%s %s%s\n", msg, + atmark1, filename1, atmark2, filename2)) + goto out; + } + head->read_bit = 0; + return true; + out: + head->read_bit = bit; + head->read_avail = pos; + return false; +} + +/** + * tomoyo_print_entry - Print an ACL entry. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @ptr: Pointer to an ACL entry. + * + * Returns true on success, false otherwise. + */ +static bool tomoyo_print_entry(struct tomoyo_io_buffer *head, + struct tomoyo_acl_info *ptr) +{ + const u8 acl_type = tomoyo_acl_type2(ptr); + + if (acl_type & TOMOYO_ACL_DELETED) + return true; + if (acl_type == TOMOYO_TYPE_SINGLE_PATH_ACL) { + struct tomoyo_single_path_acl_record *acl + = container_of(ptr, + struct tomoyo_single_path_acl_record, + head); + return tomoyo_print_single_path_acl(head, acl); + } + if (acl_type == TOMOYO_TYPE_DOUBLE_PATH_ACL) { + struct tomoyo_double_path_acl_record *acl + = container_of(ptr, + struct tomoyo_double_path_acl_record, + head); + return tomoyo_print_double_path_acl(head, acl); + } + BUG(); /* This must not happen. */ + return false; +} + +/** + * tomoyo_read_domain_policy - Read domain policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0. + */ +static int tomoyo_read_domain_policy(struct tomoyo_io_buffer *head) +{ + struct list_head *dpos; + struct list_head *apos; + bool done = true; + + if (head->read_eof) + return 0; + if (head->read_step == 0) + head->read_step = 1; + down_read(&tomoyo_domain_list_lock); + list_for_each_cookie(dpos, head->read_var1, &tomoyo_domain_list) { + struct tomoyo_domain_info *domain; + const char *quota_exceeded = ""; + const char *transition_failed = ""; + const char *ignore_global_allow_read = ""; + domain = list_entry(dpos, struct tomoyo_domain_info, list); + if (head->read_step != 1) + goto acl_loop; + if (domain->is_deleted && !head->read_single_domain) + continue; + /* Print domainname and flags. */ + if (domain->quota_warned) + quota_exceeded = "quota_exceeded\n"; + if (domain->flags & TOMOYO_DOMAIN_FLAGS_TRANSITION_FAILED) + transition_failed = "transition_failed\n"; + if (domain->flags & + TOMOYO_DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ) + ignore_global_allow_read + = TOMOYO_KEYWORD_IGNORE_GLOBAL_ALLOW_READ "\n"; + if (!tomoyo_io_printf(head, + "%s\n" TOMOYO_KEYWORD_USE_PROFILE "%u\n" + "%s%s%s\n", domain->domainname->name, + domain->profile, quota_exceeded, + transition_failed, + ignore_global_allow_read)) { + done = false; + break; + } + head->read_step = 2; +acl_loop: + if (head->read_step == 3) + goto tail_mark; + /* Print ACL entries in the domain. */ + down_read(&tomoyo_domain_acl_info_list_lock); + list_for_each_cookie(apos, head->read_var2, + &domain->acl_info_list) { + struct tomoyo_acl_info *ptr + = list_entry(apos, struct tomoyo_acl_info, + list); + if (!tomoyo_print_entry(head, ptr)) { + done = false; + break; + } + } + up_read(&tomoyo_domain_acl_info_list_lock); + if (!done) + break; + head->read_step = 3; +tail_mark: + if (!tomoyo_io_printf(head, "\n")) { + done = false; + break; + } + head->read_step = 1; + if (head->read_single_domain) + break; + } + up_read(&tomoyo_domain_list_lock); + head->read_eof = done; + return 0; +} + +/** + * tomoyo_write_domain_profile - Assign profile for specified domain. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0 on success, -EINVAL otherwise. + * + * This is equivalent to doing + * + * ( echo "select " $domainname; echo "use_profile " $profile ) | + * /usr/lib/ccs/loadpolicy -d + */ +static int tomoyo_write_domain_profile(struct tomoyo_io_buffer *head) +{ + char *data = head->write_buf; + char *cp = strchr(data, ' '); + struct tomoyo_domain_info *domain; + unsigned long profile; + + if (!cp) + return -EINVAL; + *cp = '\0'; + down_read(&tomoyo_domain_list_lock); + domain = tomoyo_find_domain(cp + 1); + up_read(&tomoyo_domain_list_lock); + if (strict_strtoul(data, 10, &profile)) + return -EINVAL; + if (domain && profile < TOMOYO_MAX_PROFILES + && (tomoyo_profile_ptr[profile] || !tomoyo_policy_loaded)) + domain->profile = (u8) profile; + return 0; +} + +/** + * tomoyo_read_domain_profile - Read only domainname and profile. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns list of profile number and domainname pairs. + * + * This is equivalent to doing + * + * grep -A 1 '^' /sys/kernel/security/tomoyo/domain_policy | + * awk ' { if ( domainname == "" ) { if ( $1 == "" ) + * domainname = $0; } else if ( $1 == "use_profile" ) { + * print $2 " " domainname; domainname = ""; } } ; ' + */ +static int tomoyo_read_domain_profile(struct tomoyo_io_buffer *head) +{ + struct list_head *pos; + bool done = true; + + if (head->read_eof) + return 0; + down_read(&tomoyo_domain_list_lock); + list_for_each_cookie(pos, head->read_var1, &tomoyo_domain_list) { + struct tomoyo_domain_info *domain; + domain = list_entry(pos, struct tomoyo_domain_info, list); + if (domain->is_deleted) + continue; + if (!tomoyo_io_printf(head, "%u %s\n", domain->profile, + domain->domainname->name)) { + done = false; + break; + } + } + up_read(&tomoyo_domain_list_lock); + head->read_eof = done; + return 0; +} + +/** + * tomoyo_write_pid: Specify PID to obtain domainname. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0. + */ +static int tomoyo_write_pid(struct tomoyo_io_buffer *head) +{ + unsigned long pid; + /* No error check. */ + strict_strtoul(head->write_buf, 10, &pid); + head->read_step = (int) pid; + head->read_eof = false; + return 0; +} + +/** + * tomoyo_read_pid - Get domainname of the specified PID. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns the domainname which the specified PID is in on success, + * empty string otherwise. + * The PID is specified by tomoyo_write_pid() so that the user can obtain + * using read()/write() interface rather than sysctl() interface. + */ +static int tomoyo_read_pid(struct tomoyo_io_buffer *head) +{ + if (head->read_avail == 0 && !head->read_eof) { + const int pid = head->read_step; + struct task_struct *p; + struct tomoyo_domain_info *domain = NULL; + /***** CRITICAL SECTION START *****/ + read_lock(&tasklist_lock); + p = find_task_by_vpid(pid); + if (p) + domain = tomoyo_real_domain(p); + read_unlock(&tasklist_lock); + /***** CRITICAL SECTION END *****/ + if (domain) + tomoyo_io_printf(head, "%d %u %s", pid, domain->profile, + domain->domainname->name); + head->read_eof = true; + } + return 0; +} + +/** + * tomoyo_write_exception_policy - Write exception policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_write_exception_policy(struct tomoyo_io_buffer *head) +{ + char *data = head->write_buf; + bool is_delete = tomoyo_str_starts(&data, TOMOYO_KEYWORD_DELETE); + + if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_KEEP_DOMAIN)) + return tomoyo_write_domain_keeper_policy(data, false, + is_delete); + if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_NO_KEEP_DOMAIN)) + return tomoyo_write_domain_keeper_policy(data, true, is_delete); + if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_INITIALIZE_DOMAIN)) + return tomoyo_write_domain_initializer_policy(data, false, + is_delete); + if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_NO_INITIALIZE_DOMAIN)) + return tomoyo_write_domain_initializer_policy(data, true, + is_delete); + if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_ALIAS)) + return tomoyo_write_alias_policy(data, is_delete); + if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_ALLOW_READ)) + return tomoyo_write_globally_readable_policy(data, is_delete); + if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_FILE_PATTERN)) + return tomoyo_write_pattern_policy(data, is_delete); + if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_DENY_REWRITE)) + return tomoyo_write_no_rewrite_policy(data, is_delete); + return -EINVAL; +} + +/** + * tomoyo_read_exception_policy - Read exception policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0 on success, -EINVAL otherwise. + */ +static int tomoyo_read_exception_policy(struct tomoyo_io_buffer *head) +{ + if (!head->read_eof) { + switch (head->read_step) { + case 0: + head->read_var2 = NULL; + head->read_step = 1; + case 1: + if (!tomoyo_read_domain_keeper_policy(head)) + break; + head->read_var2 = NULL; + head->read_step = 2; + case 2: + if (!tomoyo_read_globally_readable_policy(head)) + break; + head->read_var2 = NULL; + head->read_step = 3; + case 3: + head->read_var2 = NULL; + head->read_step = 4; + case 4: + if (!tomoyo_read_domain_initializer_policy(head)) + break; + head->read_var2 = NULL; + head->read_step = 5; + case 5: + if (!tomoyo_read_alias_policy(head)) + break; + head->read_var2 = NULL; + head->read_step = 6; + case 6: + head->read_var2 = NULL; + head->read_step = 7; + case 7: + if (!tomoyo_read_file_pattern(head)) + break; + head->read_var2 = NULL; + head->read_step = 8; + case 8: + if (!tomoyo_read_no_rewrite_policy(head)) + break; + head->read_var2 = NULL; + head->read_step = 9; + case 9: + head->read_eof = true; + break; + default: + return -EINVAL; + } + } + return 0; +} + +/* path to policy loader */ +static const char *tomoyo_loader = "/sbin/tomoyo-init"; + +/** + * tomoyo_policy_loader_exists - Check whether /sbin/tomoyo-init exists. + * + * Returns true if /sbin/tomoyo-init exists, false otherwise. + */ +static bool tomoyo_policy_loader_exists(void) +{ + /* + * Don't activate MAC if the policy loader doesn't exist. + * If the initrd includes /sbin/init but real-root-dev has not + * mounted on / yet, activating MAC will block the system since + * policies are not loaded yet. + * Thus, let do_execve() call this function everytime. + */ + struct nameidata nd; + + if (path_lookup(tomoyo_loader, LOOKUP_FOLLOW, &nd)) { + printk(KERN_INFO "Not activating Mandatory Access Control now " + "since %s doesn't exist.\n", tomoyo_loader); + return false; + } + path_put(&nd.path); + return true; +} + +/** + * tomoyo_load_policy - Run external policy loader to load policy. + * + * @filename: The program about to start. + * + * This function checks whether @filename is /sbin/init , and if so + * invoke /sbin/tomoyo-init and wait for the termination of /sbin/tomoyo-init + * and then continues invocation of /sbin/init. + * /sbin/tomoyo-init reads policy files in /etc/tomoyo/ directory and + * writes to /sys/kernel/security/tomoyo/ interfaces. + * + * Returns nothing. + */ +void tomoyo_load_policy(const char *filename) +{ + char *argv[2]; + char *envp[3]; + + if (tomoyo_policy_loaded) + return; + /* + * Check filename is /sbin/init or /sbin/tomoyo-start. + * /sbin/tomoyo-start is a dummy filename in case where /sbin/init can't + * be passed. + * You can create /sbin/tomoyo-start by + * "ln -s /bin/true /sbin/tomoyo-start". + */ + if (strcmp(filename, "/sbin/init") && + strcmp(filename, "/sbin/tomoyo-start")) + return; + if (!tomoyo_policy_loader_exists()) + return; + + printk(KERN_INFO "Calling %s to load policy. Please wait.\n", + tomoyo_loader); + argv[0] = (char *) tomoyo_loader; + argv[1] = NULL; + envp[0] = "HOME=/"; + envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; + envp[2] = NULL; + call_usermodehelper(argv[0], argv, envp, 1); + + printk(KERN_INFO "TOMOYO: 2.2.0-pre 2009/02/01\n"); + printk(KERN_INFO "Mandatory Access Control activated.\n"); + tomoyo_policy_loaded = true; + { /* Check all profiles currently assigned to domains are defined. */ + struct tomoyo_domain_info *domain; + down_read(&tomoyo_domain_list_lock); + list_for_each_entry(domain, &tomoyo_domain_list, list) { + const u8 profile = domain->profile; + if (tomoyo_profile_ptr[profile]) + continue; + panic("Profile %u (used by '%s') not defined.\n", + profile, domain->domainname->name); + } + up_read(&tomoyo_domain_list_lock); + } +} + +/** + * tomoyo_read_version: Get version. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns version information. + */ +static int tomoyo_read_version(struct tomoyo_io_buffer *head) +{ + if (!head->read_eof) { + tomoyo_io_printf(head, "2.2.0-pre"); + head->read_eof = true; + } + return 0; +} + +/** + * tomoyo_read_self_domain - Get the current process's domainname. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns the current process's domainname. + */ +static int tomoyo_read_self_domain(struct tomoyo_io_buffer *head) +{ + if (!head->read_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->read_eof = true; + } + return 0; +} + +/** + * tomoyo_open_control - open() for /sys/kernel/security/tomoyo/ interface. + * + * @type: Type of interface. + * @file: Pointer to "struct file". + * + * Associates policy handler and returns 0 on success, -ENOMEM otherwise. + */ +static int tomoyo_open_control(const u8 type, struct file *file) +{ + struct tomoyo_io_buffer *head = tomoyo_alloc(sizeof(*head)); + + if (!head) + return -ENOMEM; + mutex_init(&head->io_sem); + switch (type) { + case TOMOYO_DOMAINPOLICY: + /* /sys/kernel/security/tomoyo/domain_policy */ + head->write = tomoyo_write_domain_policy; + head->read = tomoyo_read_domain_policy; + break; + case TOMOYO_EXCEPTIONPOLICY: + /* /sys/kernel/security/tomoyo/exception_policy */ + head->write = tomoyo_write_exception_policy; + head->read = tomoyo_read_exception_policy; + break; + case TOMOYO_SELFDOMAIN: + /* /sys/kernel/security/tomoyo/self_domain */ + head->read = tomoyo_read_self_domain; + break; + case TOMOYO_DOMAIN_STATUS: + /* /sys/kernel/security/tomoyo/.domain_status */ + head->write = tomoyo_write_domain_profile; + head->read = tomoyo_read_domain_profile; + break; + case TOMOYO_PROCESS_STATUS: + /* /sys/kernel/security/tomoyo/.process_status */ + head->write = tomoyo_write_pid; + head->read = tomoyo_read_pid; + break; + case TOMOYO_VERSION: + /* /sys/kernel/security/tomoyo/version */ + head->read = tomoyo_read_version; + head->readbuf_size = 128; + break; + case TOMOYO_MEMINFO: + /* /sys/kernel/security/tomoyo/meminfo */ + head->write = tomoyo_write_memory_quota; + head->read = tomoyo_read_memory_counter; + head->readbuf_size = 512; + break; + case TOMOYO_PROFILE: + /* /sys/kernel/security/tomoyo/profile */ + head->write = tomoyo_write_profile; + head->read = tomoyo_read_profile; + break; + case TOMOYO_MANAGER: + /* /sys/kernel/security/tomoyo/manager */ + head->write = tomoyo_write_manager_policy; + head->read = tomoyo_read_manager_policy; + break; + } + if (!(file->f_mode & FMODE_READ)) { + /* + * No need to allocate read_buf since it is not opened + * for reading. + */ + head->read = NULL; + } else { + if (!head->readbuf_size) + head->readbuf_size = 4096 * 2; + head->read_buf = tomoyo_alloc(head->readbuf_size); + if (!head->read_buf) { + tomoyo_free(head); + return -ENOMEM; + } + } + if (!(file->f_mode & FMODE_WRITE)) { + /* + * No need to allocate write_buf since it is not opened + * for writing. + */ + head->write = NULL; + } else if (head->write) { + head->writebuf_size = 4096 * 2; + head->write_buf = tomoyo_alloc(head->writebuf_size); + if (!head->write_buf) { + tomoyo_free(head->read_buf); + tomoyo_free(head); + return -ENOMEM; + } + } + file->private_data = head; + /* + * Call the handler now if the file is + * /sys/kernel/security/tomoyo/self_domain + * so that the user can use + * cat < /sys/kernel/security/tomoyo/self_domain" + * to know the current process's domainname. + */ + if (type == TOMOYO_SELFDOMAIN) + tomoyo_read_control(file, NULL, 0); + return 0; +} + +/** + * tomoyo_read_control - read() for /sys/kernel/security/tomoyo/ interface. + * + * @file: Pointer to "struct file". + * @buffer: Poiner to buffer to write to. + * @buffer_len: Size of @buffer. + * + * Returns bytes read on success, negative value otherwise. + */ +static int tomoyo_read_control(struct file *file, char __user *buffer, + const int buffer_len) +{ + int len = 0; + struct tomoyo_io_buffer *head = file->private_data; + char *cp; + + if (!head->read) + return -ENOSYS; + if (mutex_lock_interruptible(&head->io_sem)) + return -EINTR; + /* Call the policy handler. */ + len = head->read(head); + if (len < 0) + goto out; + /* Write to buffer. */ + len = head->read_avail; + if (len > buffer_len) + len = buffer_len; + if (!len) + goto out; + /* head->read_buf changes by some functions. */ + cp = head->read_buf; + if (copy_to_user(buffer, cp, len)) { + len = -EFAULT; + goto out; + } + head->read_avail -= len; + memmove(cp, cp + len, head->read_avail); + out: + mutex_unlock(&head->io_sem); + return len; +} + +/** + * tomoyo_write_control - write() for /sys/kernel/security/tomoyo/ interface. + * + * @file: Pointer to "struct file". + * @buffer: Pointer to buffer to read from. + * @buffer_len: Size of @buffer. + * + * Returns @buffer_len on success, negative value otherwise. + */ +static int tomoyo_write_control(struct file *file, const char __user *buffer, + const int buffer_len) +{ + struct tomoyo_io_buffer *head = file->private_data; + int error = buffer_len; + int avail_len = buffer_len; + char *cp0 = head->write_buf; + + if (!head->write) + return -ENOSYS; + if (!access_ok(VERIFY_READ, buffer, buffer_len)) + return -EFAULT; + /* Don't allow updating policies by non manager programs. */ + if (head->write != tomoyo_write_pid && + head->write != tomoyo_write_domain_policy && + !tomoyo_is_policy_manager()) + return -EPERM; + if (mutex_lock_interruptible(&head->io_sem)) + return -EINTR; + /* Read a line and dispatch it to the policy handler. */ + while (avail_len > 0) { + char c; + if (head->write_avail >= head->writebuf_size - 1) { + error = -ENOMEM; + break; + } else if (get_user(c, buffer)) { + error = -EFAULT; + break; + } + buffer++; + avail_len--; + cp0[head->write_avail++] = c; + if (c != '\n') + continue; + cp0[head->write_avail - 1] = '\0'; + head->write_avail = 0; + tomoyo_normalize_line(cp0); + head->write(head); + } + mutex_unlock(&head->io_sem); + return error; +} + +/** + * tomoyo_close_control - close() for /sys/kernel/security/tomoyo/ interface. + * + * @file: Pointer to "struct file". + * + * Releases memory and returns 0. + */ +static int tomoyo_close_control(struct file *file) +{ + struct tomoyo_io_buffer *head = file->private_data; + + /* Release memory used for policy I/O. */ + tomoyo_free(head->read_buf); + head->read_buf = NULL; + tomoyo_free(head->write_buf); + head->write_buf = NULL; + tomoyo_free(head); + head = NULL; + file->private_data = NULL; + return 0; +} + +/** + * tomoyo_alloc_acl_element - Allocate permanent memory for ACL entry. + * + * @acl_type: Type of ACL entry. + * + * Returns pointer to the ACL entry on success, NULL otherwise. + */ +void *tomoyo_alloc_acl_element(const u8 acl_type) +{ + int len; + struct tomoyo_acl_info *ptr; + + switch (acl_type) { + case TOMOYO_TYPE_SINGLE_PATH_ACL: + len = sizeof(struct tomoyo_single_path_acl_record); + break; + case TOMOYO_TYPE_DOUBLE_PATH_ACL: + len = sizeof(struct tomoyo_double_path_acl_record); + break; + default: + return NULL; + } + ptr = tomoyo_alloc_element(len); + if (!ptr) + return NULL; + ptr->type = acl_type; + return ptr; +} + +/** + * tomoyo_open - open() for /sys/kernel/security/tomoyo/ interface. + * + * @inode: Pointer to "struct inode". + * @file: Pointer to "struct file". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_open(struct inode *inode, struct file *file) +{ + const int key = ((u8 *) file->f_path.dentry->d_inode->i_private) + - ((u8 *) NULL); + return tomoyo_open_control(key, file); +} + +/** + * tomoyo_release - close() for /sys/kernel/security/tomoyo/ interface. + * + * @inode: Pointer to "struct inode". + * @file: Pointer to "struct file". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_release(struct inode *inode, struct file *file) +{ + return tomoyo_close_control(file); +} + +/** + * tomoyo_read - read() for /sys/kernel/security/tomoyo/ interface. + * + * @file: Pointer to "struct file". + * @buf: Pointer to buffer. + * @count: Size of @buf. + * @ppos: Unused. + * + * Returns bytes read on success, negative value otherwise. + */ +static ssize_t tomoyo_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + return tomoyo_read_control(file, buf, count); +} + +/** + * tomoyo_write - write() for /sys/kernel/security/tomoyo/ interface. + * + * @file: Pointer to "struct file". + * @buf: Pointer to buffer. + * @count: Size of @buf. + * @ppos: Unused. + * + * Returns @count on success, negative value otherwise. + */ +static ssize_t tomoyo_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + return tomoyo_write_control(file, buf, count); +} + +/* Operations for /sys/kernel/security/tomoyo/ interface. */ +static const struct file_operations tomoyo_operations = { + .open = tomoyo_open, + .release = tomoyo_release, + .read = tomoyo_read, + .write = tomoyo_write, +}; + +/** + * tomoyo_create_entry - Create interface files under /sys/kernel/security/tomoyo/ directory. + * + * @name: The name of the interface file. + * @mode: The permission of the interface file. + * @parent: The parent directory. + * @key: Type of interface. + * + * Returns nothing. + */ +static void __init tomoyo_create_entry(const char *name, const mode_t mode, + struct dentry *parent, const u8 key) +{ + securityfs_create_file(name, mode, parent, ((u8 *) NULL) + key, + &tomoyo_operations); +} + +/** + * tomoyo_initerface_init - Initialize /sys/kernel/security/tomoyo/ interface. + * + * Returns 0. + */ +static int __init tomoyo_initerface_init(void) +{ + struct dentry *tomoyo_dir; + + tomoyo_dir = securityfs_create_dir("tomoyo", NULL); + tomoyo_create_entry("domain_policy", 0600, tomoyo_dir, + TOMOYO_DOMAINPOLICY); + tomoyo_create_entry("exception_policy", 0600, tomoyo_dir, + TOMOYO_EXCEPTIONPOLICY); + tomoyo_create_entry("self_domain", 0400, tomoyo_dir, + TOMOYO_SELFDOMAIN); + tomoyo_create_entry(".domain_status", 0600, tomoyo_dir, + TOMOYO_DOMAIN_STATUS); + tomoyo_create_entry(".process_status", 0600, tomoyo_dir, + TOMOYO_PROCESS_STATUS); + tomoyo_create_entry("meminfo", 0600, tomoyo_dir, + TOMOYO_MEMINFO); + tomoyo_create_entry("profile", 0600, tomoyo_dir, + TOMOYO_PROFILE); + tomoyo_create_entry("manager", 0600, tomoyo_dir, + TOMOYO_MANAGER); + tomoyo_create_entry("version", 0400, tomoyo_dir, + TOMOYO_VERSION); + return 0; +} + +fs_initcall(tomoyo_initerface_init); diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h new file mode 100644 index 00000000000..6dcb7cc0ed1 --- /dev/null +++ b/security/tomoyo/common.h @@ -0,0 +1,359 @@ +/* + * security/tomoyo/common.h + * + * Common functions for TOMOYO. + * + * Copyright (C) 2005-2009 NTT DATA CORPORATION + * + * Version: 2.2.0-pre 2009/02/01 + * + */ + +#ifndef _SECURITY_TOMOYO_COMMON_H +#define _SECURITY_TOMOYO_COMMON_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct dentry; +struct vfsmount; + +/* Temporary buffer for holding pathnames. */ +struct tomoyo_page_buffer { + char buffer[4096]; +}; + +/* Structure for holding a token. */ +struct tomoyo_path_info { + const char *name; + u32 hash; /* = full_name_hash(name, strlen(name)) */ + u16 total_len; /* = strlen(name) */ + u16 const_len; /* = tomoyo_const_part_length(name) */ + bool is_dir; /* = tomoyo_strendswith(name, "/") */ + bool is_patterned; /* = tomoyo_path_contains_pattern(name) */ + u16 depth; /* = tomoyo_path_depth(name) */ +}; + +/* + * This is the max length of a token. + * + * A token consists of only ASCII printable characters. + * Non printable characters in a token is represented in \ooo style + * octal string. Thus, \ itself is represented as \\. + */ +#define TOMOYO_MAX_PATHNAME_LEN 4000 + +/* Structure for holding requested pathname. */ +struct tomoyo_path_info_with_data { + /* Keep "head" first, for this pointer is passed to tomoyo_free(). */ + struct tomoyo_path_info head; + char bariier1[16]; /* Safeguard for overrun. */ + char body[TOMOYO_MAX_PATHNAME_LEN]; + char barrier2[16]; /* Safeguard for overrun. */ +}; + +/* + * Common header for holding ACL entries. + * + * Packing "struct tomoyo_acl_info" allows + * "struct tomoyo_single_path_acl_record" to embed "u16" and + * "struct tomoyo_double_path_acl_record" to embed "u8" + * without enlarging their structure size. + */ +struct tomoyo_acl_info { + struct list_head list; + /* + * Type of this ACL entry. + * + * MSB is is_deleted flag. + */ + u8 type; +} __packed; + +/* This ACL entry is deleted. */ +#define TOMOYO_ACL_DELETED 0x80 + +/* Structure for domain information. */ +struct tomoyo_domain_info { + struct list_head list; + struct list_head acl_info_list; + /* Name of this domain. Never NULL. */ + const struct tomoyo_path_info *domainname; + u8 profile; /* Profile number to use. */ + u8 is_deleted; /* Delete flag. + 0 = active. + 1 = deleted but undeletable. + 255 = deleted and no longer undeletable. */ + bool quota_warned; /* Quota warnning flag. */ + /* DOMAIN_FLAGS_*. Use tomoyo_set_domain_flag() to modify. */ + u8 flags; +}; + +/* Profile number is an integer between 0 and 255. */ +#define TOMOYO_MAX_PROFILES 256 + +/* Ignore "allow_read" directive in exception policy. */ +#define TOMOYO_DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ 1 +/* + * This domain was unable to create a new domain at tomoyo_find_next_domain() + * because the name of the domain to be created was too long or + * it could not allocate memory. + * More than one process continued execve() without domain transition. + */ +#define TOMOYO_DOMAIN_FLAGS_TRANSITION_FAILED 2 + +/* + * Structure for "allow_read/write", "allow_execute", "allow_read", + * "allow_write", "allow_create", "allow_unlink", "allow_mkdir", "allow_rmdir", + * "allow_mkfifo", "allow_mksock", "allow_mkblock", "allow_mkchar", + * "allow_truncate", "allow_symlink" and "allow_rewrite" directive. + */ +struct tomoyo_single_path_acl_record { + struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_SINGLE_PATH_ACL */ + u16 perm; + /* Pointer to single pathname. */ + const struct tomoyo_path_info *filename; +}; + +/* Structure for "allow_rename" and "allow_link" directive. */ +struct tomoyo_double_path_acl_record { + struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_DOUBLE_PATH_ACL */ + u8 perm; + /* Pointer to single pathname. */ + const struct tomoyo_path_info *filename1; + /* Pointer to single pathname. */ + const struct tomoyo_path_info *filename2; +}; + +/* Keywords for ACLs. */ +#define TOMOYO_KEYWORD_ALIAS "alias " +#define TOMOYO_KEYWORD_ALLOW_READ "allow_read " +#define TOMOYO_KEYWORD_DELETE "delete " +#define TOMOYO_KEYWORD_DENY_REWRITE "deny_rewrite " +#define TOMOYO_KEYWORD_FILE_PATTERN "file_pattern " +#define TOMOYO_KEYWORD_INITIALIZE_DOMAIN "initialize_domain " +#define TOMOYO_KEYWORD_KEEP_DOMAIN "keep_domain " +#define TOMOYO_KEYWORD_NO_INITIALIZE_DOMAIN "no_initialize_domain " +#define TOMOYO_KEYWORD_NO_KEEP_DOMAIN "no_keep_domain " +#define TOMOYO_KEYWORD_SELECT "select " +#define TOMOYO_KEYWORD_UNDELETE "undelete " +#define TOMOYO_KEYWORD_USE_PROFILE "use_profile " +#define TOMOYO_KEYWORD_IGNORE_GLOBAL_ALLOW_READ "ignore_global_allow_read" +/* A domain definition starts with . */ +#define TOMOYO_ROOT_NAME "" +#define TOMOYO_ROOT_NAME_LEN (sizeof(TOMOYO_ROOT_NAME) - 1) + +/* Index numbers for Access Controls. */ +#define TOMOYO_MAC_FOR_FILE 0 /* domain_policy.conf */ +#define TOMOYO_MAX_ACCEPT_ENTRY 1 +#define TOMOYO_VERBOSE 2 +#define TOMOYO_MAX_CONTROL_INDEX 3 + +/* Structure for reading/writing policy via securityfs interfaces. */ +struct tomoyo_io_buffer { + int (*read) (struct tomoyo_io_buffer *); + int (*write) (struct tomoyo_io_buffer *); + /* Exclusive lock for this structure. */ + struct mutex io_sem; + /* The position currently reading from. */ + struct list_head *read_var1; + /* Extra variables for reading. */ + struct list_head *read_var2; + /* The position currently writing to. */ + struct tomoyo_domain_info *write_var1; + /* The step for reading. */ + int read_step; + /* Buffer for reading. */ + char *read_buf; + /* EOF flag for reading. */ + bool read_eof; + /* Read domain ACL of specified PID? */ + bool read_single_domain; + /* Extra variable for reading. */ + u8 read_bit; + /* Bytes available for reading. */ + int read_avail; + /* Size of read buffer. */ + int readbuf_size; + /* Buffer for writing. */ + char *write_buf; + /* Bytes available for writing. */ + int write_avail; + /* Size of write buffer. */ + int writebuf_size; +}; + +/* Check whether the domain has too many ACL entries to hold. */ +bool tomoyo_domain_quota_is_ok(struct tomoyo_domain_info * const domain); +/* Transactional sprintf() for policy dump. */ +bool tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt, ...) + __attribute__ ((format(printf, 2, 3))); +/* Check whether the domainname is correct. */ +bool tomoyo_is_correct_domain(const unsigned char *domainname, + const char *function); +/* Check whether the token is correct. */ +bool tomoyo_is_correct_path(const char *filename, const s8 start_type, + const s8 pattern_type, const s8 end_type, + const char *function); +/* Check whether the token can be a domainname. */ +bool tomoyo_is_domain_def(const unsigned char *buffer); +/* Check whether the given filename matches the given pattern. */ +bool tomoyo_path_matches_pattern(const struct tomoyo_path_info *filename, + const struct tomoyo_path_info *pattern); +/* Read "alias" entry in exception policy. */ +bool tomoyo_read_alias_policy(struct tomoyo_io_buffer *head); +/* + * Read "initialize_domain" and "no_initialize_domain" entry + * in exception policy. + */ +bool tomoyo_read_domain_initializer_policy(struct tomoyo_io_buffer *head); +/* Read "keep_domain" and "no_keep_domain" entry in exception policy. */ +bool tomoyo_read_domain_keeper_policy(struct tomoyo_io_buffer *head); +/* Read "file_pattern" entry in exception policy. */ +bool tomoyo_read_file_pattern(struct tomoyo_io_buffer *head); +/* Read "allow_read" entry in exception policy. */ +bool tomoyo_read_globally_readable_policy(struct tomoyo_io_buffer *head); +/* Read "deny_rewrite" entry in exception policy. */ +bool tomoyo_read_no_rewrite_policy(struct tomoyo_io_buffer *head); +/* Write domain policy violation warning message to console? */ +bool tomoyo_verbose_mode(const struct tomoyo_domain_info *domain); +/* Convert double path operation to operation name. */ +const char *tomoyo_dp2keyword(const u8 operation); +/* Get the last component of the given domainname. */ +const char *tomoyo_get_last_name(const struct tomoyo_domain_info *domain); +/* Get warning message. */ +const char *tomoyo_get_msg(const bool is_enforce); +/* Convert single path operation to operation name. */ +const char *tomoyo_sp2keyword(const u8 operation); +/* Delete a domain. */ +int tomoyo_delete_domain(char *data); +/* Create "alias" entry in exception policy. */ +int tomoyo_write_alias_policy(char *data, const bool is_delete); +/* + * Create "initialize_domain" and "no_initialize_domain" entry + * in exception policy. + */ +int tomoyo_write_domain_initializer_policy(char *data, const bool is_not, + const bool is_delete); +/* Create "keep_domain" and "no_keep_domain" entry in exception policy. */ +int tomoyo_write_domain_keeper_policy(char *data, const bool is_not, + const bool is_delete); +/* + * Create "allow_read/write", "allow_execute", "allow_read", "allow_write", + * "allow_create", "allow_unlink", "allow_mkdir", "allow_rmdir", + * "allow_mkfifo", "allow_mksock", "allow_mkblock", "allow_mkchar", + * "allow_truncate", "allow_symlink", "allow_rewrite", "allow_rename" and + * "allow_link" entry in domain policy. + */ +int tomoyo_write_file_policy(char *data, struct tomoyo_domain_info *domain, + const bool is_delete); +/* Create "allow_read" entry in exception policy. */ +int tomoyo_write_globally_readable_policy(char *data, const bool is_delete); +/* Create "deny_rewrite" entry in exception policy. */ +int tomoyo_write_no_rewrite_policy(char *data, const bool is_delete); +/* Create "file_pattern" entry in exception policy. */ +int tomoyo_write_pattern_policy(char *data, const bool is_delete); +/* Find a domain by the given name. */ +struct tomoyo_domain_info *tomoyo_find_domain(const char *domainname); +/* Find or create a domain by the given name. */ +struct tomoyo_domain_info *tomoyo_find_or_assign_new_domain(const char * + domainname, + const u8 profile); +/* Undelete a domain. */ +struct tomoyo_domain_info *tomoyo_undelete_domain(const char *domainname); +/* Check mode for specified functionality. */ +unsigned int tomoyo_check_flags(const struct tomoyo_domain_info *domain, + const u8 index); +/* Allocate memory for structures. */ +void *tomoyo_alloc_acl_element(const u8 acl_type); +/* Fill in "struct tomoyo_path_info" members. */ +void tomoyo_fill_path_info(struct tomoyo_path_info *ptr); +/* Run policy loader when /sbin/init starts. */ +void tomoyo_load_policy(const char *filename); +/* Change "struct tomoyo_domain_info"->flags. */ +void tomoyo_set_domain_flag(struct tomoyo_domain_info *domain, + const bool is_delete, const u8 flags); + +/* strcmp() for "struct tomoyo_path_info" structure. */ +static inline bool tomoyo_pathcmp(const struct tomoyo_path_info *a, + const struct tomoyo_path_info *b) +{ + return a->hash != b->hash || strcmp(a->name, b->name); +} + +/* Get type of an ACL entry. */ +static inline u8 tomoyo_acl_type1(struct tomoyo_acl_info *ptr) +{ + return ptr->type & ~TOMOYO_ACL_DELETED; +} + +/* Get type of an ACL entry. */ +static inline u8 tomoyo_acl_type2(struct tomoyo_acl_info *ptr) +{ + return ptr->type; +} + +/** + * tomoyo_is_valid - Check whether the character is a valid char. + * + * @c: The character to check. + * + * Returns true if @c is a valid character, false otherwise. + */ +static inline bool tomoyo_is_valid(const unsigned char c) +{ + return c > ' ' && c < 127; +} + +/** + * tomoyo_is_invalid - Check whether the character is an invalid char. + * + * @c: The character to check. + * + * Returns true if @c is an invalid character, false otherwise. + */ +static inline bool tomoyo_is_invalid(const unsigned char c) +{ + return c && (c <= ' ' || c >= 127); +} + +/* The list for "struct tomoyo_domain_info". */ +extern struct list_head tomoyo_domain_list; +extern struct rw_semaphore tomoyo_domain_list_lock; + +/* Lock for domain->acl_info_list. */ +extern struct rw_semaphore tomoyo_domain_acl_info_list_lock; + +/* Has /sbin/init started? */ +extern bool tomoyo_policy_loaded; + +/* The kernel's domain. */ +extern struct tomoyo_domain_info tomoyo_kernel_domain; + +/** + * list_for_each_cookie - iterate over a list with cookie. + * @pos: the &struct list_head to use as a loop cursor. + * @cookie: the &struct list_head to use as a cookie. + * @head: the head for your list. + * + * Same with list_for_each() except that this primitive uses @cookie + * so that we can continue iteration. + * @cookie must be NULL when iteration starts, and @cookie will become + * NULL when iteration finishes. + */ +#define list_for_each_cookie(pos, cookie, head) \ + for (({ if (!cookie) \ + cookie = head; }), \ + pos = (cookie)->next; \ + prefetch(pos->next), pos != (head) || ((cookie) = NULL); \ + (cookie) = pos, pos = pos->next) + +#endif /* !defined(_SECURITY_TOMOYO_COMMON_H) */ -- cgit v1.2.3 From b69a54ee582373d76e4b5560970db5b8c618b12a Mon Sep 17 00:00:00 2001 From: Kentaro Takeda Date: Thu, 5 Feb 2009 17:18:14 +0900 Subject: File operation restriction part. This file controls file related operations of TOMOYO Linux. tomoyo/tomoyo.c calls the following six functions in this file. Each function handles the following access types. * tomoyo_check_file_perm sysctl()'s "read" and "write". * tomoyo_check_exec_perm "execute". * tomoyo_check_open_permission open(2) for "read" and "write". * tomoyo_check_1path_perm "create", "unlink", "mkdir", "rmdir", "mkfifo", "mksock", "mkblock", "mkchar", "truncate" and "symlink". * tomoyo_check_2path_perm "rename" and "unlink". * tomoyo_check_rewrite_permission "rewrite". ("rewrite" are operations which may lose already recorded data of a file, i.e. open(!O_APPEND) || open(O_TRUNC) || truncate() || ftruncate()) The functions which actually checks ACLs are the following three functions. Each function handles the following access types. ACL directive is expressed by "allow_". * tomoyo_check_file_acl Open() operation and execve() operation. ("read", "write", "read/write" and "execute") * tomoyo_check_single_write_acl Directory modification operations with 1 pathname. ("create", "unlink", "mkdir", "rmdir", "mkfifo", "mksock", "mkblock", "mkchar", "truncate", "symlink" and "rewrite") * tomoyo_check_double_write_acl Directory modification operations with 2 pathname. ("link" and "rename") Also, this file contains handlers of some utility directives for file related operations. * "allow_read": specifies globally (for all domains) readable files. * "path_group": specifies pathname macro. * "deny_rewrite": restricts rewrite operation. Signed-off-by: Kentaro Takeda Signed-off-by: Tetsuo Handa Signed-off-by: Toshiharu Harada Signed-off-by: James Morris --- security/tomoyo/file.c | 1241 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1241 insertions(+) create mode 100644 security/tomoyo/file.c (limited to 'security') diff --git a/security/tomoyo/file.c b/security/tomoyo/file.c new file mode 100644 index 00000000000..65f50c1c5ee --- /dev/null +++ b/security/tomoyo/file.c @@ -0,0 +1,1241 @@ +/* + * security/tomoyo/file.c + * + * Implementation of the Domain-Based Mandatory Access Control. + * + * Copyright (C) 2005-2009 NTT DATA CORPORATION + * + * Version: 2.2.0-pre 2009/02/01 + * + */ + +#include "common.h" +#include "tomoyo.h" +#include "realpath.h" +#define ACC_MODE(x) ("\000\004\002\006"[(x)&O_ACCMODE]) + +/* Structure for "allow_read" keyword. */ +struct tomoyo_globally_readable_file_entry { + struct list_head list; + const struct tomoyo_path_info *filename; + bool is_deleted; +}; + +/* Structure for "file_pattern" keyword. */ +struct tomoyo_pattern_entry { + struct list_head list; + const struct tomoyo_path_info *pattern; + bool is_deleted; +}; + +/* Structure for "deny_rewrite" keyword. */ +struct tomoyo_no_rewrite_entry { + struct list_head list; + const struct tomoyo_path_info *pattern; + bool is_deleted; +}; + +/* Keyword array for single path operations. */ +static const char *tomoyo_sp_keyword[TOMOYO_MAX_SINGLE_PATH_OPERATION] = { + [TOMOYO_TYPE_READ_WRITE_ACL] = "read/write", + [TOMOYO_TYPE_EXECUTE_ACL] = "execute", + [TOMOYO_TYPE_READ_ACL] = "read", + [TOMOYO_TYPE_WRITE_ACL] = "write", + [TOMOYO_TYPE_CREATE_ACL] = "create", + [TOMOYO_TYPE_UNLINK_ACL] = "unlink", + [TOMOYO_TYPE_MKDIR_ACL] = "mkdir", + [TOMOYO_TYPE_RMDIR_ACL] = "rmdir", + [TOMOYO_TYPE_MKFIFO_ACL] = "mkfifo", + [TOMOYO_TYPE_MKSOCK_ACL] = "mksock", + [TOMOYO_TYPE_MKBLOCK_ACL] = "mkblock", + [TOMOYO_TYPE_MKCHAR_ACL] = "mkchar", + [TOMOYO_TYPE_TRUNCATE_ACL] = "truncate", + [TOMOYO_TYPE_SYMLINK_ACL] = "symlink", + [TOMOYO_TYPE_REWRITE_ACL] = "rewrite", +}; + +/* Keyword array for double path operations. */ +static const char *tomoyo_dp_keyword[TOMOYO_MAX_DOUBLE_PATH_OPERATION] = { + [TOMOYO_TYPE_LINK_ACL] = "link", + [TOMOYO_TYPE_RENAME_ACL] = "rename", +}; + +/** + * tomoyo_sp2keyword - Get the name of single path operation. + * + * @operation: Type of operation. + * + * Returns the name of single path operation. + */ +const char *tomoyo_sp2keyword(const u8 operation) +{ + return (operation < TOMOYO_MAX_SINGLE_PATH_OPERATION) + ? tomoyo_sp_keyword[operation] : NULL; +} + +/** + * tomoyo_dp2keyword - Get the name of double path operation. + * + * @operation: Type of operation. + * + * Returns the name of double path operation. + */ +const char *tomoyo_dp2keyword(const u8 operation) +{ + return (operation < TOMOYO_MAX_DOUBLE_PATH_OPERATION) + ? tomoyo_dp_keyword[operation] : NULL; +} + +/** + * tomoyo_strendswith - Check whether the token ends with the given token. + * + * @name: The token to check. + * @tail: The token to find. + * + * Returns true if @name ends with @tail, false otherwise. + */ +static bool tomoyo_strendswith(const char *name, const char *tail) +{ + int len; + + if (!name || !tail) + return false; + len = strlen(name) - strlen(tail); + return len >= 0 && !strcmp(name + len, tail); +} + +/** + * tomoyo_get_path - Get realpath. + * + * @path: Pointer to "struct path". + * + * Returns pointer to "struct tomoyo_path_info" on success, NULL otherwise. + */ +static struct tomoyo_path_info *tomoyo_get_path(struct path *path) +{ + int error; + struct tomoyo_path_info_with_data *buf = tomoyo_alloc(sizeof(*buf)); + + if (!buf) + return NULL; + /* Reserve one byte for appending "/". */ + error = tomoyo_realpath_from_path2(path, buf->body, + sizeof(buf->body) - 2); + if (!error) { + buf->head.name = buf->body; + tomoyo_fill_path_info(&buf->head); + return &buf->head; + } + tomoyo_free(buf); + return NULL; +} + +/* Lock for domain->acl_info_list. */ +DECLARE_RWSEM(tomoyo_domain_acl_info_list_lock); + +static int tomoyo_update_double_path_acl(const u8 type, const char *filename1, + const char *filename2, + struct tomoyo_domain_info * + const domain, const bool is_delete); +static int tomoyo_update_single_path_acl(const u8 type, const char *filename, + struct tomoyo_domain_info * + const domain, const bool is_delete); + +/* The list for "struct tomoyo_globally_readable_file_entry". */ +static LIST_HEAD(tomoyo_globally_readable_list); +static DECLARE_RWSEM(tomoyo_globally_readable_list_lock); + +/** + * tomoyo_update_globally_readable_entry - Update "struct tomoyo_globally_readable_file_entry" list. + * + * @filename: Filename unconditionally permitted to open() for reading. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_update_globally_readable_entry(const char *filename, + const bool is_delete) +{ + struct tomoyo_globally_readable_file_entry *new_entry; + struct tomoyo_globally_readable_file_entry *ptr; + const struct tomoyo_path_info *saved_filename; + int error = -ENOMEM; + + if (!tomoyo_is_correct_path(filename, 1, 0, -1, __func__)) + return -EINVAL; + saved_filename = tomoyo_save_name(filename); + if (!saved_filename) + return -ENOMEM; + /***** EXCLUSIVE SECTION START *****/ + down_write(&tomoyo_globally_readable_list_lock); + list_for_each_entry(ptr, &tomoyo_globally_readable_list, list) { + if (ptr->filename != saved_filename) + continue; + ptr->is_deleted = is_delete; + error = 0; + goto out; + } + if (is_delete) { + error = -ENOENT; + goto out; + } + new_entry = tomoyo_alloc_element(sizeof(*new_entry)); + if (!new_entry) + goto out; + new_entry->filename = saved_filename; + list_add_tail(&new_entry->list, &tomoyo_globally_readable_list); + error = 0; + out: + up_write(&tomoyo_globally_readable_list_lock); + /***** EXCLUSIVE SECTION END *****/ + return error; +} + +/** + * tomoyo_is_globally_readable_file - Check if the file is unconditionnaly permitted to be open()ed for reading. + * + * @filename: The filename to check. + * + * Returns true if any domain can open @filename for reading, false otherwise. + */ +static bool tomoyo_is_globally_readable_file(const struct tomoyo_path_info * + filename) +{ + struct tomoyo_globally_readable_file_entry *ptr; + bool found = false; + down_read(&tomoyo_globally_readable_list_lock); + list_for_each_entry(ptr, &tomoyo_globally_readable_list, list) { + if (!ptr->is_deleted && + tomoyo_path_matches_pattern(filename, ptr->filename)) { + found = true; + break; + } + } + up_read(&tomoyo_globally_readable_list_lock); + return found; +} + +/** + * tomoyo_write_globally_readable_policy - Write "struct tomoyo_globally_readable_file_entry" list. + * + * @data: String to parse. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_write_globally_readable_policy(char *data, const bool is_delete) +{ + return tomoyo_update_globally_readable_entry(data, is_delete); +} + +/** + * tomoyo_read_globally_readable_policy - Read "struct tomoyo_globally_readable_file_entry" list. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns true on success, false otherwise. + */ +bool tomoyo_read_globally_readable_policy(struct tomoyo_io_buffer *head) +{ + struct list_head *pos; + bool done = true; + + down_read(&tomoyo_globally_readable_list_lock); + list_for_each_cookie(pos, head->read_var2, + &tomoyo_globally_readable_list) { + struct tomoyo_globally_readable_file_entry *ptr; + ptr = list_entry(pos, + struct tomoyo_globally_readable_file_entry, + list); + if (ptr->is_deleted) + continue; + if (!tomoyo_io_printf(head, TOMOYO_KEYWORD_ALLOW_READ "%s\n", + ptr->filename->name)) { + done = false; + break; + } + } + up_read(&tomoyo_globally_readable_list_lock); + return done; +} + +/* The list for "struct tomoyo_pattern_entry". */ +static LIST_HEAD(tomoyo_pattern_list); +static DECLARE_RWSEM(tomoyo_pattern_list_lock); + +/** + * tomoyo_update_file_pattern_entry - Update "struct tomoyo_pattern_entry" list. + * + * @pattern: Pathname pattern. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_update_file_pattern_entry(const char *pattern, + const bool is_delete) +{ + struct tomoyo_pattern_entry *new_entry; + struct tomoyo_pattern_entry *ptr; + const struct tomoyo_path_info *saved_pattern; + int error = -ENOMEM; + + if (!tomoyo_is_correct_path(pattern, 0, 1, 0, __func__)) + return -EINVAL; + saved_pattern = tomoyo_save_name(pattern); + if (!saved_pattern) + return -ENOMEM; + /***** EXCLUSIVE SECTION START *****/ + down_write(&tomoyo_pattern_list_lock); + list_for_each_entry(ptr, &tomoyo_pattern_list, list) { + if (saved_pattern != ptr->pattern) + continue; + ptr->is_deleted = is_delete; + error = 0; + goto out; + } + if (is_delete) { + error = -ENOENT; + goto out; + } + new_entry = tomoyo_alloc_element(sizeof(*new_entry)); + if (!new_entry) + goto out; + new_entry->pattern = saved_pattern; + list_add_tail(&new_entry->list, &tomoyo_pattern_list); + error = 0; + out: + up_write(&tomoyo_pattern_list_lock); + /***** EXCLUSIVE SECTION END *****/ + return error; +} + +/** + * tomoyo_get_file_pattern - Get patterned pathname. + * + * @filename: The filename to find patterned pathname. + * + * Returns pointer to pathname pattern if matched, @filename otherwise. + */ +static const struct tomoyo_path_info * +tomoyo_get_file_pattern(const struct tomoyo_path_info *filename) +{ + struct tomoyo_pattern_entry *ptr; + const struct tomoyo_path_info *pattern = NULL; + + down_read(&tomoyo_pattern_list_lock); + list_for_each_entry(ptr, &tomoyo_pattern_list, list) { + if (ptr->is_deleted) + continue; + if (!tomoyo_path_matches_pattern(filename, ptr->pattern)) + continue; + pattern = ptr->pattern; + if (tomoyo_strendswith(pattern->name, "/\\*")) { + /* Do nothing. Try to find the better match. */ + } else { + /* This would be the better match. Use this. */ + break; + } + } + up_read(&tomoyo_pattern_list_lock); + if (pattern) + filename = pattern; + return filename; +} + +/** + * tomoyo_write_pattern_policy - Write "struct tomoyo_pattern_entry" list. + * + * @data: String to parse. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_write_pattern_policy(char *data, const bool is_delete) +{ + return tomoyo_update_file_pattern_entry(data, is_delete); +} + +/** + * tomoyo_read_file_pattern - Read "struct tomoyo_pattern_entry" list. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns true on success, false otherwise. + */ +bool tomoyo_read_file_pattern(struct tomoyo_io_buffer *head) +{ + struct list_head *pos; + bool done = true; + + down_read(&tomoyo_pattern_list_lock); + list_for_each_cookie(pos, head->read_var2, &tomoyo_pattern_list) { + struct tomoyo_pattern_entry *ptr; + ptr = list_entry(pos, struct tomoyo_pattern_entry, list); + if (ptr->is_deleted) + continue; + if (!tomoyo_io_printf(head, TOMOYO_KEYWORD_FILE_PATTERN "%s\n", + ptr->pattern->name)) { + done = false; + break; + } + } + up_read(&tomoyo_pattern_list_lock); + return done; +} + +/* The list for "struct tomoyo_no_rewrite_entry". */ +static LIST_HEAD(tomoyo_no_rewrite_list); +static DECLARE_RWSEM(tomoyo_no_rewrite_list_lock); + +/** + * tomoyo_update_no_rewrite_entry - Update "struct tomoyo_no_rewrite_entry" list. + * + * @pattern: Pathname pattern that are not rewritable by default. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_update_no_rewrite_entry(const char *pattern, + const bool is_delete) +{ + struct tomoyo_no_rewrite_entry *new_entry, *ptr; + const struct tomoyo_path_info *saved_pattern; + int error = -ENOMEM; + + if (!tomoyo_is_correct_path(pattern, 0, 0, 0, __func__)) + return -EINVAL; + saved_pattern = tomoyo_save_name(pattern); + if (!saved_pattern) + return -ENOMEM; + /***** EXCLUSIVE SECTION START *****/ + down_write(&tomoyo_no_rewrite_list_lock); + list_for_each_entry(ptr, &tomoyo_no_rewrite_list, list) { + if (ptr->pattern != saved_pattern) + continue; + ptr->is_deleted = is_delete; + error = 0; + goto out; + } + if (is_delete) { + error = -ENOENT; + goto out; + } + new_entry = tomoyo_alloc_element(sizeof(*new_entry)); + if (!new_entry) + goto out; + new_entry->pattern = saved_pattern; + list_add_tail(&new_entry->list, &tomoyo_no_rewrite_list); + error = 0; + out: + up_write(&tomoyo_no_rewrite_list_lock); + /***** EXCLUSIVE SECTION END *****/ + return error; +} + +/** + * tomoyo_is_no_rewrite_file - Check if the given pathname is not permitted to be rewrited. + * + * @filename: Filename to check. + * + * Returns true if @filename is specified by "deny_rewrite" directive, + * false otherwise. + */ +static bool tomoyo_is_no_rewrite_file(const struct tomoyo_path_info *filename) +{ + struct tomoyo_no_rewrite_entry *ptr; + bool found = false; + + down_read(&tomoyo_no_rewrite_list_lock); + list_for_each_entry(ptr, &tomoyo_no_rewrite_list, list) { + if (ptr->is_deleted) + continue; + if (!tomoyo_path_matches_pattern(filename, ptr->pattern)) + continue; + found = true; + break; + } + up_read(&tomoyo_no_rewrite_list_lock); + return found; +} + +/** + * tomoyo_write_no_rewrite_policy - Write "struct tomoyo_no_rewrite_entry" list. + * + * @data: String to parse. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_write_no_rewrite_policy(char *data, const bool is_delete) +{ + return tomoyo_update_no_rewrite_entry(data, is_delete); +} + +/** + * tomoyo_read_no_rewrite_policy - Read "struct tomoyo_no_rewrite_entry" list. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns true on success, false otherwise. + */ +bool tomoyo_read_no_rewrite_policy(struct tomoyo_io_buffer *head) +{ + struct list_head *pos; + bool done = true; + + down_read(&tomoyo_no_rewrite_list_lock); + list_for_each_cookie(pos, head->read_var2, &tomoyo_no_rewrite_list) { + struct tomoyo_no_rewrite_entry *ptr; + ptr = list_entry(pos, struct tomoyo_no_rewrite_entry, list); + if (ptr->is_deleted) + continue; + if (!tomoyo_io_printf(head, TOMOYO_KEYWORD_DENY_REWRITE "%s\n", + ptr->pattern->name)) { + done = false; + break; + } + } + up_read(&tomoyo_no_rewrite_list_lock); + return done; +} + +/** + * tomoyo_update_file_acl - Update file's read/write/execute ACL. + * + * @filename: Filename. + * @perm: Permission (between 1 to 7). + * @domain: Pointer to "struct tomoyo_domain_info". + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + * + * This is legacy support interface for older policy syntax. + * Current policy syntax uses "allow_read/write" instead of "6", + * "allow_read" instead of "4", "allow_write" instead of "2", + * "allow_execute" instead of "1". + */ +static int tomoyo_update_file_acl(const char *filename, u8 perm, + struct tomoyo_domain_info * const domain, + const bool is_delete) +{ + if (perm > 7 || !perm) { + printk(KERN_DEBUG "%s: Invalid permission '%d %s'\n", + __func__, perm, filename); + return -EINVAL; + } + if (filename[0] != '@' && tomoyo_strendswith(filename, "/")) + /* + * Only 'allow_mkdir' and 'allow_rmdir' are valid for + * directory permissions. + */ + return 0; + if (perm & 4) + tomoyo_update_single_path_acl(TOMOYO_TYPE_READ_ACL, filename, + domain, is_delete); + if (perm & 2) + tomoyo_update_single_path_acl(TOMOYO_TYPE_WRITE_ACL, filename, + domain, is_delete); + if (perm & 1) + tomoyo_update_single_path_acl(TOMOYO_TYPE_EXECUTE_ACL, + filename, domain, is_delete); + return 0; +} + +/** + * tomoyo_check_single_path_acl2 - Check permission for single path operation. + * + * @domain: Pointer to "struct tomoyo_domain_info". + * @filename: Filename to check. + * @perm: Permission. + * @may_use_pattern: True if patterned ACL is permitted. + * + * Returns 0 on success, -EPERM otherwise. + */ +static int tomoyo_check_single_path_acl2(const struct tomoyo_domain_info * + domain, + const struct tomoyo_path_info * + filename, + const u16 perm, + const bool may_use_pattern) +{ + struct tomoyo_acl_info *ptr; + int error = -EPERM; + + down_read(&tomoyo_domain_acl_info_list_lock); + list_for_each_entry(ptr, &domain->acl_info_list, list) { + struct tomoyo_single_path_acl_record *acl; + if (tomoyo_acl_type2(ptr) != TOMOYO_TYPE_SINGLE_PATH_ACL) + continue; + acl = container_of(ptr, struct tomoyo_single_path_acl_record, + head); + if (!(acl->perm & perm)) + continue; + if (may_use_pattern || !acl->filename->is_patterned) { + if (!tomoyo_path_matches_pattern(filename, + acl->filename)) + continue; + } else { + continue; + } + error = 0; + break; + } + up_read(&tomoyo_domain_acl_info_list_lock); + return error; +} + +/** + * tomoyo_check_file_acl - Check permission for opening files. + * + * @domain: Pointer to "struct tomoyo_domain_info". + * @filename: Filename to check. + * @operation: Mode ("read" or "write" or "read/write" or "execute"). + * + * Returns 0 on success, -EPERM otherwise. + */ +static int tomoyo_check_file_acl(const struct tomoyo_domain_info *domain, + const struct tomoyo_path_info *filename, + const u8 operation) +{ + u16 perm = 0; + + if (!tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE)) + return 0; + if (operation == 6) + perm = 1 << TOMOYO_TYPE_READ_WRITE_ACL; + else if (operation == 4) + perm = 1 << TOMOYO_TYPE_READ_ACL; + else if (operation == 2) + perm = 1 << TOMOYO_TYPE_WRITE_ACL; + else if (operation == 1) + perm = 1 << TOMOYO_TYPE_EXECUTE_ACL; + else + BUG(); + return tomoyo_check_single_path_acl2(domain, filename, perm, + operation != 1); +} + +/** + * tomoyo_check_file_perm2 - Check permission for opening files. + * + * @domain: Pointer to "struct tomoyo_domain_info". + * @filename: Filename to check. + * @perm: Mode ("read" or "write" or "read/write" or "execute"). + * @operation: Operation name passed used for verbose mode. + * @mode: Access control mode. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_check_file_perm2(struct tomoyo_domain_info * const domain, + const struct tomoyo_path_info *filename, + const u8 perm, const char *operation, + const u8 mode) +{ + const bool is_enforce = (mode == 3); + const char *msg = ""; + int error = 0; + + if (!filename) + return 0; + error = tomoyo_check_file_acl(domain, filename, perm); + if (error && perm == 4 && + (domain->flags & TOMOYO_DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ) == 0 + && tomoyo_is_globally_readable_file(filename)) + error = 0; + if (perm == 6) + msg = tomoyo_sp2keyword(TOMOYO_TYPE_READ_WRITE_ACL); + else if (perm == 4) + msg = tomoyo_sp2keyword(TOMOYO_TYPE_READ_ACL); + else if (perm == 2) + msg = tomoyo_sp2keyword(TOMOYO_TYPE_WRITE_ACL); + else if (perm == 1) + msg = tomoyo_sp2keyword(TOMOYO_TYPE_EXECUTE_ACL); + else + BUG(); + if (!error) + return 0; + if (tomoyo_verbose_mode(domain)) + printk(KERN_WARNING "TOMOYO-%s: Access '%s(%s) %s' denied " + "for %s\n", tomoyo_get_msg(is_enforce), msg, operation, + filename->name, tomoyo_get_last_name(domain)); + if (is_enforce) + return error; + if (mode == 1 && tomoyo_domain_quota_is_ok(domain)) { + /* Don't use patterns for execute permission. */ + const struct tomoyo_path_info *patterned_file = (perm != 1) ? + tomoyo_get_file_pattern(filename) : filename; + tomoyo_update_file_acl(patterned_file->name, perm, + domain, false); + } + return 0; +} + +/** + * tomoyo_write_file_policy - Update file related list. + * + * @data: String to parse. + * @domain: Pointer to "struct tomoyo_domain_info". + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_write_file_policy(char *data, struct tomoyo_domain_info *domain, + const bool is_delete) +{ + char *filename = strchr(data, ' '); + char *filename2; + unsigned int perm; + u8 type; + + if (!filename) + return -EINVAL; + *filename++ = '\0'; + if (sscanf(data, "%u", &perm) == 1) + return tomoyo_update_file_acl(filename, (u8) perm, domain, + is_delete); + if (strncmp(data, "allow_", 6)) + goto out; + data += 6; + for (type = 0; type < TOMOYO_MAX_SINGLE_PATH_OPERATION; type++) { + if (strcmp(data, tomoyo_sp_keyword[type])) + continue; + return tomoyo_update_single_path_acl(type, filename, + domain, is_delete); + } + filename2 = strchr(filename, ' '); + if (!filename2) + goto out; + *filename2++ = '\0'; + for (type = 0; type < TOMOYO_MAX_DOUBLE_PATH_OPERATION; type++) { + if (strcmp(data, tomoyo_dp_keyword[type])) + continue; + return tomoyo_update_double_path_acl(type, filename, filename2, + domain, is_delete); + } + out: + return -EINVAL; +} + +/** + * tomoyo_update_single_path_acl - Update "struct tomoyo_single_path_acl_record" list. + * + * @type: Type of operation. + * @filename: Filename. + * @domain: Pointer to "struct tomoyo_domain_info". + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_update_single_path_acl(const u8 type, const char *filename, + struct tomoyo_domain_info * + const domain, const bool is_delete) +{ + static const u16 rw_mask = + (1 << TOMOYO_TYPE_READ_ACL) | (1 << TOMOYO_TYPE_WRITE_ACL); + const struct tomoyo_path_info *saved_filename; + struct tomoyo_acl_info *ptr; + struct tomoyo_single_path_acl_record *acl; + int error = -ENOMEM; + const u16 perm = 1 << type; + + if (!domain) + return -EINVAL; + if (!tomoyo_is_correct_path(filename, 0, 0, 0, __func__)) + return -EINVAL; + saved_filename = tomoyo_save_name(filename); + if (!saved_filename) + return -ENOMEM; + /***** EXCLUSIVE SECTION START *****/ + down_write(&tomoyo_domain_acl_info_list_lock); + if (is_delete) + goto delete; + list_for_each_entry(ptr, &domain->acl_info_list, list) { + if (tomoyo_acl_type1(ptr) != TOMOYO_TYPE_SINGLE_PATH_ACL) + continue; + acl = container_of(ptr, struct tomoyo_single_path_acl_record, + head); + if (acl->filename != saved_filename) + continue; + /* Special case. Clear all bits if marked as deleted. */ + if (ptr->type & TOMOYO_ACL_DELETED) + acl->perm = 0; + acl->perm |= perm; + if ((acl->perm & rw_mask) == rw_mask) + acl->perm |= 1 << TOMOYO_TYPE_READ_WRITE_ACL; + else if (acl->perm & (1 << TOMOYO_TYPE_READ_WRITE_ACL)) + acl->perm |= rw_mask; + ptr->type &= ~TOMOYO_ACL_DELETED; + error = 0; + goto out; + } + /* Not found. Append it to the tail. */ + acl = tomoyo_alloc_acl_element(TOMOYO_TYPE_SINGLE_PATH_ACL); + if (!acl) + goto out; + acl->perm = perm; + if (perm == (1 << TOMOYO_TYPE_READ_WRITE_ACL)) + acl->perm |= rw_mask; + acl->filename = saved_filename; + list_add_tail(&acl->head.list, &domain->acl_info_list); + error = 0; + goto out; + delete: + error = -ENOENT; + list_for_each_entry(ptr, &domain->acl_info_list, list) { + if (tomoyo_acl_type2(ptr) != TOMOYO_TYPE_SINGLE_PATH_ACL) + continue; + acl = container_of(ptr, struct tomoyo_single_path_acl_record, + head); + if (acl->filename != saved_filename) + continue; + acl->perm &= ~perm; + if ((acl->perm & rw_mask) != rw_mask) + acl->perm &= ~(1 << TOMOYO_TYPE_READ_WRITE_ACL); + else if (!(acl->perm & (1 << TOMOYO_TYPE_READ_WRITE_ACL))) + acl->perm &= ~rw_mask; + if (!acl->perm) + ptr->type |= TOMOYO_ACL_DELETED; + error = 0; + break; + } + out: + up_write(&tomoyo_domain_acl_info_list_lock); + /***** EXCLUSIVE SECTION END *****/ + return error; +} + +/** + * tomoyo_update_double_path_acl - Update "struct tomoyo_double_path_acl_record" list. + * + * @type: Type of operation. + * @filename1: First filename. + * @filename2: Second filename. + * @domain: Pointer to "struct tomoyo_domain_info". + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_update_double_path_acl(const u8 type, const char *filename1, + const char *filename2, + struct tomoyo_domain_info * + const domain, const bool is_delete) +{ + const struct tomoyo_path_info *saved_filename1; + const struct tomoyo_path_info *saved_filename2; + struct tomoyo_acl_info *ptr; + struct tomoyo_double_path_acl_record *acl; + int error = -ENOMEM; + const u8 perm = 1 << type; + + if (!domain) + return -EINVAL; + if (!tomoyo_is_correct_path(filename1, 0, 0, 0, __func__) || + !tomoyo_is_correct_path(filename2, 0, 0, 0, __func__)) + return -EINVAL; + saved_filename1 = tomoyo_save_name(filename1); + saved_filename2 = tomoyo_save_name(filename2); + if (!saved_filename1 || !saved_filename2) + return -ENOMEM; + /***** EXCLUSIVE SECTION START *****/ + down_write(&tomoyo_domain_acl_info_list_lock); + if (is_delete) + goto delete; + list_for_each_entry(ptr, &domain->acl_info_list, list) { + if (tomoyo_acl_type1(ptr) != TOMOYO_TYPE_DOUBLE_PATH_ACL) + continue; + acl = container_of(ptr, struct tomoyo_double_path_acl_record, + head); + if (acl->filename1 != saved_filename1 || + acl->filename2 != saved_filename2) + continue; + /* Special case. Clear all bits if marked as deleted. */ + if (ptr->type & TOMOYO_ACL_DELETED) + acl->perm = 0; + acl->perm |= perm; + ptr->type &= ~TOMOYO_ACL_DELETED; + error = 0; + goto out; + } + /* Not found. Append it to the tail. */ + acl = tomoyo_alloc_acl_element(TOMOYO_TYPE_DOUBLE_PATH_ACL); + if (!acl) + goto out; + acl->perm = perm; + acl->filename1 = saved_filename1; + acl->filename2 = saved_filename2; + list_add_tail(&acl->head.list, &domain->acl_info_list); + error = 0; + goto out; + delete: + error = -ENOENT; + list_for_each_entry(ptr, &domain->acl_info_list, list) { + if (tomoyo_acl_type2(ptr) != TOMOYO_TYPE_DOUBLE_PATH_ACL) + continue; + acl = container_of(ptr, struct tomoyo_double_path_acl_record, + head); + if (acl->filename1 != saved_filename1 || + acl->filename2 != saved_filename2) + continue; + acl->perm &= ~perm; + if (!acl->perm) + ptr->type |= TOMOYO_ACL_DELETED; + error = 0; + break; + } + out: + up_write(&tomoyo_domain_acl_info_list_lock); + /***** EXCLUSIVE SECTION END *****/ + return error; +} + +/** + * tomoyo_check_single_path_acl - Check permission for single path operation. + * + * @domain: Pointer to "struct tomoyo_domain_info". + * @type: Type of operation. + * @filename: Filename to check. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_check_single_path_acl(struct tomoyo_domain_info *domain, + const u8 type, + const struct tomoyo_path_info *filename) +{ + if (!tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE)) + return 0; + return tomoyo_check_single_path_acl2(domain, filename, 1 << type, 1); +} + +/** + * tomoyo_check_double_path_acl - Check permission for double path operation. + * + * @domain: Pointer to "struct tomoyo_domain_info". + * @type: Type of operation. + * @filename1: First filename to check. + * @filename2: Second filename to check. + * + * Returns 0 on success, -EPERM otherwise. + */ +static int tomoyo_check_double_path_acl(const struct tomoyo_domain_info *domain, + const u8 type, + const struct tomoyo_path_info * + filename1, + const struct tomoyo_path_info * + filename2) +{ + struct tomoyo_acl_info *ptr; + const u8 perm = 1 << type; + int error = -EPERM; + + if (!tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE)) + return 0; + down_read(&tomoyo_domain_acl_info_list_lock); + list_for_each_entry(ptr, &domain->acl_info_list, list) { + struct tomoyo_double_path_acl_record *acl; + if (tomoyo_acl_type2(ptr) != TOMOYO_TYPE_DOUBLE_PATH_ACL) + continue; + acl = container_of(ptr, struct tomoyo_double_path_acl_record, + head); + if (!(acl->perm & perm)) + continue; + if (!tomoyo_path_matches_pattern(filename1, acl->filename1)) + continue; + if (!tomoyo_path_matches_pattern(filename2, acl->filename2)) + continue; + error = 0; + break; + } + up_read(&tomoyo_domain_acl_info_list_lock); + return error; +} + +/** + * tomoyo_check_single_path_permission2 - Check permission for single path operation. + * + * @domain: Pointer to "struct tomoyo_domain_info". + * @operation: Type of operation. + * @filename: Filename to check. + * @mode: Access control mode. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_check_single_path_permission2(struct tomoyo_domain_info * + const domain, u8 operation, + const struct tomoyo_path_info * + filename, const u8 mode) +{ + const char *msg; + int error; + const bool is_enforce = (mode == 3); + + if (!mode) + return 0; + next: + error = tomoyo_check_single_path_acl(domain, operation, filename); + msg = tomoyo_sp2keyword(operation); + if (!error) + goto ok; + if (tomoyo_verbose_mode(domain)) + printk(KERN_WARNING "TOMOYO-%s: Access '%s %s' denied for %s\n", + tomoyo_get_msg(is_enforce), msg, filename->name, + tomoyo_get_last_name(domain)); + if (mode == 1 && tomoyo_domain_quota_is_ok(domain)) { + const char *name = tomoyo_get_file_pattern(filename)->name; + tomoyo_update_single_path_acl(operation, name, domain, false); + } + if (!is_enforce) + error = 0; + ok: + /* + * Since "allow_truncate" doesn't imply "allow_rewrite" permission, + * we need to check "allow_rewrite" permission if the filename is + * specified by "deny_rewrite" keyword. + */ + if (!error && operation == TOMOYO_TYPE_TRUNCATE_ACL && + tomoyo_is_no_rewrite_file(filename)) { + operation = TOMOYO_TYPE_REWRITE_ACL; + goto next; + } + return error; +} + +/** + * tomoyo_check_file_perm - Check permission for sysctl()'s "read" and "write". + * + * @domain: Pointer to "struct tomoyo_domain_info". + * @filename: Filename to check. + * @perm: Mode ("read" or "write" or "read/write"). + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_check_file_perm(struct tomoyo_domain_info *domain, + const char *filename, const u8 perm) +{ + struct tomoyo_path_info name; + const u8 mode = tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE); + + if (!mode) + return 0; + name.name = filename; + tomoyo_fill_path_info(&name); + return tomoyo_check_file_perm2(domain, &name, perm, "sysctl", mode); +} + +/** + * tomoyo_check_exec_perm - Check permission for "execute". + * + * @domain: Pointer to "struct tomoyo_domain_info". + * @filename: Check permission for "execute". + * @tmp: Buffer for temporary use. + * + * Returns 0 on success, negativevalue otherwise. + */ +int tomoyo_check_exec_perm(struct tomoyo_domain_info *domain, + const struct tomoyo_path_info *filename, + struct tomoyo_page_buffer *tmp) +{ + const u8 mode = tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE); + + if (!mode) + return 0; + return tomoyo_check_file_perm2(domain, filename, 1, "do_execve", mode); +} + +/** + * tomoyo_check_open_permission - Check permission for "read" and "write". + * + * @domain: Pointer to "struct tomoyo_domain_info". + * @path: Pointer to "struct path". + * @flag: Flags for open(). + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_check_open_permission(struct tomoyo_domain_info *domain, + struct path *path, const int flag) +{ + const u8 acc_mode = ACC_MODE(flag); + int error = -ENOMEM; + struct tomoyo_path_info *buf; + const u8 mode = tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE); + const bool is_enforce = (mode == 3); + + if (!mode || !path->mnt) + return 0; + if (acc_mode == 0) + return 0; + if (path->dentry->d_inode && S_ISDIR(path->dentry->d_inode->i_mode)) + /* + * I don't check directories here because mkdir() and rmdir() + * don't call me. + */ + return 0; + buf = tomoyo_get_path(path); + if (!buf) + goto out; + error = 0; + /* + * If the filename is specified by "deny_rewrite" keyword, + * we need to check "allow_rewrite" permission when the filename is not + * opened for append mode or the filename is truncated at open time. + */ + if ((acc_mode & MAY_WRITE) && + ((flag & O_TRUNC) || !(flag & O_APPEND)) && + (tomoyo_is_no_rewrite_file(buf))) { + error = tomoyo_check_single_path_permission2(domain, + TOMOYO_TYPE_REWRITE_ACL, + buf, mode); + } + if (!error) + error = tomoyo_check_file_perm2(domain, buf, acc_mode, "open", + mode); + if (!error && (flag & O_TRUNC)) + error = tomoyo_check_single_path_permission2(domain, + TOMOYO_TYPE_TRUNCATE_ACL, + buf, mode); + out: + tomoyo_free(buf); + if (!is_enforce) + error = 0; + return error; +} + +/** + * tomoyo_check_1path_perm - Check permission for "create", "unlink", "mkdir", "rmdir", "mkfifo", "mksock", "mkblock", "mkchar", "truncate" and "symlink". + * + * @domain: Pointer to "struct tomoyo_domain_info". + * @operation: Type of operation. + * @path: Pointer to "struct path". + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_check_1path_perm(struct tomoyo_domain_info *domain, + const u8 operation, struct path *path) +{ + int error = -ENOMEM; + struct tomoyo_path_info *buf; + const u8 mode = tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE); + const bool is_enforce = (mode == 3); + + if (!mode || !path->mnt) + return 0; + buf = tomoyo_get_path(path); + if (!buf) + goto out; + switch (operation) { + case TOMOYO_TYPE_MKDIR_ACL: + case TOMOYO_TYPE_RMDIR_ACL: + if (!buf->is_dir) { + /* + * tomoyo_get_path() reserves space for appending "/." + */ + strcat((char *) buf->name, "/"); + tomoyo_fill_path_info(buf); + } + } + error = tomoyo_check_single_path_permission2(domain, operation, buf, + mode); + out: + tomoyo_free(buf); + if (!is_enforce) + error = 0; + return error; +} + +/** + * tomoyo_check_rewrite_permission - Check permission for "rewrite". + * + * @domain: Pointer to "struct tomoyo_domain_info". + * @filp: Pointer to "struct file". + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_check_rewrite_permission(struct tomoyo_domain_info *domain, + struct file *filp) +{ + int error = -ENOMEM; + const u8 mode = tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE); + const bool is_enforce = (mode == 3); + struct tomoyo_path_info *buf; + + if (!mode || !filp->f_path.mnt) + return 0; + buf = tomoyo_get_path(&filp->f_path); + if (!buf) + goto out; + if (!tomoyo_is_no_rewrite_file(buf)) { + error = 0; + goto out; + } + error = tomoyo_check_single_path_permission2(domain, + TOMOYO_TYPE_REWRITE_ACL, + buf, mode); + out: + tomoyo_free(buf); + if (!is_enforce) + error = 0; + return error; +} + +/** + * tomoyo_check_2path_perm - Check permission for "rename" and "link". + * + * @domain: Pointer to "struct tomoyo_domain_info". + * @operation: Type of operation. + * @path1: Pointer to "struct path". + * @path2: Pointer to "struct path". + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_check_2path_perm(struct tomoyo_domain_info * const domain, + const u8 operation, struct path *path1, + struct path *path2) +{ + int error = -ENOMEM; + struct tomoyo_path_info *buf1, *buf2; + const u8 mode = tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE); + const bool is_enforce = (mode == 3); + const char *msg; + + if (!mode || !path1->mnt || !path2->mnt) + return 0; + buf1 = tomoyo_get_path(path1); + buf2 = tomoyo_get_path(path2); + if (!buf1 || !buf2) + goto out; + { + struct dentry *dentry = path1->dentry; + if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)) { + /* + * tomoyo_get_path() reserves space for appending "/." + */ + if (!buf1->is_dir) { + strcat((char *) buf1->name, "/"); + tomoyo_fill_path_info(buf1); + } + if (!buf2->is_dir) { + strcat((char *) buf2->name, "/"); + tomoyo_fill_path_info(buf2); + } + } + } + error = tomoyo_check_double_path_acl(domain, operation, buf1, buf2); + msg = tomoyo_dp2keyword(operation); + if (!error) + goto out; + if (tomoyo_verbose_mode(domain)) + printk(KERN_WARNING "TOMOYO-%s: Access '%s %s %s' " + "denied for %s\n", tomoyo_get_msg(is_enforce), + msg, buf1->name, buf2->name, + tomoyo_get_last_name(domain)); + if (mode == 1 && tomoyo_domain_quota_is_ok(domain)) { + const char *name1 = tomoyo_get_file_pattern(buf1)->name; + const char *name2 = tomoyo_get_file_pattern(buf2)->name; + tomoyo_update_double_path_acl(operation, name1, name2, domain, + false); + } + out: + tomoyo_free(buf1); + tomoyo_free(buf2); + if (!is_enforce) + error = 0; + return error; +} -- cgit v1.2.3 From 26a2a1c9eb88d9aca8891575b3b986812e073872 Mon Sep 17 00:00:00 2001 From: Kentaro Takeda Date: Thu, 5 Feb 2009 17:18:15 +0900 Subject: Domain transition handler. This file controls domain creation/deletion/transition. Every process belongs to a domain in TOMOYO Linux. Domain transition occurs when execve(2) is called and the domain is expressed as 'process invocation history', such as ' /sbin/init /etc/init.d/rc'. Domain information is stored in current->cred->security field. Signed-off-by: Kentaro Takeda Signed-off-by: Tetsuo Handa Signed-off-by: Toshiharu Harada Signed-off-by: James Morris --- security/tomoyo/domain.c | 878 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 878 insertions(+) create mode 100644 security/tomoyo/domain.c (limited to 'security') diff --git a/security/tomoyo/domain.c b/security/tomoyo/domain.c new file mode 100644 index 00000000000..92af8f50e0f --- /dev/null +++ b/security/tomoyo/domain.c @@ -0,0 +1,878 @@ +/* + * security/tomoyo/domain.c + * + * Implementation of the Domain-Based Mandatory Access Control. + * + * Copyright (C) 2005-2009 NTT DATA CORPORATION + * + * Version: 2.2.0-pre 2009/02/01 + * + */ + +#include "common.h" +#include "tomoyo.h" +#include "realpath.h" +#include + +/* Variables definitions.*/ + +/* The initial domain. */ +struct tomoyo_domain_info tomoyo_kernel_domain; + +/* The list for "struct tomoyo_domain_info". */ +LIST_HEAD(tomoyo_domain_list); +DECLARE_RWSEM(tomoyo_domain_list_lock); + +/* Structure for "initialize_domain" and "no_initialize_domain" keyword. */ +struct tomoyo_domain_initializer_entry { + struct list_head list; + const struct tomoyo_path_info *domainname; /* This may be NULL */ + const struct tomoyo_path_info *program; + bool is_deleted; + bool is_not; /* True if this entry is "no_initialize_domain". */ + /* True if the domainname is tomoyo_get_last_name(). */ + bool is_last_name; +}; + +/* Structure for "keep_domain" and "no_keep_domain" keyword. */ +struct tomoyo_domain_keeper_entry { + struct list_head list; + const struct tomoyo_path_info *domainname; + const struct tomoyo_path_info *program; /* This may be NULL */ + bool is_deleted; + bool is_not; /* True if this entry is "no_keep_domain". */ + /* True if the domainname is tomoyo_get_last_name(). */ + bool is_last_name; +}; + +/* Structure for "alias" keyword. */ +struct tomoyo_alias_entry { + struct list_head list; + const struct tomoyo_path_info *original_name; + const struct tomoyo_path_info *aliased_name; + bool is_deleted; +}; + +/** + * tomoyo_set_domain_flag - Set or clear domain's attribute flags. + * + * @domain: Pointer to "struct tomoyo_domain_info". + * @is_delete: True if it is a delete request. + * @flags: Flags to set or clear. + * + * Returns nothing. + */ +void tomoyo_set_domain_flag(struct tomoyo_domain_info *domain, + const bool is_delete, const u8 flags) +{ + /* We need to serialize because this is bitfield operation. */ + static DEFINE_SPINLOCK(lock); + /***** CRITICAL SECTION START *****/ + spin_lock(&lock); + if (!is_delete) + domain->flags |= flags; + else + domain->flags &= ~flags; + spin_unlock(&lock); + /***** CRITICAL SECTION END *****/ +} + +/** + * tomoyo_get_last_name - Get last component of a domainname. + * + * @domain: Pointer to "struct tomoyo_domain_info". + * + * Returns the last component of the domainname. + */ +const char *tomoyo_get_last_name(const struct tomoyo_domain_info *domain) +{ + const char *cp0 = domain->domainname->name; + const char *cp1 = strrchr(cp0, ' '); + + if (cp1) + return cp1 + 1; + return cp0; +} + +/* The list for "struct tomoyo_domain_initializer_entry". */ +static LIST_HEAD(tomoyo_domain_initializer_list); +static DECLARE_RWSEM(tomoyo_domain_initializer_list_lock); + +/** + * tomoyo_update_domain_initializer_entry - Update "struct tomoyo_domain_initializer_entry" list. + * + * @domainname: The name of domain. May be NULL. + * @program: The name of program. + * @is_not: True if it is "no_initialize_domain" entry. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_update_domain_initializer_entry(const char *domainname, + const char *program, + const bool is_not, + const bool is_delete) +{ + struct tomoyo_domain_initializer_entry *new_entry; + struct tomoyo_domain_initializer_entry *ptr; + const struct tomoyo_path_info *saved_program; + const struct tomoyo_path_info *saved_domainname = NULL; + int error = -ENOMEM; + bool is_last_name = false; + + if (!tomoyo_is_correct_path(program, 1, -1, -1, __func__)) + return -EINVAL; /* No patterns allowed. */ + if (domainname) { + if (!tomoyo_is_domain_def(domainname) && + tomoyo_is_correct_path(domainname, 1, -1, -1, __func__)) + is_last_name = true; + else if (!tomoyo_is_correct_domain(domainname, __func__)) + return -EINVAL; + saved_domainname = tomoyo_save_name(domainname); + if (!saved_domainname) + return -ENOMEM; + } + saved_program = tomoyo_save_name(program); + if (!saved_program) + return -ENOMEM; + /***** EXCLUSIVE SECTION START *****/ + down_write(&tomoyo_domain_initializer_list_lock); + list_for_each_entry(ptr, &tomoyo_domain_initializer_list, list) { + if (ptr->is_not != is_not || + ptr->domainname != saved_domainname || + ptr->program != saved_program) + continue; + ptr->is_deleted = is_delete; + error = 0; + goto out; + } + if (is_delete) { + error = -ENOENT; + goto out; + } + new_entry = tomoyo_alloc_element(sizeof(*new_entry)); + if (!new_entry) + goto out; + new_entry->domainname = saved_domainname; + new_entry->program = saved_program; + new_entry->is_not = is_not; + new_entry->is_last_name = is_last_name; + list_add_tail(&new_entry->list, &tomoyo_domain_initializer_list); + error = 0; + out: + up_write(&tomoyo_domain_initializer_list_lock); + /***** EXCLUSIVE SECTION END *****/ + return error; +} + +/** + * tomoyo_read_domain_initializer_policy - Read "struct tomoyo_domain_initializer_entry" list. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns true on success, false otherwise. + */ +bool tomoyo_read_domain_initializer_policy(struct tomoyo_io_buffer *head) +{ + struct list_head *pos; + bool done = true; + + down_read(&tomoyo_domain_initializer_list_lock); + list_for_each_cookie(pos, head->read_var2, + &tomoyo_domain_initializer_list) { + const char *no; + const char *from = ""; + const char *domain = ""; + struct tomoyo_domain_initializer_entry *ptr; + ptr = list_entry(pos, struct tomoyo_domain_initializer_entry, + list); + if (ptr->is_deleted) + continue; + no = ptr->is_not ? "no_" : ""; + if (ptr->domainname) { + from = " from "; + domain = ptr->domainname->name; + } + if (!tomoyo_io_printf(head, + "%s" TOMOYO_KEYWORD_INITIALIZE_DOMAIN + "%s%s%s\n", no, ptr->program->name, from, + domain)) { + done = false; + break; + } + } + up_read(&tomoyo_domain_initializer_list_lock); + return done; +} + +/** + * tomoyo_write_domain_initializer_policy - Write "struct tomoyo_domain_initializer_entry" list. + * + * @data: String to parse. + * @is_not: True if it is "no_initialize_domain" entry. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_write_domain_initializer_policy(char *data, const bool is_not, + const bool is_delete) +{ + char *cp = strstr(data, " from "); + + if (cp) { + *cp = '\0'; + return tomoyo_update_domain_initializer_entry(cp + 6, data, + is_not, + is_delete); + } + return tomoyo_update_domain_initializer_entry(NULL, data, is_not, + is_delete); +} + +/** + * tomoyo_is_domain_initializer - Check whether the given program causes domainname reinitialization. + * + * @domainname: The name of domain. + * @program: The name of program. + * @last_name: The last component of @domainname. + * + * Returns true if executing @program reinitializes domain transition, + * false otherwise. + */ +static bool tomoyo_is_domain_initializer(const struct tomoyo_path_info * + domainname, + const struct tomoyo_path_info *program, + const struct tomoyo_path_info * + last_name) +{ + struct tomoyo_domain_initializer_entry *ptr; + bool flag = false; + + down_read(&tomoyo_domain_initializer_list_lock); + list_for_each_entry(ptr, &tomoyo_domain_initializer_list, list) { + if (ptr->is_deleted) + continue; + if (ptr->domainname) { + if (!ptr->is_last_name) { + if (ptr->domainname != domainname) + continue; + } else { + if (tomoyo_pathcmp(ptr->domainname, last_name)) + continue; + } + } + if (tomoyo_pathcmp(ptr->program, program)) + continue; + if (ptr->is_not) { + flag = false; + break; + } + flag = true; + } + up_read(&tomoyo_domain_initializer_list_lock); + return flag; +} + +/* The list for "struct tomoyo_domain_keeper_entry". */ +static LIST_HEAD(tomoyo_domain_keeper_list); +static DECLARE_RWSEM(tomoyo_domain_keeper_list_lock); + +/** + * tomoyo_update_domain_keeper_entry - Update "struct tomoyo_domain_keeper_entry" list. + * + * @domainname: The name of domain. + * @program: The name of program. May be NULL. + * @is_not: True if it is "no_keep_domain" entry. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_update_domain_keeper_entry(const char *domainname, + const char *program, + const bool is_not, + const bool is_delete) +{ + struct tomoyo_domain_keeper_entry *new_entry; + struct tomoyo_domain_keeper_entry *ptr; + const struct tomoyo_path_info *saved_domainname; + const struct tomoyo_path_info *saved_program = NULL; + static DEFINE_MUTEX(lock); + int error = -ENOMEM; + bool is_last_name = false; + + if (!tomoyo_is_domain_def(domainname) && + tomoyo_is_correct_path(domainname, 1, -1, -1, __func__)) + is_last_name = true; + else if (!tomoyo_is_correct_domain(domainname, __func__)) + return -EINVAL; + if (program) { + if (!tomoyo_is_correct_path(program, 1, -1, -1, __func__)) + return -EINVAL; + saved_program = tomoyo_save_name(program); + if (!saved_program) + return -ENOMEM; + } + saved_domainname = tomoyo_save_name(domainname); + if (!saved_domainname) + return -ENOMEM; + /***** EXCLUSIVE SECTION START *****/ + down_write(&tomoyo_domain_keeper_list_lock); + list_for_each_entry(ptr, &tomoyo_domain_keeper_list, list) { + if (ptr->is_not != is_not || + ptr->domainname != saved_domainname || + ptr->program != saved_program) + continue; + ptr->is_deleted = is_delete; + error = 0; + goto out; + } + if (is_delete) { + error = -ENOENT; + goto out; + } + new_entry = tomoyo_alloc_element(sizeof(*new_entry)); + if (!new_entry) + goto out; + new_entry->domainname = saved_domainname; + new_entry->program = saved_program; + new_entry->is_not = is_not; + new_entry->is_last_name = is_last_name; + list_add_tail(&new_entry->list, &tomoyo_domain_keeper_list); + error = 0; + out: + up_write(&tomoyo_domain_keeper_list_lock); + /***** EXCLUSIVE SECTION END *****/ + return error; +} + +/** + * tomoyo_write_domain_keeper_policy - Write "struct tomoyo_domain_keeper_entry" list. + * + * @data: String to parse. + * @is_not: True if it is "no_keep_domain" entry. + * @is_delete: True if it is a delete request. + * + */ +int tomoyo_write_domain_keeper_policy(char *data, const bool is_not, + const bool is_delete) +{ + char *cp = strstr(data, " from "); + + if (cp) { + *cp = '\0'; + return tomoyo_update_domain_keeper_entry(cp + 6, data, is_not, + is_delete); + } + return tomoyo_update_domain_keeper_entry(data, NULL, is_not, is_delete); +} + +/** + * tomoyo_read_domain_keeper_policy - Read "struct tomoyo_domain_keeper_entry" list. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns true on success, false otherwise. + */ +bool tomoyo_read_domain_keeper_policy(struct tomoyo_io_buffer *head) +{ + struct list_head *pos; + bool done = false; + + down_read(&tomoyo_domain_keeper_list_lock); + list_for_each_cookie(pos, head->read_var2, + &tomoyo_domain_keeper_list) { + struct tomoyo_domain_keeper_entry *ptr; + const char *no; + const char *from = ""; + const char *program = ""; + + ptr = list_entry(pos, struct tomoyo_domain_keeper_entry, list); + if (ptr->is_deleted) + continue; + no = ptr->is_not ? "no_" : ""; + if (ptr->program) { + from = " from "; + program = ptr->program->name; + } + if (!tomoyo_io_printf(head, + "%s" TOMOYO_KEYWORD_KEEP_DOMAIN + "%s%s%s\n", no, program, from, + ptr->domainname->name)) { + done = false; + break; + } + } + up_read(&tomoyo_domain_keeper_list_lock); + return done; +} + +/** + * tomoyo_is_domain_keeper - Check whether the given program causes domain transition suppression. + * + * @domainname: The name of domain. + * @program: The name of program. + * @last_name: The last component of @domainname. + * + * Returns true if executing @program supresses domain transition, + * false otherwise. + */ +static bool tomoyo_is_domain_keeper(const struct tomoyo_path_info *domainname, + const struct tomoyo_path_info *program, + const struct tomoyo_path_info *last_name) +{ + struct tomoyo_domain_keeper_entry *ptr; + bool flag = false; + + down_read(&tomoyo_domain_keeper_list_lock); + list_for_each_entry(ptr, &tomoyo_domain_keeper_list, list) { + if (ptr->is_deleted) + continue; + if (!ptr->is_last_name) { + if (ptr->domainname != domainname) + continue; + } else { + if (tomoyo_pathcmp(ptr->domainname, last_name)) + continue; + } + if (ptr->program && tomoyo_pathcmp(ptr->program, program)) + continue; + if (ptr->is_not) { + flag = false; + break; + } + flag = true; + } + up_read(&tomoyo_domain_keeper_list_lock); + return flag; +} + +/* The list for "struct tomoyo_alias_entry". */ +static LIST_HEAD(tomoyo_alias_list); +static DECLARE_RWSEM(tomoyo_alias_list_lock); + +/** + * tomoyo_update_alias_entry - Update "struct tomoyo_alias_entry" list. + * + * @original_name: The original program's real name. + * @aliased_name: The symbolic program's symbolic link's name. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_update_alias_entry(const char *original_name, + const char *aliased_name, + const bool is_delete) +{ + struct tomoyo_alias_entry *new_entry; + struct tomoyo_alias_entry *ptr; + const struct tomoyo_path_info *saved_original_name; + const struct tomoyo_path_info *saved_aliased_name; + int error = -ENOMEM; + + if (!tomoyo_is_correct_path(original_name, 1, -1, -1, __func__) || + !tomoyo_is_correct_path(aliased_name, 1, -1, -1, __func__)) + return -EINVAL; /* No patterns allowed. */ + saved_original_name = tomoyo_save_name(original_name); + saved_aliased_name = tomoyo_save_name(aliased_name); + if (!saved_original_name || !saved_aliased_name) + return -ENOMEM; + /***** EXCLUSIVE SECTION START *****/ + down_write(&tomoyo_alias_list_lock); + list_for_each_entry(ptr, &tomoyo_alias_list, list) { + if (ptr->original_name != saved_original_name || + ptr->aliased_name != saved_aliased_name) + continue; + ptr->is_deleted = is_delete; + error = 0; + goto out; + } + if (is_delete) { + error = -ENOENT; + goto out; + } + new_entry = tomoyo_alloc_element(sizeof(*new_entry)); + if (!new_entry) + goto out; + new_entry->original_name = saved_original_name; + new_entry->aliased_name = saved_aliased_name; + list_add_tail(&new_entry->list, &tomoyo_alias_list); + error = 0; + out: + up_write(&tomoyo_alias_list_lock); + /***** EXCLUSIVE SECTION END *****/ + return error; +} + +/** + * tomoyo_read_alias_policy - Read "struct tomoyo_alias_entry" list. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns true on success, false otherwise. + */ +bool tomoyo_read_alias_policy(struct tomoyo_io_buffer *head) +{ + struct list_head *pos; + bool done = true; + + down_read(&tomoyo_alias_list_lock); + list_for_each_cookie(pos, head->read_var2, &tomoyo_alias_list) { + struct tomoyo_alias_entry *ptr; + + ptr = list_entry(pos, struct tomoyo_alias_entry, list); + if (ptr->is_deleted) + continue; + if (!tomoyo_io_printf(head, TOMOYO_KEYWORD_ALIAS "%s %s\n", + ptr->original_name->name, + ptr->aliased_name->name)) { + done = false; + break; + } + } + up_read(&tomoyo_alias_list_lock); + return done; +} + +/** + * tomoyo_write_alias_policy - Write "struct tomoyo_alias_entry" list. + * + * @data: String to parse. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_write_alias_policy(char *data, const bool is_delete) +{ + char *cp = strchr(data, ' '); + + if (!cp) + return -EINVAL; + *cp++ = '\0'; + return tomoyo_update_alias_entry(data, cp, is_delete); +} + +/* Domain create/delete/undelete handler. */ + +/* #define TOMOYO_DEBUG_DOMAIN_UNDELETE */ + +/** + * tomoyo_delete_domain - Delete a domain. + * + * @domainname: The name of domain. + * + * Returns 0. + */ +int tomoyo_delete_domain(char *domainname) +{ + struct tomoyo_domain_info *domain; + struct tomoyo_path_info name; + + name.name = domainname; + tomoyo_fill_path_info(&name); + /***** EXCLUSIVE SECTION START *****/ + down_write(&tomoyo_domain_list_lock); +#ifdef TOMOYO_DEBUG_DOMAIN_UNDELETE + printk(KERN_DEBUG "tomoyo_delete_domain %s\n", domainname); + list_for_each_entry(domain, &tomoyo_domain_list, list) { + if (tomoyo_pathcmp(domain->domainname, &name)) + continue; + printk(KERN_DEBUG "List: %p %u\n", domain, domain->is_deleted); + } +#endif + /* Is there an active domain? */ + list_for_each_entry(domain, &tomoyo_domain_list, list) { + struct tomoyo_domain_info *domain2; + /* Never delete tomoyo_kernel_domain */ + if (domain == &tomoyo_kernel_domain) + continue; + if (domain->is_deleted || + tomoyo_pathcmp(domain->domainname, &name)) + continue; + /* Mark already deleted domains as non undeletable. */ + list_for_each_entry(domain2, &tomoyo_domain_list, list) { + if (!domain2->is_deleted || + tomoyo_pathcmp(domain2->domainname, &name)) + continue; +#ifdef TOMOYO_DEBUG_DOMAIN_UNDELETE + if (domain2->is_deleted != 255) + printk(KERN_DEBUG + "Marked %p as non undeletable\n", + domain2); +#endif + domain2->is_deleted = 255; + } + /* Delete and mark active domain as undeletable. */ + domain->is_deleted = 1; +#ifdef TOMOYO_DEBUG_DOMAIN_UNDELETE + printk(KERN_DEBUG "Marked %p as undeletable\n", domain); +#endif + break; + } + up_write(&tomoyo_domain_list_lock); + /***** EXCLUSIVE SECTION END *****/ + return 0; +} + +/** + * tomoyo_undelete_domain - Undelete a domain. + * + * @domainname: The name of domain. + * + * Returns pointer to "struct tomoyo_domain_info" on success, NULL otherwise. + */ +struct tomoyo_domain_info *tomoyo_undelete_domain(const char *domainname) +{ + struct tomoyo_domain_info *domain; + struct tomoyo_domain_info *candidate_domain = NULL; + struct tomoyo_path_info name; + + name.name = domainname; + tomoyo_fill_path_info(&name); + /***** EXCLUSIVE SECTION START *****/ + down_write(&tomoyo_domain_list_lock); +#ifdef TOMOYO_DEBUG_DOMAIN_UNDELETE + printk(KERN_DEBUG "tomoyo_undelete_domain %s\n", domainname); + list_for_each_entry(domain, &tomoyo_domain_list, list) { + if (tomoyo_pathcmp(domain->domainname, &name)) + continue; + printk(KERN_DEBUG "List: %p %u\n", domain, domain->is_deleted); + } +#endif + list_for_each_entry(domain, &tomoyo_domain_list, list) { + if (tomoyo_pathcmp(&name, domain->domainname)) + continue; + if (!domain->is_deleted) { + /* This domain is active. I can't undelete. */ + candidate_domain = NULL; +#ifdef TOMOYO_DEBUG_DOMAIN_UNDELETE + printk(KERN_DEBUG "%p is active. I can't undelete.\n", + domain); +#endif + break; + } + /* Is this domain undeletable? */ + if (domain->is_deleted == 1) + candidate_domain = domain; + } + if (candidate_domain) { + candidate_domain->is_deleted = 0; +#ifdef TOMOYO_DEBUG_DOMAIN_UNDELETE + printk(KERN_DEBUG "%p was undeleted.\n", candidate_domain); +#endif + } + up_write(&tomoyo_domain_list_lock); + /***** EXCLUSIVE SECTION END *****/ + return candidate_domain; +} + +/** + * tomoyo_find_or_assign_new_domain - Create a domain. + * + * @domainname: The name of domain. + * @profile: Profile number to assign if the domain was newly created. + * + * Returns pointer to "struct tomoyo_domain_info" on success, NULL otherwise. + */ +struct tomoyo_domain_info *tomoyo_find_or_assign_new_domain(const char * + domainname, + const u8 profile) +{ + struct tomoyo_domain_info *domain = NULL; + const struct tomoyo_path_info *saved_domainname; + + /***** EXCLUSIVE SECTION START *****/ + down_write(&tomoyo_domain_list_lock); + domain = tomoyo_find_domain(domainname); + if (domain) + goto out; + if (!tomoyo_is_correct_domain(domainname, __func__)) + goto out; + saved_domainname = tomoyo_save_name(domainname); + if (!saved_domainname) + goto out; + /* Can I reuse memory of deleted domain? */ + list_for_each_entry(domain, &tomoyo_domain_list, list) { + struct task_struct *p; + struct tomoyo_acl_info *ptr; + bool flag; + if (!domain->is_deleted || + domain->domainname != saved_domainname) + continue; + flag = false; + /***** CRITICAL SECTION START *****/ + read_lock(&tasklist_lock); + for_each_process(p) { + if (tomoyo_real_domain(p) != domain) + continue; + flag = true; + break; + } + read_unlock(&tasklist_lock); + /***** CRITICAL SECTION END *****/ + if (flag) + continue; +#ifdef TOMOYO_DEBUG_DOMAIN_UNDELETE + printk(KERN_DEBUG "Reusing %p %s\n", domain, + domain->domainname->name); +#endif + list_for_each_entry(ptr, &domain->acl_info_list, list) { + ptr->type |= TOMOYO_ACL_DELETED; + } + tomoyo_set_domain_flag(domain, true, domain->flags); + domain->profile = profile; + domain->quota_warned = false; + mb(); /* Avoid out-of-order execution. */ + domain->is_deleted = 0; + goto out; + } + /* No memory reusable. Create using new memory. */ + domain = tomoyo_alloc_element(sizeof(*domain)); + if (domain) { + INIT_LIST_HEAD(&domain->acl_info_list); + domain->domainname = saved_domainname; + domain->profile = profile; + list_add_tail(&domain->list, &tomoyo_domain_list); + } + out: + up_write(&tomoyo_domain_list_lock); + /***** EXCLUSIVE SECTION END *****/ + return domain; +} + +/** + * tomoyo_find_next_domain - Find a domain. + * + * @bprm: Pointer to "struct linux_binprm". + * @next_domain: Pointer to pointer to "struct tomoyo_domain_info". + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_find_next_domain(struct linux_binprm *bprm, + struct tomoyo_domain_info **next_domain) +{ + /* + * This function assumes that the size of buffer returned by + * tomoyo_realpath() = TOMOYO_MAX_PATHNAME_LEN. + */ + struct tomoyo_page_buffer *tmp = tomoyo_alloc(sizeof(*tmp)); + struct tomoyo_domain_info *old_domain = tomoyo_domain(); + struct tomoyo_domain_info *domain = NULL; + const char *old_domain_name = old_domain->domainname->name; + const char *original_name = bprm->filename; + char *new_domain_name = NULL; + char *real_program_name = NULL; + char *symlink_program_name = NULL; + const u8 mode = tomoyo_check_flags(old_domain, TOMOYO_MAC_FOR_FILE); + const bool is_enforce = (mode == 3); + int retval = -ENOMEM; + struct tomoyo_path_info r; /* real name */ + struct tomoyo_path_info s; /* symlink name */ + struct tomoyo_path_info l; /* last name */ + static bool initialized; + + if (!tmp) + goto out; + + if (!initialized) { + /* + * Built-in initializers. This is needed because policies are + * not loaded until starting /sbin/init. + */ + tomoyo_update_domain_initializer_entry(NULL, "/sbin/hotplug", + false, false); + tomoyo_update_domain_initializer_entry(NULL, "/sbin/modprobe", + false, false); + initialized = true; + } + + /* Get tomoyo_realpath of program. */ + retval = -ENOENT; + /* I hope tomoyo_realpath() won't fail with -ENOMEM. */ + real_program_name = tomoyo_realpath(original_name); + if (!real_program_name) + goto out; + /* Get tomoyo_realpath of symbolic link. */ + symlink_program_name = tomoyo_realpath_nofollow(original_name); + if (!symlink_program_name) + goto out; + + r.name = real_program_name; + tomoyo_fill_path_info(&r); + s.name = symlink_program_name; + tomoyo_fill_path_info(&s); + l.name = tomoyo_get_last_name(old_domain); + tomoyo_fill_path_info(&l); + + /* Check 'alias' directive. */ + if (tomoyo_pathcmp(&r, &s)) { + struct tomoyo_alias_entry *ptr; + /* Is this program allowed to be called via symbolic links? */ + down_read(&tomoyo_alias_list_lock); + list_for_each_entry(ptr, &tomoyo_alias_list, list) { + if (ptr->is_deleted || + tomoyo_pathcmp(&r, ptr->original_name) || + tomoyo_pathcmp(&s, ptr->aliased_name)) + continue; + memset(real_program_name, 0, TOMOYO_MAX_PATHNAME_LEN); + strncpy(real_program_name, ptr->aliased_name->name, + TOMOYO_MAX_PATHNAME_LEN - 1); + tomoyo_fill_path_info(&r); + break; + } + up_read(&tomoyo_alias_list_lock); + } + + /* Check execute permission. */ + retval = tomoyo_check_exec_perm(old_domain, &r, tmp); + if (retval < 0) + goto out; + + new_domain_name = tmp->buffer; + if (tomoyo_is_domain_initializer(old_domain->domainname, &r, &l)) { + /* Transit to the child of tomoyo_kernel_domain domain. */ + snprintf(new_domain_name, TOMOYO_MAX_PATHNAME_LEN + 1, + TOMOYO_ROOT_NAME " " "%s", real_program_name); + } else if (old_domain == &tomoyo_kernel_domain && + !tomoyo_policy_loaded) { + /* + * Needn't to transit from kernel domain before starting + * /sbin/init. But transit from kernel domain if executing + * initializers because they might start before /sbin/init. + */ + domain = old_domain; + } else if (tomoyo_is_domain_keeper(old_domain->domainname, &r, &l)) { + /* Keep current domain. */ + domain = old_domain; + } else { + /* Normal domain transition. */ + snprintf(new_domain_name, TOMOYO_MAX_PATHNAME_LEN + 1, + "%s %s", old_domain_name, real_program_name); + } + if (domain || strlen(new_domain_name) >= TOMOYO_MAX_PATHNAME_LEN) + goto done; + down_read(&tomoyo_domain_list_lock); + domain = tomoyo_find_domain(new_domain_name); + up_read(&tomoyo_domain_list_lock); + if (domain) + goto done; + if (is_enforce) + goto done; + domain = tomoyo_find_or_assign_new_domain(new_domain_name, + old_domain->profile); + done: + if (domain) + goto out; + printk(KERN_WARNING "TOMOYO-ERROR: Domain '%s' not defined.\n", + new_domain_name); + if (is_enforce) + retval = -EPERM; + else + tomoyo_set_domain_flag(old_domain, false, + TOMOYO_DOMAIN_FLAGS_TRANSITION_FAILED); + out: + tomoyo_free(real_program_name); + tomoyo_free(symlink_program_name); + *next_domain = domain ? domain : old_domain; + tomoyo_free(tmp); + return retval; +} -- cgit v1.2.3 From f7433243770c77979c396b4c7449a10e9b3521db Mon Sep 17 00:00:00 2001 From: Kentaro Takeda Date: Thu, 5 Feb 2009 17:18:16 +0900 Subject: LSM adapter functions. DAC's permissions and TOMOYO's permissions are not one-to-one mapping. Regarding DAC, there are "read", "write", "execute" permissions. Regarding TOMOYO, there are "allow_read", "allow_write", "allow_read/write", "allow_execute", "allow_create", "allow_unlink", "allow_mkdir", "allow_rmdir", "allow_mkfifo", "allow_mksock", "allow_mkblock", "allow_mkchar", "allow_truncate", "allow_symlink", "allow_rewrite", "allow_link", "allow_rename" permissions. +----------------------------------+----------------------------------+ | requested operation | required TOMOYO's permission | +----------------------------------+----------------------------------+ | sys_open(O_RDONLY) | allow_read | +----------------------------------+----------------------------------+ | sys_open(O_WRONLY) | allow_write | +----------------------------------+----------------------------------+ | sys_open(O_RDWR) | allow_read/write | +----------------------------------+----------------------------------+ | open_exec() from do_execve() | allow_execute | +----------------------------------+----------------------------------+ | open_exec() from !do_execve() | allow_read | +----------------------------------+----------------------------------+ | sys_read() | (none) | +----------------------------------+----------------------------------+ | sys_write() | (none) | +----------------------------------+----------------------------------+ | sys_mmap() | (none) | +----------------------------------+----------------------------------+ | sys_uselib() | allow_read | +----------------------------------+----------------------------------+ | sys_open(O_CREAT) | allow_create | +----------------------------------+----------------------------------+ | sys_open(O_TRUNC) | allow_truncate | +----------------------------------+----------------------------------+ | sys_truncate() | allow_truncate | +----------------------------------+----------------------------------+ | sys_ftruncate() | allow_truncate | +----------------------------------+----------------------------------+ | sys_open() without O_APPEND | allow_rewrite | +----------------------------------+----------------------------------+ | setfl() without O_APPEND | allow_rewrite | +----------------------------------+----------------------------------+ | sys_sysctl() for writing | allow_write | +----------------------------------+----------------------------------+ | sys_sysctl() for reading | allow_read | +----------------------------------+----------------------------------+ | sys_unlink() | allow_unlink | +----------------------------------+----------------------------------+ | sys_mknod(S_IFREG) | allow_create | +----------------------------------+----------------------------------+ | sys_mknod(0) | allow_create | +----------------------------------+----------------------------------+ | sys_mknod(S_IFIFO) | allow_mkfifo | +----------------------------------+----------------------------------+ | sys_mknod(S_IFSOCK) | allow_mksock | +----------------------------------+----------------------------------+ | sys_bind(AF_UNIX) | allow_mksock | +----------------------------------+----------------------------------+ | sys_mknod(S_IFBLK) | allow_mkblock | +----------------------------------+----------------------------------+ | sys_mknod(S_IFCHR) | allow_mkchar | +----------------------------------+----------------------------------+ | sys_symlink() | allow_symlink | +----------------------------------+----------------------------------+ | sys_mkdir() | allow_mkdir | +----------------------------------+----------------------------------+ | sys_rmdir() | allow_rmdir | +----------------------------------+----------------------------------+ | sys_link() | allow_link | +----------------------------------+----------------------------------+ | sys_rename() | allow_rename | +----------------------------------+----------------------------------+ TOMOYO requires "allow_execute" permission of a pathname passed to do_execve() but does not require "allow_read" permission of that pathname. Let's consider 3 patterns (statically linked, dynamically linked, shell script). This description is to some degree simplified. $ cat hello.c #include int main() { printf("Hello\n"); return 0; } $ cat hello.sh #! /bin/sh echo "Hello" $ gcc -static -o hello-static hello.c $ gcc -o hello-dynamic hello.c $ chmod 755 hello.sh Case 1 -- Executing hello-static from bash. (1) The bash process calls fork() and the child process requests do_execve("hello-static"). (2) The kernel checks "allow_execute hello-static" from "bash" domain. (3) The kernel calculates "bash hello-static" as the domain to transit to. (4) The kernel overwrites the child process by "hello-static". (5) The child process transits to "bash hello-static" domain. (6) The "hello-static" starts and finishes. Case 2 -- Executing hello-dynamic from bash. (1) The bash process calls fork() and the child process requests do_execve("hello-dynamic"). (2) The kernel checks "allow_execute hello-dynamic" from "bash" domain. (3) The kernel calculates "bash hello-dynamic" as the domain to transit to. (4) The kernel checks "allow_read ld-linux.so" from "bash hello-dynamic" domain. I think permission to access ld-linux.so should be charged hello-dynamic program, for "hello-dynamic needs ld-linux.so" is not a fault of bash program. (5) The kernel overwrites the child process by "hello-dynamic". (6) The child process transits to "bash hello-dynamic" domain. (7) The "hello-dynamic" starts and finishes. Case 3 -- Executing hello.sh from bash. (1) The bash process calls fork() and the child process requests do_execve("hello.sh"). (2) The kernel checks "allow_execute hello.sh" from "bash" domain. (3) The kernel calculates "bash hello.sh" as the domain to transit to. (4) The kernel checks "allow_read /bin/sh" from "bash hello.sh" domain. I think permission to access /bin/sh should be charged hello.sh program, for "hello.sh needs /bin/sh" is not a fault of bash program. (5) The kernel overwrites the child process by "/bin/sh". (6) The child process transits to "bash hello.sh" domain. (7) The "/bin/sh" requests open("hello.sh"). (8) The kernel checks "allow_read hello.sh" from "bash hello.sh" domain. (9) The "/bin/sh" starts and finishes. Whether a file is interpreted as a program or not depends on an application. The kernel cannot know whether the file is interpreted as a program or not. Thus, TOMOYO treats "hello-static" "hello-dynamic" "ld-linux.so" "hello.sh" "/bin/sh" equally as merely files; no distinction between executable and non-executable. Therefore, TOMOYO doesn't check DAC's execute permission. TOMOYO checks "allow_read" permission instead. Calling do_execve() is a bold gesture that an old program's instance (i.e. current process) is ready to be overwritten by a new program and is ready to transfer control to the new program. To split purview of programs, TOMOYO requires "allow_execute" permission of the new program against the old program's instance and performs domain transition. If do_execve() succeeds, the old program is no longer responsible against the consequence of the new program's behavior. Only the new program is responsible for all consequences. But TOMOYO doesn't require "allow_read" permission of the new program. If TOMOYO requires "allow_read" permission of the new program, TOMOYO will allow an attacker (who hijacked the old program's instance) to open the new program and steal data from the new program. Requiring "allow_read" permission will widen purview of the old program. Not requiring "allow_read" permission of the new program against the old program's instance is my design for reducing purview of the old program. To be able to know whether the current process is in do_execve() or not, I want to add in_execve flag to "task_struct". Signed-off-by: Kentaro Takeda Signed-off-by: Tetsuo Handa Signed-off-by: Toshiharu Harada Signed-off-by: James Morris --- security/tomoyo/tomoyo.c | 293 +++++++++++++++++++++++++++++++++++++++++++++++ security/tomoyo/tomoyo.h | 106 +++++++++++++++++ 2 files changed, 399 insertions(+) create mode 100644 security/tomoyo/tomoyo.c create mode 100644 security/tomoyo/tomoyo.h (limited to 'security') diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c new file mode 100644 index 00000000000..f3ab20758c0 --- /dev/null +++ b/security/tomoyo/tomoyo.c @@ -0,0 +1,293 @@ +/* + * security/tomoyo/tomoyo.c + * + * LSM hooks for TOMOYO Linux. + * + * Copyright (C) 2005-2009 NTT DATA CORPORATION + * + * Version: 2.2.0-pre 2009/02/01 + * + */ + +#include +#include "common.h" +#include "tomoyo.h" +#include "realpath.h" + +static int tomoyo_cred_prepare(struct cred *new, const struct cred *old, + gfp_t gfp) +{ + /* + * Since "struct tomoyo_domain_info *" is a sharable pointer, + * we don't need to duplicate. + */ + new->security = old->security; + return 0; +} + +static int tomoyo_bprm_set_creds(struct linux_binprm *bprm) +{ + /* + * Do only if this function is called for the first time of an execve + * operation. + */ + if (bprm->cred_prepared) + return 0; + /* + * Load policy if /sbin/tomoyo-init exists and /sbin/init is requested + * for the first time. + */ + if (!tomoyo_policy_loaded) + tomoyo_load_policy(bprm->filename); + /* + * Tell tomoyo_bprm_check_security() is called for the first time of an + * execve operation. + */ + bprm->cred->security = NULL; + return 0; +} + +static int tomoyo_bprm_check_security(struct linux_binprm *bprm) +{ + struct tomoyo_domain_info *domain = bprm->cred->security; + + /* + * Execute permission is checked against pathname passed to do_execve() + * using current domain. + */ + if (!domain) { + struct tomoyo_domain_info *next_domain = NULL; + int retval = tomoyo_find_next_domain(bprm, &next_domain); + + if (!retval) + bprm->cred->security = next_domain; + return retval; + } + /* + * Read permission is checked against interpreters using next domain. + * '1' is the result of open_to_namei_flags(O_RDONLY). + */ + return tomoyo_check_open_permission(domain, &bprm->file->f_path, 1); +} + +#ifdef CONFIG_SYSCTL + +static int tomoyo_prepend(char **buffer, int *buflen, const char *str) +{ + int namelen = strlen(str); + + if (*buflen < namelen) + return -ENOMEM; + *buflen -= namelen; + *buffer -= namelen; + memcpy(*buffer, str, namelen); + return 0; +} + +/** + * tomoyo_sysctl_path - return the realpath of a ctl_table. + * @table: pointer to "struct ctl_table". + * + * Returns realpath(3) of the @table on success. + * Returns NULL on failure. + * + * This function uses tomoyo_alloc(), so the caller must call tomoyo_free() + * if this function didn't return NULL. + */ +static char *tomoyo_sysctl_path(struct ctl_table *table) +{ + int buflen = TOMOYO_MAX_PATHNAME_LEN; + char *buf = tomoyo_alloc(buflen); + char *end = buf + buflen; + int error = -ENOMEM; + + if (!buf) + return NULL; + + *--end = '\0'; + buflen--; + while (table) { + char buf[32]; + const char *sp = table->procname; + + if (!sp) { + memset(buf, 0, sizeof(buf)); + snprintf(buf, sizeof(buf) - 1, "=%d=", table->ctl_name); + sp = buf; + } + if (tomoyo_prepend(&end, &buflen, sp) || + tomoyo_prepend(&end, &buflen, "/")) + goto out; + table = table->parent; + } + if (tomoyo_prepend(&end, &buflen, "/proc/sys")) + goto out; + error = tomoyo_encode(buf, end - buf, end); + out: + if (!error) + return buf; + tomoyo_free(buf); + return NULL; +} + +static int tomoyo_sysctl(struct ctl_table *table, int op) +{ + int error; + char *name; + + op &= MAY_READ | MAY_WRITE; + if (!op) + return 0; + name = tomoyo_sysctl_path(table); + if (!name) + return -ENOMEM; + error = tomoyo_check_file_perm(tomoyo_domain(), name, op); + tomoyo_free(name); + return error; +} +#endif + +static int tomoyo_path_truncate(struct path *path, loff_t length, + unsigned int time_attrs) +{ + return tomoyo_check_1path_perm(tomoyo_domain(), + TOMOYO_TYPE_TRUNCATE_ACL, + path); +} + +static int tomoyo_path_unlink(struct path *parent, struct dentry *dentry) +{ + struct path path = { parent->mnt, dentry }; + return tomoyo_check_1path_perm(tomoyo_domain(), + TOMOYO_TYPE_UNLINK_ACL, + &path); +} + +static int tomoyo_path_mkdir(struct path *parent, struct dentry *dentry, + int mode) +{ + struct path path = { parent->mnt, dentry }; + return tomoyo_check_1path_perm(tomoyo_domain(), + TOMOYO_TYPE_MKDIR_ACL, + &path); +} + +static int tomoyo_path_rmdir(struct path *parent, struct dentry *dentry) +{ + struct path path = { parent->mnt, dentry }; + return tomoyo_check_1path_perm(tomoyo_domain(), + TOMOYO_TYPE_RMDIR_ACL, + &path); +} + +static int tomoyo_path_symlink(struct path *parent, struct dentry *dentry, + const char *old_name) +{ + struct path path = { parent->mnt, dentry }; + return tomoyo_check_1path_perm(tomoyo_domain(), + TOMOYO_TYPE_SYMLINK_ACL, + &path); +} + +static int tomoyo_path_mknod(struct path *parent, struct dentry *dentry, + int mode, unsigned int dev) +{ + struct path path = { parent->mnt, dentry }; + int type = TOMOYO_TYPE_CREATE_ACL; + + switch (mode & S_IFMT) { + case S_IFCHR: + type = TOMOYO_TYPE_MKCHAR_ACL; + break; + case S_IFBLK: + type = TOMOYO_TYPE_MKBLOCK_ACL; + break; + case S_IFIFO: + type = TOMOYO_TYPE_MKFIFO_ACL; + break; + case S_IFSOCK: + type = TOMOYO_TYPE_MKSOCK_ACL; + break; + } + return tomoyo_check_1path_perm(tomoyo_domain(), + type, &path); +} + +static int tomoyo_path_link(struct dentry *old_dentry, struct path *new_dir, + struct dentry *new_dentry) +{ + struct path path1 = { new_dir->mnt, old_dentry }; + struct path path2 = { new_dir->mnt, new_dentry }; + return tomoyo_check_2path_perm(tomoyo_domain(), + TOMOYO_TYPE_LINK_ACL, + &path1, &path2); +} + +static int tomoyo_path_rename(struct path *old_parent, + struct dentry *old_dentry, + struct path *new_parent, + struct dentry *new_dentry) +{ + struct path path1 = { old_parent->mnt, old_dentry }; + struct path path2 = { new_parent->mnt, new_dentry }; + return tomoyo_check_2path_perm(tomoyo_domain(), + TOMOYO_TYPE_RENAME_ACL, + &path1, &path2); +} + +static int tomoyo_file_fcntl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + if (cmd == F_SETFL && ((arg ^ file->f_flags) & O_APPEND)) + return tomoyo_check_rewrite_permission(tomoyo_domain(), file); + return 0; +} + +static int tomoyo_dentry_open(struct file *f, const struct cred *cred) +{ + int flags = f->f_flags; + + if ((flags + 1) & O_ACCMODE) + flags++; + flags |= f->f_flags & (O_APPEND | O_TRUNC); + /* Don't check read permission here if called from do_execve(). */ + if (current->in_execve) + return 0; + return tomoyo_check_open_permission(tomoyo_domain(), &f->f_path, flags); +} + +static struct security_operations tomoyo_security_ops = { + .name = "tomoyo", + .cred_prepare = tomoyo_cred_prepare, + .bprm_set_creds = tomoyo_bprm_set_creds, + .bprm_check_security = tomoyo_bprm_check_security, +#ifdef CONFIG_SYSCTL + .sysctl = tomoyo_sysctl, +#endif + .file_fcntl = tomoyo_file_fcntl, + .dentry_open = tomoyo_dentry_open, + .path_truncate = tomoyo_path_truncate, + .path_unlink = tomoyo_path_unlink, + .path_mkdir = tomoyo_path_mkdir, + .path_rmdir = tomoyo_path_rmdir, + .path_symlink = tomoyo_path_symlink, + .path_mknod = tomoyo_path_mknod, + .path_link = tomoyo_path_link, + .path_rename = tomoyo_path_rename, +}; + +static int __init tomoyo_init(void) +{ + struct cred *cred = (struct cred *) current_cred(); + + if (!security_module_enable(&tomoyo_security_ops)) + return 0; + /* register ourselves with the security framework */ + if (register_security(&tomoyo_security_ops)) + panic("Failure registering TOMOYO Linux"); + printk(KERN_INFO "TOMOYO Linux initialized\n"); + cred->security = &tomoyo_kernel_domain; + return 0; +} + +security_initcall(tomoyo_init); diff --git a/security/tomoyo/tomoyo.h b/security/tomoyo/tomoyo.h new file mode 100644 index 00000000000..a0c8f6e0bea --- /dev/null +++ b/security/tomoyo/tomoyo.h @@ -0,0 +1,106 @@ +/* + * security/tomoyo/tomoyo.h + * + * Implementation of the Domain-Based Mandatory Access Control. + * + * Copyright (C) 2005-2009 NTT DATA CORPORATION + * + * Version: 2.2.0-pre 2009/02/01 + * + */ + +#ifndef _SECURITY_TOMOYO_TOMOYO_H +#define _SECURITY_TOMOYO_TOMOYO_H + +struct tomoyo_path_info; +struct path; +struct inode; +struct linux_binprm; +struct pt_regs; +struct tomoyo_page_buffer; + +int tomoyo_check_file_perm(struct tomoyo_domain_info *domain, + const char *filename, const u8 perm); +int tomoyo_check_exec_perm(struct tomoyo_domain_info *domain, + const struct tomoyo_path_info *filename, + struct tomoyo_page_buffer *buf); +int tomoyo_check_open_permission(struct tomoyo_domain_info *domain, + struct path *path, const int flag); +int tomoyo_check_1path_perm(struct tomoyo_domain_info *domain, + const u8 operation, struct path *path); +int tomoyo_check_2path_perm(struct tomoyo_domain_info *domain, + const u8 operation, struct path *path1, + struct path *path2); +int tomoyo_check_rewrite_permission(struct tomoyo_domain_info *domain, + struct file *filp); +int tomoyo_find_next_domain(struct linux_binprm *bprm, + struct tomoyo_domain_info **next_domain); + +/* Index numbers for Access Controls. */ + +#define TOMOYO_TYPE_SINGLE_PATH_ACL 0 +#define TOMOYO_TYPE_DOUBLE_PATH_ACL 1 + +/* Index numbers for File Controls. */ + +/* + * TYPE_READ_WRITE_ACL is special. TYPE_READ_WRITE_ACL is automatically set + * if both TYPE_READ_ACL and TYPE_WRITE_ACL are set. Both TYPE_READ_ACL and + * TYPE_WRITE_ACL are automatically set if TYPE_READ_WRITE_ACL is set. + * TYPE_READ_WRITE_ACL is automatically cleared if either TYPE_READ_ACL or + * TYPE_WRITE_ACL is cleared. Both TYPE_READ_ACL and TYPE_WRITE_ACL are + * automatically cleared if TYPE_READ_WRITE_ACL is cleared. + */ + +#define TOMOYO_TYPE_READ_WRITE_ACL 0 +#define TOMOYO_TYPE_EXECUTE_ACL 1 +#define TOMOYO_TYPE_READ_ACL 2 +#define TOMOYO_TYPE_WRITE_ACL 3 +#define TOMOYO_TYPE_CREATE_ACL 4 +#define TOMOYO_TYPE_UNLINK_ACL 5 +#define TOMOYO_TYPE_MKDIR_ACL 6 +#define TOMOYO_TYPE_RMDIR_ACL 7 +#define TOMOYO_TYPE_MKFIFO_ACL 8 +#define TOMOYO_TYPE_MKSOCK_ACL 9 +#define TOMOYO_TYPE_MKBLOCK_ACL 10 +#define TOMOYO_TYPE_MKCHAR_ACL 11 +#define TOMOYO_TYPE_TRUNCATE_ACL 12 +#define TOMOYO_TYPE_SYMLINK_ACL 13 +#define TOMOYO_TYPE_REWRITE_ACL 14 +#define TOMOYO_MAX_SINGLE_PATH_OPERATION 15 + +#define TOMOYO_TYPE_LINK_ACL 0 +#define TOMOYO_TYPE_RENAME_ACL 1 +#define TOMOYO_MAX_DOUBLE_PATH_OPERATION 2 + +#define TOMOYO_DOMAINPOLICY 0 +#define TOMOYO_EXCEPTIONPOLICY 1 +#define TOMOYO_DOMAIN_STATUS 2 +#define TOMOYO_PROCESS_STATUS 3 +#define TOMOYO_MEMINFO 4 +#define TOMOYO_SELFDOMAIN 5 +#define TOMOYO_VERSION 6 +#define TOMOYO_PROFILE 7 +#define TOMOYO_MANAGER 8 + +extern struct tomoyo_domain_info tomoyo_kernel_domain; + +static inline struct tomoyo_domain_info *tomoyo_domain(void) +{ + return current_cred()->security; +} + +/* Caller holds tasklist_lock spinlock. */ +static inline struct tomoyo_domain_info *tomoyo_real_domain(struct task_struct + *task) +{ + /***** CRITICAL SECTION START *****/ + const struct cred *cred = get_task_cred(task); + struct tomoyo_domain_info *domain = cred->security; + + put_cred(cred); + return domain; + /***** CRITICAL SECTION END *****/ +} + +#endif /* !defined(_SECURITY_TOMOYO_TOMOYO_H) */ -- cgit v1.2.3 From 00d7d6f840ddc947237307e022de5e75ded4105f Mon Sep 17 00:00:00 2001 From: Kentaro Takeda Date: Thu, 5 Feb 2009 17:18:17 +0900 Subject: Kconfig and Makefile TOMOYO uses LSM hooks for pathname based access control and securityfs support. Signed-off-by: Kentaro Takeda Signed-off-by: Tetsuo Handa Signed-off-by: James Morris --- security/Kconfig | 1 + security/Makefile | 2 ++ security/tomoyo/Kconfig | 11 +++++++++++ security/tomoyo/Makefile | 1 + 4 files changed, 15 insertions(+) create mode 100644 security/tomoyo/Kconfig create mode 100644 security/tomoyo/Makefile (limited to 'security') diff --git a/security/Kconfig b/security/Kconfig index bf129f87de7..bb244774e9d 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -135,6 +135,7 @@ config SECURITY_DEFAULT_MMAP_MIN_ADDR source security/selinux/Kconfig source security/smack/Kconfig +source security/tomoyo/Kconfig source security/integrity/ima/Kconfig diff --git a/security/Makefile b/security/Makefile index 595536cbffb..35f50c585e2 100644 --- a/security/Makefile +++ b/security/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_KEYS) += keys/ subdir-$(CONFIG_SECURITY_SELINUX) += selinux subdir-$(CONFIG_SECURITY_SMACK) += smack +subdir-$(CONFIG_SECURITY_TOMOYO) += tomoyo # always enable default capabilities obj-y += commoncap.o @@ -17,6 +18,7 @@ obj-$(CONFIG_SECURITY_SELINUX) += selinux/built-in.o obj-$(CONFIG_SECURITY_SMACK) += smack/built-in.o obj-$(CONFIG_SECURITY_ROOTPLUG) += root_plug.o obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o +obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/built-in.o # Object integrity file lists subdir-$(CONFIG_IMA) += integrity/ima diff --git a/security/tomoyo/Kconfig b/security/tomoyo/Kconfig new file mode 100644 index 00000000000..c8f38579323 --- /dev/null +++ b/security/tomoyo/Kconfig @@ -0,0 +1,11 @@ +config SECURITY_TOMOYO + bool "TOMOYO Linux Support" + depends on SECURITY + select SECURITYFS + select SECURITY_PATH + default n + help + This selects TOMOYO Linux, pathname-based access control. + Required userspace tools and further information may be + found at . + If you are unsure how to answer this question, answer N. diff --git a/security/tomoyo/Makefile b/security/tomoyo/Makefile new file mode 100644 index 00000000000..10ccd686b29 --- /dev/null +++ b/security/tomoyo/Makefile @@ -0,0 +1 @@ +obj-y = common.o realpath.o tomoyo.o domain.o file.o -- cgit v1.2.3 From 42d5aaa2d826f54924e260b58a8e410e59d54163 Mon Sep 17 00:00:00 2001 From: James Morris Date: Thu, 12 Feb 2009 16:29:04 +1100 Subject: security: change link order of LSMs so security=tomoyo works LSMs need to be linked before root_plug to ensure the security= boot parameter works with them. Do this for Tomoyo. (root_plug probably needs to be taken out and shot at some point, too). Signed-off-by: James Morris --- security/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'security') diff --git a/security/Makefile b/security/Makefile index 35f50c585e2..fa77021d977 100644 --- a/security/Makefile +++ b/security/Makefile @@ -16,9 +16,9 @@ obj-$(CONFIG_SECURITYFS) += inode.o # Must precede capability.o in order to stack properly. obj-$(CONFIG_SECURITY_SELINUX) += selinux/built-in.o obj-$(CONFIG_SECURITY_SMACK) += smack/built-in.o +obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/built-in.o obj-$(CONFIG_SECURITY_ROOTPLUG) += root_plug.o obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o -obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/built-in.o # Object integrity file lists subdir-$(CONFIG_IMA) += integrity/ima -- cgit v1.2.3 From 35d50e60e8b12e4adc2fa317343a176d87294a72 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Thu, 12 Feb 2009 15:53:38 +0900 Subject: tomoyo: fix sparse warning Fix sparse warning. $ make C=2 SUBDIRS=security/tomoyo CF="-D__cold__=" CHECK security/tomoyo/common.c CHECK security/tomoyo/realpath.c CHECK security/tomoyo/tomoyo.c security/tomoyo/tomoyo.c:110:8: warning: symbol 'buf' shadows an earlier one security/tomoyo/tomoyo.c:100:7: originally declared here Signed-off-by: Kentaro Takeda Signed-off-by: Tetsuo Handa Signed-off-by: Toshiharu Harada Signed-off-by: James Morris --- security/tomoyo/tomoyo.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'security') diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c index f3ab20758c0..cc599b36a3a 100644 --- a/security/tomoyo/tomoyo.c +++ b/security/tomoyo/tomoyo.c @@ -107,13 +107,13 @@ static char *tomoyo_sysctl_path(struct ctl_table *table) *--end = '\0'; buflen--; while (table) { - char buf[32]; + char num[32]; const char *sp = table->procname; if (!sp) { - memset(buf, 0, sizeof(buf)); - snprintf(buf, sizeof(buf) - 1, "=%d=", table->ctl_name); - sp = buf; + memset(num, 0, sizeof(num)); + snprintf(num, sizeof(num) - 1, "=%d=", table->ctl_name); + sp = num; } if (tomoyo_prepend(&end, &buflen, sp) || tomoyo_prepend(&end, &buflen, "/")) -- cgit v1.2.3 From b53fab9d48e9bd9aeba0b500dec550becd981a91 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 12 Feb 2009 09:54:14 -0800 Subject: ima: fix build error IMA_LSM_RULES requires AUDIT. This is automatic if SECURITY_SELINUX=y but not when SECURITY_SMACK=y (and SECURITY_SELINUX=n), so make the dependency explicit. This fixes the following build error: security/integrity/ima/ima_policy.c:111:error: implicit declaration of function 'security_audit_rule_match' security/integrity/ima/ima_policy.c:230:error: implicit declaration of function 'security_audit_rule_init' Signed-off-by: Randy Dunlap Acked-by: Mimi Zohar Signed-off-by: James Morris --- security/integrity/ima/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index 3d2b6ee778a..53d9764e8f0 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -49,7 +49,7 @@ config IMA_AUDIT config IMA_LSM_RULES bool - depends on IMA && (SECURITY_SELINUX || SECURITY_SMACK) + depends on IMA && AUDIT && (SECURITY_SELINUX || SECURITY_SMACK) default y help - Disabling this option will disregard LSM based policy rules + Disabling this option will disregard LSM based policy rules. -- cgit v1.2.3 From 200ac532a4bc3134147ca06686c56a6420e66c46 Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Thu, 12 Feb 2009 15:01:04 -0500 Subject: SELinux: call capabilities code directory For cleanliness and efficiency remove all calls to secondary-> and instead call capabilities code directly. capabilities are the only module that selinux stacks with and so the code should not indicate that other stacking might be possible. Signed-off-by: Eric Paris Acked-by: Stephen Smalley Signed-off-by: James Morris --- security/selinux/hooks.c | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index a69d6f8970c..e9011e5f5ff 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -1841,7 +1841,7 @@ static int selinux_ptrace_may_access(struct task_struct *child, { int rc; - rc = secondary_ops->ptrace_may_access(child, mode); + rc = cap_ptrace_may_access(child, mode); if (rc) return rc; @@ -1858,7 +1858,7 @@ static int selinux_ptrace_traceme(struct task_struct *parent) { int rc; - rc = secondary_ops->ptrace_traceme(parent); + rc = cap_ptrace_traceme(parent); if (rc) return rc; @@ -1874,7 +1874,7 @@ static int selinux_capget(struct task_struct *target, kernel_cap_t *effective, if (error) return error; - return secondary_ops->capget(target, effective, inheritable, permitted); + return cap_capget(target, effective, inheritable, permitted); } static int selinux_capset(struct cred *new, const struct cred *old, @@ -1884,7 +1884,7 @@ static int selinux_capset(struct cred *new, const struct cred *old, { int error; - error = secondary_ops->capset(new, old, + error = cap_capset(new, old, effective, inheritable, permitted); if (error) return error; @@ -1907,7 +1907,7 @@ static int selinux_capable(struct task_struct *tsk, const struct cred *cred, { int rc; - rc = secondary_ops->capable(tsk, cred, cap, audit); + rc = cap_capable(tsk, cred, cap, audit); if (rc) return rc; @@ -2033,7 +2033,7 @@ static int selinux_syslog(int type) { int rc; - rc = secondary_ops->syslog(type); + rc = cap_syslog(type); if (rc) return rc; @@ -2064,10 +2064,6 @@ static int selinux_syslog(int type) * mapping. 0 means there is enough memory for the allocation to * succeed and -ENOMEM implies there is not. * - * Note that secondary_ops->capable and task_has_perm_noaudit return 0 - * if the capability is granted, but __vm_enough_memory requires 1 if - * the capability is granted. - * * Do not audit the selinux permission check, as this is applied to all * processes that allocate mappings. */ @@ -2094,7 +2090,7 @@ static int selinux_bprm_set_creds(struct linux_binprm *bprm) struct inode *inode = bprm->file->f_path.dentry->d_inode; int rc; - rc = secondary_ops->bprm_set_creds(bprm); + rc = cap_bprm_set_creds(bprm); if (rc) return rc; @@ -2211,7 +2207,7 @@ static int selinux_bprm_secureexec(struct linux_binprm *bprm) PROCESS__NOATSECURE, NULL); } - return (atsecure || secondary_ops->bprm_secureexec(bprm)); + return (atsecure || cap_bprm_secureexec(bprm)); } extern struct vfsmount *selinuxfs_mount; @@ -3312,7 +3308,7 @@ static int selinux_task_setnice(struct task_struct *p, int nice) { int rc; - rc = secondary_ops->task_setnice(p, nice); + rc = cap_task_setnice(p, nice); if (rc) return rc; @@ -3323,7 +3319,7 @@ static int selinux_task_setioprio(struct task_struct *p, int ioprio) { int rc; - rc = secondary_ops->task_setioprio(p, ioprio); + rc = cap_task_setioprio(p, ioprio); if (rc) return rc; @@ -3353,7 +3349,7 @@ static int selinux_task_setscheduler(struct task_struct *p, int policy, struct s { int rc; - rc = secondary_ops->task_setscheduler(p, policy, lp); + rc = cap_task_setscheduler(p, policy, lp); if (rc) return rc; @@ -4749,7 +4745,7 @@ static int selinux_netlink_send(struct sock *sk, struct sk_buff *skb) { int err; - err = secondary_ops->netlink_send(sk, skb); + err = cap_netlink_send(sk, skb); if (err) return err; @@ -4764,7 +4760,7 @@ static int selinux_netlink_recv(struct sk_buff *skb, int capability) int err; struct avc_audit_data ad; - err = secondary_ops->netlink_recv(skb, capability); + err = cap_netlink_recv(skb, capability); if (err) return err; -- cgit v1.2.3 From 4ba0a8ad63e12a03ae01c039482967cc496b9174 Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Thu, 12 Feb 2009 15:01:10 -0500 Subject: SELinux: better printk when file with invalid label found Currently when an inode is read into the kernel with an invalid label string (can often happen with removable media) we output a string like: SELinux: inode_doinit_with_dentry: context_to_sid([SOME INVALID LABEL]) returned -22 dor dev=[blah] ino=[blah] Which is all but incomprehensible to all but a couple of us. Instead, on EINVAL only, I plan to output a much more user friendly string and I plan to ratelimit the printk since many of these could be generated very rapidly. Signed-off-by: Eric Paris Acked-by: Stephen Smalley Signed-off-by: James Morris --- security/selinux/hooks.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index e9011e5f5ff..aebcfad5613 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -1315,10 +1315,19 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent sbsec->def_sid, GFP_NOFS); if (rc) { - printk(KERN_WARNING "SELinux: %s: context_to_sid(%s) " - "returned %d for dev=%s ino=%ld\n", - __func__, context, -rc, - inode->i_sb->s_id, inode->i_ino); + char *dev = inode->i_sb->s_id; + unsigned long ino = inode->i_ino; + + if (rc == -EINVAL) { + if (printk_ratelimit()) + printk(KERN_NOTICE "SELinux: inode=%lu on dev=%s was found to have an invalid " + "context=%s. This indicates you may need to relabel the inode or the " + "filesystem in question.\n", ino, dev, context); + } else { + printk(KERN_WARNING "SELinux: %s: context_to_sid(%s) " + "returned %d for dev=%s ino=%ld\n", + __func__, context, -rc, dev, ino); + } kfree(context); /* Leave with the unlabeled SID */ rc = 0; -- cgit v1.2.3 From 4cb912f1d1447077160ace9ce3b3a10696dd74e5 Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Thu, 12 Feb 2009 14:50:05 -0500 Subject: SELinux: NULL terminate al contexts from disk When a context is pulled in from disk we don't know that it is null terminated. This patch forecebly null terminates contexts when we pull them from disk. Signed-off-by: Eric Paris Acked-by: Stephen Smalley Signed-off-by: James Morris --- security/selinux/hooks.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index aebcfad5613..309648c573d 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -1270,12 +1270,13 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent } len = INITCONTEXTLEN; - context = kmalloc(len, GFP_NOFS); + context = kmalloc(len+1, GFP_NOFS); if (!context) { rc = -ENOMEM; dput(dentry); goto out_unlock; } + context[len] = '\0'; rc = inode->i_op->getxattr(dentry, XATTR_NAME_SELINUX, context, len); if (rc == -ERANGE) { @@ -1288,12 +1289,13 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent } kfree(context); len = rc; - context = kmalloc(len, GFP_NOFS); + context = kmalloc(len+1, GFP_NOFS); if (!context) { rc = -ENOMEM; dput(dentry); goto out_unlock; } + context[len] = '\0'; rc = inode->i_op->getxattr(dentry, XATTR_NAME_SELINUX, context, len); -- cgit v1.2.3 From a5dda683328f99c781f92c66cc52ffc0639bef58 Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Thu, 12 Feb 2009 14:50:11 -0500 Subject: SELinux: check seqno when updating an avc_node The avc update node callbacks do not check the seqno of the caller with the seqno of the node found. It is possible that a policy change could happen (although almost impossibly unlikely) in which a permissive or permissive_domain decision is not valid for the entry found. Simply pass and check that the seqno of the caller and the seqno of the node found match. Signed-off-by: Eric Paris Acked-by: Stephen Smalley Signed-off-by: James Morris --- security/selinux/avc.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'security') diff --git a/security/selinux/avc.c b/security/selinux/avc.c index eb41f43e277..0d00f4874f3 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -742,13 +742,15 @@ static inline int avc_sidcmp(u32 x, u32 y) * @event : Updating event * @perms : Permission mask bits * @ssid,@tsid,@tclass : identifier of an AVC entry + * @seqno : sequence number when decision was made * * if a valid AVC entry doesn't exist,this function returns -ENOENT. * if kmalloc() called internal returns NULL, this function returns -ENOMEM. * otherwise, this function update the AVC entry. The original AVC-entry object * will release later by RCU. */ -static int avc_update_node(u32 event, u32 perms, u32 ssid, u32 tsid, u16 tclass) +static int avc_update_node(u32 event, u32 perms, u32 ssid, u32 tsid, u16 tclass, + u32 seqno) { int hvalue, rc = 0; unsigned long flag; @@ -767,7 +769,8 @@ static int avc_update_node(u32 event, u32 perms, u32 ssid, u32 tsid, u16 tclass) list_for_each_entry(pos, &avc_cache.slots[hvalue], list) { if (ssid == pos->ae.ssid && tsid == pos->ae.tsid && - tclass == pos->ae.tclass){ + tclass == pos->ae.tclass && + seqno == pos->ae.avd.seqno){ orig = pos; break; } @@ -908,7 +911,7 @@ int avc_has_perm_noaudit(u32 ssid, u32 tsid, rc = -EACCES; else if (!selinux_enforcing || security_permissive_sid(ssid)) avc_update_node(AVC_CALLBACK_GRANT, requested, ssid, - tsid, tclass); + tsid, tclass, p_ae->avd.seqno); else rc = -EACCES; } -- cgit v1.2.3 From 906d27d9d28fd50fb40026e56842d8f6806a7a04 Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Thu, 12 Feb 2009 14:50:43 -0500 Subject: SELinux: remove the unused ae.used Currently SELinux code has an atomic which was intended to track how many times an avc entry was used and to evict entries when they haven't been used recently. Instead we never let this atomic get above 1 and evict when it is first checked for eviction since it hits zero. This is a total waste of time so I'm completely dropping ae.used. This change resulted in about a 3% faster avc_has_perm_noaudit when running oprofile against a tbench benchmark. Signed-off-by: Eric Paris Reviewed by: Paul Moore Acked-by: Stephen Smalley Signed-off-by: James Morris --- security/selinux/avc.c | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) (limited to 'security') diff --git a/security/selinux/avc.c b/security/selinux/avc.c index 0d00f4874f3..0afb990fdfa 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -88,7 +88,6 @@ struct avc_entry { u32 tsid; u16 tclass; struct av_decision avd; - atomic_t used; /* used recently */ }; struct avc_node { @@ -316,16 +315,13 @@ static inline int avc_reclaim_node(void) rcu_read_lock(); list_for_each_entry(node, &avc_cache.slots[hvalue], list) { - if (atomic_dec_and_test(&node->ae.used)) { - /* Recently Unused */ - avc_node_delete(node); - avc_cache_stats_incr(reclaims); - ecx++; - if (ecx >= AVC_CACHE_RECLAIM) { - rcu_read_unlock(); - spin_unlock_irqrestore(&avc_cache.slots_lock[hvalue], flags); - goto out; - } + avc_node_delete(node); + avc_cache_stats_incr(reclaims); + ecx++; + if (ecx >= AVC_CACHE_RECLAIM) { + rcu_read_unlock(); + spin_unlock_irqrestore(&avc_cache.slots_lock[hvalue], flags); + goto out; } } rcu_read_unlock(); @@ -345,7 +341,6 @@ static struct avc_node *avc_alloc_node(void) INIT_RCU_HEAD(&node->rhead); INIT_LIST_HEAD(&node->list); - atomic_set(&node->ae.used, 1); avc_cache_stats_incr(allocations); if (atomic_inc_return(&avc_cache.active_nodes) > avc_cache_threshold) @@ -378,15 +373,6 @@ static inline struct avc_node *avc_search_node(u32 ssid, u32 tsid, u16 tclass) } } - if (ret == NULL) { - /* cache miss */ - goto out; - } - - /* cache hit */ - if (atomic_read(&ret->ae.used) != 1) - atomic_set(&ret->ae.used, 1); -out: return ret; } -- cgit v1.2.3 From 21193dcd1f3570ddfd8a04f4465e484c1f94252f Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Thu, 12 Feb 2009 14:50:49 -0500 Subject: SELinux: more careful use of avd in avc_has_perm_noaudit we are often needlessly jumping through hoops when it comes to avd entries in avc_has_perm_noaudit and we have extra initialization and memcpy which are just wasting performance. Try to clean the function up a bit. This patch resulted in a 13% drop in time spent in avc_has_perm_noaudit in my oprofile sampling of a tbench benchmark. Signed-off-by: Eric Paris Reviewed-by: Paul Moore Acked-by: Stephen Smalley Signed-off-by: James Morris --- security/selinux/avc.c | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) (limited to 'security') diff --git a/security/selinux/avc.c b/security/selinux/avc.c index 0afb990fdfa..2a84dec4adf 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -350,12 +350,12 @@ out: return node; } -static void avc_node_populate(struct avc_node *node, u32 ssid, u32 tsid, u16 tclass, struct avc_entry *ae) +static void avc_node_populate(struct avc_node *node, u32 ssid, u32 tsid, u16 tclass, struct av_decision *avd) { node->ae.ssid = ssid; node->ae.tsid = tsid; node->ae.tclass = tclass; - memcpy(&node->ae.avd, &ae->avd, sizeof(node->ae.avd)); + memcpy(&node->ae.avd, avd, sizeof(node->ae.avd)); } static inline struct avc_node *avc_search_node(u32 ssid, u32 tsid, u16 tclass) @@ -435,31 +435,31 @@ static int avc_latest_notif_update(int seqno, int is_insert) * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class - * @ae: AVC entry + * @avd: resulting av decision * * Insert an AVC entry for the SID pair * (@ssid, @tsid) and class @tclass. * The access vectors and the sequence number are * normally provided by the security server in * response to a security_compute_av() call. If the - * sequence number @ae->avd.seqno is not less than the latest + * sequence number @avd->seqno is not less than the latest * revocation notification, then the function copies * the access vectors into a cache entry, returns * avc_node inserted. Otherwise, this function returns NULL. */ -static struct avc_node *avc_insert(u32 ssid, u32 tsid, u16 tclass, struct avc_entry *ae) +static struct avc_node *avc_insert(u32 ssid, u32 tsid, u16 tclass, struct av_decision *avd) { struct avc_node *pos, *node = NULL; int hvalue; unsigned long flag; - if (avc_latest_notif_update(ae->avd.seqno, 1)) + if (avc_latest_notif_update(avd->seqno, 1)) goto out; node = avc_alloc_node(); if (node) { hvalue = avc_hash(ssid, tsid, tclass); - avc_node_populate(node, ssid, tsid, tclass, ae); + avc_node_populate(node, ssid, tsid, tclass, avd); spin_lock_irqsave(&avc_cache.slots_lock[hvalue], flag); list_for_each_entry(pos, &avc_cache.slots[hvalue], list) { @@ -772,7 +772,7 @@ static int avc_update_node(u32 event, u32 perms, u32 ssid, u32 tsid, u16 tclass, * Copy and replace original node. */ - avc_node_populate(node, ssid, tsid, tclass, &orig->ae); + avc_node_populate(node, ssid, tsid, tclass, &orig->ae.avd); switch (event) { case AVC_CALLBACK_GRANT: @@ -864,10 +864,10 @@ int avc_ss_reset(u32 seqno) int avc_has_perm_noaudit(u32 ssid, u32 tsid, u16 tclass, u32 requested, unsigned flags, - struct av_decision *avd) + struct av_decision *in_avd) { struct avc_node *node; - struct avc_entry entry, *p_ae; + struct av_decision avd_entry, *avd; int rc = 0; u32 denied; @@ -878,26 +878,31 @@ int avc_has_perm_noaudit(u32 ssid, u32 tsid, node = avc_lookup(ssid, tsid, tclass, requested); if (!node) { rcu_read_unlock(); - rc = security_compute_av(ssid, tsid, tclass, requested, &entry.avd); + + if (in_avd) + avd = in_avd; + else + avd = &avd_entry; + + rc = security_compute_av(ssid, tsid, tclass, requested, avd); if (rc) goto out; rcu_read_lock(); - node = avc_insert(ssid, tsid, tclass, &entry); + node = avc_insert(ssid, tsid, tclass, avd); + } else { + if (in_avd) + memcpy(in_avd, &node->ae.avd, sizeof(*in_avd)); + avd = &node->ae.avd; } - p_ae = node ? &node->ae : &entry; - - if (avd) - memcpy(avd, &p_ae->avd, sizeof(*avd)); - - denied = requested & ~(p_ae->avd.allowed); + denied = requested & ~(avd->allowed); if (denied) { if (flags & AVC_STRICT) rc = -EACCES; else if (!selinux_enforcing || security_permissive_sid(ssid)) avc_update_node(AVC_CALLBACK_GRANT, requested, ssid, - tsid, tclass, p_ae->avd.seqno); + tsid, tclass, avd->seqno); else rc = -EACCES; } -- cgit v1.2.3 From f1c6381a6e337adcecf84be2a838bd9e610e2365 Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Thu, 12 Feb 2009 14:50:54 -0500 Subject: SELinux: remove unused av.decided field It appears there was an intention to have the security server only decide certain permissions and leave other for later as some sort of a portential performance win. We are currently always deciding all 32 bits of permissions and this is a useless couple of branches and wasted space. This patch completely drops the av.decided concept. This in a 17% reduction in the time spent in avc_has_perm_noaudit based on oprofile sampling of a tbench benchmark. Signed-off-by: Eric Paris Reviewed-by: Paul Moore Acked-by: Stephen Smalley Signed-off-by: James Morris --- security/selinux/avc.c | 15 +++++---------- security/selinux/include/security.h | 1 - security/selinux/selinuxfs.c | 2 +- security/selinux/ss/services.c | 2 -- 4 files changed, 6 insertions(+), 14 deletions(-) (limited to 'security') diff --git a/security/selinux/avc.c b/security/selinux/avc.c index 2a84dec4adf..326aa78bd42 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -381,30 +381,25 @@ static inline struct avc_node *avc_search_node(u32 ssid, u32 tsid, u16 tclass) * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class - * @requested: requested permissions, interpreted based on @tclass * * Look up an AVC entry that is valid for the - * @requested permissions between the SID pair * (@ssid, @tsid), interpreting the permissions * based on @tclass. If a valid AVC entry exists, * then this function return the avc_node. * Otherwise, this function returns NULL. */ -static struct avc_node *avc_lookup(u32 ssid, u32 tsid, u16 tclass, u32 requested) +static struct avc_node *avc_lookup(u32 ssid, u32 tsid, u16 tclass) { struct avc_node *node; avc_cache_stats_incr(lookups); node = avc_search_node(ssid, tsid, tclass); - if (node && ((node->ae.avd.decided & requested) == requested)) { + if (node) avc_cache_stats_incr(hits); - goto out; - } + else + avc_cache_stats_incr(misses); - node = NULL; - avc_cache_stats_incr(misses); -out: return node; } @@ -875,7 +870,7 @@ int avc_has_perm_noaudit(u32 ssid, u32 tsid, rcu_read_lock(); - node = avc_lookup(ssid, tsid, tclass, requested); + node = avc_lookup(ssid, tsid, tclass); if (!node) { rcu_read_unlock(); diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index e1d9db77998..5c3434f7626 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -88,7 +88,6 @@ int security_policycap_supported(unsigned int req_cap); #define SEL_VEC_MAX 32 struct av_decision { u32 allowed; - u32 decided; u32 auditallow; u32 auditdeny; u32 seqno; diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 01ec6d2c6b9..d3c8b982cfb 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -595,7 +595,7 @@ static ssize_t sel_write_access(struct file *file, char *buf, size_t size) length = scnprintf(buf, SIMPLE_TRANSACTION_LIMIT, "%x %x %x %x %u", - avd.allowed, avd.decided, + avd.allowed, 0xffffffff, avd.auditallow, avd.auditdeny, avd.seqno); out2: diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index c65e4fe4a0f..deeec6c013a 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -407,7 +407,6 @@ static int context_struct_compute_av(struct context *scontext, * Initialize the access vectors to the default values. */ avd->allowed = 0; - avd->decided = 0xffffffff; avd->auditallow = 0; avd->auditdeny = 0xffffffff; avd->seqno = latest_granting; @@ -743,7 +742,6 @@ int security_compute_av(u32 ssid, if (!ss_initialized) { avd->allowed = 0xffffffff; - avd->decided = 0xffffffff; avd->auditallow = 0; avd->auditdeny = 0xffffffff; avd->seqno = latest_granting; -- cgit v1.2.3 From edf3d1aecd0d608acbd561b0c527e1d41abcb657 Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Thu, 12 Feb 2009 14:50:59 -0500 Subject: SELinux: code readability with avc_cache The code making use of struct avc_cache was not easy to read thanks to liberal use of &avc_cache.{slots_lock,slots}[hvalue] throughout. This patch simply creates local pointers and uses those instead of the long global names. Signed-off-by: Eric Paris Signed-off-by: James Morris --- security/selinux/avc.c | 63 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 19 deletions(-) (limited to 'security') diff --git a/security/selinux/avc.c b/security/selinux/avc.c index 326aa78bd42..9dd5c506a82 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -92,12 +92,12 @@ struct avc_entry { struct avc_node { struct avc_entry ae; - struct list_head list; + struct list_head list; /* anchored in avc_cache->slots[i] */ struct rcu_head rhead; }; struct avc_cache { - struct list_head slots[AVC_CACHE_SLOTS]; + struct list_head slots[AVC_CACHE_SLOTS]; /* head for avc_node->list */ spinlock_t slots_lock[AVC_CACHE_SLOTS]; /* lock for writes */ atomic_t lru_hint; /* LRU hint for reclaim scan */ atomic_t active_nodes; @@ -249,16 +249,18 @@ int avc_get_hash_stats(char *page) { int i, chain_len, max_chain_len, slots_used; struct avc_node *node; + struct list_head *head; rcu_read_lock(); slots_used = 0; max_chain_len = 0; for (i = 0; i < AVC_CACHE_SLOTS; i++) { - if (!list_empty(&avc_cache.slots[i])) { + head = &avc_cache.slots[i]; + if (!list_empty(head)) { slots_used++; chain_len = 0; - list_for_each_entry_rcu(node, &avc_cache.slots[i], list) + list_for_each_entry_rcu(node, head, list) chain_len++; if (chain_len > max_chain_len) max_chain_len = chain_len; @@ -306,26 +308,30 @@ static inline int avc_reclaim_node(void) struct avc_node *node; int hvalue, try, ecx; unsigned long flags; + struct list_head *head; + spinlock_t *lock; for (try = 0, ecx = 0; try < AVC_CACHE_SLOTS; try++) { hvalue = atomic_inc_return(&avc_cache.lru_hint) & (AVC_CACHE_SLOTS - 1); + head = &avc_cache.slots[hvalue]; + lock = &avc_cache.slots_lock[hvalue]; - if (!spin_trylock_irqsave(&avc_cache.slots_lock[hvalue], flags)) + if (!spin_trylock_irqsave(lock, flags)) continue; rcu_read_lock(); - list_for_each_entry(node, &avc_cache.slots[hvalue], list) { + list_for_each_entry(node, head, list) { avc_node_delete(node); avc_cache_stats_incr(reclaims); ecx++; if (ecx >= AVC_CACHE_RECLAIM) { rcu_read_unlock(); - spin_unlock_irqrestore(&avc_cache.slots_lock[hvalue], flags); + spin_unlock_irqrestore(lock, flags); goto out; } } rcu_read_unlock(); - spin_unlock_irqrestore(&avc_cache.slots_lock[hvalue], flags); + spin_unlock_irqrestore(lock, flags); } out: return ecx; @@ -362,9 +368,11 @@ static inline struct avc_node *avc_search_node(u32 ssid, u32 tsid, u16 tclass) { struct avc_node *node, *ret = NULL; int hvalue; + struct list_head *head; hvalue = avc_hash(ssid, tsid, tclass); - list_for_each_entry_rcu(node, &avc_cache.slots[hvalue], list) { + head = &avc_cache.slots[hvalue]; + list_for_each_entry_rcu(node, head, list) { if (ssid == node->ae.ssid && tclass == node->ae.tclass && tsid == node->ae.tsid) { @@ -453,11 +461,17 @@ static struct avc_node *avc_insert(u32 ssid, u32 tsid, u16 tclass, struct av_dec node = avc_alloc_node(); if (node) { + struct list_head *head; + spinlock_t *lock; + hvalue = avc_hash(ssid, tsid, tclass); avc_node_populate(node, ssid, tsid, tclass, avd); - spin_lock_irqsave(&avc_cache.slots_lock[hvalue], flag); - list_for_each_entry(pos, &avc_cache.slots[hvalue], list) { + head = &avc_cache.slots[hvalue]; + lock = &avc_cache.slots_lock[hvalue]; + + spin_lock_irqsave(lock, flag); + list_for_each_entry(pos, head, list) { if (pos->ae.ssid == ssid && pos->ae.tsid == tsid && pos->ae.tclass == tclass) { @@ -465,9 +479,9 @@ static struct avc_node *avc_insert(u32 ssid, u32 tsid, u16 tclass, struct av_dec goto found; } } - list_add_rcu(&node->list, &avc_cache.slots[hvalue]); + list_add_rcu(&node->list, head); found: - spin_unlock_irqrestore(&avc_cache.slots_lock[hvalue], flag); + spin_unlock_irqrestore(lock, flag); } out: return node; @@ -736,6 +750,8 @@ static int avc_update_node(u32 event, u32 perms, u32 ssid, u32 tsid, u16 tclass, int hvalue, rc = 0; unsigned long flag; struct avc_node *pos, *node, *orig = NULL; + struct list_head *head; + spinlock_t *lock; node = avc_alloc_node(); if (!node) { @@ -745,9 +761,13 @@ static int avc_update_node(u32 event, u32 perms, u32 ssid, u32 tsid, u16 tclass, /* Lock the target slot */ hvalue = avc_hash(ssid, tsid, tclass); - spin_lock_irqsave(&avc_cache.slots_lock[hvalue], flag); - list_for_each_entry(pos, &avc_cache.slots[hvalue], list) { + head = &avc_cache.slots[hvalue]; + lock = &avc_cache.slots_lock[hvalue]; + + spin_lock_irqsave(lock, flag); + + list_for_each_entry(pos, head, list) { if (ssid == pos->ae.ssid && tsid == pos->ae.tsid && tclass == pos->ae.tclass && @@ -792,7 +812,7 @@ static int avc_update_node(u32 event, u32 perms, u32 ssid, u32 tsid, u16 tclass, } avc_node_replace(node, orig); out_unlock: - spin_unlock_irqrestore(&avc_cache.slots_lock[hvalue], flag); + spin_unlock_irqrestore(lock, flag); out: return rc; } @@ -807,18 +827,23 @@ int avc_ss_reset(u32 seqno) int i, rc = 0, tmprc; unsigned long flag; struct avc_node *node; + struct list_head *head; + spinlock_t *lock; for (i = 0; i < AVC_CACHE_SLOTS; i++) { - spin_lock_irqsave(&avc_cache.slots_lock[i], flag); + head = &avc_cache.slots[i]; + lock = &avc_cache.slots_lock[i]; + + spin_lock_irqsave(lock, flag); /* * With preemptable RCU, the outer spinlock does not * prevent RCU grace periods from ending. */ rcu_read_lock(); - list_for_each_entry(node, &avc_cache.slots[i], list) + list_for_each_entry(node, head, list) avc_node_delete(node); rcu_read_unlock(); - spin_unlock_irqrestore(&avc_cache.slots_lock[i], flag); + spin_unlock_irqrestore(lock, flag); } for (c = avc_callbacks; c; c = c->next) { -- cgit v1.2.3 From 26036651c562609d1f52d181f9d2cccbf89929b1 Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Thu, 12 Feb 2009 14:51:04 -0500 Subject: SELinux: convert the avc cache hash list to an hlist We do not need O(1) access to the tail of the avc cache lists and so we are wasting lots of space using struct list_head instead of struct hlist_head. This patch converts the avc cache to use hlists in which there is a single pointer from the head which saves us about 4k of global memory. Resulted in about a 1.5% decrease in time spent in avc_has_perm_noaudit based on oprofile sampling of tbench. Although likely within the noise.... Signed-off-by: Eric Paris Reviewed-by: Paul Moore Signed-off-by: James Morris --- security/selinux/avc.c | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) (limited to 'security') diff --git a/security/selinux/avc.c b/security/selinux/avc.c index 9dd5c506a82..7f9b5fac877 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -92,12 +92,12 @@ struct avc_entry { struct avc_node { struct avc_entry ae; - struct list_head list; /* anchored in avc_cache->slots[i] */ + struct hlist_node list; /* anchored in avc_cache->slots[i] */ struct rcu_head rhead; }; struct avc_cache { - struct list_head slots[AVC_CACHE_SLOTS]; /* head for avc_node->list */ + struct hlist_head slots[AVC_CACHE_SLOTS]; /* head for avc_node->list */ spinlock_t slots_lock[AVC_CACHE_SLOTS]; /* lock for writes */ atomic_t lru_hint; /* LRU hint for reclaim scan */ atomic_t active_nodes; @@ -233,7 +233,7 @@ void __init avc_init(void) int i; for (i = 0; i < AVC_CACHE_SLOTS; i++) { - INIT_LIST_HEAD(&avc_cache.slots[i]); + INIT_HLIST_HEAD(&avc_cache.slots[i]); spin_lock_init(&avc_cache.slots_lock[i]); } atomic_set(&avc_cache.active_nodes, 0); @@ -249,7 +249,7 @@ int avc_get_hash_stats(char *page) { int i, chain_len, max_chain_len, slots_used; struct avc_node *node; - struct list_head *head; + struct hlist_head *head; rcu_read_lock(); @@ -257,10 +257,12 @@ int avc_get_hash_stats(char *page) max_chain_len = 0; for (i = 0; i < AVC_CACHE_SLOTS; i++) { head = &avc_cache.slots[i]; - if (!list_empty(head)) { + if (!hlist_empty(head)) { + struct hlist_node *next; + slots_used++; chain_len = 0; - list_for_each_entry_rcu(node, head, list) + hlist_for_each_entry_rcu(node, next, head, list) chain_len++; if (chain_len > max_chain_len) max_chain_len = chain_len; @@ -284,7 +286,7 @@ static void avc_node_free(struct rcu_head *rhead) static void avc_node_delete(struct avc_node *node) { - list_del_rcu(&node->list); + hlist_del_rcu(&node->list); call_rcu(&node->rhead, avc_node_free); atomic_dec(&avc_cache.active_nodes); } @@ -298,7 +300,7 @@ static void avc_node_kill(struct avc_node *node) static void avc_node_replace(struct avc_node *new, struct avc_node *old) { - list_replace_rcu(&old->list, &new->list); + hlist_replace_rcu(&old->list, &new->list); call_rcu(&old->rhead, avc_node_free); atomic_dec(&avc_cache.active_nodes); } @@ -308,7 +310,8 @@ static inline int avc_reclaim_node(void) struct avc_node *node; int hvalue, try, ecx; unsigned long flags; - struct list_head *head; + struct hlist_head *head; + struct hlist_node *next; spinlock_t *lock; for (try = 0, ecx = 0; try < AVC_CACHE_SLOTS; try++) { @@ -320,7 +323,7 @@ static inline int avc_reclaim_node(void) continue; rcu_read_lock(); - list_for_each_entry(node, head, list) { + hlist_for_each_entry(node, next, head, list) { avc_node_delete(node); avc_cache_stats_incr(reclaims); ecx++; @@ -346,7 +349,7 @@ static struct avc_node *avc_alloc_node(void) goto out; INIT_RCU_HEAD(&node->rhead); - INIT_LIST_HEAD(&node->list); + INIT_HLIST_NODE(&node->list); avc_cache_stats_incr(allocations); if (atomic_inc_return(&avc_cache.active_nodes) > avc_cache_threshold) @@ -368,11 +371,12 @@ static inline struct avc_node *avc_search_node(u32 ssid, u32 tsid, u16 tclass) { struct avc_node *node, *ret = NULL; int hvalue; - struct list_head *head; + struct hlist_head *head; + struct hlist_node *next; hvalue = avc_hash(ssid, tsid, tclass); head = &avc_cache.slots[hvalue]; - list_for_each_entry_rcu(node, head, list) { + hlist_for_each_entry_rcu(node, next, head, list) { if (ssid == node->ae.ssid && tclass == node->ae.tclass && tsid == node->ae.tsid) { @@ -461,7 +465,8 @@ static struct avc_node *avc_insert(u32 ssid, u32 tsid, u16 tclass, struct av_dec node = avc_alloc_node(); if (node) { - struct list_head *head; + struct hlist_head *head; + struct hlist_node *next; spinlock_t *lock; hvalue = avc_hash(ssid, tsid, tclass); @@ -471,7 +476,7 @@ static struct avc_node *avc_insert(u32 ssid, u32 tsid, u16 tclass, struct av_dec lock = &avc_cache.slots_lock[hvalue]; spin_lock_irqsave(lock, flag); - list_for_each_entry(pos, head, list) { + hlist_for_each_entry(pos, next, head, list) { if (pos->ae.ssid == ssid && pos->ae.tsid == tsid && pos->ae.tclass == tclass) { @@ -479,7 +484,7 @@ static struct avc_node *avc_insert(u32 ssid, u32 tsid, u16 tclass, struct av_dec goto found; } } - list_add_rcu(&node->list, head); + hlist_add_head_rcu(&node->list, head); found: spin_unlock_irqrestore(lock, flag); } @@ -750,7 +755,8 @@ static int avc_update_node(u32 event, u32 perms, u32 ssid, u32 tsid, u16 tclass, int hvalue, rc = 0; unsigned long flag; struct avc_node *pos, *node, *orig = NULL; - struct list_head *head; + struct hlist_head *head; + struct hlist_node *next; spinlock_t *lock; node = avc_alloc_node(); @@ -767,7 +773,7 @@ static int avc_update_node(u32 event, u32 perms, u32 ssid, u32 tsid, u16 tclass, spin_lock_irqsave(lock, flag); - list_for_each_entry(pos, head, list) { + hlist_for_each_entry(pos, next, head, list) { if (ssid == pos->ae.ssid && tsid == pos->ae.tsid && tclass == pos->ae.tclass && @@ -827,7 +833,8 @@ int avc_ss_reset(u32 seqno) int i, rc = 0, tmprc; unsigned long flag; struct avc_node *node; - struct list_head *head; + struct hlist_head *head; + struct hlist_node *next; spinlock_t *lock; for (i = 0; i < AVC_CACHE_SLOTS; i++) { @@ -840,7 +847,7 @@ int avc_ss_reset(u32 seqno) * prevent RCU grace periods from ending. */ rcu_read_lock(); - list_for_each_entry(node, head, list) + hlist_for_each_entry(node, next, head, list) avc_node_delete(node); rcu_read_unlock(); spin_unlock_irqrestore(lock, flag); -- cgit v1.2.3 From 33043cbb9fd49a957089f5948fe814764d7abbd6 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Fri, 13 Feb 2009 16:00:58 +0900 Subject: TOMOYO: Fix exception policy read failure. Due to wrong initialization, "cat /sys/kernel/security/tomoyo/exception_policy" returned nothing. Signed-off-by: Kentaro Takeda Signed-off-by: Tetsuo Handa Signed-off-by: Toshiharu Harada Signed-off-by: James Morris --- security/tomoyo/domain.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'security') diff --git a/security/tomoyo/domain.c b/security/tomoyo/domain.c index 92af8f50e0f..093a756030b 100644 --- a/security/tomoyo/domain.c +++ b/security/tomoyo/domain.c @@ -376,7 +376,7 @@ int tomoyo_write_domain_keeper_policy(char *data, const bool is_not, bool tomoyo_read_domain_keeper_policy(struct tomoyo_io_buffer *head) { struct list_head *pos; - bool done = false; + bool done = true; down_read(&tomoyo_domain_keeper_list_lock); list_for_each_cookie(pos, head->read_var2, -- cgit v1.2.3 From e5a3b95f581da62e2054ef79d3be2d383e9ed664 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Sat, 14 Feb 2009 11:46:56 +0900 Subject: TOMOYO: Don't create securityfs entries unless registered. TOMOYO should not create /sys/kernel/security/tomoyo/ interface unless TOMOYO is registered. Signed-off-by: Kentaro Takeda Signed-off-by: Tetsuo Handa Signed-off-by: Toshiharu Harada Signed-off-by: James Morris --- security/tomoyo/common.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'security') diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c index 8bedfb1992e..92cea656ad2 100644 --- a/security/tomoyo/common.c +++ b/security/tomoyo/common.c @@ -2177,6 +2177,10 @@ static int __init tomoyo_initerface_init(void) { struct dentry *tomoyo_dir; + /* Don't create securityfs entries unless registered. */ + if (current_cred()->security != &tomoyo_kernel_domain) + return 0; + tomoyo_dir = securityfs_create_dir("tomoyo", NULL); tomoyo_create_entry("domain_policy", 0600, tomoyo_dir, TOMOYO_DOMAINPOLICY); -- cgit v1.2.3 From 251a2a958b0455d11b711aeeb57cabad66259461 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Wed, 18 Feb 2009 11:42:33 -0800 Subject: smack: fix lots of kernel-doc notation Fix/add kernel-doc notation and fix typos in security/smack/. Signed-off-by: Randy Dunlap Acked-by: Casey Schaufler Signed-off-by: James Morris --- security/smack/smack_access.c | 4 ++-- security/smack/smack_lsm.c | 52 ++++++++++++++++++++----------------------- security/smack/smackfs.c | 15 +++++++------ 3 files changed, 34 insertions(+), 37 deletions(-) (limited to 'security') diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c index 2e0b83e77ff..cfa19ca125e 100644 --- a/security/smack/smack_access.c +++ b/security/smack/smack_access.c @@ -162,8 +162,8 @@ int smk_access(char *subject_label, char *object_label, int request) /** * smk_curacc - determine if current has a specific access to an object - * @object_label: a pointer to the object's Smack label - * @request: the access requested, in "MAY" format + * @obj_label: a pointer to the object's Smack label + * @mode: the access requested, in "MAY" format * * This function checks the current subject label/object label pair * in the access rule list and returns 0 if the access is permitted, diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 0278bc08304..4f48da5b08c 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -91,6 +91,7 @@ struct inode_smack *new_inode_smack(char *smack) /** * smack_ptrace_may_access - Smack approval on PTRACE_ATTACH * @ctp: child task pointer + * @mode: ptrace attachment mode * * Returns 0 if access is OK, an error code otherwise * @@ -203,9 +204,8 @@ static void smack_sb_free_security(struct super_block *sb) /** * smack_sb_copy_data - copy mount options data for processing - * @type: file system type * @orig: where to start - * @smackopts + * @smackopts: mount options string * * Returns 0 on success or -ENOMEM on error. * @@ -331,7 +331,7 @@ static int smack_sb_statfs(struct dentry *dentry) /** * smack_sb_mount - Smack check for mounting * @dev_name: unused - * @nd: mount point + * @path: mount point * @type: unused * @flags: unused * @data: unused @@ -370,7 +370,7 @@ static int smack_sb_umount(struct vfsmount *mnt, int flags) /** * smack_inode_alloc_security - allocate an inode blob - * @inode - the inode in need of a blob + * @inode: the inode in need of a blob * * Returns 0 if it gets a blob, -ENOMEM otherwise */ @@ -384,7 +384,7 @@ static int smack_inode_alloc_security(struct inode *inode) /** * smack_inode_free_security - free an inode blob - * @inode - the inode with a blob + * @inode: the inode with a blob * * Clears the blob pointer in inode */ @@ -538,7 +538,6 @@ static int smack_inode_rename(struct inode *old_inode, * smack_inode_permission - Smack version of permission() * @inode: the inode in question * @mask: the access requested - * @nd: unused * * This is the important Smack hook. * @@ -701,8 +700,7 @@ static int smack_inode_removexattr(struct dentry *dentry, const char *name) * @inode: the object * @name: attribute name * @buffer: where to put the result - * @size: size of the buffer - * @err: unused + * @alloc: unused * * Returns the size of the attribute or an error code */ @@ -864,7 +862,7 @@ static int smack_file_ioctl(struct file *file, unsigned int cmd, /** * smack_file_lock - Smack check on file locking * @file: the object - * @cmd unused + * @cmd: unused * * Returns 0 if current has write access, error code otherwise */ @@ -1003,8 +1001,8 @@ static int smack_cred_prepare(struct cred *new, const struct cred *old, return 0; } -/* - * commit new credentials +/** + * smack_cred_commit - commit new credentials * @new: the new credentials * @old: the original credentials */ @@ -1014,8 +1012,8 @@ static void smack_cred_commit(struct cred *new, const struct cred *old) /** * smack_kernel_act_as - Set the subjective context in a set of credentials - * @new points to the set of credentials to be modified. - * @secid specifies the security ID to be set + * @new: points to the set of credentials to be modified. + * @secid: specifies the security ID to be set * * Set the security data for a kernel service. */ @@ -1032,8 +1030,8 @@ static int smack_kernel_act_as(struct cred *new, u32 secid) /** * smack_kernel_create_files_as - Set the file creation label in a set of creds - * @new points to the set of credentials to be modified - * @inode points to the inode to use as a reference + * @new: points to the set of credentials to be modified + * @inode: points to the inode to use as a reference * * Set the file creation context in a set of credentials to the same * as the objective context of the specified inode @@ -1242,7 +1240,7 @@ static int smack_task_wait(struct task_struct *p) /** * smack_task_to_inode - copy task smack into the inode blob * @p: task to copy from - * inode: inode to copy to + * @inode: inode to copy to * * Sets the smack pointer in the inode security blob */ @@ -1260,7 +1258,7 @@ static void smack_task_to_inode(struct task_struct *p, struct inode *inode) * smack_sk_alloc_security - Allocate a socket blob * @sk: the socket * @family: unused - * @priority: memory allocation priority + * @gfp_flags: memory allocation flags * * Assign Smack pointers to current * @@ -2001,7 +1999,7 @@ static int smack_ipc_permission(struct kern_ipc_perm *ipp, short flag) /** * smack_ipc_getsecid - Extract smack security id - * @ipcp: the object permissions + * @ipp: the object permissions * @secid: where result will be saved */ static void smack_ipc_getsecid(struct kern_ipc_perm *ipp, u32 *secid) @@ -2278,7 +2276,7 @@ static int smack_unix_may_send(struct socket *sock, struct socket *other) /** * smack_socket_sendmsg - Smack check based on destination host * @sock: the socket - * @msghdr: the message + * @msg: the message * @size: the size of the message * * Return 0 if the current subject can write to the destination @@ -2319,8 +2317,7 @@ static int smack_socket_sendmsg(struct socket *sock, struct msghdr *msg, /** - * smack_from_secattr - Convert a netlabel attr.mls.lvl/attr.mls.cat - * pair to smack + * smack_from_secattr - Convert a netlabel attr.mls.lvl/attr.mls.cat pair to smack * @sap: netlabel secattr * @sip: where to put the result * @@ -2441,7 +2438,7 @@ static int smack_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) * @sock: the socket * @optval: user's destination * @optlen: size thereof - * @len: max thereoe + * @len: max thereof * * returns zero on success, an error code otherwise */ @@ -2776,7 +2773,7 @@ static void smack_audit_rule_free(void *vrule) #endif /* CONFIG_AUDIT */ -/* +/** * smack_secid_to_secctx - return the smack label for a secid * @secid: incoming integer * @secdata: destination @@ -2793,7 +2790,7 @@ static int smack_secid_to_secctx(u32 secid, char **secdata, u32 *seclen) return 0; } -/* +/** * smack_secctx_to_secid - return the secid for a smack label * @secdata: smack label * @seclen: how long result is @@ -2807,11 +2804,10 @@ static int smack_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid) return 0; } -/* +/** * smack_release_secctx - don't do anything. - * @key_ref: unused - * @context: unused - * @perm: unused + * @secdata: unused + * @seclen: unused * * Exists to make sure nothing gets done, and properly */ diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index 8e42800878f..fd8d1eb4370 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -245,7 +245,7 @@ out: /** * smk_write_load - write() for /smack/load - * @filp: file pointer, not actually used + * @file: file pointer, not actually used * @buf: where to get the data from * @count: bytes sent * @ppos: where to start - must be 0 @@ -402,6 +402,7 @@ static void smk_cipso_doi(void) /** * smk_unlbl_ambient - initialize the unlabeled domain + * @oldambient: previous domain string */ static void smk_unlbl_ambient(char *oldambient) { @@ -513,7 +514,7 @@ static int smk_open_cipso(struct inode *inode, struct file *file) /** * smk_write_cipso - write() for /smack/cipso - * @filp: file pointer, not actually used + * @file: file pointer, not actually used * @buf: where to get the data from * @count: bytes sent * @ppos: where to start @@ -703,7 +704,7 @@ static int smk_open_netlbladdr(struct inode *inode, struct file *file) /** * smk_write_netlbladdr - write() for /smack/netlabel - * @filp: file pointer, not actually used + * @file: file pointer, not actually used * @buf: where to get the data from * @count: bytes sent * @ppos: where to start @@ -850,7 +851,7 @@ static ssize_t smk_read_doi(struct file *filp, char __user *buf, /** * smk_write_doi - write() for /smack/doi - * @filp: file pointer, not actually used + * @file: file pointer, not actually used * @buf: where to get the data from * @count: bytes sent * @ppos: where to start @@ -915,7 +916,7 @@ static ssize_t smk_read_direct(struct file *filp, char __user *buf, /** * smk_write_direct - write() for /smack/direct - * @filp: file pointer, not actually used + * @file: file pointer, not actually used * @buf: where to get the data from * @count: bytes sent * @ppos: where to start @@ -990,7 +991,7 @@ static ssize_t smk_read_ambient(struct file *filp, char __user *buf, /** * smk_write_ambient - write() for /smack/ambient - * @filp: file pointer, not actually used + * @file: file pointer, not actually used * @buf: where to get the data from * @count: bytes sent * @ppos: where to start @@ -1065,7 +1066,7 @@ static ssize_t smk_read_onlycap(struct file *filp, char __user *buf, /** * smk_write_onlycap - write() for /smack/onlycap - * @filp: file pointer, not actually used + * @file: file pointer, not actually used * @buf: where to get the data from * @count: bytes sent * @ppos: where to start -- cgit v1.2.3 From 0da0a420bb542b13ebae142109a9d2045ade0cb1 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Thu, 19 Feb 2009 21:23:50 -0500 Subject: integrity: ima scatterlist bug fix Based on Alexander Beregalov's post http://lkml.org/lkml/2009/2/19/198 - replaced sg_set_buf() with sg_init_one() kernel BUG at include/linux/scatterlist.h:65! invalid opcode: 0000 [#1] PREEMPT SMP DEBUG_PAGEALLOC last sysfs file: CPU 2 Modules linked in: Pid: 1, comm: swapper Not tainted 2.6.29-rc5-next-20090219 #5 PowerEdge 1950 RIP: 0010:[] [] ima_calc_hash+0xc0/0x160 RSP: 0018:ffff88007f46bc40 EFLAGS: 00010286 RAX: ffffe200032c45e8 RBX: 00000000fffffff4 RCX: 0000000087654321 RDX: 0000000000000002 RSI: 0000000000000001 RDI: ffff88007cf71048 RBP: ffff88007f46bcd0 R08: 0000000000000000 R09: 0000000000000163 R10: ffff88007f4707a8 R11: 0000000000000000 R12: ffff88007cf71048 R13: 0000000000001000 R14: 0000000000000000 R15: 0000000000009d98 FS: 0000000000000000(0000) GS:ffff8800051ac000(0000) knlGS:0000000000000000 CS: 0010 DS: 0018 ES: 0018 CR0: 000000008005003b CR2: 0000000000000000 CR3: 0000000000201000 CR4: 00000000000006e0 DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400 Signed-off-by: Mimi Zohar Tested-by: Alexander Beregalov Signed-off-by: James Morris --- security/integrity/ima/ima_crypto.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima_crypto.c b/security/integrity/ima/ima_crypto.c index c2a46e40999..50d572b74ca 100644 --- a/security/integrity/ima/ima_crypto.c +++ b/security/integrity/ima/ima_crypto.c @@ -68,7 +68,7 @@ int ima_calc_hash(struct file *file, char *digest) break; } offset += rbuf_len; - sg_set_buf(sg, rbuf, rbuf_len); + sg_init_one(sg, rbuf, rbuf_len); rc = crypto_hash_update(&desc, sg, rbuf_len); if (rc) @@ -95,7 +95,7 @@ int ima_calc_template_hash(int template_len, void *template, char *digest) if (rc != 0) return rc; - sg_set_buf(sg, template, template_len); + sg_init_one(sg, template, template_len); rc = crypto_hash_update(&desc, sg, template_len); if (!rc) rc = crypto_hash_final(&desc, digest); -- cgit v1.2.3 From 1581e7ddbdd97443a134e1a0cc9d81256baf77a4 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Sat, 21 Feb 2009 20:40:50 +0900 Subject: TOMOYO: Do not call tomoyo_realpath_init unless registered. tomoyo_realpath_init() is unconditionally called by security_initcall(). But nobody will use realpath related functions if TOMOYO is not registered. So, let tomoyo_init() call tomoyo_realpath_init(). This patch saves 4KB of memory allocation if TOMOYO is not registered. Signed-off-by: Kentaro Takeda Signed-off-by: Tetsuo Handa Signed-off-by: Toshiharu Harada Signed-off-by: James Morris --- security/tomoyo/realpath.c | 7 +------ security/tomoyo/realpath.h | 3 +++ security/tomoyo/tomoyo.c | 1 + 3 files changed, 5 insertions(+), 6 deletions(-) (limited to 'security') diff --git a/security/tomoyo/realpath.c b/security/tomoyo/realpath.c index 5fd48d23a21..d47f16b844b 100644 --- a/security/tomoyo/realpath.c +++ b/security/tomoyo/realpath.c @@ -371,10 +371,8 @@ const struct tomoyo_path_info *tomoyo_save_name(const char *name) /** * tomoyo_realpath_init - Initialize realpath related code. - * - * Returns 0. */ -static int __init tomoyo_realpath_init(void) +void __init tomoyo_realpath_init(void) { int i; @@ -388,11 +386,8 @@ static int __init tomoyo_realpath_init(void) if (tomoyo_find_domain(TOMOYO_ROOT_NAME) != &tomoyo_kernel_domain) panic("Can't register tomoyo_kernel_domain"); up_read(&tomoyo_domain_list_lock); - return 0; } -security_initcall(tomoyo_realpath_init); - /* Memory allocated for temporary purpose. */ static atomic_t tomoyo_dynamic_memory_size; diff --git a/security/tomoyo/realpath.h b/security/tomoyo/realpath.h index 0ea541fcb53..7ec9fc9cbc0 100644 --- a/security/tomoyo/realpath.h +++ b/security/tomoyo/realpath.h @@ -60,4 +60,7 @@ int tomoyo_read_memory_counter(struct tomoyo_io_buffer *head); /* Set memory quota. */ int tomoyo_write_memory_quota(struct tomoyo_io_buffer *head); +/* Initialize realpath related code. */ +void __init tomoyo_realpath_init(void); + #endif /* !defined(_SECURITY_TOMOYO_REALPATH_H) */ diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c index cc599b36a3a..3eeeae12c4d 100644 --- a/security/tomoyo/tomoyo.c +++ b/security/tomoyo/tomoyo.c @@ -287,6 +287,7 @@ static int __init tomoyo_init(void) panic("Failure registering TOMOYO Linux"); printk(KERN_INFO "TOMOYO Linux initialized\n"); cred->security = &tomoyo_kernel_domain; + tomoyo_realpath_init(); return 0; } -- cgit v1.2.3 From be38e0fd5f90a91d09e0a85ffb294b70a7be6259 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Fri, 20 Feb 2009 14:28:29 -0800 Subject: integrity: ima iint radix_tree_lookup locking fix Based on Andrew Morton's comments: - add missing locks around radix_tree_lookup in ima_iint_insert() Signed-off-by: Mimi Zohar Cc: James Morris Signed-off-by: Andrew Morton Signed-off-by: James Morris --- security/integrity/ima/ima_iint.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'security') diff --git a/security/integrity/ima/ima_iint.c b/security/integrity/ima/ima_iint.c index 1f035e8d29c..ec79f1ee992 100644 --- a/security/integrity/ima/ima_iint.c +++ b/security/integrity/ima/ima_iint.c @@ -73,8 +73,10 @@ out: if (rc < 0) { kmem_cache_free(iint_cache, iint); if (rc == -EEXIST) { + spin_lock(&ima_iint_lock); iint = radix_tree_lookup(&ima_iint_store, (unsigned long)inode); + spin_unlock(&ima_iint_lock); } else iint = NULL; } -- cgit v1.2.3 From 1d1e97562e5e2ac60fb7b25437ba619f95f67fab Mon Sep 17 00:00:00 2001 From: "Serge E. Hallyn" Date: Thu, 26 Feb 2009 18:27:38 -0600 Subject: keys: distinguish per-uid keys in different namespaces per-uid keys were looked by uid only. Use the user namespace to distinguish the same uid in different namespaces. This does not address key_permission. So a task can for instance try to join a keyring owned by the same uid in another namespace. That will be handled by a separate patch. Signed-off-by: Serge E. Hallyn Acked-by: David Howells Signed-off-by: James Morris --- security/keys/internal.h | 4 +++- security/keys/key.c | 11 +++++++++-- security/keys/keyctl.c | 2 +- security/keys/process_keys.c | 2 ++ security/keys/request_key.c | 2 +- 5 files changed, 16 insertions(+), 5 deletions(-) (limited to 'security') diff --git a/security/keys/internal.h b/security/keys/internal.h index 81932abefe7..9fb679c66b8 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -53,6 +53,7 @@ struct key_user { atomic_t nkeys; /* number of keys */ atomic_t nikeys; /* number of instantiated keys */ uid_t uid; + struct user_namespace *user_ns; int qnkeys; /* number of keys allocated to this user */ int qnbytes; /* number of bytes allocated to this user */ }; @@ -61,7 +62,8 @@ extern struct rb_root key_user_tree; extern spinlock_t key_user_lock; extern struct key_user root_key_user; -extern struct key_user *key_user_lookup(uid_t uid); +extern struct key_user *key_user_lookup(uid_t uid, + struct user_namespace *user_ns); extern void key_user_put(struct key_user *user); /* diff --git a/security/keys/key.c b/security/keys/key.c index f76c8a546fd..4a1297d1ada 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "internal.h" static struct kmem_cache *key_jar; @@ -60,7 +61,7 @@ void __key_check(const struct key *key) * get the key quota record for a user, allocating a new record if one doesn't * already exist */ -struct key_user *key_user_lookup(uid_t uid) +struct key_user *key_user_lookup(uid_t uid, struct user_namespace *user_ns) { struct key_user *candidate = NULL, *user; struct rb_node *parent = NULL; @@ -79,6 +80,10 @@ struct key_user *key_user_lookup(uid_t uid) p = &(*p)->rb_left; else if (uid > user->uid) p = &(*p)->rb_right; + else if (user_ns < user->user_ns) + p = &(*p)->rb_left; + else if (user_ns > user->user_ns) + p = &(*p)->rb_right; else goto found; } @@ -106,6 +111,7 @@ struct key_user *key_user_lookup(uid_t uid) atomic_set(&candidate->nkeys, 0); atomic_set(&candidate->nikeys, 0); candidate->uid = uid; + candidate->user_ns = get_user_ns(user_ns); candidate->qnkeys = 0; candidate->qnbytes = 0; spin_lock_init(&candidate->lock); @@ -136,6 +142,7 @@ void key_user_put(struct key_user *user) if (atomic_dec_and_lock(&user->usage, &key_user_lock)) { rb_erase(&user->node, &key_user_tree); spin_unlock(&key_user_lock); + put_user_ns(user->user_ns); kfree(user); } @@ -234,7 +241,7 @@ struct key *key_alloc(struct key_type *type, const char *desc, quotalen = desclen + type->def_datalen; /* get hold of the key tracking for this user */ - user = key_user_lookup(uid); + user = key_user_lookup(uid, cred->user->user_ns); if (!user) goto no_memory_1; diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c index b1ec3b4ee17..7f09fb897d2 100644 --- a/security/keys/keyctl.c +++ b/security/keys/keyctl.c @@ -726,7 +726,7 @@ long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid) /* change the UID */ if (uid != (uid_t) -1 && uid != key->uid) { ret = -ENOMEM; - newowner = key_user_lookup(uid); + newowner = key_user_lookup(uid, current_user_ns()); if (!newowner) goto error_put; diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c index 2f5d89e92b8..276d27882ce 100644 --- a/security/keys/process_keys.c +++ b/security/keys/process_keys.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "internal.h" @@ -34,6 +35,7 @@ struct key_user root_key_user = { .nkeys = ATOMIC_INIT(2), .nikeys = ATOMIC_INIT(2), .uid = 0, + .user_ns = &init_user_ns, }; /*****************************************************************************/ diff --git a/security/keys/request_key.c b/security/keys/request_key.c index 0e04f72ef2d..22a31582bfa 100644 --- a/security/keys/request_key.c +++ b/security/keys/request_key.c @@ -365,7 +365,7 @@ static struct key *construct_key_and_link(struct key_type *type, kenter(""); - user = key_user_lookup(current_fsuid()); + user = key_user_lookup(current_fsuid(), current_user_ns()); if (!user) return ERR_PTR(-ENOMEM); -- cgit v1.2.3 From 8ff3bc3138a400294ee9e126ac75fc9a9fae4e0b Mon Sep 17 00:00:00 2001 From: "Serge E. Hallyn" Date: Thu, 26 Feb 2009 18:27:47 -0600 Subject: keys: consider user namespace in key_permission If a key is owned by another user namespace, then treat the key as though it is owned by both another uid and gid. Signed-off-by: Serge E. Hallyn Acked-by: David Howells Signed-off-by: James Morris --- security/keys/permission.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'security') diff --git a/security/keys/permission.c b/security/keys/permission.c index 5d9fc7b93f2..0ed802c9e69 100644 --- a/security/keys/permission.c +++ b/security/keys/permission.c @@ -35,6 +35,9 @@ int key_task_permission(const key_ref_t key_ref, const struct cred *cred, key = key_ref_to_ptr(key_ref); + if (key->user->user_ns != cred->user->user_ns) + goto use_other_perms; + /* use the second 8-bits of permissions for keys the caller owns */ if (key->uid == cred->fsuid) { kperm = key->perm >> 16; @@ -56,6 +59,8 @@ int key_task_permission(const key_ref_t key_ref, const struct cred *cred, } } +use_other_perms: + /* otherwise use the least-significant 8-bits */ kperm = key->perm; -- cgit v1.2.3 From 2ea190d0a006ce5218baa6e798512652446a605a Mon Sep 17 00:00:00 2001 From: "Serge E. Hallyn" Date: Thu, 26 Feb 2009 18:27:55 -0600 Subject: keys: skip keys from another user namespace When listing keys, do not return keys belonging to the same uid in another user namespace. Otherwise uid 500 in another user namespace will return keyrings called uid.500 for another user namespace. Signed-off-by: Serge E. Hallyn Acked-by: David Howells Signed-off-by: James Morris --- security/keys/keyring.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'security') diff --git a/security/keys/keyring.c b/security/keys/keyring.c index ed851574d07..3dba81c2eba 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -539,6 +539,9 @@ struct key *find_keyring_by_name(const char *name, bool skip_perm_check) &keyring_name_hash[bucket], type_data.link ) { + if (keyring->user->user_ns != current_user_ns()) + continue; + if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) continue; -- cgit v1.2.3 From 454804ab0302b354e35d992d08e53fe03313baaf Mon Sep 17 00:00:00 2001 From: "Serge E. Hallyn" Date: Thu, 26 Feb 2009 18:28:04 -0600 Subject: keys: make procfiles per-user-namespace Restrict the /proc/keys and /proc/key-users output to keys belonging to the same user namespace as the reading task. We may want to make this more complicated - so that any keys in a user-namespace which is belongs to the reading task are also shown. But let's see if anyone wants that first. Signed-off-by: Serge E. Hallyn Acked-by: David Howells Signed-off-by: James Morris --- security/keys/proc.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 6 deletions(-) (limited to 'security') diff --git a/security/keys/proc.c b/security/keys/proc.c index 7f508def50e..769f9bdfd2b 100644 --- a/security/keys/proc.c +++ b/security/keys/proc.c @@ -91,6 +91,28 @@ __initcall(key_proc_init); */ #ifdef CONFIG_KEYS_DEBUG_PROC_KEYS +static struct rb_node *__key_serial_next(struct rb_node *n) +{ + while (n) { + struct key *key = rb_entry(n, struct key, serial_node); + if (key->user->user_ns == current_user_ns()) + break; + n = rb_next(n); + } + return n; +} + +static struct rb_node *key_serial_next(struct rb_node *n) +{ + return __key_serial_next(rb_next(n)); +} + +static struct rb_node *key_serial_first(struct rb_root *r) +{ + struct rb_node *n = rb_first(r); + return __key_serial_next(n); +} + static int proc_keys_open(struct inode *inode, struct file *file) { return seq_open(file, &proc_keys_ops); @@ -104,10 +126,10 @@ static void *proc_keys_start(struct seq_file *p, loff_t *_pos) spin_lock(&key_serial_lock); - _p = rb_first(&key_serial_tree); + _p = key_serial_first(&key_serial_tree); while (pos > 0 && _p) { pos--; - _p = rb_next(_p); + _p = key_serial_next(_p); } return _p; @@ -117,7 +139,7 @@ static void *proc_keys_start(struct seq_file *p, loff_t *_pos) static void *proc_keys_next(struct seq_file *p, void *v, loff_t *_pos) { (*_pos)++; - return rb_next((struct rb_node *) v); + return key_serial_next((struct rb_node *) v); } @@ -203,6 +225,27 @@ static int proc_keys_show(struct seq_file *m, void *v) #endif /* CONFIG_KEYS_DEBUG_PROC_KEYS */ +static struct rb_node *__key_user_next(struct rb_node *n) +{ + while (n) { + struct key_user *user = rb_entry(n, struct key_user, node); + if (user->user_ns == current_user_ns()) + break; + n = rb_next(n); + } + return n; +} + +static struct rb_node *key_user_next(struct rb_node *n) +{ + return __key_user_next(rb_next(n)); +} + +static struct rb_node *key_user_first(struct rb_root *r) +{ + struct rb_node *n = rb_first(r); + return __key_user_next(n); +} /*****************************************************************************/ /* * implement "/proc/key-users" to provides a list of the key users @@ -220,10 +263,10 @@ static void *proc_key_users_start(struct seq_file *p, loff_t *_pos) spin_lock(&key_user_lock); - _p = rb_first(&key_user_tree); + _p = key_user_first(&key_user_tree); while (pos > 0 && _p) { pos--; - _p = rb_next(_p); + _p = key_user_next(_p); } return _p; @@ -233,7 +276,7 @@ static void *proc_key_users_start(struct seq_file *p, loff_t *_pos) static void *proc_key_users_next(struct seq_file *p, void *v, loff_t *_pos) { (*_pos)++; - return rb_next((struct rb_node *) v); + return key_user_next((struct rb_node *) v); } -- cgit v1.2.3 From 113a0e4590881ce579ca992a80ddc562b3372ede Mon Sep 17 00:00:00 2001 From: etienne Date: Wed, 4 Mar 2009 07:33:51 +0100 Subject: smack: fixes for unlabeled host support The following patch (against 2.6.29rc5) fixes a few issues in the smack/netlabel "unlabeled host support" functionnality that was added in 2.6.29rc. It should go in before -final. 1) smack_host_label disregard a "0.0.0.0/0 @" rule (or other label), preventing 'tagged' tasks to access Internet (many systems drop packets with IP options) 2) netmasks were not handled correctly, they were stored in a way _not equivalent_ to conversion to be32 (it was equivalent for /0, /8, /16, /24, /32 masks but not other masks) 3) smack_netlbladdr prefixes (IP/mask) were not consistent (mask&IP was not done), so there could have been different list entries for the same IP prefix; if those entries had different labels, well ... 4) they were not sorted 1) 2) 3) are bugs, 4) is a more cosmetic issue. The patch : -creates a new helper smk_netlbladdr_insert to insert a smk_netlbladdr, -sorted by netmask length -use the new sorted nature of smack_netlbladdrs list to simplify smack_host_label : the first match _will_ be the more specific -corrects endianness issues in smk_write_netlbladdr & netlbladdr_seq_show Signed-off-by: Acked-by: Casey Schaufler Reviewed-by: Paul Moore Signed-off-by: James Morris --- security/smack/smackfs.c | 64 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 15 deletions(-) (limited to 'security') diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index fd8d1eb4370..a1b57e4dba3 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -651,10 +651,6 @@ static void *netlbladdr_seq_next(struct seq_file *s, void *v, loff_t *pos) return skp; } -/* -#define BEMASK 0x80000000 -*/ -#define BEMASK 0x00000001 #define BEBITS (sizeof(__be32) * 8) /* @@ -664,12 +660,10 @@ static int netlbladdr_seq_show(struct seq_file *s, void *v) { struct smk_netlbladdr *skp = (struct smk_netlbladdr *) v; unsigned char *hp = (char *) &skp->smk_host.sin_addr.s_addr; - __be32 bebits; - int maskn = 0; + int maskn; + u32 temp_mask = be32_to_cpu(skp->smk_mask.s_addr); - for (bebits = BEMASK; bebits != 0; maskn++, bebits <<= 1) - if ((skp->smk_mask.s_addr & bebits) == 0) - break; + for (maskn = 0; temp_mask; temp_mask <<= 1, maskn++); seq_printf(s, "%u.%u.%u.%u/%d %s\n", hp[0], hp[1], hp[2], hp[3], maskn, skp->smk_label); @@ -702,6 +696,42 @@ static int smk_open_netlbladdr(struct inode *inode, struct file *file) return seq_open(file, &netlbladdr_seq_ops); } +/** + * smk_netlbladdr_insert + * @new : netlabel to insert + * + * This helper insert netlabel in the smack_netlbladdrs list + * sorted by netmask length (longest to smallest) + */ +static void smk_netlbladdr_insert(struct smk_netlbladdr *new) +{ + struct smk_netlbladdr *m; + + if (smack_netlbladdrs == NULL) { + smack_netlbladdrs = new; + return; + } + + /* the comparison '>' is a bit hacky, but works */ + if (new->smk_mask.s_addr > smack_netlbladdrs->smk_mask.s_addr) { + new->smk_next = smack_netlbladdrs; + smack_netlbladdrs = new; + return; + } + for (m = smack_netlbladdrs; m != NULL; m = m->smk_next) { + if (m->smk_next == NULL) { + m->smk_next = new; + return; + } + if (new->smk_mask.s_addr > m->smk_next->smk_mask.s_addr) { + new->smk_next = m->smk_next; + m->smk_next = new; + return; + } + } +} + + /** * smk_write_netlbladdr - write() for /smack/netlabel * @file: file pointer, not actually used @@ -725,8 +755,9 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, struct netlbl_audit audit_info; struct in_addr mask; unsigned int m; - __be32 bebits = BEMASK; + u32 mask_bits = (1<<31); __be32 nsa; + u32 temp_mask; /* * Must have privilege. @@ -762,10 +793,13 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, if (sp == NULL) return -EINVAL; - for (mask.s_addr = 0; m > 0; m--) { - mask.s_addr |= bebits; - bebits <<= 1; + for (temp_mask = 0; m > 0; m--) { + temp_mask |= mask_bits; + mask_bits >>= 1; } + mask.s_addr = cpu_to_be32(temp_mask); + + newname.sin_addr.s_addr &= mask.s_addr; /* * Only allow one writer at a time. Writes should be * quite rare and small in any case. @@ -773,6 +807,7 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, mutex_lock(&smk_netlbladdr_lock); nsa = newname.sin_addr.s_addr; + /* try to find if the prefix is already in the list */ for (skp = smack_netlbladdrs; skp != NULL; skp = skp->smk_next) if (skp->smk_host.sin_addr.s_addr == nsa && skp->smk_mask.s_addr == mask.s_addr) @@ -788,9 +823,8 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, rc = 0; skp->smk_host.sin_addr.s_addr = newname.sin_addr.s_addr; skp->smk_mask.s_addr = mask.s_addr; - skp->smk_next = smack_netlbladdrs; skp->smk_label = sp; - smack_netlbladdrs = skp; + smk_netlbladdr_insert(skp); } } else { rc = netlbl_cfg_unlbl_static_del(&init_net, NULL, -- cgit v1.2.3 From 6a25b27d602aac24f3c642722377ba5d778417ec Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Thu, 5 Mar 2009 13:40:35 -0500 Subject: SELinux: open perm for sock files When I did open permissions I didn't think any sockets would have an open. Turns out AF_UNIX sockets can have an open when they are bound to the filesystem namespace. This patch adds a new SOCK_FILE__OPEN permission. It's safe to add this as the open perms are already predicated on capabilities and capabilities means we have unknown perm handling so systems should be as backwards compatible as the policy wants them to be. https://bugzilla.redhat.com/show_bug.cgi?id=475224 Signed-off-by: Eric Paris Acked-by: Stephen Smalley Signed-off-by: James Morris --- security/selinux/hooks.c | 2 ++ security/selinux/include/av_perm_to_string.h | 1 + security/selinux/include/av_permissions.h | 1 + 3 files changed, 4 insertions(+) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 309648c573d..cd3307a26d1 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -1838,6 +1838,8 @@ static inline u32 open_file_to_av(struct file *file) av |= FIFO_FILE__OPEN; else if (S_ISDIR(mode)) av |= DIR__OPEN; + else if (S_ISSOCK(mode)) + av |= SOCK_FILE__OPEN; else printk(KERN_ERR "SELinux: WARNING: inside %s with " "unknown mode:%o\n", __func__, mode); diff --git a/security/selinux/include/av_perm_to_string.h b/security/selinux/include/av_perm_to_string.h index c0c885427b9..c7531ee9c7b 100644 --- a/security/selinux/include/av_perm_to_string.h +++ b/security/selinux/include/av_perm_to_string.h @@ -24,6 +24,7 @@ S_(SECCLASS_CHR_FILE, CHR_FILE__EXECMOD, "execmod") S_(SECCLASS_CHR_FILE, CHR_FILE__OPEN, "open") S_(SECCLASS_BLK_FILE, BLK_FILE__OPEN, "open") + S_(SECCLASS_SOCK_FILE, SOCK_FILE__OPEN, "open") S_(SECCLASS_FIFO_FILE, FIFO_FILE__OPEN, "open") S_(SECCLASS_FD, FD__USE, "use") S_(SECCLASS_TCP_SOCKET, TCP_SOCKET__CONNECTTO, "connectto") diff --git a/security/selinux/include/av_permissions.h b/security/selinux/include/av_permissions.h index 0ba79fe00e1..0b8f9b2bbde 100644 --- a/security/selinux/include/av_permissions.h +++ b/security/selinux/include/av_permissions.h @@ -174,6 +174,7 @@ #define SOCK_FILE__SWAPON 0x00004000UL #define SOCK_FILE__QUOTAON 0x00008000UL #define SOCK_FILE__MOUNTON 0x00010000UL +#define SOCK_FILE__OPEN 0x00020000UL #define FIFO_FILE__IOCTL 0x00000001UL #define FIFO_FILE__READ 0x00000002UL #define FIFO_FILE__WRITE 0x00000004UL -- cgit v1.2.3 From dd34b5d75a0405814a3de83f02a44ac297e81629 Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Thu, 5 Mar 2009 13:43:35 -0500 Subject: SELinux: new permission between tty audit and audit socket New selinux permission to separate the ability to turn on tty auditing from the ability to set audit rules. Signed-off-by: Eric Paris Acked-by: Stephen Smalley Signed-off-by: James Morris --- security/selinux/include/av_perm_to_string.h | 1 + security/selinux/include/av_permissions.h | 1 + security/selinux/nlmsgtab.c | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) (limited to 'security') diff --git a/security/selinux/include/av_perm_to_string.h b/security/selinux/include/av_perm_to_string.h index c7531ee9c7b..31df1d7c1ae 100644 --- a/security/selinux/include/av_perm_to_string.h +++ b/security/selinux/include/av_perm_to_string.h @@ -153,6 +153,7 @@ S_(SECCLASS_NETLINK_AUDIT_SOCKET, NETLINK_AUDIT_SOCKET__NLMSG_WRITE, "nlmsg_write") S_(SECCLASS_NETLINK_AUDIT_SOCKET, NETLINK_AUDIT_SOCKET__NLMSG_RELAY, "nlmsg_relay") S_(SECCLASS_NETLINK_AUDIT_SOCKET, NETLINK_AUDIT_SOCKET__NLMSG_READPRIV, "nlmsg_readpriv") + S_(SECCLASS_NETLINK_AUDIT_SOCKET, NETLINK_AUDIT_SOCKET__NLMSG_TTY_AUDIT, "nlmsg_tty_audit") S_(SECCLASS_NETLINK_IP6FW_SOCKET, NETLINK_IP6FW_SOCKET__NLMSG_READ, "nlmsg_read") S_(SECCLASS_NETLINK_IP6FW_SOCKET, NETLINK_IP6FW_SOCKET__NLMSG_WRITE, "nlmsg_write") S_(SECCLASS_ASSOCIATION, ASSOCIATION__SENDTO, "sendto") diff --git a/security/selinux/include/av_permissions.h b/security/selinux/include/av_permissions.h index 0b8f9b2bbde..d645192ee95 100644 --- a/security/selinux/include/av_permissions.h +++ b/security/selinux/include/av_permissions.h @@ -708,6 +708,7 @@ #define NETLINK_AUDIT_SOCKET__NLMSG_WRITE 0x00800000UL #define NETLINK_AUDIT_SOCKET__NLMSG_RELAY 0x01000000UL #define NETLINK_AUDIT_SOCKET__NLMSG_READPRIV 0x02000000UL +#define NETLINK_AUDIT_SOCKET__NLMSG_TTY_AUDIT 0x04000000UL #define NETLINK_IP6FW_SOCKET__IOCTL 0x00000001UL #define NETLINK_IP6FW_SOCKET__READ 0x00000002UL #define NETLINK_IP6FW_SOCKET__WRITE 0x00000004UL diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c index 4ed7bab89c5..c6875fd3b9d 100644 --- a/security/selinux/nlmsgtab.c +++ b/security/selinux/nlmsgtab.c @@ -113,7 +113,7 @@ static struct nlmsg_perm nlmsg_audit_perms[] = { AUDIT_USER, NETLINK_AUDIT_SOCKET__NLMSG_RELAY }, { AUDIT_SIGNAL_INFO, NETLINK_AUDIT_SOCKET__NLMSG_READ }, { AUDIT_TTY_GET, NETLINK_AUDIT_SOCKET__NLMSG_READ }, - { AUDIT_TTY_SET, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_TTY_SET, NETLINK_AUDIT_SOCKET__NLMSG_TTY_AUDIT }, }; -- cgit v1.2.3 From df7f54c012b92ec93d56b68547351dcdf8a163d3 Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Mon, 9 Mar 2009 14:35:58 -0400 Subject: SELinux: inode_doinit_with_dentry drop no dentry printk Drop the printk message when an inode is found without an associated dentry. This should only happen when userspace can't be accessing those inodes and those labels will get set correctly on the next d_instantiate. Thus there is no reason to send this message. Signed-off-by: Eric Paris Signed-off-by: James Morris --- security/selinux/hooks.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index cd3307a26d1..7c52ba243c6 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -1263,9 +1263,15 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent dentry = d_find_alias(inode); } if (!dentry) { - printk(KERN_WARNING "SELinux: %s: no dentry for dev=%s " - "ino=%ld\n", __func__, inode->i_sb->s_id, - inode->i_ino); + /* + * this is can be hit on boot when a file is accessed + * before the policy is loaded. When we load policy we + * may find inodes that have no dentry on the + * sbsec->isec_head list. No reason to complain as these + * will get fixed up the next time we go through + * inode_doinit with a dentry, before these inodes could + * be used again by userspace. + */ goto out_unlock; } -- cgit v1.2.3 From 7198e2eeb44b3fe7cc97f997824002da47a9c644 Mon Sep 17 00:00:00 2001 From: Etienne Basset Date: Tue, 24 Mar 2009 20:53:24 +0100 Subject: smack: convert smack to standard linux lists the following patch (on top of 2.6.29) converts Smack lists to standard linux lists Please review and consider for inclusion in 2.6.30-rc regards, Etienne Signed-off-by: Etienne Basset Acked-by: Casey Schaufler --- security/smack/smack.h | 28 +++---- security/smack/smack_access.c | 63 +++++++++------ security/smack/smack_lsm.c | 19 ++++- security/smack/smackfs.c | 180 ++++++++++++++++++++++++------------------ 4 files changed, 168 insertions(+), 122 deletions(-) (limited to 'security') diff --git a/security/smack/smack.h b/security/smack/smack.h index b79582e4fbf..64164f8fde7 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -18,6 +18,8 @@ #include #include #include +#include +#include /* * Why 23? CIPSO is constrained to 30, so a 32 byte buffer is @@ -59,17 +61,10 @@ struct inode_smack { * A label access rule. */ struct smack_rule { - char *smk_subject; - char *smk_object; - int smk_access; -}; - -/* - * An entry in the table of permitted label accesses. - */ -struct smk_list_entry { - struct smk_list_entry *smk_next; - struct smack_rule smk_rule; + struct list_head list; + char *smk_subject; + char *smk_object; + int smk_access; }; /* @@ -85,7 +80,7 @@ struct smack_cipso { * An entry in the table identifying hosts. */ struct smk_netlbladdr { - struct smk_netlbladdr *smk_next; + struct list_head list; struct sockaddr_in smk_host; /* network address */ struct in_addr smk_mask; /* network mask */ char *smk_label; /* label */ @@ -113,7 +108,7 @@ struct smk_netlbladdr { * the cipso direct mapping in used internally. */ struct smack_known { - struct smack_known *smk_next; + struct list_head list; char smk_known[SMK_LABELLEN]; u32 smk_secid; struct smack_cipso *smk_cipso; @@ -206,7 +201,6 @@ extern int smack_cipso_direct; extern char *smack_net_ambient; extern char *smack_onlycap; -extern struct smack_known *smack_known; extern struct smack_known smack_known_floor; extern struct smack_known smack_known_hat; extern struct smack_known smack_known_huh; @@ -214,8 +208,10 @@ extern struct smack_known smack_known_invalid; extern struct smack_known smack_known_star; extern struct smack_known smack_known_web; -extern struct smk_list_entry *smack_list; -extern struct smk_netlbladdr *smack_netlbladdrs; +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 cfa19ca125e..58564195bb0 100644 --- a/security/smack/smack_access.c +++ b/security/smack/smack_access.c @@ -16,48 +16,42 @@ #include "smack.h" struct smack_known smack_known_huh = { - .smk_next = NULL, .smk_known = "?", .smk_secid = 2, .smk_cipso = NULL, }; struct smack_known smack_known_hat = { - .smk_next = &smack_known_huh, .smk_known = "^", .smk_secid = 3, .smk_cipso = NULL, }; struct smack_known smack_known_star = { - .smk_next = &smack_known_hat, .smk_known = "*", .smk_secid = 4, .smk_cipso = NULL, }; struct smack_known smack_known_floor = { - .smk_next = &smack_known_star, .smk_known = "_", .smk_secid = 5, .smk_cipso = NULL, }; struct smack_known smack_known_invalid = { - .smk_next = &smack_known_floor, .smk_known = "", .smk_secid = 6, .smk_cipso = NULL, }; struct smack_known smack_known_web = { - .smk_next = &smack_known_invalid, .smk_known = "@", .smk_secid = 7, .smk_cipso = NULL, }; -struct smack_known *smack_known = &smack_known_web; +LIST_HEAD(smack_known_list); /* * The initial value needs to be bigger than any of the @@ -87,7 +81,6 @@ static u32 smack_next_secid = 10; int smk_access(char *subject_label, char *object_label, int request) { u32 may = MAY_NOT; - struct smk_list_entry *sp; struct smack_rule *srp; /* @@ -139,9 +132,8 @@ int smk_access(char *subject_label, char *object_label, int request) * access (e.g. read is included in readwrite) it's * good. */ - for (sp = smack_list; sp != NULL; sp = sp->smk_next) { - srp = &sp->smk_rule; - + rcu_read_lock(); + list_for_each_entry_rcu(srp, &smack_rule_list, list) { if (srp->smk_subject == subject_label || strcmp(srp->smk_subject, subject_label) == 0) { if (srp->smk_object == object_label || @@ -151,6 +143,7 @@ int smk_access(char *subject_label, char *object_label, int request) } } } + rcu_read_unlock(); /* * This is a bit map operation. */ @@ -228,14 +221,17 @@ struct smack_known *smk_import_entry(const char *string, int len) mutex_lock(&smack_known_lock); - for (skp = smack_known; skp != NULL; skp = skp->smk_next) - if (strncmp(skp->smk_known, smack, SMK_MAXLEN) == 0) + found = 0; + list_for_each_entry_rcu(skp, &smack_known_list, list) { + if (strncmp(skp->smk_known, smack, SMK_MAXLEN) == 0) { + found = 1; break; + } + } - if (skp == NULL) { + if (found == 0) { skp = kzalloc(sizeof(struct smack_known), GFP_KERNEL); if (skp != NULL) { - skp->smk_next = smack_known; strncpy(skp->smk_known, smack, SMK_MAXLEN); skp->smk_secid = smack_next_secid++; skp->smk_cipso = NULL; @@ -244,8 +240,7 @@ struct smack_known *smk_import_entry(const char *string, int len) * Make sure that the entry is actually * filled before putting it on the list. */ - smp_mb(); - smack_known = skp; + list_add_rcu(&skp->list, &smack_known_list); } } @@ -283,14 +278,19 @@ char *smack_from_secid(const u32 secid) { struct smack_known *skp; - for (skp = smack_known; skp != NULL; skp = skp->smk_next) - if (skp->smk_secid == secid) + rcu_read_lock(); + list_for_each_entry_rcu(skp, &smack_known_list, list) { + if (skp->smk_secid == secid) { + rcu_read_unlock(); return skp->smk_known; + } + } /* * If we got this far someone asked for the translation * of a secid that is not on the list. */ + rcu_read_unlock(); return smack_known_invalid.smk_known; } @@ -305,9 +305,14 @@ u32 smack_to_secid(const char *smack) { struct smack_known *skp; - for (skp = smack_known; skp != NULL; skp = skp->smk_next) - if (strncmp(skp->smk_known, smack, SMK_MAXLEN) == 0) + rcu_read_lock(); + list_for_each_entry_rcu(skp, &smack_known_list, list) { + if (strncmp(skp->smk_known, smack, SMK_MAXLEN) == 0) { + rcu_read_unlock(); return skp->smk_secid; + } + } + rcu_read_unlock(); return 0; } @@ -332,7 +337,8 @@ void smack_from_cipso(u32 level, char *cp, char *result) struct smack_known *kp; char *final = NULL; - for (kp = smack_known; final == NULL && kp != NULL; kp = kp->smk_next) { + rcu_read_lock(); + list_for_each_entry(kp, &smack_known_list, list) { if (kp->smk_cipso == NULL) continue; @@ -344,6 +350,7 @@ void smack_from_cipso(u32 level, char *cp, char *result) spin_unlock_bh(&kp->smk_cipsolock); } + rcu_read_unlock(); if (final == NULL) final = smack_known_huh.smk_known; strncpy(result, final, SMK_MAXLEN); @@ -360,13 +367,19 @@ void smack_from_cipso(u32 level, char *cp, char *result) int smack_to_cipso(const char *smack, struct smack_cipso *cp) { struct smack_known *kp; + int found = 0; - for (kp = smack_known; kp != NULL; kp = kp->smk_next) + rcu_read_lock(); + list_for_each_entry_rcu(kp, &smack_known_list, list) { if (kp->smk_known == smack || - strcmp(kp->smk_known, smack) == 0) + strcmp(kp->smk_known, smack) == 0) { + found = 1; break; + } + } + rcu_read_unlock(); - if (kp == NULL || kp->smk_cipso == NULL) + if (found == 0 || kp->smk_cipso == NULL) return -ENOENT; memcpy(cp, kp->smk_cipso, sizeof(struct smack_cipso)); diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 84b62b5e9e2..fd20d15f5b9 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -1508,7 +1508,8 @@ static char *smack_host_label(struct sockaddr_in *sip) if (siap->s_addr == 0) return NULL; - for (snp = smack_netlbladdrs; snp != NULL; snp = snp->smk_next) { + rcu_read_lock(); + list_for_each_entry_rcu(snp, &smk_netlbladdr_list, list) { /* * we break after finding the first match because * the list is sorted from longest to shortest mask @@ -1516,10 +1517,11 @@ static char *smack_host_label(struct sockaddr_in *sip) */ if ((&snp->smk_host.sin_addr)->s_addr == (siap->s_addr & (&snp->smk_mask)->s_addr)) { + rcu_read_unlock(); return snp->smk_label; } } - + rcu_read_unlock(); return NULL; } @@ -2930,6 +2932,17 @@ struct security_operations smack_ops = { .release_secctx = smack_release_secctx, }; + +static __init void init_smack_know_list(void) +{ + list_add(&smack_known_huh.list, &smack_known_list); + list_add(&smack_known_hat.list, &smack_known_list); + list_add(&smack_known_star.list, &smack_known_list); + list_add(&smack_known_floor.list, &smack_known_list); + list_add(&smack_known_invalid.list, &smack_known_list); + list_add(&smack_known_web.list, &smack_known_list); +} + /** * smack_init - initialize the smack system * @@ -2950,6 +2963,8 @@ static __init int smack_init(void) cred = (struct cred *) current->cred; cred->security = &smack_known_floor.smk_known; + /* initilize the smack_know_list */ + init_smack_know_list(); /* * Initialize locks */ diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index a1b57e4dba3..856c8a28752 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -80,10 +80,11 @@ char *smack_onlycap; * Packets are sent there unlabeled, but only from tasks that * can write to the specified label. */ -struct smk_netlbladdr *smack_netlbladdrs; + +LIST_HEAD(smk_netlbladdr_list); +LIST_HEAD(smack_rule_list); static int smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT; -struct smk_list_entry *smack_list; #define SEQ_READ_FINISHED 1 @@ -134,24 +135,27 @@ static void *load_seq_start(struct seq_file *s, loff_t *pos) { if (*pos == SEQ_READ_FINISHED) return NULL; - - return smack_list; + if (list_empty(&smack_rule_list)) + return NULL; + return smack_rule_list.next; } static void *load_seq_next(struct seq_file *s, void *v, loff_t *pos) { - struct smk_list_entry *skp = ((struct smk_list_entry *) v)->smk_next; + struct list_head *list = v; - if (skp == NULL) + if (list_is_last(list, &smack_rule_list)) { *pos = SEQ_READ_FINISHED; - - return skp; + return NULL; + } + return list->next; } static int load_seq_show(struct seq_file *s, void *v) { - struct smk_list_entry *slp = (struct smk_list_entry *) v; - struct smack_rule *srp = &slp->smk_rule; + struct list_head *list = v; + struct smack_rule *srp = + list_entry(list, struct smack_rule, list); seq_printf(s, "%s %s", (char *)srp->smk_subject, (char *)srp->smk_object); @@ -212,32 +216,23 @@ static int smk_open_load(struct inode *inode, struct file *file) */ static int smk_set_access(struct smack_rule *srp) { - struct smk_list_entry *sp; - struct smk_list_entry *newp; + struct smack_rule *sp; int ret = 0; - + int found; mutex_lock(&smack_list_lock); - for (sp = smack_list; sp != NULL; sp = sp->smk_next) - if (sp->smk_rule.smk_subject == srp->smk_subject && - sp->smk_rule.smk_object == srp->smk_object) { - sp->smk_rule.smk_access = srp->smk_access; + found = 0; + list_for_each_entry_rcu(sp, &smack_rule_list, list) { + if (sp->smk_subject == srp->smk_subject && + sp->smk_object == srp->smk_object) { + found = 1; + sp->smk_access = srp->smk_access; break; } - - if (sp == NULL) { - newp = kzalloc(sizeof(struct smk_list_entry), GFP_KERNEL); - if (newp == NULL) { - ret = -ENOMEM; - goto out; - } - - newp->smk_rule = *srp; - newp->smk_next = smack_list; - smack_list = newp; } + if (found == 0) + list_add_rcu(&srp->list, &smack_rule_list); -out: mutex_unlock(&smack_list_lock); return ret; @@ -261,7 +256,7 @@ out: static ssize_t smk_write_load(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { - struct smack_rule rule; + struct smack_rule *rule; char *data; int rc = -EINVAL; @@ -272,9 +267,8 @@ static ssize_t smk_write_load(struct file *file, const char __user *buf, */ if (!capable(CAP_MAC_ADMIN)) return -EPERM; - if (*ppos != 0) - return -EINVAL; - if (count != SMK_LOADLEN) + + if (*ppos != 0 || count != SMK_LOADLEN) return -EINVAL; data = kzalloc(count, GFP_KERNEL); @@ -286,25 +280,31 @@ static ssize_t smk_write_load(struct file *file, const char __user *buf, goto out; } - rule.smk_subject = smk_import(data, 0); - if (rule.smk_subject == NULL) + rule = kzalloc(sizeof(*rule), GFP_KERNEL); + if (rule == NULL) { + rc = -ENOMEM; goto out; + } - rule.smk_object = smk_import(data + SMK_LABELLEN, 0); - if (rule.smk_object == NULL) - goto out; + rule->smk_subject = smk_import(data, 0); + if (rule->smk_subject == NULL) + goto out_free_rule; - rule.smk_access = 0; + 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; + rule->smk_access |= MAY_READ; break; default: - goto out; + goto out_free_rule; } switch (data[SMK_LABELLEN + SMK_LABELLEN + 1]) { @@ -312,10 +312,10 @@ static ssize_t smk_write_load(struct file *file, const char __user *buf, break; case 'w': case 'W': - rule.smk_access |= MAY_WRITE; + rule->smk_access |= MAY_WRITE; break; default: - goto out; + goto out_free_rule; } switch (data[SMK_LABELLEN + SMK_LABELLEN + 2]) { @@ -323,10 +323,10 @@ static ssize_t smk_write_load(struct file *file, const char __user *buf, break; case 'x': case 'X': - rule.smk_access |= MAY_EXEC; + rule->smk_access |= MAY_EXEC; break; default: - goto out; + goto out_free_rule; } switch (data[SMK_LABELLEN + SMK_LABELLEN + 3]) { @@ -334,17 +334,20 @@ static ssize_t smk_write_load(struct file *file, const char __user *buf, break; case 'a': case 'A': - rule.smk_access |= MAY_APPEND; + rule->smk_access |= MAY_APPEND; break; default: - goto out; + goto out_free_rule; } - rc = smk_set_access(&rule); + rc = smk_set_access(rule); if (!rc) rc = count; + goto out; +out_free_rule: + kfree(rule); out: kfree(data); return rc; @@ -433,24 +436,26 @@ static void *cipso_seq_start(struct seq_file *s, loff_t *pos) { if (*pos == SEQ_READ_FINISHED) return NULL; + if (list_empty(&smack_known_list)) + return NULL; - return smack_known; + return smack_known_list.next; } static void *cipso_seq_next(struct seq_file *s, void *v, loff_t *pos) { - struct smack_known *skp = ((struct smack_known *) v)->smk_next; + struct list_head *list = v; /* - * Omit labels with no associated cipso value + * labels with no associated cipso value wont be printed + * in cipso_seq_show */ - while (skp != NULL && !skp->smk_cipso) - skp = skp->smk_next; - - if (skp == NULL) + if (list_is_last(list, &smack_known_list)) { *pos = SEQ_READ_FINISHED; + return NULL; + } - return skp; + return list->next; } /* @@ -459,7 +464,9 @@ static void *cipso_seq_next(struct seq_file *s, void *v, loff_t *pos) */ static int cipso_seq_show(struct seq_file *s, void *v) { - struct smack_known *skp = (struct smack_known *) v; + struct list_head *list = v; + struct smack_known *skp = + list_entry(list, struct smack_known, list); struct smack_cipso *scp = skp->smk_cipso; char *cbp; char sep = '/'; @@ -638,18 +645,21 @@ static void *netlbladdr_seq_start(struct seq_file *s, loff_t *pos) { if (*pos == SEQ_READ_FINISHED) return NULL; - - return smack_netlbladdrs; + if (list_empty(&smk_netlbladdr_list)) + return NULL; + return smk_netlbladdr_list.next; } static void *netlbladdr_seq_next(struct seq_file *s, void *v, loff_t *pos) { - struct smk_netlbladdr *skp = ((struct smk_netlbladdr *) v)->smk_next; + struct list_head *list = v; - if (skp == NULL) + if (list_is_last(list, &smk_netlbladdr_list)) { *pos = SEQ_READ_FINISHED; + return NULL; + } - return skp; + return list->next; } #define BEBITS (sizeof(__be32) * 8) @@ -658,7 +668,9 @@ static void *netlbladdr_seq_next(struct seq_file *s, void *v, loff_t *pos) */ static int netlbladdr_seq_show(struct seq_file *s, void *v) { - struct smk_netlbladdr *skp = (struct smk_netlbladdr *) v; + struct list_head *list = v; + struct smk_netlbladdr *skp = + list_entry(list, struct smk_netlbladdr, list); unsigned char *hp = (char *) &skp->smk_host.sin_addr.s_addr; int maskn; u32 temp_mask = be32_to_cpu(skp->smk_mask.s_addr); @@ -702,30 +714,36 @@ static int smk_open_netlbladdr(struct inode *inode, struct file *file) * * This helper insert netlabel in the smack_netlbladdrs list * sorted by netmask length (longest to smallest) + * locked by &smk_netlbladdr_lock in smk_write_netlbladdr + * */ static void smk_netlbladdr_insert(struct smk_netlbladdr *new) { - struct smk_netlbladdr *m; + struct smk_netlbladdr *m, *m_next; - if (smack_netlbladdrs == NULL) { - smack_netlbladdrs = new; + if (list_empty(&smk_netlbladdr_list)) { + list_add_rcu(&new->list, &smk_netlbladdr_list); return; } + m = list_entry(rcu_dereference(smk_netlbladdr_list.next), + struct smk_netlbladdr, list); + /* the comparison '>' is a bit hacky, but works */ - if (new->smk_mask.s_addr > smack_netlbladdrs->smk_mask.s_addr) { - new->smk_next = smack_netlbladdrs; - smack_netlbladdrs = new; + if (new->smk_mask.s_addr > m->smk_mask.s_addr) { + list_add_rcu(&new->list, &smk_netlbladdr_list); return; } - for (m = smack_netlbladdrs; m != NULL; m = m->smk_next) { - if (m->smk_next == NULL) { - m->smk_next = new; + + list_for_each_entry_rcu(m, &smk_netlbladdr_list, list) { + if (list_is_last(&m->list, &smk_netlbladdr_list)) { + list_add_rcu(&new->list, &m->list); return; } - if (new->smk_mask.s_addr > m->smk_next->smk_mask.s_addr) { - new->smk_next = m->smk_next; - m->smk_next = new; + m_next = list_entry(rcu_dereference(m->list.next), + struct smk_netlbladdr, list); + if (new->smk_mask.s_addr > m_next->smk_mask.s_addr) { + list_add_rcu(&new->list, &m->list); return; } } @@ -755,6 +773,7 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, struct netlbl_audit audit_info; struct in_addr mask; unsigned int m; + int found; u32 mask_bits = (1<<31); __be32 nsa; u32 temp_mask; @@ -808,14 +827,17 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, nsa = newname.sin_addr.s_addr; /* try to find if the prefix is already in the list */ - for (skp = smack_netlbladdrs; skp != NULL; skp = skp->smk_next) + found = 0; + list_for_each_entry_rcu(skp, &smk_netlbladdr_list, list) { if (skp->smk_host.sin_addr.s_addr == nsa && - skp->smk_mask.s_addr == mask.s_addr) + skp->smk_mask.s_addr == mask.s_addr) { + found = 1; break; - + } + } smk_netlabel_audit_set(&audit_info); - if (skp == NULL) { + if (found == 0) { skp = kzalloc(sizeof(*skp), GFP_KERNEL); if (skp == NULL) rc = -ENOMEM; -- cgit v1.2.3 From a106cbfd1f3703402fc2d95d97e7a054102250f0 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Fri, 27 Mar 2009 13:12:16 +0900 Subject: TOMOYO: Fix a typo. Fix a typo. Reported-by: Pavel Machek Signed-off-by: Kentaro Takeda Signed-off-by: Tetsuo Handa Signed-off-by: Toshiharu Harada Signed-off-by: James Morris --- security/tomoyo/common.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'security') diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h index 6dcb7cc0ed1..26a76d67aa1 100644 --- a/security/tomoyo/common.h +++ b/security/tomoyo/common.h @@ -55,7 +55,7 @@ struct tomoyo_path_info { struct tomoyo_path_info_with_data { /* Keep "head" first, for this pointer is passed to tomoyo_free(). */ struct tomoyo_path_info head; - char bariier1[16]; /* Safeguard for overrun. */ + char barrier1[16]; /* Safeguard for overrun. */ char body[TOMOYO_MAX_PATHNAME_LEN]; char barrier2[16]; /* Safeguard for overrun. */ }; -- cgit v1.2.3 From 389fb800ac8be2832efedd19978a2b8ced37eb61 Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Fri, 27 Mar 2009 17:10:34 -0400 Subject: netlabel: Label incoming TCP connections correctly in SELinux The current NetLabel/SELinux behavior for incoming TCP connections works but only through a series of happy coincidences that rely on the limited nature of standard CIPSO (only able to convey MLS attributes) and the write equality imposed by the SELinux MLS constraints. The problem is that network sockets created as the result of an incoming TCP connection were not on-the-wire labeled based on the security attributes of the parent socket but rather based on the wire label of the remote peer. The issue had to do with how IP options were managed as part of the network stack and where the LSM hooks were in relation to the code which set the IP options on these newly created child sockets. While NetLabel/SELinux did correctly set the socket's on-the-wire label it was promptly cleared by the network stack and reset based on the IP options of the remote peer. This patch, in conjunction with a prior patch that adjusted the LSM hook locations, works to set the correct on-the-wire label format for new incoming connections through the security_inet_conn_request() hook. Besides the correct behavior there are many advantages to this change, the most significant is that all of the NetLabel socket labeling code in SELinux now lives in hooks which can return error codes to the core stack which allows us to finally get ride of the selinux_netlbl_inode_permission() logic which greatly simplfies the NetLabel/SELinux glue code. In the process of developing this patch I also ran into a small handful of AF_INET6 cleanliness issues that have been fixed which should make the code safer and easier to extend in the future. Signed-off-by: Paul Moore Acked-by: Casey Schaufler Signed-off-by: James Morris --- security/selinux/hooks.c | 54 +++-------- security/selinux/include/netlabel.h | 27 +++--- security/selinux/netlabel.c | 186 ++++++++++-------------------------- security/smack/smack_lsm.c | 2 +- 4 files changed, 82 insertions(+), 187 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 7c52ba243c6..ee2e781d11d 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -311,7 +311,7 @@ static int sk_alloc_security(struct sock *sk, int family, gfp_t priority) ssec->sid = SECINITSID_UNLABELED; sk->sk_security = ssec; - selinux_netlbl_sk_security_reset(ssec, family); + selinux_netlbl_sk_security_reset(ssec); return 0; } @@ -2945,7 +2945,6 @@ static void selinux_inode_getsecid(const struct inode *inode, u32 *secid) static int selinux_revalidate_file_permission(struct file *file, int mask) { const struct cred *cred = current_cred(); - int rc; struct inode *inode = file->f_path.dentry->d_inode; if (!mask) { @@ -2957,29 +2956,15 @@ static int selinux_revalidate_file_permission(struct file *file, int mask) if ((file->f_flags & O_APPEND) && (mask & MAY_WRITE)) mask |= MAY_APPEND; - rc = file_has_perm(cred, file, - file_mask_to_av(inode->i_mode, mask)); - if (rc) - return rc; - - return selinux_netlbl_inode_permission(inode, mask); + return file_has_perm(cred, file, + file_mask_to_av(inode->i_mode, mask)); } static int selinux_file_permission(struct file *file, int mask) { - struct inode *inode = file->f_path.dentry->d_inode; - struct file_security_struct *fsec = file->f_security; - struct inode_security_struct *isec = inode->i_security; - u32 sid = current_sid(); - - if (!mask) { + if (!mask) /* No permission to check. Existence test. */ return 0; - } - - if (sid == fsec->sid && fsec->isid == isec->sid - && fsec->pseqno == avc_policy_seqno()) - return selinux_netlbl_inode_permission(inode, mask); return selinux_revalidate_file_permission(file, mask); } @@ -3723,7 +3708,7 @@ static int selinux_socket_post_create(struct socket *sock, int family, sksec = sock->sk->sk_security; sksec->sid = isec->sid; sksec->sclass = isec->sclass; - err = selinux_netlbl_socket_post_create(sock); + err = selinux_netlbl_socket_post_create(sock->sk, family); } return err; @@ -3914,13 +3899,7 @@ static int selinux_socket_accept(struct socket *sock, struct socket *newsock) static int selinux_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size) { - int rc; - - rc = socket_has_perm(current, sock, SOCKET__WRITE); - if (rc) - return rc; - - return selinux_netlbl_inode_permission(SOCK_INODE(sock), MAY_WRITE); + return socket_has_perm(current, sock, SOCKET__WRITE); } static int selinux_socket_recvmsg(struct socket *sock, struct msghdr *msg, @@ -4304,7 +4283,7 @@ static void selinux_sk_clone_security(const struct sock *sk, struct sock *newsk) newssec->peer_sid = ssec->peer_sid; newssec->sclass = ssec->sclass; - selinux_netlbl_sk_security_reset(newssec, newsk->sk_family); + selinux_netlbl_sk_security_reset(newssec); } static void selinux_sk_getsecid(struct sock *sk, u32 *secid) @@ -4348,16 +4327,15 @@ static int selinux_inet_conn_request(struct sock *sk, struct sk_buff *skb, if (peersid == SECSID_NULL) { req->secid = sksec->sid; req->peer_secid = SECSID_NULL; - return 0; + } else { + err = security_sid_mls_copy(sksec->sid, peersid, &newsid); + if (err) + return err; + req->secid = newsid; + req->peer_secid = peersid; } - err = security_sid_mls_copy(sksec->sid, peersid, &newsid); - if (err) - return err; - - req->secid = newsid; - req->peer_secid = peersid; - return 0; + return selinux_netlbl_inet_conn_request(req, family); } static void selinux_inet_csk_clone(struct sock *newsk, @@ -4374,7 +4352,7 @@ static void selinux_inet_csk_clone(struct sock *newsk, /* We don't need to take any sort of lock here as we are the only * thread with access to newsksec */ - selinux_netlbl_sk_security_reset(newsksec, req->rsk_ops->family); + selinux_netlbl_inet_csk_clone(newsk, req->rsk_ops->family); } static void selinux_inet_conn_established(struct sock *sk, struct sk_buff *skb) @@ -4387,8 +4365,6 @@ static void selinux_inet_conn_established(struct sock *sk, struct sk_buff *skb) family = PF_INET; selinux_skb_peerlbl_sid(skb, family, &sksec->peer_sid); - - selinux_netlbl_inet_conn_established(sk, family); } static void selinux_req_classify_flow(const struct request_sock *req, diff --git a/security/selinux/include/netlabel.h b/security/selinux/include/netlabel.h index b913c8d0603..b4b5b9b2f0b 100644 --- a/security/selinux/include/netlabel.h +++ b/security/selinux/include/netlabel.h @@ -32,6 +32,7 @@ #include #include #include +#include #include "avc.h" #include "objsec.h" @@ -42,8 +43,7 @@ void selinux_netlbl_cache_invalidate(void); void selinux_netlbl_err(struct sk_buff *skb, int error, int gateway); void selinux_netlbl_sk_security_free(struct sk_security_struct *ssec); -void selinux_netlbl_sk_security_reset(struct sk_security_struct *ssec, - int family); +void selinux_netlbl_sk_security_reset(struct sk_security_struct *ssec); int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, u16 family, @@ -53,9 +53,9 @@ int selinux_netlbl_skbuff_setsid(struct sk_buff *skb, u16 family, u32 sid); -void selinux_netlbl_inet_conn_established(struct sock *sk, u16 family); -int selinux_netlbl_socket_post_create(struct socket *sock); -int selinux_netlbl_inode_permission(struct inode *inode, int mask); +int selinux_netlbl_inet_conn_request(struct request_sock *req, u16 family); +void selinux_netlbl_inet_csk_clone(struct sock *sk, u16 family); +int selinux_netlbl_socket_post_create(struct sock *sk, u16 family); int selinux_netlbl_sock_rcv_skb(struct sk_security_struct *sksec, struct sk_buff *skb, u16 family, @@ -85,8 +85,7 @@ static inline void selinux_netlbl_sk_security_free( } static inline void selinux_netlbl_sk_security_reset( - struct sk_security_struct *ssec, - int family) + struct sk_security_struct *ssec) { return; } @@ -113,17 +112,17 @@ static inline int selinux_netlbl_conn_setsid(struct sock *sk, return 0; } -static inline void selinux_netlbl_inet_conn_established(struct sock *sk, - u16 family) +static inline int selinux_netlbl_inet_conn_request(struct request_sock *req, + u16 family) { - return; + return 0; } -static inline int selinux_netlbl_socket_post_create(struct socket *sock) +static inline void selinux_netlbl_inet_csk_clone(struct sock *sk, u16 family) { - return 0; + return; } -static inline int selinux_netlbl_inode_permission(struct inode *inode, - int mask) +static inline int selinux_netlbl_socket_post_create(struct sock *sk, + u16 family) { return 0; } diff --git a/security/selinux/netlabel.c b/security/selinux/netlabel.c index 350794ab9b4..2e984413c7b 100644 --- a/security/selinux/netlabel.c +++ b/security/selinux/netlabel.c @@ -99,41 +99,6 @@ static struct netlbl_lsm_secattr *selinux_netlbl_sock_genattr(struct sock *sk) return secattr; } -/** - * selinux_netlbl_sock_setsid - Label a socket using the NetLabel mechanism - * @sk: the socket to label - * - * Description: - * Attempt to label a socket using the NetLabel mechanism. Returns zero values - * on success, negative values on failure. - * - */ -static int selinux_netlbl_sock_setsid(struct sock *sk) -{ - int rc; - struct sk_security_struct *sksec = sk->sk_security; - struct netlbl_lsm_secattr *secattr; - - if (sksec->nlbl_state != NLBL_REQUIRE) - return 0; - - secattr = selinux_netlbl_sock_genattr(sk); - if (secattr == NULL) - return -ENOMEM; - rc = netlbl_sock_setattr(sk, secattr); - switch (rc) { - case 0: - sksec->nlbl_state = NLBL_LABELED; - break; - case -EDESTADDRREQ: - sksec->nlbl_state = NLBL_REQSKB; - rc = 0; - break; - } - - return rc; -} - /** * selinux_netlbl_cache_invalidate - Invalidate the NetLabel cache * @@ -188,13 +153,9 @@ void selinux_netlbl_sk_security_free(struct sk_security_struct *ssec) * The caller is responsibile for all the NetLabel sk_security_struct locking. * */ -void selinux_netlbl_sk_security_reset(struct sk_security_struct *ssec, - int family) +void selinux_netlbl_sk_security_reset(struct sk_security_struct *ssec) { - if (family == PF_INET) - ssec->nlbl_state = NLBL_REQUIRE; - else - ssec->nlbl_state = NLBL_UNSET; + ssec->nlbl_state = NLBL_UNSET; } /** @@ -281,127 +242,86 @@ skbuff_setsid_return: } /** - * selinux_netlbl_inet_conn_established - Netlabel the newly accepted connection - * @sk: the new connection + * selinux_netlbl_inet_conn_request - Label an incoming stream connection + * @req: incoming connection request socket * * Description: - * A new connection has been established on @sk so make sure it is labeled - * correctly with the NetLabel susbsystem. + * A new incoming connection request is represented by @req, we need to label + * the new request_sock here and the stack will ensure the on-the-wire label + * will get preserved when a full sock is created once the connection handshake + * is complete. Returns zero on success, negative values on failure. * */ -void selinux_netlbl_inet_conn_established(struct sock *sk, u16 family) +int selinux_netlbl_inet_conn_request(struct request_sock *req, u16 family) { int rc; - struct sk_security_struct *sksec = sk->sk_security; - struct netlbl_lsm_secattr *secattr; - struct inet_sock *sk_inet = inet_sk(sk); - struct sockaddr_in addr; - - if (sksec->nlbl_state != NLBL_REQUIRE) - return; + struct netlbl_lsm_secattr secattr; - secattr = selinux_netlbl_sock_genattr(sk); - if (secattr == NULL) - return; + if (family != PF_INET) + return 0; - rc = netlbl_sock_setattr(sk, secattr); - switch (rc) { - case 0: - sksec->nlbl_state = NLBL_LABELED; - break; - case -EDESTADDRREQ: - /* no PF_INET6 support yet because we don't support any IPv6 - * labeling protocols */ - if (family != PF_INET) { - sksec->nlbl_state = NLBL_UNSET; - return; - } - - addr.sin_family = family; - addr.sin_addr.s_addr = sk_inet->daddr; - if (netlbl_conn_setattr(sk, (struct sockaddr *)&addr, - secattr) != 0) { - /* we failed to label the connected socket (could be - * for a variety of reasons, the actual "why" isn't - * important here) so we have to go to our backup plan, - * labeling the packets individually in the netfilter - * local output hook. this is okay but we need to - * adjust the MSS of the connection to take into - * account any labeling overhead, since we don't know - * the exact overhead at this point we'll use the worst - * case value which is 40 bytes for IPv4 */ - struct inet_connection_sock *sk_conn = inet_csk(sk); - sk_conn->icsk_ext_hdr_len += 40 - - (sk_inet->opt ? sk_inet->opt->optlen : 0); - sk_conn->icsk_sync_mss(sk, sk_conn->icsk_pmtu_cookie); - - sksec->nlbl_state = NLBL_REQSKB; - } else - sksec->nlbl_state = NLBL_CONNLABELED; - break; - default: - /* note that we are failing to label the socket which could be - * a bad thing since it means traffic could leave the system - * without the desired labeling, however, all is not lost as - * we have a check in selinux_netlbl_inode_permission() to - * pick up the pieces that we might drop here because we can't - * return an error code */ - break; - } + netlbl_secattr_init(&secattr); + rc = security_netlbl_sid_to_secattr(req->secid, &secattr); + if (rc != 0) + goto inet_conn_request_return; + rc = netlbl_req_setattr(req, &secattr); +inet_conn_request_return: + netlbl_secattr_destroy(&secattr); + return rc; } /** - * selinux_netlbl_socket_post_create - Label a socket using NetLabel - * @sock: the socket to label + * selinux_netlbl_inet_csk_clone - Initialize the newly created sock + * @sk: the new sock * * Description: - * Attempt to label a socket using the NetLabel mechanism using the given - * SID. Returns zero values on success, negative values on failure. + * A new connection has been established using @sk, we've already labeled the + * socket via the request_sock struct in selinux_netlbl_inet_conn_request() but + * we need to set the NetLabel state here since we now have a sock structure. * */ -int selinux_netlbl_socket_post_create(struct socket *sock) +void selinux_netlbl_inet_csk_clone(struct sock *sk, u16 family) { - return selinux_netlbl_sock_setsid(sock->sk); + struct sk_security_struct *sksec = sk->sk_security; + + if (family == PF_INET) + sksec->nlbl_state = NLBL_LABELED; + else + sksec->nlbl_state = NLBL_UNSET; } /** - * selinux_netlbl_inode_permission - Verify the socket is NetLabel labeled - * @inode: the file descriptor's inode - * @mask: the permission mask + * selinux_netlbl_socket_post_create - Label a socket using NetLabel + * @sock: the socket to label + * @family: protocol family * * Description: - * Looks at a file's inode and if it is marked as a socket protected by - * NetLabel then verify that the socket has been labeled, if not try to label - * the socket now with the inode's SID. Returns zero on success, negative - * values on failure. + * Attempt to label a socket using the NetLabel mechanism using the given + * SID. Returns zero values on success, negative values on failure. * */ -int selinux_netlbl_inode_permission(struct inode *inode, int mask) +int selinux_netlbl_socket_post_create(struct sock *sk, u16 family) { int rc; - struct sock *sk; - struct socket *sock; - struct sk_security_struct *sksec; + struct sk_security_struct *sksec = sk->sk_security; + struct netlbl_lsm_secattr *secattr; - if (!S_ISSOCK(inode->i_mode) || - ((mask & (MAY_WRITE | MAY_APPEND)) == 0)) - return 0; - sock = SOCKET_I(inode); - sk = sock->sk; - if (sk == NULL) - return 0; - sksec = sk->sk_security; - if (sksec == NULL || sksec->nlbl_state != NLBL_REQUIRE) + if (family != PF_INET) return 0; - local_bh_disable(); - bh_lock_sock_nested(sk); - if (likely(sksec->nlbl_state == NLBL_REQUIRE)) - rc = selinux_netlbl_sock_setsid(sk); - else + secattr = selinux_netlbl_sock_genattr(sk); + if (secattr == NULL) + return -ENOMEM; + rc = netlbl_sock_setattr(sk, family, secattr); + switch (rc) { + case 0: + sksec->nlbl_state = NLBL_LABELED; + break; + case -EDESTADDRREQ: + sksec->nlbl_state = NLBL_REQSKB; rc = 0; - bh_unlock_sock(sk); - local_bh_enable(); + break; + } return rc; } diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index fd20d15f5b9..23ad420a49a 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -1387,7 +1387,7 @@ static int smack_netlabel(struct sock *sk, int labeled) else { netlbl_secattr_init(&secattr); smack_to_secattr(ssp->smk_out, &secattr); - rc = netlbl_sock_setattr(sk, &secattr); + rc = netlbl_sock_setattr(sk, sk->sk_family, &secattr); netlbl_secattr_destroy(&secattr); } -- cgit v1.2.3 From 58bfbb51ff2b0fdc6c732ff3d72f50aa632b67a2 Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Fri, 27 Mar 2009 17:10:41 -0400 Subject: selinux: Remove the "compat_net" compatibility code The SELinux "compat_net" is marked as deprecated, the time has come to finally remove it from the kernel. Further code simplifications are likely in the future, but this patch was intended to be a simple, straight-up removal of the compat_net code. Signed-off-by: Paul Moore Signed-off-by: James Morris --- security/selinux/hooks.c | 153 ++----------------------------------------- security/selinux/selinuxfs.c | 68 ------------------- 2 files changed, 7 insertions(+), 214 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index ee2e781d11d..ba808ef6bab 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -93,7 +93,6 @@ extern unsigned int policydb_loaded_version; extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm); -extern int selinux_compat_net; extern struct security_operations *security_ops; /* SECMARK reference count */ @@ -4019,72 +4018,6 @@ static int selinux_inet_sys_rcv_skb(int ifindex, char *addrp, u16 family, SECCLASS_NODE, NODE__RECVFROM, ad); } -static int selinux_sock_rcv_skb_iptables_compat(struct sock *sk, - struct sk_buff *skb, - struct avc_audit_data *ad, - u16 family, - char *addrp) -{ - int err; - struct sk_security_struct *sksec = sk->sk_security; - u16 sk_class; - u32 netif_perm, node_perm, recv_perm; - u32 port_sid, node_sid, if_sid, sk_sid; - - sk_sid = sksec->sid; - sk_class = sksec->sclass; - - switch (sk_class) { - case SECCLASS_UDP_SOCKET: - netif_perm = NETIF__UDP_RECV; - node_perm = NODE__UDP_RECV; - recv_perm = UDP_SOCKET__RECV_MSG; - break; - case SECCLASS_TCP_SOCKET: - netif_perm = NETIF__TCP_RECV; - node_perm = NODE__TCP_RECV; - recv_perm = TCP_SOCKET__RECV_MSG; - break; - case SECCLASS_DCCP_SOCKET: - netif_perm = NETIF__DCCP_RECV; - node_perm = NODE__DCCP_RECV; - recv_perm = DCCP_SOCKET__RECV_MSG; - break; - default: - netif_perm = NETIF__RAWIP_RECV; - node_perm = NODE__RAWIP_RECV; - recv_perm = 0; - break; - } - - err = sel_netif_sid(skb->iif, &if_sid); - if (err) - return err; - err = avc_has_perm(sk_sid, if_sid, SECCLASS_NETIF, netif_perm, ad); - if (err) - return err; - - err = sel_netnode_sid(addrp, family, &node_sid); - if (err) - return err; - err = avc_has_perm(sk_sid, node_sid, SECCLASS_NODE, node_perm, ad); - if (err) - return err; - - if (!recv_perm) - return 0; - err = sel_netport_sid(sk->sk_protocol, - ntohs(ad->u.net.sport), &port_sid); - if (unlikely(err)) { - printk(KERN_WARNING - "SELinux: failure in" - " selinux_sock_rcv_skb_iptables_compat()," - " network port label not found\n"); - return err; - } - return avc_has_perm(sk_sid, port_sid, sk_class, recv_perm, ad); -} - static int selinux_sock_rcv_skb_compat(struct sock *sk, struct sk_buff *skb, u16 family) { @@ -4102,14 +4035,12 @@ static int selinux_sock_rcv_skb_compat(struct sock *sk, struct sk_buff *skb, if (err) return err; - if (selinux_compat_net) - err = selinux_sock_rcv_skb_iptables_compat(sk, skb, &ad, - family, addrp); - else if (selinux_secmark_enabled()) + if (selinux_secmark_enabled()) { err = avc_has_perm(sk_sid, skb->secmark, SECCLASS_PACKET, PACKET__RECV, &ad); - if (err) - return err; + if (err) + return err; + } if (selinux_policycap_netpeer) { err = selinux_skb_peerlbl_sid(skb, family, &peer_sid); @@ -4151,7 +4082,7 @@ static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) * to the selinux_sock_rcv_skb_compat() function to deal with the * special handling. We do this in an attempt to keep this function * as fast and as clean as possible. */ - if (selinux_compat_net || !selinux_policycap_netpeer) + if (!selinux_policycap_netpeer) return selinux_sock_rcv_skb_compat(sk, skb, family); secmark_active = selinux_secmark_enabled(); @@ -4516,71 +4447,6 @@ static unsigned int selinux_ipv4_output(unsigned int hooknum, return selinux_ip_output(skb, PF_INET); } -static int selinux_ip_postroute_iptables_compat(struct sock *sk, - int ifindex, - struct avc_audit_data *ad, - u16 family, char *addrp) -{ - int err; - struct sk_security_struct *sksec = sk->sk_security; - u16 sk_class; - u32 netif_perm, node_perm, send_perm; - u32 port_sid, node_sid, if_sid, sk_sid; - - sk_sid = sksec->sid; - sk_class = sksec->sclass; - - switch (sk_class) { - case SECCLASS_UDP_SOCKET: - netif_perm = NETIF__UDP_SEND; - node_perm = NODE__UDP_SEND; - send_perm = UDP_SOCKET__SEND_MSG; - break; - case SECCLASS_TCP_SOCKET: - netif_perm = NETIF__TCP_SEND; - node_perm = NODE__TCP_SEND; - send_perm = TCP_SOCKET__SEND_MSG; - break; - case SECCLASS_DCCP_SOCKET: - netif_perm = NETIF__DCCP_SEND; - node_perm = NODE__DCCP_SEND; - send_perm = DCCP_SOCKET__SEND_MSG; - break; - default: - netif_perm = NETIF__RAWIP_SEND; - node_perm = NODE__RAWIP_SEND; - send_perm = 0; - break; - } - - err = sel_netif_sid(ifindex, &if_sid); - if (err) - return err; - err = avc_has_perm(sk_sid, if_sid, SECCLASS_NETIF, netif_perm, ad); - return err; - - err = sel_netnode_sid(addrp, family, &node_sid); - if (err) - return err; - err = avc_has_perm(sk_sid, node_sid, SECCLASS_NODE, node_perm, ad); - if (err) - return err; - - if (send_perm != 0) - return 0; - - err = sel_netport_sid(sk->sk_protocol, - ntohs(ad->u.net.dport), &port_sid); - if (unlikely(err)) { - printk(KERN_WARNING - "SELinux: failure in" - " selinux_ip_postroute_iptables_compat()," - " network port label not found\n"); - return err; - } - return avc_has_perm(sk_sid, port_sid, sk_class, send_perm, ad); -} - static unsigned int selinux_ip_postroute_compat(struct sk_buff *skb, int ifindex, u16 family) @@ -4601,15 +4467,10 @@ static unsigned int selinux_ip_postroute_compat(struct sk_buff *skb, if (selinux_parse_skb(skb, &ad, &addrp, 0, &proto)) return NF_DROP; - if (selinux_compat_net) { - if (selinux_ip_postroute_iptables_compat(skb->sk, ifindex, - &ad, family, addrp)) - return NF_DROP; - } else if (selinux_secmark_enabled()) { + if (selinux_secmark_enabled()) if (avc_has_perm(sksec->sid, skb->secmark, SECCLASS_PACKET, PACKET__SEND, &ad)) return NF_DROP; - } if (selinux_policycap_netpeer) if (selinux_xfrm_postroute_last(sksec->sid, skb, &ad, proto)) @@ -4633,7 +4494,7 @@ static unsigned int selinux_ip_postroute(struct sk_buff *skb, int ifindex, * to the selinux_ip_postroute_compat() function to deal with the * special handling. We do this in an attempt to keep this function * as fast and as clean as possible. */ - if (selinux_compat_net || !selinux_policycap_netpeer) + if (!selinux_policycap_netpeer) return selinux_ip_postroute_compat(skb, ifindex, family); #ifdef CONFIG_XFRM /* If skb->dst->xfrm is non-NULL then the packet is undergoing an IPsec diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index d3c8b982cfb..2d5136ec3d5 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -47,8 +47,6 @@ static char *policycap_names[] = { unsigned int selinux_checkreqprot = CONFIG_SECURITY_SELINUX_CHECKREQPROT_VALUE; -int selinux_compat_net = 0; - static int __init checkreqprot_setup(char *str) { unsigned long checkreqprot; @@ -58,16 +56,6 @@ static int __init checkreqprot_setup(char *str) } __setup("checkreqprot=", checkreqprot_setup); -static int __init selinux_compat_net_setup(char *str) -{ - unsigned long compat_net; - if (!strict_strtoul(str, 0, &compat_net)) - selinux_compat_net = compat_net ? 1 : 0; - return 1; -} -__setup("selinux_compat_net=", selinux_compat_net_setup); - - static DEFINE_MUTEX(sel_mutex); /* global data for booleans */ @@ -450,61 +438,6 @@ static const struct file_operations sel_checkreqprot_ops = { .write = sel_write_checkreqprot, }; -static ssize_t sel_read_compat_net(struct file *filp, char __user *buf, - size_t count, loff_t *ppos) -{ - char tmpbuf[TMPBUFLEN]; - ssize_t length; - - length = scnprintf(tmpbuf, TMPBUFLEN, "%d", selinux_compat_net); - return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); -} - -static ssize_t sel_write_compat_net(struct file *file, const char __user *buf, - size_t count, loff_t *ppos) -{ - char *page; - ssize_t length; - int new_value; - - length = task_has_security(current, SECURITY__LOAD_POLICY); - if (length) - return length; - - if (count >= PAGE_SIZE) - return -ENOMEM; - if (*ppos != 0) { - /* No partial writes. */ - return -EINVAL; - } - page = (char *)get_zeroed_page(GFP_KERNEL); - if (!page) - return -ENOMEM; - length = -EFAULT; - if (copy_from_user(page, buf, count)) - goto out; - - length = -EINVAL; - if (sscanf(page, "%d", &new_value) != 1) - goto out; - - if (new_value) { - printk(KERN_NOTICE - "SELinux: compat_net is deprecated, please use secmark" - " instead\n"); - selinux_compat_net = 1; - } else - selinux_compat_net = 0; - length = count; -out: - free_page((unsigned long) page); - return length; -} -static const struct file_operations sel_compat_net_ops = { - .read = sel_read_compat_net, - .write = sel_write_compat_net, -}; - /* * Remaining nodes use transaction based IO methods like nfsd/nfsctl.c */ @@ -1665,7 +1598,6 @@ static int sel_fill_super(struct super_block *sb, void *data, int silent) [SEL_DISABLE] = {"disable", &sel_disable_ops, S_IWUSR}, [SEL_MEMBER] = {"member", &transaction_ops, S_IRUGO|S_IWUGO}, [SEL_CHECKREQPROT] = {"checkreqprot", &sel_checkreqprot_ops, S_IRUGO|S_IWUSR}, - [SEL_COMPAT_NET] = {"compat_net", &sel_compat_net_ops, S_IRUGO|S_IWUSR}, [SEL_REJECT_UNKNOWN] = {"reject_unknown", &sel_handle_unknown_ops, S_IRUGO}, [SEL_DENY_UNKNOWN] = {"deny_unknown", &sel_handle_unknown_ops, S_IRUGO}, /* last one */ {""} -- cgit v1.2.3 From 8651d5c0b1f874c5b8307ae2b858bc40f9f02482 Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Fri, 27 Mar 2009 17:10:48 -0400 Subject: lsm: Remove the socket_post_accept() hook The socket_post_accept() hook is not currently used by any in-tree modules and its existence continues to cause problems by confusing people about what can be safely accomplished using this hook. If a legitimate need for this hook arises in the future it can always be reintroduced. Signed-off-by: Paul Moore Signed-off-by: James Morris --- security/capability.c | 5 ----- security/security.c | 5 ----- 2 files changed, 10 deletions(-) (limited to 'security') diff --git a/security/capability.c b/security/capability.c index c545bd1300b..21b6cead6a8 100644 --- a/security/capability.c +++ b/security/capability.c @@ -620,10 +620,6 @@ static int cap_socket_accept(struct socket *sock, struct socket *newsock) return 0; } -static void cap_socket_post_accept(struct socket *sock, struct socket *newsock) -{ -} - static int cap_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size) { return 0; @@ -1014,7 +1010,6 @@ void security_fixup_ops(struct security_operations *ops) set_to_cap_if_null(ops, socket_connect); set_to_cap_if_null(ops, socket_listen); set_to_cap_if_null(ops, socket_accept); - set_to_cap_if_null(ops, socket_post_accept); set_to_cap_if_null(ops, socket_sendmsg); set_to_cap_if_null(ops, socket_recvmsg); set_to_cap_if_null(ops, socket_getsockname); diff --git a/security/security.c b/security/security.c index c3586c0d97e..206e53844d2 100644 --- a/security/security.c +++ b/security/security.c @@ -1007,11 +1007,6 @@ int security_socket_accept(struct socket *sock, struct socket *newsock) return security_ops->socket_accept(sock, newsock); } -void security_socket_post_accept(struct socket *sock, struct socket *newsock) -{ - security_ops->socket_post_accept(sock, newsock); -} - int security_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size) { return security_ops->socket_sendmsg(sock, msg, size); -- cgit v1.2.3 From 07feee8f812f7327a46186f7604df312c8c81962 Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Fri, 27 Mar 2009 17:10:54 -0400 Subject: netlabel: Cleanup the Smack/NetLabel code to fix incoming TCP connections This patch cleans up a lot of the Smack network access control code. The largest changes are to fix the labeling of incoming TCP connections in a manner similar to the recent SELinux changes which use the security_inet_conn_request() hook to label the request_sock and let the label move to the child socket via the normal network stack mechanisms. In addition to the incoming TCP connection fixes this patch also removes the smk_labled field from the socket_smack struct as the minor optimization advantage was outweighed by the difficulty in maintaining it's proper state. Signed-off-by: Paul Moore Acked-by: Casey Schaufler Signed-off-by: James Morris --- security/smack/smack.h | 1 - security/smack/smack_lsm.c | 260 +++++++++++++++++++++++++-------------------- 2 files changed, 143 insertions(+), 118 deletions(-) (limited to 'security') diff --git a/security/smack/smack.h b/security/smack/smack.h index 64164f8fde7..5e5a3bcb599 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -42,7 +42,6 @@ struct superblock_smack { struct socket_smack { char *smk_out; /* outbound label */ char *smk_in; /* inbound label */ - int smk_labeled; /* label scheme */ char smk_packet[SMK_LABELLEN]; /* TCP peer label */ }; diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 23ad420a49a..8ed502c2ad4 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -7,6 +7,8 @@ * Casey Schaufler * * Copyright (C) 2007 Casey Schaufler + * Copyright (C) 2009 Hewlett-Packard Development Company, L.P. + * 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, @@ -20,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -1275,7 +1278,6 @@ 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_labeled = SMACK_CIPSO_SOCKET; ssp->smk_packet[0] = '\0'; sk->sk_security = ssp; @@ -1294,6 +1296,39 @@ static void smack_sk_free_security(struct sock *sk) kfree(sk->sk_security); } +/** +* smack_host_label - check host based restrictions +* @sip: the object end +* +* looks for host based access restrictions +* +* This version will only be appropriate for really small sets of single label +* hosts. The caller is responsible for ensuring that the RCU read lock is +* taken before calling this function. +* +* Returns the label of the far end or NULL if it's not special. +*/ +static char *smack_host_label(struct sockaddr_in *sip) +{ + struct smk_netlbladdr *snp; + struct in_addr *siap = &sip->sin_addr; + + if (siap->s_addr == 0) + return NULL; + + list_for_each_entry_rcu(snp, &smk_netlbladdr_list, list) + /* + * we break after finding the first match because + * the list is sorted from longest to shortest mask + * so we have found the most specific match + */ + if ((&snp->smk_host.sin_addr)->s_addr == + (siap->s_addr & (&snp->smk_mask)->s_addr)) + return snp->smk_label; + + return NULL; +} + /** * smack_set_catset - convert a capset to netlabel mls categories * @catset: the Smack categories @@ -1365,11 +1400,10 @@ static void smack_to_secattr(char *smack, struct netlbl_lsm_secattr *nlsp) */ static int smack_netlabel(struct sock *sk, int labeled) { - struct socket_smack *ssp; + struct socket_smack *ssp = sk->sk_security; struct netlbl_lsm_secattr secattr; int rc = 0; - ssp = sk->sk_security; /* * Usually the netlabel code will handle changing the * packet labeling based on the label. @@ -1393,20 +1427,44 @@ static int smack_netlabel(struct sock *sk, int labeled) bh_unlock_sock(sk); local_bh_enable(); - /* - * Remember the label scheme used so that it is not - * necessary to do the netlabel setting if it has not - * changed the next time through. - * - * The -EDESTADDRREQ case is an indication that there's - * a single level host involved. - */ - if (rc == 0) - ssp->smk_labeled = labeled; return rc; } +/** + * smack_netlbel_send - Set the secattr on a socket and perform access checks + * @sk: the socket + * @sap: the destination address + * + * Set the correct secattr for the given socket based on the destination + * address and perform any outbound access checks needed. + * + * Returns 0 on success or an error code. + * + */ +static int smack_netlabel_send(struct sock *sk, struct sockaddr_in *sap) +{ + int rc; + int sk_lbl; + char *hostsp; + struct socket_smack *ssp = sk->sk_security; + + rcu_read_lock(); + hostsp = smack_host_label(sap); + if (hostsp != NULL) { + sk_lbl = SMACK_UNLABELED_SOCKET; + rc = smk_access(ssp->smk_out, hostsp, MAY_WRITE); + } else { + sk_lbl = SMACK_CIPSO_SOCKET; + rc = 0; + } + rcu_read_unlock(); + if (rc != 0) + return rc; + + return smack_netlabel(sk, sk_lbl); +} + /** * smack_inode_setsecurity - set smack xattrs * @inode: the object @@ -1488,43 +1546,6 @@ static int smack_socket_post_create(struct socket *sock, int family, return smack_netlabel(sock->sk, SMACK_CIPSO_SOCKET); } - -/** - * smack_host_label - check host based restrictions - * @sip: the object end - * - * looks for host based access restrictions - * - * This version will only be appropriate for really small - * sets of single label hosts. - * - * Returns the label of the far end or NULL if it's not special. - */ -static char *smack_host_label(struct sockaddr_in *sip) -{ - struct smk_netlbladdr *snp; - struct in_addr *siap = &sip->sin_addr; - - if (siap->s_addr == 0) - return NULL; - - rcu_read_lock(); - list_for_each_entry_rcu(snp, &smk_netlbladdr_list, list) { - /* - * we break after finding the first match because - * the list is sorted from longest to shortest mask - * so we have found the most specific match - */ - if ((&snp->smk_host.sin_addr)->s_addr == - (siap->s_addr & (&snp->smk_mask)->s_addr)) { - rcu_read_unlock(); - return snp->smk_label; - } - } - rcu_read_unlock(); - return NULL; -} - /** * smack_socket_connect - connect access check * @sock: the socket @@ -1538,30 +1559,12 @@ static char *smack_host_label(struct sockaddr_in *sip) static int smack_socket_connect(struct socket *sock, struct sockaddr *sap, int addrlen) { - struct socket_smack *ssp = sock->sk->sk_security; - char *hostsp; - int rc; - if (sock->sk == NULL || sock->sk->sk_family != PF_INET) return 0; - if (addrlen < sizeof(struct sockaddr_in)) return -EINVAL; - hostsp = smack_host_label((struct sockaddr_in *)sap); - if (hostsp == NULL) { - if (ssp->smk_labeled != SMACK_CIPSO_SOCKET) - return smack_netlabel(sock->sk, SMACK_CIPSO_SOCKET); - return 0; - } - - rc = smk_access(ssp->smk_out, hostsp, MAY_WRITE); - if (rc != 0) - return rc; - - if (ssp->smk_labeled != SMACK_UNLABELED_SOCKET) - return smack_netlabel(sock->sk, SMACK_UNLABELED_SOCKET); - return 0; + return smack_netlabel_send(sock->sk, (struct sockaddr_in *)sap); } /** @@ -2262,9 +2265,6 @@ static int smack_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size) { struct sockaddr_in *sip = (struct sockaddr_in *) msg->msg_name; - struct socket_smack *ssp = sock->sk->sk_security; - char *hostsp; - int rc; /* * Perfectly reasonable for this to be NULL @@ -2272,22 +2272,7 @@ static int smack_socket_sendmsg(struct socket *sock, struct msghdr *msg, if (sip == NULL || sip->sin_family != PF_INET) return 0; - hostsp = smack_host_label(sip); - if (hostsp == NULL) { - if (ssp->smk_labeled != SMACK_CIPSO_SOCKET) - return smack_netlabel(sock->sk, SMACK_CIPSO_SOCKET); - return 0; - } - - rc = smk_access(ssp->smk_out, hostsp, MAY_WRITE); - if (rc != 0) - return rc; - - if (ssp->smk_labeled != SMACK_UNLABELED_SOCKET) - return smack_netlabel(sock->sk, SMACK_UNLABELED_SOCKET); - - return 0; - + return smack_netlabel_send(sock->sk, sip); } @@ -2492,31 +2477,24 @@ static int smack_socket_getpeersec_dgram(struct socket *sock, } /** - * smack_sock_graft - graft access state between two sockets - * @sk: fresh sock - * @parent: donor socket + * smack_sock_graft - Initialize a newly created socket with an existing sock + * @sk: child sock + * @parent: parent socket * - * Sets the netlabel socket state on sk from parent + * Set the smk_{in,out} state of an existing sock based on the process that + * is creating the new socket. */ static void smack_sock_graft(struct sock *sk, struct socket *parent) { struct socket_smack *ssp; - int rc; - - if (sk == NULL) - return; - if (sk->sk_family != PF_INET && sk->sk_family != PF_INET6) + if (sk == NULL || + (sk->sk_family != PF_INET && sk->sk_family != PF_INET6)) return; ssp = sk->sk_security; ssp->smk_in = ssp->smk_out = current_security(); - ssp->smk_packet[0] = '\0'; - - rc = smack_netlabel(sk, SMACK_CIPSO_SOCKET); - if (rc != 0) - printk(KERN_WARNING "Smack: \"%s\" netlbl error %d.\n", - __func__, -rc); + /* cssp->smk_packet is already set in smack_inet_csk_clone() */ } /** @@ -2531,35 +2509,82 @@ static void smack_sock_graft(struct sock *sk, struct socket *parent) static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb, struct request_sock *req) { - struct netlbl_lsm_secattr skb_secattr; + u16 family = sk->sk_family; struct socket_smack *ssp = sk->sk_security; + struct netlbl_lsm_secattr secattr; + struct sockaddr_in addr; + struct iphdr *hdr; char smack[SMK_LABELLEN]; int rc; - if (skb == NULL) - return -EACCES; + /* handle mapped IPv4 packets arriving via IPv6 sockets */ + if (family == PF_INET6 && skb->protocol == htons(ETH_P_IP)) + family = PF_INET; - netlbl_secattr_init(&skb_secattr); - rc = netlbl_skbuff_getattr(skb, sk->sk_family, &skb_secattr); + netlbl_secattr_init(&secattr); + rc = netlbl_skbuff_getattr(skb, family, &secattr); if (rc == 0) - smack_from_secattr(&skb_secattr, smack); + smack_from_secattr(&secattr, smack); else strncpy(smack, smack_known_huh.smk_known, SMK_MAXLEN); - netlbl_secattr_destroy(&skb_secattr); + netlbl_secattr_destroy(&secattr); + /* - * Receiving a packet requires that the other end - * be able to write here. Read access is not required. - * - * If the request is successful save the peer's label - * so that SO_PEERCRED can report it. + * 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); - if (rc == 0) - strncpy(ssp->smk_packet, smack, SMK_MAXLEN); + if (rc != 0) + return rc; + + /* + * 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); + + /* + * We need to decide if we want to label the incoming connection here + * if we do we only need to label the request_sock and the stack will + * propogate the wire-label to the sock when it is created. + */ + hdr = ip_hdr(skb); + addr.sin_addr.s_addr = hdr->saddr; + rcu_read_lock(); + if (smack_host_label(&addr) == NULL) { + rcu_read_unlock(); + netlbl_secattr_init(&secattr); + smack_to_secattr(smack, &secattr); + rc = netlbl_req_setattr(req, &secattr); + netlbl_secattr_destroy(&secattr); + } else { + rcu_read_unlock(); + netlbl_req_delattr(req); + } return rc; } +/** + * smack_inet_csk_clone - Copy the connection information to the new socket + * @sk: the new socket + * @req: the connection's request_sock + * + * Transfer the connection's peer label to the newly created socket. + */ +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'; +} + /* * Key management security hooks * @@ -2911,6 +2936,7 @@ struct security_operations smack_ops = { .sk_free_security = smack_sk_free_security, .sock_graft = smack_sock_graft, .inet_conn_request = smack_inet_conn_request, + .inet_csk_clone = smack_inet_csk_clone, /* key management security hooks */ #ifdef CONFIG_KEYS -- cgit v1.2.3 From 4303154e86597885bc3cbc178a48ccbc8213875f Mon Sep 17 00:00:00 2001 From: Etienne Basset Date: Fri, 27 Mar 2009 17:11:01 -0400 Subject: smack: Add a new '-CIPSO' option to the network address label configuration This patch adds a new special option '-CIPSO' to the Smack subsystem. When used in the netlabel list, it means "use CIPSO networking". A use case is when your local network speaks CIPSO and you want also to connect to the unlabeled Internet. This patch also add some documentation describing that. The patch also corrects an oops when setting a '' SMACK64 xattr to a file. Signed-off-by: Etienne Basset Signed-off-by: Paul Moore Acked-by: Casey Schaufler Signed-off-by: James Morris --- security/smack/smack.h | 3 +++ security/smack/smack_access.c | 3 +++ security/smack/smack_lsm.c | 11 +++++++++-- security/smack/smackfs.c | 38 ++++++++++++++++++++++++++++++-------- 4 files changed, 45 insertions(+), 10 deletions(-) (limited to 'security') diff --git a/security/smack/smack.h b/security/smack/smack.h index 5e5a3bcb599..42ef313f985 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -132,6 +132,8 @@ struct smack_known { #define XATTR_NAME_SMACKIPIN XATTR_SECURITY_PREFIX XATTR_SMACK_IPIN #define XATTR_NAME_SMACKIPOUT XATTR_SECURITY_PREFIX XATTR_SMACK_IPOUT +#define SMACK_CIPSO_OPTION "-CIPSO" + /* * How communications on this socket are treated. * Usually it's determined by the underlying netlabel code @@ -199,6 +201,7 @@ u32 smack_to_secid(const char *); extern int smack_cipso_direct; extern char *smack_net_ambient; extern char *smack_onlycap; +extern const char *smack_cipso_option; extern struct smack_known smack_known_floor; extern struct smack_known smack_known_hat; diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c index 58564195bb0..ac0a2707f6d 100644 --- a/security/smack/smack_access.c +++ b/security/smack/smack_access.c @@ -261,6 +261,9 @@ char *smk_import(const char *string, int len) { struct smack_known *skp; + /* labels cannot begin with a '-' */ + if (string[0] == '-') + return NULL; skp = smk_import_entry(string, len); if (skp == NULL) return NULL; diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 8ed502c2ad4..921514902ec 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -609,6 +609,9 @@ static int smack_inode_setxattr(struct dentry *dentry, const char *name, strcmp(name, XATTR_NAME_SMACKIPOUT) == 0) { if (!capable(CAP_MAC_ADMIN)) rc = -EPERM; + /* a label cannot be void and cannot begin with '-' */ + if (size == 0 || (size > 0 && ((char *)value)[0] == '-')) + rc = -EINVAL; } else rc = cap_inode_setxattr(dentry, name, value, size, flags); @@ -1323,8 +1326,12 @@ static char *smack_host_label(struct sockaddr_in *sip) * so we have found the most specific match */ if ((&snp->smk_host.sin_addr)->s_addr == - (siap->s_addr & (&snp->smk_mask)->s_addr)) + (siap->s_addr & (&snp->smk_mask)->s_addr)) { + /* we have found the special CIPSO option */ + if (snp->smk_label == smack_cipso_option) + return NULL; return snp->smk_label; + } return NULL; } @@ -1486,7 +1493,7 @@ static int smack_inode_setsecurity(struct inode *inode, const char *name, struct socket *sock; int rc = 0; - if (value == NULL || size > SMK_LABELLEN) + if (value == NULL || size > SMK_LABELLEN || size == 0) return -EACCES; sp = smk_import(value, size); diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index 856c8a28752..e03a7e19c73 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -86,6 +86,9 @@ LIST_HEAD(smack_rule_list); static int smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT; +const char *smack_cipso_option = SMACK_CIPSO_OPTION; + + #define SEQ_READ_FINISHED 1 /* @@ -565,6 +568,11 @@ static ssize_t smk_write_cipso(struct file *file, const char __user *buf, goto unlockedout; } + /* labels cannot begin with a '-' */ + if (data[0] == '-') { + rc = -EINVAL; + goto unlockedout; + } data[count] = '\0'; rule = data; /* @@ -808,9 +816,18 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, if (m > BEBITS) return -EINVAL; - sp = smk_import(smack, 0); - if (sp == NULL) - return -EINVAL; + /* if smack begins with '-', its an option, don't import it */ + if (smack[0] != '-') { + sp = smk_import(smack, 0); + if (sp == NULL) + return -EINVAL; + } else { + /* check known options */ + if (strcmp(smack, smack_cipso_option) == 0) + sp = (char *)smack_cipso_option; + else + return -EINVAL; + } for (temp_mask = 0; m > 0; m--) { temp_mask |= mask_bits; @@ -849,18 +866,23 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, smk_netlbladdr_insert(skp); } } else { - rc = netlbl_cfg_unlbl_static_del(&init_net, NULL, - &skp->smk_host.sin_addr, &skp->smk_mask, - PF_INET, &audit_info); + /* we delete the unlabeled entry, only if the previous label + * wasnt the special CIPSO option */ + if (skp->smk_label != smack_cipso_option) + rc = netlbl_cfg_unlbl_static_del(&init_net, NULL, + &skp->smk_host.sin_addr, &skp->smk_mask, + PF_INET, &audit_info); + else + rc = 0; skp->smk_label = sp; } /* * Now tell netlabel about the single label nature of * this host so that incoming packets get labeled. + * but only if we didn't get the special CIPSO option */ - - if (rc == 0) + if (rc == 0 && sp != smack_cipso_option) rc = netlbl_cfg_unlbl_static_add(&init_net, NULL, &skp->smk_host.sin_addr, &skp->smk_mask, PF_INET, smack_to_secid(skp->smk_label), &audit_info); -- cgit v1.2.3