LSM/SELinux: Interfaces to allow FS to control mount options
[sfrench/cifs-2.6.git] / security / selinux / hooks.c
index c90e865a8603bb442b60cb7a243f6affc76b70af..4bf4807f2d44351f9a46084a21e26567e8602448 100644 (file)
@@ -12,8 +12,8 @@
  *  Copyright (C) 2003 Red Hat, Inc., James Morris <jmorris@redhat.com>
  *  Copyright (C) 2004-2005 Trusted Computer Solutions, Inc.
  *                          <dgoeddel@trustedcs.com>
- *  Copyright (C) 2006 Hewlett-Packard Development Company, L.P.
- *                     Paul Moore, <paul.moore@hp.com>
+ *  Copyright (C) 2006, 2007 Hewlett-Packard Development Company, L.P.
+ *                Paul Moore <paul.moore@hp.com>
  *  Copyright (C) 2007 Hitachi Software Engineering Co., Ltd.
  *                     Yuichi Nakamura <ynakam@hitachisoft.jp>
  *
@@ -136,32 +136,6 @@ 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)
-{
-       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;
-}
-
 /**
  * selinux_secmark_enabled - Check to see if SECMARK is currently enabled
  *
@@ -469,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;
@@ -478,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;
@@ -496,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;
        }
@@ -517,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;
@@ -541,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;
 }
 
@@ -579,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;
@@ -594,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);
 
@@ -614,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
@@ -818,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;
-
-       if (!data)
-               goto out;
+       int rc, num_mnt_opts = 0;
 
-       /* 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) {
@@ -927,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);
@@ -954,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)
 {
@@ -1298,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;
 
@@ -1311,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. */
@@ -1365,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);
 }
 
@@ -1384,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,
@@ -1427,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,
@@ -1485,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);
@@ -1532,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)
@@ -1548,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;
@@ -1927,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;
@@ -2266,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;
@@ -2276,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;
@@ -2324,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);
 }
 
@@ -2333,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);
 }
 
@@ -2350,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);
 }
 
@@ -2596,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);
@@ -2675,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,
@@ -3443,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;
 }
 
@@ -3463,6 +3479,7 @@ 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;
@@ -3470,10 +3487,13 @@ static int selinux_skb_peerlbl_sid(struct sk_buff *skb, u16 family, u32 *sid)
        selinux_skb_xfrm_sid(skb, &xfrm_sid);
        selinux_netlbl_skbuff_getsid(skb, family, &nlbl_type, &nlbl_sid);
 
-       if (security_net_peersid_resolve(nlbl_sid, nlbl_type,
-                                        xfrm_sid,
-                                        sid) != 0)
+       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;
 }
@@ -3845,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,
@@ -3902,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);
 }
 
@@ -3972,23 +4020,27 @@ 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);
 
-       if (selinux_secmark_enabled()) {
-               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;
+               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;
 }
 
@@ -4201,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 */
@@ -5101,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,
@@ -5333,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 */
@@ -5356,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:
@@ -5383,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