Commit 5e752b7e authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] selinux: add IPv6 support

From: James Morris <jmorris@redhat.com>

The patch below adds explicit IPv6 support to SELinux.

Brief description of changes:

o IPv6 networking is now subject to the same controls as IPv4 (in
  addition to the generic socket permissions which cover all protocols),
  namely: bind to local node address; bind to local port; send & receive
  TCP/UDP and raw IP packets based on local network interface and remote
  node address.

o Packet parsing has been extended to IPv6 packets for logging and
  control, and simplified for IPv4.

o Support for logging of IPv6 addresses has also been added.

o The kernel policy database code has been modified to support IPv6, and
  reworked to provide generic security policy version handling so that
  older policy versions will still work, making upgrading simpler.

Corresponding userspace patches are available at
<http://people.redhat.com/jmorris/selinux/ipv6/>, although current
userspace tools will continue to function normally (but without explicit
IPv6 support).

For more details at the security management level, see
<http://marc.theaimsgroup.com/?l=selinux&m=108068187630948&w=2>

This code has been under testing and review for several weeks.
parent bcf506bd
......@@ -22,6 +22,8 @@
#include <linux/un.h>
#include <net/af_unix.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <net/ipv6.h>
#include "avc.h"
#include "avc_ss.h"
#include "class_to_string.h"
......@@ -418,6 +420,16 @@ int avc_insert(u32 ssid, u32 tsid, u16 tclass,
return rc;
}
static inline void avc_print_ipv6_addr(struct in6_addr *addr, u16 port,
char *name1, char *name2)
{
if (!ipv6_addr_any(addr))
printk(" %s=%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x",
name1, NIP6(*addr));
if (port)
printk(" %s=%d", name2, ntohs(port));
}
static inline void avc_print_ipv4_addr(u32 addr, u16 port, char *name1, char *name2)
{
if (addr)
......@@ -602,11 +614,11 @@ void avc_audit(u32 ssid, u32 tsid,
if (a->u.net.sk) {
struct sock *sk = a->u.net.sk;
struct unix_sock *u;
struct inet_opt *inet;
switch (sk->sk_family) {
case AF_INET:
inet = inet_sk(sk);
case AF_INET: {
struct inet_opt *inet = inet_sk(sk);
avc_print_ipv4_addr(inet->rcv_saddr,
inet->sport,
"laddr", "lport");
......@@ -614,6 +626,19 @@ void avc_audit(u32 ssid, u32 tsid,
inet->dport,
"faddr", "fport");
break;
}
case AF_INET6: {
struct inet_opt *inet = inet_sk(sk);
struct ipv6_pinfo *inet6 = inet6_sk(sk);
avc_print_ipv6_addr(&inet6->rcv_saddr,
inet->sport,
"laddr", "lport");
avc_print_ipv6_addr(&inet6->daddr,
inet->dport,
"faddr", "fport");
break;
}
case AF_UNIX:
u = unix_sk(sk);
if (u->dentry) {
......@@ -639,11 +664,24 @@ void avc_audit(u32 ssid, u32 tsid,
}
}
avc_print_ipv4_addr(a->u.net.saddr, a->u.net.sport,
"saddr", "src");
avc_print_ipv4_addr(a->u.net.daddr, a->u.net.dport,
"daddr", "dest");
switch (a->u.net.family) {
case AF_INET:
avc_print_ipv4_addr(a->u.net.v4info.saddr,
a->u.net.sport,
"saddr", "src");
avc_print_ipv4_addr(a->u.net.v4info.daddr,
a->u.net.dport,
"daddr", "dest");
break;
case AF_INET6:
avc_print_ipv6_addr(&a->u.net.v6info.saddr,
a->u.net.sport,
"saddr", "src");
avc_print_ipv6_addr(&a->u.net.v6info.daddr,
a->u.net.dport,
"daddr", "dest");
break;
}
if (a->u.net.netif)
printk(" netif=%s", a->u.net.netif);
break;
......
This diff is collapsed.
......@@ -12,6 +12,7 @@
#include <linux/kdev_t.h>
#include <linux/spinlock.h>
#include <linux/init.h>
#include <linux/in6.h>
#include <asm/system.h>
#include "flask.h"
#include "av_permissions.h"
......@@ -65,16 +66,28 @@ struct avc_audit_data {
struct {
char *netif;
struct sock *sk;
u16 family;
u16 dport;
u16 sport;
u32 daddr;
u32 saddr;
union {
struct {
u32 daddr;
u32 saddr;
} v4;
struct {
struct in6_addr daddr;
struct in6_addr saddr;
} v6;
} fam;
} net;
int cap;
int ipc_id;
} u;
};
#define v4info fam.v4
#define v6info fam.v6
/* Initialize an AVC audit data structure. */
#define AVC_AUDIT_DATA_INIT(_d,_t) \
{ memset((_d), 0, sizeof(struct avc_audit_data)); (_d)->type = AVC_AUDIT_DATA_##_t; }
......
......@@ -15,8 +15,15 @@
#define SECCLASS_NULL 0x0000 /* no class */
#define SELINUX_MAGIC 0xf97cff8c
#define POLICYDB_VERSION 16
#define POLICYDB_VERSION_COMPAT 15
/* Identify specific policy version changes */
#define POLICYDB_VERSION_BASE 15
#define POLICYDB_VERSION_BOOL 16
#define POLICYDB_VERSION_IPV6 17
/* Range of policy versions we understand*/
#define POLICYDB_VERSION_MIN POLICYDB_VERSION_BASE
#define POLICYDB_VERSION_MAX POLICYDB_VERSION_IPV6
#ifdef CONFIG_SECURITY_SELINUX_BOOTPARAM
extern int selinux_enabled;
......
......@@ -164,7 +164,7 @@ static ssize_t sel_read_policyvers(struct file *filp, char *buf,
return -ENOMEM;
memset(page, 0, PAGE_SIZE);
length = scnprintf(page, PAGE_SIZE, "%u", POLICYDB_VERSION);
length = scnprintf(page, PAGE_SIZE, "%u", POLICYDB_VERSION_MAX);
if (length < 0) {
free_page((unsigned long)page);
return length;
......
......@@ -48,6 +48,45 @@ static unsigned int symtab_sizes[SYM_NUM] = {
16
};
struct policydb_compat_info {
int version;
int sym_num;
int ocon_num;
};
/* These need to be updated if SYM_NUM or OCON_NUM changes */
static struct policydb_compat_info policydb_compat[] = {
{
.version = POLICYDB_VERSION_BASE,
.sym_num = SYM_NUM - 1,
.ocon_num = OCON_NUM - 1,
},
{
.version = POLICYDB_VERSION_BOOL,
.sym_num = SYM_NUM,
.ocon_num = OCON_NUM - 1,
},
{
.version = POLICYDB_VERSION_IPV6,
.sym_num = SYM_NUM,
.ocon_num = OCON_NUM,
},
};
static struct policydb_compat_info *policydb_lookup_compat(int version)
{
int i;
struct policydb_compat_info *info = NULL;
for (i = 0; i < sizeof(policydb_compat)/sizeof(*info); i++) {
if (policydb_compat[i].version == version) {
info = &policydb_compat[i];
break;
}
}
return info;
}
/*
* Initialize the role table.
*/
......@@ -1086,9 +1125,10 @@ int policydb_read(struct policydb *p, void *fp)
struct role_trans *tr, *ltr;
struct ocontext *l, *c, *newc;
struct genfs *genfs_p, *genfs, *newgenfs;
int i, j, rc, policy_ver, num_syms;
int i, j, rc, r_policyvers;
u32 *buf, len, len2, config, nprim, nel, nel2;
char *policydb_str;
struct policydb_compat_info *info;
config = 0;
mls_set_config(config);
......@@ -1151,12 +1191,15 @@ int policydb_read(struct policydb *p, void *fp)
for (i = 0; i < 4; i++)
buf[i] = le32_to_cpu(buf[i]);
policy_ver = buf[0];
if (policy_ver != POLICYDB_VERSION && policy_ver != POLICYDB_VERSION_COMPAT) {
printk(KERN_ERR "security: policydb version %d does not match "
"my version %d\n", buf[0], POLICYDB_VERSION);
goto bad;
r_policyvers = buf[0];
if (r_policyvers < POLICYDB_VERSION_MIN ||
r_policyvers > POLICYDB_VERSION_MAX) {
printk(KERN_ERR "security: policydb version %d does not match "
"my version range %d-%d\n",
buf[0], POLICYDB_VERSION_MIN, POLICYDB_VERSION_MAX);
goto bad;
}
if (buf[1] != config) {
printk(KERN_ERR "security: policydb configuration (%s) does "
"not match my configuration (%s)\n",
......@@ -1165,29 +1208,26 @@ int policydb_read(struct policydb *p, void *fp)
goto bad;
}
if (policy_ver == POLICYDB_VERSION_COMPAT) {
if (buf[2] != (SYM_NUM - 1) || buf[3] != OCON_NUM) {
printk(KERN_ERR "security: policydb table sizes (%d,%d) do "
"not match mine (%d,%d)\n",
buf[2], buf[3], SYM_NUM, OCON_NUM);
goto bad;
}
num_syms = SYM_NUM - 1;
} else {
if (buf[2] != SYM_NUM || buf[3] != OCON_NUM) {
printk(KERN_ERR "security: policydb table sizes (%d,%d) do "
"not match mine (%d,%d)\n",
buf[2], buf[3], SYM_NUM, OCON_NUM);
goto bad;
}
num_syms = SYM_NUM;
info = policydb_lookup_compat(r_policyvers);
if (!info) {
printk(KERN_ERR "security: unable to find policy compat info "
"for version %d\n", r_policyvers);
goto bad;
}
if (buf[2] != info->sym_num || buf[3] != info->ocon_num) {
printk(KERN_ERR "security: policydb table sizes (%d,%d) do "
"not match mine (%d,%d)\n", buf[2], buf[3],
info->sym_num, info->ocon_num);
goto bad;
}
rc = mls_read_nlevels(p, fp);
if (rc)
goto bad;
for (i = 0; i < num_syms; i++) {
for (i = 0; i < info->sym_num; i++) {
buf = next_entry(fp, sizeof(u32)*2);
if (!buf) {
rc = -EINVAL;
......@@ -1208,7 +1248,7 @@ int policydb_read(struct policydb *p, void *fp)
if (rc)
goto bad;
if (policy_ver == POLICYDB_VERSION) {
if (r_policyvers >= POLICYDB_VERSION_BOOL) {
rc = cond_read_list(p, fp);
if (rc)
goto bad;
......@@ -1281,7 +1321,7 @@ int policydb_read(struct policydb *p, void *fp)
if (rc)
goto bad;
for (i = 0; i < OCON_NUM; i++) {
for (i = 0; i < info->ocon_num; i++) {
buf = next_entry(fp, sizeof(u32));
if (!buf) {
rc = -EINVAL;
......@@ -1379,6 +1419,20 @@ int policydb_read(struct policydb *p, void *fp)
if (rc)
goto bad;
break;
case OCON_NODE6: {
int k;
buf = next_entry(fp, sizeof(u32) * 8);
if (!buf)
goto bad;
for (k = 0; k < 4; k++)
c->u.node6.addr[k] = le32_to_cpu(buf[k]);
for (k = 0; k < 4; k++)
c->u.node6.mask[k] = le32_to_cpu(buf[k+4]);
if (context_read_and_validate(&c->context[0], p, fp))
goto bad;
break;
}
}
}
}
......
......@@ -138,6 +138,10 @@ struct ocontext {
u32 addr;
u32 mask;
} node; /* node information */
struct {
u32 addr[4];
u32 mask[4];
} node6; /* IPv6 node information */
} u;
union {
u32 sclass; /* security class for genfs */
......@@ -177,7 +181,8 @@ struct genfs {
#define OCON_NETIF 3 /* network interfaces */
#define OCON_NODE 4 /* nodes */
#define OCON_FSUSE 5 /* fs_use */
#define OCON_NUM 6
#define OCON_NODE6 6 /* IPv6 nodes */
#define OCON_NUM 7
/* The policy database */
struct policydb {
......
......@@ -1187,6 +1187,18 @@ int security_netif_sid(char *name,
return rc;
}
static int match_ipv6_addrmask(u32 *input, u32 *addr, u32 *mask)
{
int i, fail = 0;
for(i = 0; i < 4; i++)
if(addr[i] != (input[i] & mask[i])) {
fail = 1;
break;
}
return !fail;
}
/**
* security_node_sid - Obtain the SID for a node (host).
......@@ -1201,22 +1213,47 @@ int security_node_sid(u16 domain,
u32 *out_sid)
{
int rc = 0;
u32 addr;
struct ocontext *c;
POLICY_RDLOCK;
if (domain != AF_INET || addrlen != sizeof(u32)) {
*out_sid = SECINITSID_NODE;
goto out;
switch (domain) {
case AF_INET: {
u32 addr;
if (addrlen != sizeof(u32)) {
rc = -EINVAL;
goto out;
}
addr = *((u32 *)addrp);
c = policydb.ocontexts[OCON_NODE];
while (c) {
if (c->u.node.addr == (addr & c->u.node.mask))
break;
c = c->next;
}
break;
}
addr = *((u32 *)addrp);
c = policydb.ocontexts[OCON_NODE];
while (c) {
if (c->u.node.addr == (addr & c->u.node.mask))
break;
c = c->next;
case AF_INET6:
if (addrlen != sizeof(u64) * 2) {
rc = -EINVAL;
goto out;
}
c = policydb.ocontexts[OCON_NODE6];
while (c) {
if (match_ipv6_addrmask(addrp, c->u.node6.addr,
c->u.node6.mask))
break;
c = c->next;
}
break;
default:
*out_sid = SECINITSID_NODE;
goto out;
}
if (c) {
......
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