Commit 4e5ab4cb authored by James Morris's avatar James Morris Committed by David S. Miller

[SECMARK]: Add new packet controls to SELinux

Add new per-packet access controls to SELinux, replacing the old
packet controls.

Packets are labeled with the iptables SECMARK and CONNSECMARK targets,
then security policy for the packets is enforced with these controls.

To allow for a smooth transition to the new controls, the old code is
still present, but not active by default.  To restore previous
behavior, the old controls may be activated at runtime by writing a
'1' to /selinux/compat_net, and also via the kernel boot parameter
selinux_compat_net.  Switching between the network control models
requires the security load_policy permission.  The old controls will
probably eventually be removed and any continued use is discouraged.

With this patch, the new secmark controls for SElinux are disabled by
default, so existing behavior is entirely preserved, and the user is
not affected at all.

It also provides a config option to enable the secmark controls by
default (which can always be overridden at boot and runtime).  It is
also noted in the kconfig help that the user will need updated
userspace if enabling secmark controls for SELinux and that they'll
probably need the SECMARK and CONNMARK targets, and conntrack protocol
helpers, although such decisions are beyond the scope of kernel
configuration.
Signed-off-by: default avatarJames Morris <jmorris@namei.org>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 100468e9
...@@ -1402,6 +1402,15 @@ running once the system is up. ...@@ -1402,6 +1402,15 @@ running once the system is up.
If enabled at boot time, /selinux/disable can be used If enabled at boot time, /selinux/disable can be used
later to disable prior to initial policy load. later to disable prior to initial policy load.
selinux_compat_net =
[SELINUX] Set initial selinux_compat_net flag value.
Format: { "0" | "1" }
0 -- use new secmark-based packet controls
1 -- use legacy packet controls
Default value is 0 (preferred).
Value can be changed at runtime via
/selinux/compat_net.
serialnumber [BUGS=IA-32] serialnumber [BUGS=IA-32]
sg_def_reserved_size= [SCSI] sg_def_reserved_size= [SCSI]
......
config SECURITY_SELINUX config SECURITY_SELINUX
bool "NSA SELinux Support" bool "NSA SELinux Support"
depends on SECURITY_NETWORK && AUDIT && NET && INET depends on SECURITY_NETWORK && AUDIT && NET && INET
select NETWORK_SECMARK
default n default n
help help
This selects NSA Security-Enhanced Linux (SELinux). This selects NSA Security-Enhanced Linux (SELinux).
...@@ -95,3 +96,31 @@ config SECURITY_SELINUX_CHECKREQPROT_VALUE ...@@ -95,3 +96,31 @@ config SECURITY_SELINUX_CHECKREQPROT_VALUE
via /selinux/checkreqprot if authorized by policy. via /selinux/checkreqprot if authorized by policy.
If you are unsure how to answer this question, answer 1. If you are unsure how to answer this question, answer 1.
config SECURITY_SELINUX_ENABLE_SECMARK_DEFAULT
bool "NSA SELinux enable new secmark network controls by default"
depends on SECURITY_SELINUX
default n
help
This option determines whether the new secmark-based network
controls will be enabled by default. If not, the old internal
per-packet controls will be enabled by default, preserving
old behavior.
If you enable the new controls, you will need updated
SELinux userspace libraries, tools and policy. Typically,
your distribution will provide these and enable the new controls
in the kernel they also distribute.
Note that this option can be overriden at boot with the
selinux_compat_net parameter, and after boot via
/selinux/compat_net. See Documentation/kernel-parameters.txt
for details on this parameter.
If you enable the new network controls, you will likely
also require the SECMARK and CONNSECMARK targets, as
well as any conntrack helpers for protocols which you
wish to control.
If you are unsure what do do here, select N.
...@@ -80,6 +80,7 @@ ...@@ -80,6 +80,7 @@
extern unsigned int policydb_loaded_version; extern unsigned int policydb_loaded_version;
extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm); extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm);
extern int selinux_compat_net;
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP #ifdef CONFIG_SECURITY_SELINUX_DEVELOP
int selinux_enforcing = 0; int selinux_enforcing = 0;
...@@ -3216,47 +3217,17 @@ static int selinux_socket_unix_may_send(struct socket *sock, ...@@ -3216,47 +3217,17 @@ static int selinux_socket_unix_may_send(struct socket *sock,
return 0; return 0;
} }
static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) static int selinux_sock_rcv_skb_compat(struct sock *sk, struct sk_buff *skb,
struct avc_audit_data *ad, u32 sock_sid, u16 sock_class,
u16 family, char *addrp, int len)
{ {
u16 family; int err = 0;
char *addrp;
int len, err = 0;
u32 netif_perm, node_perm, node_sid, if_sid, recv_perm = 0; u32 netif_perm, node_perm, node_sid, if_sid, recv_perm = 0;
u32 sock_sid = 0;
u16 sock_class = 0;
struct socket *sock;
struct net_device *dev;
struct avc_audit_data ad;
family = sk->sk_family; if (!skb->dev)
if (family != PF_INET && family != PF_INET6)
goto out; goto out;
/* Handle mapped IPv4 packets arriving via IPv6 sockets */ err = sel_netif_sids(skb->dev, &if_sid, NULL);
if (family == PF_INET6 && skb->protocol == htons(ETH_P_IP))
family = PF_INET;
read_lock_bh(&sk->sk_callback_lock);
sock = sk->sk_socket;
if (sock) {
struct inode *inode;
inode = SOCK_INODE(sock);
if (inode) {
struct inode_security_struct *isec;
isec = inode->i_security;
sock_sid = isec->sid;
sock_class = isec->sclass;
}
}
read_unlock_bh(&sk->sk_callback_lock);
if (!sock_sid)
goto out;
dev = skb->dev;
if (!dev)
goto out;
err = sel_netif_sids(dev, &if_sid, NULL);
if (err) if (err)
goto out; goto out;
...@@ -3279,44 +3250,88 @@ static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) ...@@ -3279,44 +3250,88 @@ static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
break; break;
} }
AVC_AUDIT_DATA_INIT(&ad, NET); err = avc_has_perm(sock_sid, if_sid, SECCLASS_NETIF, netif_perm, ad);
ad.u.net.netif = dev->name;
ad.u.net.family = family;
err = selinux_parse_skb(skb, &ad, &addrp, &len, 1);
if (err) if (err)
goto out; goto out;
err = avc_has_perm(sock_sid, if_sid, SECCLASS_NETIF, netif_perm, &ad);
if (err)
goto out;
/* Fixme: this lookup is inefficient */
err = security_node_sid(family, addrp, len, &node_sid); err = security_node_sid(family, addrp, len, &node_sid);
if (err) if (err)
goto out; goto out;
err = avc_has_perm(sock_sid, node_sid, SECCLASS_NODE, node_perm, &ad); err = avc_has_perm(sock_sid, node_sid, SECCLASS_NODE, node_perm, ad);
if (err) if (err)
goto out; goto out;
if (recv_perm) { if (recv_perm) {
u32 port_sid; u32 port_sid;
/* Fixme: make this more efficient */
err = security_port_sid(sk->sk_family, sk->sk_type, err = security_port_sid(sk->sk_family, sk->sk_type,
sk->sk_protocol, ntohs(ad.u.net.sport), sk->sk_protocol, ntohs(ad->u.net.sport),
&port_sid); &port_sid);
if (err) if (err)
goto out; goto out;
err = avc_has_perm(sock_sid, port_sid, err = avc_has_perm(sock_sid, port_sid,
sock_class, recv_perm, &ad); sock_class, recv_perm, ad);
} }
if (!err) out:
err = selinux_xfrm_sock_rcv_skb(sock_sid, skb); return err;
}
static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
{
u16 family;
u16 sock_class = 0;
char *addrp;
int len, err = 0;
u32 sock_sid = 0;
struct socket *sock;
struct avc_audit_data ad;
family = sk->sk_family;
if (family != PF_INET && family != PF_INET6)
goto out;
/* Handle mapped IPv4 packets arriving via IPv6 sockets */
if (family == PF_INET6 && skb->protocol == ntohs(ETH_P_IP))
family = PF_INET;
read_lock_bh(&sk->sk_callback_lock);
sock = sk->sk_socket;
if (sock) {
struct inode *inode;
inode = SOCK_INODE(sock);
if (inode) {
struct inode_security_struct *isec;
isec = inode->i_security;
sock_sid = isec->sid;
sock_class = isec->sclass;
}
}
read_unlock_bh(&sk->sk_callback_lock);
if (!sock_sid)
goto out;
AVC_AUDIT_DATA_INIT(&ad, NET);
ad.u.net.netif = skb->dev ? skb->dev->name : "[unknown]";
ad.u.net.family = family;
err = selinux_parse_skb(skb, &ad, &addrp, &len, 1);
if (err)
goto out;
if (selinux_compat_net)
err = selinux_sock_rcv_skb_compat(sk, skb, &ad, sock_sid,
sock_class, family,
addrp, len);
else
err = avc_has_perm(sock_sid, skb->secmark, SECCLASS_PACKET,
PACKET__RECV, &ad);
if (err)
goto out;
err = selinux_xfrm_sock_rcv_skb(sock_sid, skb);
out: out:
return err; return err;
} }
...@@ -3456,42 +3471,18 @@ out: ...@@ -3456,42 +3471,18 @@ out:
#ifdef CONFIG_NETFILTER #ifdef CONFIG_NETFILTER
static unsigned int selinux_ip_postroute_last(unsigned int hooknum, static int selinux_ip_postroute_last_compat(struct sock *sk, struct net_device *dev,
struct sk_buff **pskb, struct inode_security_struct *isec,
const struct net_device *in, struct avc_audit_data *ad,
const struct net_device *out, u16 family, char *addrp, int len)
int (*okfn)(struct sk_buff *),
u16 family)
{ {
char *addrp; int err;
int len, err = NF_ACCEPT;
u32 netif_perm, node_perm, node_sid, if_sid, send_perm = 0; u32 netif_perm, node_perm, node_sid, if_sid, send_perm = 0;
struct sock *sk;
struct socket *sock;
struct inode *inode;
struct sk_buff *skb = *pskb;
struct inode_security_struct *isec;
struct avc_audit_data ad;
struct net_device *dev = (struct net_device *)out;
sk = skb->sk;
if (!sk)
goto out;
sock = sk->sk_socket;
if (!sock)
goto out;
inode = SOCK_INODE(sock);
if (!inode)
goto out;
err = sel_netif_sids(dev, &if_sid, NULL); err = sel_netif_sids(dev, &if_sid, NULL);
if (err) if (err)
goto out; goto out;
isec = inode->i_security;
switch (isec->sclass) { switch (isec->sclass) {
case SECCLASS_UDP_SOCKET: case SECCLASS_UDP_SOCKET:
netif_perm = NETIF__UDP_SEND; netif_perm = NETIF__UDP_SEND;
...@@ -3511,55 +3502,88 @@ static unsigned int selinux_ip_postroute_last(unsigned int hooknum, ...@@ -3511,55 +3502,88 @@ static unsigned int selinux_ip_postroute_last(unsigned int hooknum,
break; break;
} }
err = avc_has_perm(isec->sid, if_sid, SECCLASS_NETIF, netif_perm, ad);
AVC_AUDIT_DATA_INIT(&ad, NET); if (err)
ad.u.net.netif = dev->name;
ad.u.net.family = family;
err = selinux_parse_skb(skb, &ad, &addrp,
&len, 0) ? NF_DROP : NF_ACCEPT;
if (err != NF_ACCEPT)
goto out;
err = avc_has_perm(isec->sid, if_sid, SECCLASS_NETIF,
netif_perm, &ad) ? NF_DROP : NF_ACCEPT;
if (err != NF_ACCEPT)
goto out; goto out;
/* Fixme: this lookup is inefficient */ err = security_node_sid(family, addrp, len, &node_sid);
err = security_node_sid(family, addrp, len, if (err)
&node_sid) ? NF_DROP : NF_ACCEPT;
if (err != NF_ACCEPT)
goto out; goto out;
err = avc_has_perm(isec->sid, node_sid, SECCLASS_NODE, err = avc_has_perm(isec->sid, node_sid, SECCLASS_NODE, node_perm, ad);
node_perm, &ad) ? NF_DROP : NF_ACCEPT; if (err)
if (err != NF_ACCEPT)
goto out; goto out;
if (send_perm) { if (send_perm) {
u32 port_sid; u32 port_sid;
/* Fixme: make this more efficient */
err = security_port_sid(sk->sk_family, err = security_port_sid(sk->sk_family,
sk->sk_type, sk->sk_type,
sk->sk_protocol, sk->sk_protocol,
ntohs(ad.u.net.dport), ntohs(ad->u.net.dport),
&port_sid) ? NF_DROP : NF_ACCEPT; &port_sid);
if (err != NF_ACCEPT) if (err)
goto out; goto out;
err = avc_has_perm(isec->sid, port_sid, isec->sclass, err = avc_has_perm(isec->sid, port_sid, isec->sclass,
send_perm, &ad) ? NF_DROP : NF_ACCEPT; send_perm, ad);
} }
out:
return err;
}
static unsigned int selinux_ip_postroute_last(unsigned int hooknum,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *),
u16 family)
{
char *addrp;
int len, err = 0;
struct sock *sk;
struct socket *sock;
struct inode *inode;
struct sk_buff *skb = *pskb;
struct inode_security_struct *isec;
struct avc_audit_data ad;
struct net_device *dev = (struct net_device *)out;
if (err != NF_ACCEPT) sk = skb->sk;
if (!sk)
goto out; goto out;
err = selinux_xfrm_postroute_last(isec->sid, skb); sock = sk->sk_socket;
if (!sock)
goto out;
inode = SOCK_INODE(sock);
if (!inode)
goto out;
isec = inode->i_security;
AVC_AUDIT_DATA_INIT(&ad, NET);
ad.u.net.netif = dev->name;
ad.u.net.family = family;
err = selinux_parse_skb(skb, &ad, &addrp, &len, 0);
if (err)
goto out;
if (selinux_compat_net)
err = selinux_ip_postroute_last_compat(sk, dev, isec, &ad,
family, addrp, len);
else
err = avc_has_perm(isec->sid, skb->secmark, SECCLASS_PACKET,
PACKET__SEND, &ad);
if (err)
goto out;
err = selinux_xfrm_postroute_last(isec->sid, skb);
out: out:
return err; return err ? NF_DROP : NF_ACCEPT;
} }
static unsigned int selinux_ipv4_postroute_last(unsigned int hooknum, static unsigned int selinux_ipv4_postroute_last(unsigned int hooknum,
......
...@@ -51,7 +51,7 @@ static inline int selinux_xfrm_sock_rcv_skb(u32 isec_sid, struct sk_buff *skb) ...@@ -51,7 +51,7 @@ static inline int selinux_xfrm_sock_rcv_skb(u32 isec_sid, struct sk_buff *skb)
static inline int selinux_xfrm_postroute_last(u32 isec_sid, struct sk_buff *skb) static inline int selinux_xfrm_postroute_last(u32 isec_sid, struct sk_buff *skb)
{ {
return NF_ACCEPT; return 0;
} }
static inline int selinux_socket_getpeer_stream(struct sock *sk) static inline int selinux_socket_getpeer_stream(struct sock *sk)
......
...@@ -38,6 +38,14 @@ ...@@ -38,6 +38,14 @@
unsigned int selinux_checkreqprot = CONFIG_SECURITY_SELINUX_CHECKREQPROT_VALUE; unsigned int selinux_checkreqprot = CONFIG_SECURITY_SELINUX_CHECKREQPROT_VALUE;
#ifdef CONFIG_SECURITY_SELINUX_ENABLE_SECMARK_DEFAULT
#define SELINUX_COMPAT_NET_VALUE 0
#else
#define SELINUX_COMPAT_NET_VALUE 1
#endif
int selinux_compat_net = SELINUX_COMPAT_NET_VALUE;
static int __init checkreqprot_setup(char *str) static int __init checkreqprot_setup(char *str)
{ {
selinux_checkreqprot = simple_strtoul(str,NULL,0) ? 1 : 0; selinux_checkreqprot = simple_strtoul(str,NULL,0) ? 1 : 0;
...@@ -45,6 +53,13 @@ static int __init checkreqprot_setup(char *str) ...@@ -45,6 +53,13 @@ static int __init checkreqprot_setup(char *str)
} }
__setup("checkreqprot=", checkreqprot_setup); __setup("checkreqprot=", checkreqprot_setup);
static int __init selinux_compat_net_setup(char *str)
{
selinux_compat_net = simple_strtoul(str,NULL,0) ? 1 : 0;
return 1;
}
__setup("selinux_compat_net=", selinux_compat_net_setup);
static DEFINE_MUTEX(sel_mutex); static DEFINE_MUTEX(sel_mutex);
...@@ -85,6 +100,7 @@ enum sel_inos { ...@@ -85,6 +100,7 @@ enum sel_inos {
SEL_AVC, /* AVC management directory */ SEL_AVC, /* AVC management directory */
SEL_MEMBER, /* compute polyinstantiation membership decision */ SEL_MEMBER, /* compute polyinstantiation membership decision */
SEL_CHECKREQPROT, /* check requested protection, not kernel-applied one */ SEL_CHECKREQPROT, /* check requested protection, not kernel-applied one */
SEL_COMPAT_NET, /* whether to use old compat network packet controls */
}; };
#define TMPBUFLEN 12 #define TMPBUFLEN 12
...@@ -364,6 +380,55 @@ static struct file_operations sel_checkreqprot_ops = { ...@@ -364,6 +380,55 @@ static struct file_operations sel_checkreqprot_ops = {
.write = sel_write_checkreqprot, .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;
selinux_compat_net = new_value ? 1 : 0;
length = count;
out:
free_page((unsigned long) page);
return length;
}
static 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 * Remaining nodes use transaction based IO methods like nfsd/nfsctl.c
*/ */
...@@ -1219,6 +1284,7 @@ static int sel_fill_super(struct super_block * sb, void * data, int silent) ...@@ -1219,6 +1284,7 @@ static int sel_fill_super(struct super_block * sb, void * data, int silent)
[SEL_DISABLE] = {"disable", &sel_disable_ops, S_IWUSR}, [SEL_DISABLE] = {"disable", &sel_disable_ops, S_IWUSR},
[SEL_MEMBER] = {"member", &transaction_ops, S_IRUGO|S_IWUGO}, [SEL_MEMBER] = {"member", &transaction_ops, S_IRUGO|S_IWUGO},
[SEL_CHECKREQPROT] = {"checkreqprot", &sel_checkreqprot_ops, S_IRUGO|S_IWUSR}, [SEL_CHECKREQPROT] = {"checkreqprot", &sel_checkreqprot_ops, S_IRUGO|S_IWUSR},
[SEL_COMPAT_NET] = {"compat_net", &sel_compat_net_ops, S_IRUGO|S_IWUSR},
/* last one */ {""} /* last one */ {""}
}; };
ret = simple_fill_super(sb, SELINUX_MAGIC, selinux_files); ret = simple_fill_super(sb, SELINUX_MAGIC, selinux_files);
......
...@@ -387,18 +387,12 @@ int selinux_xfrm_postroute_last(u32 isec_sid, struct sk_buff *skb) ...@@ -387,18 +387,12 @@ int selinux_xfrm_postroute_last(u32 isec_sid, struct sk_buff *skb)
struct xfrm_state *x = dst_test->xfrm; struct xfrm_state *x = dst_test->xfrm;
if (x && selinux_authorizable_xfrm(x)) if (x && selinux_authorizable_xfrm(x))
goto accept; goto out;
} }
} }
rc = avc_has_perm(isec_sid, SECINITSID_UNLABELED, SECCLASS_ASSOCIATION, rc = avc_has_perm(isec_sid, SECINITSID_UNLABELED, SECCLASS_ASSOCIATION,
ASSOCIATION__SENDTO, NULL); ASSOCIATION__SENDTO, NULL);
if (rc) out:
goto drop; return rc;
accept:
return NF_ACCEPT;
drop:
return NF_DROP;
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment