X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=security%2Fselinux%2Fhooks.c;h=4bf4807f2d44351f9a46084a21e26567e8602448;hb=ce4796d1e16cf3761dc2a02b8d588667d05b3078;hp=bfe9a05db3a2e67de8d36dee4cb2894e4d212b1d;hpb=220deb966ea51e0dedb6a187c0763120809f3e64;p=linux-2.6 diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index bfe9a05db3..4bf4807f2d 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -12,8 +12,8 @@ * Copyright (C) 2003 Red Hat, Inc., James Morris * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. * - * Copyright (C) 2006 Hewlett-Packard Development Company, L.P. - * Paul Moore, + * Copyright (C) 2006, 2007 Hewlett-Packard Development Company, L.P. + * Paul Moore * Copyright (C) 2007 Hitachi Software Engineering Co., Ltd. * Yuichi Nakamura * @@ -51,8 +51,10 @@ #include /* for local_port_range[] */ #include /* struct or_callable used in sock_rcv_skb */ #include +#include #include #include +#include #include #include #include /* for network interface checks */ @@ -91,6 +93,9 @@ 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 */ +atomic_t selinux_secmark_refcount = ATOMIC_INIT(0); + #ifdef CONFIG_SECURITY_SELINUX_DEVELOP int selinux_enforcing = 0; @@ -131,30 +136,19 @@ static DEFINE_SPINLOCK(sb_security_lock); static struct kmem_cache *sel_inode_cache; -/* Return security context for a given sid or just the context - length if the buffer is null or length is 0 */ -static int selinux_getsecurity(u32 sid, void *buffer, size_t size) +/** + * selinux_secmark_enabled - Check to see if SECMARK is currently enabled + * + * Description: + * This function checks the SECMARK reference counter to see if any SECMARK + * targets are currently configured, if the reference counter is greater than + * zero SECMARK is considered to be enabled. Returns true (1) if SECMARK is + * enabled, false (0) if SECMARK is disabled. + * + */ +static int selinux_secmark_enabled(void) { - char *context; - unsigned len; - int rc; - - rc = security_sid_to_context(sid, &context, &len); - if (rc) - return rc; - - if (!buffer || !size) - goto getsecurity_exit; - - if (size < len) { - len = -ERANGE; - goto getsecurity_exit; - } - memcpy(buffer, context, len); - -getsecurity_exit: - kfree(context); - return len; + return (atomic_read(&selinux_secmark_refcount) > 0); } /* Allocate and free functions for each kind of security blob. */ @@ -449,8 +443,7 @@ out: * mount options, or whatever. */ static int selinux_get_mnt_opts(const struct super_block *sb, - char ***mount_options, int **mnt_opts_flags, - int *num_opts) + struct security_mnt_opts *opts) { int rc = 0, i; struct superblock_security_struct *sbsec = sb->s_security; @@ -458,9 +451,7 @@ static int selinux_get_mnt_opts(const struct super_block *sb, u32 len; char tmp; - *num_opts = 0; - *mount_options = NULL; - *mnt_opts_flags = NULL; + security_init_mnt_opts(opts); if (!sbsec->initialized) return -EINVAL; @@ -476,18 +467,18 @@ static int selinux_get_mnt_opts(const struct super_block *sb, /* count the number of mount options for this sb */ for (i = 0; i < 8; i++) { if (tmp & 0x01) - (*num_opts)++; + opts->num_mnt_opts++; tmp >>= 1; } - *mount_options = kcalloc(*num_opts, sizeof(char *), GFP_ATOMIC); - if (!*mount_options) { + opts->mnt_opts = kcalloc(opts->num_mnt_opts, sizeof(char *), GFP_ATOMIC); + if (!opts->mnt_opts) { rc = -ENOMEM; goto out_free; } - *mnt_opts_flags = kcalloc(*num_opts, sizeof(int), GFP_ATOMIC); - if (!*mnt_opts_flags) { + opts->mnt_opts_flags = kcalloc(opts->num_mnt_opts, sizeof(int), GFP_ATOMIC); + if (!opts->mnt_opts_flags) { rc = -ENOMEM; goto out_free; } @@ -497,22 +488,22 @@ static int selinux_get_mnt_opts(const struct super_block *sb, rc = security_sid_to_context(sbsec->sid, &context, &len); if (rc) goto out_free; - (*mount_options)[i] = context; - (*mnt_opts_flags)[i++] = FSCONTEXT_MNT; + opts->mnt_opts[i] = context; + opts->mnt_opts_flags[i++] = FSCONTEXT_MNT; } if (sbsec->flags & CONTEXT_MNT) { rc = security_sid_to_context(sbsec->mntpoint_sid, &context, &len); if (rc) goto out_free; - (*mount_options)[i] = context; - (*mnt_opts_flags)[i++] = CONTEXT_MNT; + opts->mnt_opts[i] = context; + opts->mnt_opts_flags[i++] = CONTEXT_MNT; } if (sbsec->flags & DEFCONTEXT_MNT) { rc = security_sid_to_context(sbsec->def_sid, &context, &len); if (rc) goto out_free; - (*mount_options)[i] = context; - (*mnt_opts_flags)[i++] = DEFCONTEXT_MNT; + opts->mnt_opts[i] = context; + opts->mnt_opts_flags[i++] = DEFCONTEXT_MNT; } if (sbsec->flags & ROOTCONTEXT_MNT) { struct inode *root = sbsec->sb->s_root->d_inode; @@ -521,24 +512,16 @@ static int selinux_get_mnt_opts(const struct super_block *sb, rc = security_sid_to_context(isec->sid, &context, &len); if (rc) goto out_free; - (*mount_options)[i] = context; - (*mnt_opts_flags)[i++] = ROOTCONTEXT_MNT; + opts->mnt_opts[i] = context; + opts->mnt_opts_flags[i++] = ROOTCONTEXT_MNT; } - BUG_ON(i != *num_opts); + BUG_ON(i != opts->num_mnt_opts); return 0; out_free: - /* don't leak context string if security_sid_to_context had an error */ - if (*mount_options && i) - for (; i > 0; i--) - kfree((*mount_options)[i-1]); - kfree(*mount_options); - *mount_options = NULL; - kfree(*mnt_opts_flags); - *mnt_opts_flags = NULL; - *num_opts = 0; + security_free_mnt_opts(opts); return rc; } @@ -559,12 +542,13 @@ static int bad_option(struct superblock_security_struct *sbsec, char flag, return 1; return 0; } + /* * Allow filesystems with binary mount data to explicitly set mount point * labeling information. */ -int selinux_set_mnt_opts(struct super_block *sb, char **mount_options, - int *flags, int num_opts) +static int selinux_set_mnt_opts(struct super_block *sb, + struct security_mnt_opts *opts) { int rc = 0, i; struct task_security_struct *tsec = current->security; @@ -574,6 +558,9 @@ int selinux_set_mnt_opts(struct super_block *sb, char **mount_options, struct inode_security_struct *root_isec = inode->i_security; u32 fscontext_sid = 0, context_sid = 0, rootcontext_sid = 0; u32 defcontext_sid = 0; + char **mount_options = opts->mnt_opts; + int *flags = opts->mnt_opts_flags; + int num_opts = opts->num_mnt_opts; mutex_lock(&sbsec->lock); @@ -594,6 +581,21 @@ int selinux_set_mnt_opts(struct super_block *sb, char **mount_options, goto out; } + /* + * Binary mount data FS will come through this function twice. Once + * from an explicit call and once from the generic calls from the vfs. + * Since the generic VFS calls will not contain any security mount data + * we need to skip the double mount verification. + * + * This does open a hole in which we will not notice if the first + * mount using this sb set explict options and a second mount using + * 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) + && (num_opts == 0)) + goto out; + /* * parse the mount options, check if they are valid sids. * also check if someone is trying to mount the same sb more @@ -798,43 +800,14 @@ static void selinux_sb_clone_mnt_opts(const struct super_block *oldsb, mutex_unlock(&newsbsec->lock); } -/* - * string mount options parsing and call set the sbsec - */ -static int superblock_doinit(struct super_block *sb, void *data) +int selinux_parse_opts_str(char *options, struct security_mnt_opts *opts) { + char *p; char *context = NULL, *defcontext = NULL; char *fscontext = NULL, *rootcontext = NULL; - int rc = 0; - char *p, *options = data; - /* selinux only know about a fixed number of mount options */ - char *mnt_opts[NUM_SEL_MNT_OPTS]; - int mnt_opts_flags[NUM_SEL_MNT_OPTS], num_mnt_opts = 0; + int rc, num_mnt_opts = 0; - if (!data) - goto out; - - /* with the nfs patch this will become a goto out; */ - if (sb->s_type->fs_flags & FS_BINARY_MOUNTDATA) { - const char *name = sb->s_type->name; - /* NFS we understand. */ - if (!strcmp(name, "nfs")) { - struct nfs_mount_data *d = data; - - if (d->version != NFS_MOUNT_VERSION) - goto out; - - if (d->context[0]) { - context = kstrdup(d->context, GFP_KERNEL); - if (!context) { - rc = -ENOMEM; - goto out; - } - } - goto build_flags; - } else - goto out; - } + opts->num_mnt_opts = 0; /* Standard string-based options. */ while ((p = strsep(&options, "|")) != NULL) { @@ -907,26 +880,37 @@ static int superblock_doinit(struct super_block *sb, void *data) } } -build_flags: + rc = -ENOMEM; + opts->mnt_opts = kcalloc(NUM_SEL_MNT_OPTS, sizeof(char *), GFP_ATOMIC); + if (!opts->mnt_opts) + goto out_err; + + opts->mnt_opts_flags = kcalloc(NUM_SEL_MNT_OPTS, sizeof(int), GFP_ATOMIC); + if (!opts->mnt_opts_flags) { + kfree(opts->mnt_opts); + goto out_err; + } + if (fscontext) { - mnt_opts[num_mnt_opts] = fscontext; - mnt_opts_flags[num_mnt_opts++] = FSCONTEXT_MNT; + opts->mnt_opts[num_mnt_opts] = fscontext; + opts->mnt_opts_flags[num_mnt_opts++] = FSCONTEXT_MNT; } if (context) { - mnt_opts[num_mnt_opts] = context; - mnt_opts_flags[num_mnt_opts++] = CONTEXT_MNT; + opts->mnt_opts[num_mnt_opts] = context; + opts->mnt_opts_flags[num_mnt_opts++] = CONTEXT_MNT; } if (rootcontext) { - mnt_opts[num_mnt_opts] = rootcontext; - mnt_opts_flags[num_mnt_opts++] = ROOTCONTEXT_MNT; + opts->mnt_opts[num_mnt_opts] = rootcontext; + opts->mnt_opts_flags[num_mnt_opts++] = ROOTCONTEXT_MNT; } if (defcontext) { - mnt_opts[num_mnt_opts] = defcontext; - mnt_opts_flags[num_mnt_opts++] = DEFCONTEXT_MNT; + opts->mnt_opts[num_mnt_opts] = defcontext; + opts->mnt_opts_flags[num_mnt_opts++] = DEFCONTEXT_MNT; } -out: - rc = selinux_set_mnt_opts(sb, mnt_opts, mnt_opts_flags, num_mnt_opts); + opts->num_mnt_opts = num_mnt_opts; + return 0; + out_err: kfree(context); kfree(defcontext); @@ -934,6 +918,33 @@ out_err: kfree(rootcontext); return rc; } +/* + * string mount options parsing and call set the sbsec + */ +static int superblock_doinit(struct super_block *sb, void *data) +{ + int rc = 0; + char *options = data; + struct security_mnt_opts opts; + + security_init_mnt_opts(&opts); + + if (!data) + goto out; + + BUG_ON(sb->s_type->fs_flags & FS_BINARY_MOUNTDATA); + + rc = selinux_parse_opts_str(options, &opts); + if (rc) + goto out_err; + +out: + rc = selinux_set_mnt_opts(sb, &opts); + +out_err: + security_free_mnt_opts(&opts); + return rc; +} static inline u16 inode_mode_to_security_class(umode_t mode) { @@ -1278,12 +1289,18 @@ static int task_has_perm(struct task_struct *tsk1, SECCLASS_PROCESS, perms, NULL); } +#if CAP_LAST_CAP > 63 +#error Fix SELinux to handle capabilities > 63. +#endif + /* Check whether a task is allowed to use a capability. */ static int task_has_capability(struct task_struct *tsk, int cap) { struct task_security_struct *tsec; struct avc_audit_data ad; + u16 sclass; + u32 av = CAP_TO_MASK(cap); tsec = tsk->security; @@ -1291,8 +1308,19 @@ static int task_has_capability(struct task_struct *tsk, ad.tsk = tsk; ad.u.cap = cap; - return avc_has_perm(tsec->sid, tsec->sid, - SECCLASS_CAPABILITY, CAP_TO_MASK(cap), &ad); + switch (CAP_TO_INDEX(cap)) { + case 0: + sclass = SECCLASS_CAPABILITY; + break; + case 1: + sclass = SECCLASS_CAPABILITY2; + break; + default: + printk(KERN_ERR + "SELinux: out of range capability %d\n", cap); + BUG(); + } + return avc_has_perm(tsec->sid, tsec->sid, sclass, av, &ad); } /* Check whether a task is allowed to use a system operation. */ @@ -1345,8 +1373,8 @@ static inline int dentry_has_perm(struct task_struct *tsk, struct inode *inode = dentry->d_inode; struct avc_audit_data ad; AVC_AUDIT_DATA_INIT(&ad,FS); - ad.u.fs.mnt = mnt; - ad.u.fs.dentry = dentry; + ad.u.fs.path.mnt = mnt; + ad.u.fs.path.dentry = dentry; return inode_has_perm(tsk, inode, av, &ad); } @@ -1364,15 +1392,12 @@ static int file_has_perm(struct task_struct *tsk, { struct task_security_struct *tsec = tsk->security; struct file_security_struct *fsec = file->f_security; - struct vfsmount *mnt = file->f_path.mnt; - struct dentry *dentry = file->f_path.dentry; - struct inode *inode = dentry->d_inode; + struct inode *inode = file->f_path.dentry->d_inode; struct avc_audit_data ad; int rc; AVC_AUDIT_DATA_INIT(&ad, FS); - ad.u.fs.mnt = mnt; - ad.u.fs.dentry = dentry; + ad.u.fs.path = file->f_path; if (tsec->sid != fsec->sid) { rc = avc_has_perm(tsec->sid, fsec->sid, @@ -1407,7 +1432,7 @@ static int may_create(struct inode *dir, sbsec = dir->i_sb->s_security; AVC_AUDIT_DATA_INIT(&ad, FS); - ad.u.fs.dentry = dentry; + ad.u.fs.path.dentry = dentry; rc = avc_has_perm(tsec->sid, dsec->sid, SECCLASS_DIR, DIR__ADD_NAME | DIR__SEARCH, @@ -1465,7 +1490,7 @@ static int may_link(struct inode *dir, isec = dentry->d_inode->i_security; AVC_AUDIT_DATA_INIT(&ad, FS); - ad.u.fs.dentry = dentry; + ad.u.fs.path.dentry = dentry; av = DIR__SEARCH; av |= (kind ? DIR__REMOVE_NAME : DIR__ADD_NAME); @@ -1512,7 +1537,7 @@ static inline int may_rename(struct inode *old_dir, AVC_AUDIT_DATA_INIT(&ad, FS); - ad.u.fs.dentry = old_dentry; + ad.u.fs.path.dentry = old_dentry; rc = avc_has_perm(tsec->sid, old_dsec->sid, SECCLASS_DIR, DIR__REMOVE_NAME | DIR__SEARCH, &ad); if (rc) @@ -1528,7 +1553,7 @@ static inline int may_rename(struct inode *old_dir, return rc; } - ad.u.fs.dentry = new_dentry; + ad.u.fs.path.dentry = new_dentry; av = DIR__ADD_NAME | DIR__SEARCH; if (new_dentry->d_inode) av |= DIR__REMOVE_NAME; @@ -1907,8 +1932,7 @@ static int selinux_bprm_set_security(struct linux_binprm *bprm) } AVC_AUDIT_DATA_INIT(&ad, FS); - ad.u.fs.mnt = bprm->file->f_path.mnt; - ad.u.fs.dentry = bprm->file->f_path.dentry; + ad.u.fs.path = bprm->file->f_path; if (bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID) newsid = tsec->sid; @@ -2246,7 +2270,7 @@ static inline void take_selinux_option(char **to, char *from, int *first, } } -static int selinux_sb_copy_data(struct file_system_type *type, void *orig, void *copy) +static int selinux_sb_copy_data(char *orig, char *copy) { int fnosec, fsec, rc = 0; char *in_save, *in_curr, *in_end; @@ -2256,12 +2280,6 @@ static int selinux_sb_copy_data(struct file_system_type *type, void *orig, void in_curr = orig; sec_curr = copy; - /* Binary mount data: just copy */ - if (type->fs_flags & FS_BINARY_MOUNTDATA) { - copy_page(sec_curr, in_curr); - goto out; - } - nosec = (char *)get_zeroed_page(GFP_KERNEL); if (!nosec) { rc = -ENOMEM; @@ -2304,7 +2322,7 @@ static int selinux_sb_kern_mount(struct super_block *sb, void *data) return rc; AVC_AUDIT_DATA_INIT(&ad,FS); - ad.u.fs.dentry = sb->s_root; + ad.u.fs.path.dentry = sb->s_root; return superblock_has_perm(current, sb, FILESYSTEM__MOUNT, &ad); } @@ -2313,7 +2331,7 @@ static int selinux_sb_statfs(struct dentry *dentry) struct avc_audit_data ad; AVC_AUDIT_DATA_INIT(&ad,FS); - ad.u.fs.dentry = dentry->d_sb->s_root; + ad.u.fs.path.dentry = dentry->d_sb->s_root; return superblock_has_perm(current, dentry->d_sb, FILESYSTEM__GETATTR, &ad); } @@ -2330,10 +2348,10 @@ static int selinux_mount(char * dev_name, return rc; if (flags & MS_REMOUNT) - return superblock_has_perm(current, nd->mnt->mnt_sb, + return superblock_has_perm(current, nd->path.mnt->mnt_sb, FILESYSTEM__REMOUNT, NULL); else - return dentry_has_perm(current, nd->mnt, nd->dentry, + return dentry_has_perm(current, nd->path.mnt, nd->path.dentry, FILE__MOUNTON); } @@ -2576,7 +2594,7 @@ static int selinux_inode_setxattr(struct dentry *dentry, char *name, void *value return -EPERM; AVC_AUDIT_DATA_INIT(&ad,FS); - ad.u.fs.dentry = dentry; + ad.u.fs.path.dentry = dentry; rc = avc_has_perm(tsec->sid, isec->sid, isec->sclass, FILE__RELABELFROM, &ad); @@ -2655,14 +2673,27 @@ static int selinux_inode_removexattr (struct dentry *dentry, char *name) * * Permission check is handled by selinux_inode_getxattr hook. */ -static int selinux_inode_getsecurity(const struct inode *inode, const char *name, void *buffer, size_t size, int err) +static int selinux_inode_getsecurity(const struct inode *inode, const char *name, void **buffer, bool alloc) { + u32 size; + int error; + char *context = NULL; struct inode_security_struct *isec = inode->i_security; if (strcmp(name, XATTR_SELINUX_SUFFIX)) return -EOPNOTSUPP; - return selinux_getsecurity(isec->sid, buffer, size); + error = security_sid_to_context(isec->sid, &context, &size); + if (error) + return error; + error = size; + if (alloc) { + *buffer = context; + goto out_nofree; + } + kfree(context); +out_nofree: + return error; } static int selinux_inode_setsecurity(struct inode *inode, const char *name, @@ -3423,6 +3454,11 @@ static int selinux_parse_skb(struct sk_buff *skb, struct avc_audit_data *ad, break; } + if (unlikely(ret)) + printk(KERN_WARNING + "SELinux: failure in selinux_parse_skb()," + " unable to parse packet\n"); + return ret; } @@ -3443,21 +3479,21 @@ static int selinux_parse_skb(struct sk_buff *skb, struct avc_audit_data *ad, */ static int selinux_skb_peerlbl_sid(struct sk_buff *skb, u16 family, u32 *sid) { + int err; u32 xfrm_sid; u32 nlbl_sid; u32 nlbl_type; selinux_skb_xfrm_sid(skb, &xfrm_sid); - selinux_netlbl_skbuff_getsid(skb, - family, - SECINITSID_NETMSG, - &nlbl_type, - &nlbl_sid); - - if (security_net_peersid_resolve(nlbl_sid, nlbl_type, - xfrm_sid, - sid) != 0) + selinux_netlbl_skbuff_getsid(skb, family, &nlbl_type, &nlbl_sid); + + err = security_net_peersid_resolve(nlbl_sid, nlbl_type, xfrm_sid, sid); + if (unlikely(err)) { + printk(KERN_WARNING + "SELinux: failure in selinux_skb_peerlbl_sid()," + " unable to determine packet's peer label\n"); return -EACCES; + } return 0; } @@ -3829,6 +3865,29 @@ static int selinux_socket_unix_may_send(struct socket *sock, return 0; } +static int selinux_inet_sys_rcv_skb(int ifindex, char *addrp, u16 family, + u32 peer_sid, + struct avc_audit_data *ad) +{ + int err; + u32 if_sid; + u32 node_sid; + + err = sel_netif_sid(ifindex, &if_sid); + if (err) + return err; + err = avc_has_perm(peer_sid, if_sid, + SECCLASS_NETIF, NETIF__INGRESS, ad); + if (err) + return err; + + err = sel_netnode_sid(addrp, family, &node_sid); + if (err) + return err; + return avc_has_perm(peer_sid, node_sid, + 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, @@ -3886,8 +3945,13 @@ static int selinux_sock_rcv_skb_iptables_compat(struct sock *sk, err = security_port_sid(sk->sk_family, sk->sk_type, sk->sk_protocol, ntohs(ad->u.net.sport), &port_sid); - if (err) + 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); } @@ -3931,7 +3995,6 @@ static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) struct sk_security_struct *sksec = sk->sk_security; u16 family = sk->sk_family; u32 sk_sid = sksec->sid; - u32 peer_sid; struct avc_audit_data ad; char *addrp; @@ -3957,15 +4020,28 @@ static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) return selinux_sock_rcv_skb_compat(sk, skb, &ad, family, addrp); - err = avc_has_perm(sk_sid, skb->secmark, SECCLASS_PACKET, - PACKET__RECV, &ad); - if (err) - return err; + if (netlbl_enabled() || selinux_xfrm_enabled()) { + u32 peer_sid; - err = selinux_skb_peerlbl_sid(skb, family, &peer_sid); - if (err) - return err; - return avc_has_perm(sk_sid, peer_sid, SECCLASS_PEER, PEER__RECV, &ad); + err = selinux_skb_peerlbl_sid(skb, family, &peer_sid); + if (err) + return err; + err = selinux_inet_sys_rcv_skb(skb->iif, addrp, family, + peer_sid, &ad); + if (err) + return err; + err = avc_has_perm(sk_sid, peer_sid, SECCLASS_PEER, + PEER__RECV, &ad); + } + + if (selinux_secmark_enabled()) { + err = avc_has_perm(sk_sid, skb->secmark, SECCLASS_PACKET, + PACKET__RECV, &ad); + if (err) + return err; + } + + return err; } static int selinux_socket_getpeersec_stream(struct socket *sock, char __user *optval, @@ -4177,151 +4253,260 @@ out: #ifdef CONFIG_NETFILTER -static int selinux_ip_postroute_last_compat(struct sock *sk, - struct net_device *dev, - struct avc_audit_data *ad, - u16 family, - char *addrp) +static unsigned int selinux_ip_forward(struct sk_buff *skb, int ifindex, + u16 family) { - int err = 0; - u32 netif_perm, node_perm, node_sid, if_sid, send_perm = 0; - struct socket *sock; - struct inode *inode; - struct inode_security_struct *isec; + char *addrp; + u32 peer_sid; + struct avc_audit_data ad; + u8 secmark_active; + u8 peerlbl_active; - sock = sk->sk_socket; - if (!sock) - goto out; + if (!selinux_policycap_netpeer) + return NF_ACCEPT; - inode = SOCK_INODE(sock); - if (!inode) - goto out; + secmark_active = selinux_secmark_enabled(); + peerlbl_active = netlbl_enabled() || selinux_xfrm_enabled(); + if (!secmark_active && !peerlbl_active) + return NF_ACCEPT; - isec = inode->i_security; - - err = sel_netif_sid(dev->ifindex, &if_sid); - if (err) - goto out; + AVC_AUDIT_DATA_INIT(&ad, NET); + ad.u.net.netif = ifindex; + ad.u.net.family = family; + if (selinux_parse_skb(skb, &ad, &addrp, 1, NULL) != 0) + return NF_DROP; + + if (selinux_skb_peerlbl_sid(skb, family, &peer_sid) != 0) + return NF_DROP; - switch (isec->sclass) { + if (peerlbl_active) + if (selinux_inet_sys_rcv_skb(ifindex, addrp, family, + peer_sid, &ad) != 0) + return NF_DROP; + + if (secmark_active) + if (avc_has_perm(peer_sid, skb->secmark, + SECCLASS_PACKET, PACKET__FORWARD_IN, &ad)) + return NF_DROP; + + return NF_ACCEPT; +} + +static unsigned int selinux_ipv4_forward(unsigned int hooknum, + struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) +{ + return selinux_ip_forward(skb, in->ifindex, PF_INET); +} + +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) +static unsigned int selinux_ipv6_forward(unsigned int hooknum, + struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) +{ + return selinux_ip_forward(skb, in->ifindex, PF_INET6); +} +#endif /* IPV6 */ + +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 = avc_has_perm(isec->sid, if_sid, SECCLASS_NETIF, netif_perm, ad); + err = sel_netif_sid(ifindex, &if_sid); if (err) - goto out; + 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) - goto out; - - err = avc_has_perm(isec->sid, node_sid, SECCLASS_NODE, node_perm, ad); + return err; + err = avc_has_perm(sk_sid, node_sid, SECCLASS_NODE, node_perm, ad); if (err) - goto out; + return err; - if (send_perm) { - u32 port_sid; - - err = security_port_sid(sk->sk_family, - sk->sk_type, - sk->sk_protocol, - ntohs(ad->u.net.dport), - &port_sid); - if (err) - goto out; + if (send_perm != 0) + return 0; - err = avc_has_perm(isec->sid, port_sid, isec->sclass, - send_perm, ad); + err = security_port_sid(sk->sk_family, sk->sk_type, + 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; } -out: - return err; + return avc_has_perm(sk_sid, port_sid, sk_class, send_perm, ad); } -static unsigned int selinux_ip_postroute_last(unsigned int hooknum, - struct sk_buff *skb, - const struct net_device *in, - const struct net_device *out, - int (*okfn)(struct sk_buff *), - u16 family) +static unsigned int selinux_ip_postroute_compat(struct sk_buff *skb, + int ifindex, + struct avc_audit_data *ad, + u16 family, + char *addrp, + u8 proto) { - char *addrp; - int err = 0; - struct sock *sk; - struct avc_audit_data ad; - struct net_device *dev = (struct net_device *)out; + struct sock *sk = skb->sk; struct sk_security_struct *sksec; - u8 proto; - - sk = skb->sk; - if (!sk) - goto out; + if (sk == NULL) + return NF_ACCEPT; sksec = sk->sk_security; + if (selinux_compat_net) { + if (selinux_ip_postroute_iptables_compat(skb->sk, ifindex, + ad, family, addrp)) + return NF_DROP; + } else { + 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)) + return NF_DROP; + + return NF_ACCEPT; +} + +static unsigned int selinux_ip_postroute(struct sk_buff *skb, int ifindex, + u16 family) +{ + u32 secmark_perm; + u32 peer_sid; + struct sock *sk; + struct avc_audit_data ad; + char *addrp; + u8 proto; + u8 secmark_active; + u8 peerlbl_active; + AVC_AUDIT_DATA_INIT(&ad, NET); - ad.u.net.netif = dev->ifindex; + ad.u.net.netif = ifindex; ad.u.net.family = family; + if (selinux_parse_skb(skb, &ad, &addrp, 0, &proto)) + return NF_DROP; - err = selinux_parse_skb(skb, &ad, &addrp, 0, &proto); - if (err) - goto out; - - if (selinux_compat_net) - err = selinux_ip_postroute_last_compat(sk, dev, &ad, - family, addrp); - else - err = avc_has_perm(sksec->sid, skb->secmark, SECCLASS_PACKET, - PACKET__SEND, &ad); + /* If any sort of compatibility mode is enabled then handoff processing + * 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) + return selinux_ip_postroute_compat(skb, ifindex, &ad, + family, addrp, proto); + + /* If skb->dst->xfrm is non-NULL then the packet is undergoing an IPsec + * packet transformation so allow the packet to pass without any checks + * since we'll have another chance to perform access control checks + * when the packet is on it's final way out. + * NOTE: there appear to be some IPv6 multicast cases where skb->dst + * is NULL, in this case go ahead and apply access control. */ + if (skb->dst != NULL && skb->dst->xfrm != NULL) + return NF_ACCEPT; + + secmark_active = selinux_secmark_enabled(); + peerlbl_active = netlbl_enabled() || selinux_xfrm_enabled(); + if (!secmark_active && !peerlbl_active) + return NF_ACCEPT; + + /* if the packet is locally generated (skb->sk != NULL) then use the + * socket's label as the peer label, otherwise the packet is being + * forwarded through this system and we need to fetch the peer label + * directly from the packet */ + sk = skb->sk; + if (sk) { + struct sk_security_struct *sksec = sk->sk_security; + peer_sid = sksec->sid; + secmark_perm = PACKET__SEND; + } else { + if (selinux_skb_peerlbl_sid(skb, family, &peer_sid)) + return NF_DROP; + secmark_perm = PACKET__FORWARD_OUT; + } - if (err) - goto out; + if (secmark_active) + if (avc_has_perm(peer_sid, skb->secmark, + SECCLASS_PACKET, secmark_perm, &ad)) + return NF_DROP; + + if (peerlbl_active) { + u32 if_sid; + u32 node_sid; + + if (sel_netif_sid(ifindex, &if_sid)) + return NF_DROP; + if (avc_has_perm(peer_sid, if_sid, + SECCLASS_NETIF, NETIF__EGRESS, &ad)) + return NF_DROP; + + if (sel_netnode_sid(addrp, family, &node_sid)) + return NF_DROP; + if (avc_has_perm(peer_sid, node_sid, + SECCLASS_NODE, NODE__SENDTO, &ad)) + return NF_DROP; + } - err = selinux_xfrm_postroute_last(sksec->sid, skb, &ad, proto); -out: - return err ? NF_DROP : NF_ACCEPT; + return NF_ACCEPT; } -static unsigned int selinux_ipv4_postroute_last(unsigned int hooknum, - struct sk_buff *skb, - const struct net_device *in, - const struct net_device *out, - int (*okfn)(struct sk_buff *)) +static unsigned int selinux_ipv4_postroute(unsigned int hooknum, + struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) { - return selinux_ip_postroute_last(hooknum, skb, in, out, okfn, PF_INET); + return selinux_ip_postroute(skb, out->ifindex, PF_INET); } #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) - -static unsigned int selinux_ipv6_postroute_last(unsigned int hooknum, - struct sk_buff *skb, - const struct net_device *in, - const struct net_device *out, - int (*okfn)(struct sk_buff *)) +static unsigned int selinux_ipv6_postroute(unsigned int hooknum, + struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) { - return selinux_ip_postroute_last(hooknum, skb, in, out, okfn, PF_INET6); + return selinux_ip_postroute(skb, out->ifindex, PF_INET6); } - #endif /* IPV6 */ #endif /* CONFIG_NETFILTER */ @@ -5077,6 +5262,8 @@ static struct security_operations selinux_ops = { .sb_get_mnt_opts = selinux_get_mnt_opts, .sb_set_mnt_opts = selinux_set_mnt_opts, .sb_clone_mnt_opts = selinux_sb_clone_mnt_opts, + .sb_parse_opts_str = selinux_parse_opts_str, + .inode_alloc_security = selinux_inode_alloc_security, .inode_free_security = selinux_inode_free_security, @@ -5309,22 +5496,40 @@ security_initcall(selinux_init); #if defined(CONFIG_NETFILTER) -static struct nf_hook_ops selinux_ipv4_op = { - .hook = selinux_ipv4_postroute_last, - .owner = THIS_MODULE, - .pf = PF_INET, - .hooknum = NF_INET_POST_ROUTING, - .priority = NF_IP_PRI_SELINUX_LAST, +static struct nf_hook_ops selinux_ipv4_ops[] = { + { + .hook = selinux_ipv4_postroute, + .owner = THIS_MODULE, + .pf = PF_INET, + .hooknum = NF_INET_POST_ROUTING, + .priority = NF_IP_PRI_SELINUX_LAST, + }, + { + .hook = selinux_ipv4_forward, + .owner = THIS_MODULE, + .pf = PF_INET, + .hooknum = NF_INET_FORWARD, + .priority = NF_IP_PRI_SELINUX_FIRST, + } }; #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) -static struct nf_hook_ops selinux_ipv6_op = { - .hook = selinux_ipv6_postroute_last, - .owner = THIS_MODULE, - .pf = PF_INET6, - .hooknum = NF_INET_POST_ROUTING, - .priority = NF_IP6_PRI_SELINUX_LAST, +static struct nf_hook_ops selinux_ipv6_ops[] = { + { + .hook = selinux_ipv6_postroute, + .owner = THIS_MODULE, + .pf = PF_INET6, + .hooknum = NF_INET_POST_ROUTING, + .priority = NF_IP6_PRI_SELINUX_LAST, + }, + { + .hook = selinux_ipv6_forward, + .owner = THIS_MODULE, + .pf = PF_INET6, + .hooknum = NF_INET_FORWARD, + .priority = NF_IP6_PRI_SELINUX_FIRST, + } }; #endif /* IPV6 */ @@ -5332,22 +5537,27 @@ static struct nf_hook_ops selinux_ipv6_op = { static int __init selinux_nf_ip_init(void) { int err = 0; + u32 iter; if (!selinux_enabled) goto out; printk(KERN_DEBUG "SELinux: Registering netfilter hooks\n"); - err = nf_register_hook(&selinux_ipv4_op); - if (err) - panic("SELinux: nf_register_hook for IPv4: error %d\n", err); + for (iter = 0; iter < ARRAY_SIZE(selinux_ipv4_ops); iter++) { + err = nf_register_hook(&selinux_ipv4_ops[iter]); + if (err) + panic("SELinux: nf_register_hook for IPv4: error %d\n", + err); + } #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) - - err = nf_register_hook(&selinux_ipv6_op); - if (err) - panic("SELinux: nf_register_hook for IPv6: error %d\n", err); - + for (iter = 0; iter < ARRAY_SIZE(selinux_ipv6_ops); iter++) { + err = nf_register_hook(&selinux_ipv6_ops[iter]); + if (err) + panic("SELinux: nf_register_hook for IPv6: error %d\n", + err); + } #endif /* IPV6 */ out: @@ -5359,11 +5569,15 @@ __initcall(selinux_nf_ip_init); #ifdef CONFIG_SECURITY_SELINUX_DISABLE static void selinux_nf_ip_exit(void) { + u32 iter; + printk(KERN_DEBUG "SELinux: Unregistering netfilter hooks\n"); - nf_unregister_hook(&selinux_ipv4_op); + for (iter = 0; iter < ARRAY_SIZE(selinux_ipv4_ops); iter++) + nf_unregister_hook(&selinux_ipv4_ops[iter]); #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) - nf_unregister_hook(&selinux_ipv6_op); + for (iter = 0; iter < ARRAY_SIZE(selinux_ipv6_ops); iter++) + nf_unregister_hook(&selinux_ipv6_ops[iter]); #endif /* IPV6 */ } #endif