smack: Add support for unlabeled network hosts and networks

Add support for unlabeled network hosts and networks.
Relies heavily on Paul Moore's netlabel support.

Creates a new entry in /smack called netlabel. Writes to /smack/netlabel
take the form:

    A.B.C.D LABEL
or
    A.B.C.D/N LABEL

where A.B.C.D is a network address, N is an integer between 0-32,
and LABEL is the Smack label to be used. If /N is omitted /32 is
assumed. N designates the netmask for the address. Entries are
matched by the most specific address/mask pair. 0.0.0.0/0 will
match everything, while 192.168.1.117/32 will match exactly one
host.

A new system label "@", pronounced "web", is defined. Processes
can not be assigned the web label. An address assigned the web
label can be written to by any process, and packets coming from
a web address can be written to any socket. Use of the web label
is a violation of any strict MAC policy, but the web label has
been requested many times.

The nltype entry has been removed from /smack. It did not work right
and the netlabel interface can be used to specify that all hosts
be treated as unlabeled.

CIPSO labels on incoming packets will be honored, even from designated
single label hosts. Single label hosts can only be written to by
processes with labels that can write to the label of the host.
Packets sent to single label hosts will always be unlabeled.

Once added a single label designation cannot be removed, however
the label may be changed.

The behavior of the ambient label remains unchanged.


Signed-off-by: Casey Schaufler <casey@schaufler-ca.com>
Signed-off-by: Paul Moore <paul.moore@hp.com>
diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c
index 594e934..bf107a3 100644
--- a/security/smack/smackfs.c
+++ b/security/smack/smackfs.c
@@ -20,6 +20,7 @@
 #include <linux/vmalloc.h>
 #include <linux/security.h>
 #include <linux/mutex.h>
+#include <net/net_namespace.h>
 #include <net/netlabel.h>
 #include <net/cipso_ipv4.h>
 #include <linux/seq_file.h>
@@ -38,7 +39,7 @@
 	SMK_DOI		= 5,	/* CIPSO DOI */
 	SMK_DIRECT	= 6,	/* CIPSO level indicating direct label */
 	SMK_AMBIENT	= 7,	/* internet ambient label */
-	SMK_NLTYPE	= 8,	/* label scheme to use by default */
+	SMK_NETLBLADDR	= 8,	/* single label hosts */
 	SMK_ONLYCAP	= 9,	/* the only "capable" label */
 };
 
@@ -48,6 +49,7 @@
 static DEFINE_MUTEX(smack_list_lock);
 static DEFINE_MUTEX(smack_cipso_lock);
 static DEFINE_MUTEX(smack_ambient_lock);
+static DEFINE_MUTEX(smk_netlbladdr_lock);
 
 /*
  * This is the "ambient" label for network traffic.
@@ -57,12 +59,6 @@
 char *smack_net_ambient = smack_known_floor.smk_known;
 
 /*
- * This is the default packet marking scheme for network traffic.
- * It can be reset via smackfs/nltype
- */
-int smack_net_nltype = NETLBL_NLTYPE_CIPSOV4;
-
-/*
  * This is the level in a CIPSO header that indicates a
  * smack label is contained directly in the category set.
  * It can be reset via smackfs/direct
@@ -79,6 +75,13 @@
  */
 char *smack_onlycap;
 
+/*
+ * Certain IP addresses may be designated as single label hosts.
+ * Packets are sent there unlabeled, but only from tasks that
+ * can write to the specified label.
+ */
+struct smk_netlbladdr *smack_netlbladdrs;
+
 static int smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT;
 struct smk_list_entry *smack_list;
 
@@ -104,6 +107,24 @@
 #define SMK_ACCESSLEN (sizeof(SMK_ACCESS) - 1)
 #define SMK_LOADLEN   (SMK_LABELLEN + SMK_LABELLEN + SMK_ACCESSLEN)
 
+/**
+ * smk_netlabel_audit_set - fill a netlbl_audit struct
+ * @nap: structure to fill
+ */
+static void smk_netlabel_audit_set(struct netlbl_audit *nap)
+{
+	nap->loginuid = audit_get_loginuid(current);
+	nap->sessionid = audit_get_sessionid(current);
+	nap->secid = smack_to_secid(current_security());
+}
+
+/*
+ * Values for parsing single label host rules
+ * "1.2.3.4 X"
+ * "192.168.138.129/32 abcdefghijklmnopqrstuvw"
+ */
+#define SMK_NETLBLADDRMIN	9
+#define SMK_NETLBLADDRMAX	42
 
 /*
  * Seq_file read operations for /smack/load
@@ -344,13 +365,11 @@
 {
 	int rc;
 	struct cipso_v4_doi *doip;
-	struct netlbl_audit audit_info;
+	struct netlbl_audit nai;
 
-	audit_info.loginuid = audit_get_loginuid(current);
-	audit_info.sessionid = audit_get_sessionid(current);
-	audit_info.secid = smack_to_secid(current_security());
+	smk_netlabel_audit_set(&nai);
 
-	rc = netlbl_cfg_map_del(NULL, PF_UNSPEC, NULL, NULL, &audit_info);
+	rc = netlbl_cfg_map_del(NULL, PF_INET, NULL, NULL, &nai);
 	if (rc != 0)
 		printk(KERN_WARNING "%s:%d remove rc = %d\n",
 		       __func__, __LINE__, rc);
@@ -365,15 +384,14 @@
 	for (rc = 1; rc < CIPSO_V4_TAG_MAXCNT; rc++)
 		doip->tags[rc] = CIPSO_V4_TAG_INVALID;
 
-	rc = netlbl_cfg_cipsov4_add(doip, &audit_info);
+	rc = netlbl_cfg_cipsov4_add(doip, &nai);
 	if (rc != 0) {
 		printk(KERN_WARNING "%s:%d cipso add rc = %d\n",
 		       __func__, __LINE__, rc);
 		kfree(doip);
 		return;
 	}
-	rc = netlbl_cfg_cipsov4_map_add(doip->doi,
-					NULL, NULL, NULL, &audit_info);
+	rc = netlbl_cfg_cipsov4_map_add(doip->doi, NULL, NULL, NULL, &nai);
 	if (rc != 0) {
 		printk(KERN_WARNING "%s:%d map add rc = %d\n",
 		       __func__, __LINE__, rc);
@@ -388,22 +406,19 @@
 static void smk_unlbl_ambient(char *oldambient)
 {
 	int rc;
-	struct netlbl_audit audit_info;
+	struct netlbl_audit nai;
 
-	audit_info.loginuid = audit_get_loginuid(current);
-	audit_info.sessionid = audit_get_sessionid(current);
-	audit_info.secid = smack_to_secid(current_security());
+	smk_netlabel_audit_set(&nai);
 
 	if (oldambient != NULL) {
-		rc = netlbl_cfg_map_del(oldambient,
-					PF_UNSPEC, NULL, NULL, &audit_info);
+		rc = netlbl_cfg_map_del(oldambient, PF_INET, NULL, NULL, &nai);
 		if (rc != 0)
 			printk(KERN_WARNING "%s:%d remove rc = %d\n",
 			       __func__, __LINE__, rc);
 	}
 
-	rc = netlbl_cfg_unlbl_map_add(smack_net_ambient,
-				      PF_INET, NULL, NULL, &audit_info);
+	rc = netlbl_cfg_unlbl_map_add(smack_net_ambient, PF_INET,
+				      NULL, NULL, &nai);
 	if (rc != 0)
 		printk(KERN_WARNING "%s:%d add rc = %d\n",
 		       __func__, __LINE__, rc);
@@ -614,6 +629,201 @@
 	.release        = seq_release,
 };
 
+/*
+ * Seq_file read operations for /smack/netlabel
+ */
+
+static void *netlbladdr_seq_start(struct seq_file *s, loff_t *pos)
+{
+	if (*pos == SEQ_READ_FINISHED)
+		return NULL;
+
+	return smack_netlbladdrs;
+}
+
+static void *netlbladdr_seq_next(struct seq_file *s, void *v, loff_t *pos)
+{
+	struct smk_netlbladdr *skp = ((struct smk_netlbladdr *) v)->smk_next;
+
+	if (skp == NULL)
+		*pos = SEQ_READ_FINISHED;
+
+	return skp;
+}
+/*
+#define BEMASK	0x80000000
+*/
+#define BEMASK	0x00000001
+#define BEBITS	(sizeof(__be32) * 8)
+
+/*
+ * Print host/label pairs
+ */
+static int netlbladdr_seq_show(struct seq_file *s, void *v)
+{
+	struct smk_netlbladdr *skp = (struct smk_netlbladdr *) v;
+	unsigned char *hp = (char *) &skp->smk_host.sin_addr.s_addr;
+	__be32 bebits;
+	int maskn = 0;
+
+	for (bebits = BEMASK; bebits != 0; maskn++, bebits <<= 1)
+		if ((skp->smk_mask.s_addr & bebits) == 0)
+			break;
+
+	seq_printf(s, "%u.%u.%u.%u/%d %s\n",
+		hp[0], hp[1], hp[2], hp[3], maskn, skp->smk_label);
+
+	return 0;
+}
+
+static void netlbladdr_seq_stop(struct seq_file *s, void *v)
+{
+	/* No-op */
+}
+
+static struct seq_operations netlbladdr_seq_ops = {
+	.start = netlbladdr_seq_start,
+	.stop  = netlbladdr_seq_stop,
+	.next  = netlbladdr_seq_next,
+	.show  = netlbladdr_seq_show,
+};
+
+/**
+ * smk_open_netlbladdr - open() for /smack/netlabel
+ * @inode: inode structure representing file
+ * @file: "netlabel" file pointer
+ *
+ * Connect our netlbladdr_seq_* operations with /smack/netlabel
+ * file_operations
+ */
+static int smk_open_netlbladdr(struct inode *inode, struct file *file)
+{
+	return seq_open(file, &netlbladdr_seq_ops);
+}
+
+/**
+ * smk_write_netlbladdr - write() for /smack/netlabel
+ * @filp: file pointer, not actually used
+ * @buf: where to get the data from
+ * @count: bytes sent
+ * @ppos: where to start
+ *
+ * Accepts only one netlbladdr per write call.
+ * Returns number of bytes written or error code, as appropriate
+ */
+static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf,
+				size_t count, loff_t *ppos)
+{
+	struct smk_netlbladdr *skp;
+	struct sockaddr_in newname;
+	char smack[SMK_LABELLEN];
+	char *sp;
+	char data[SMK_NETLBLADDRMAX];
+	char *host = (char *)&newname.sin_addr.s_addr;
+	int rc;
+	struct netlbl_audit audit_info;
+	struct in_addr mask;
+	unsigned int m;
+	__be32 bebits = BEMASK;
+	__be32 nsa;
+
+	/*
+	 * Must have privilege.
+	 * No partial writes.
+	 * Enough data must be present.
+	 * "<addr/mask, as a.b.c.d/e><space><label>"
+	 * "<addr, as a.b.c.d><space><label>"
+	 */
+	if (!capable(CAP_MAC_ADMIN))
+		return -EPERM;
+	if (*ppos != 0)
+		return -EINVAL;
+	if (count < SMK_NETLBLADDRMIN || count > SMK_NETLBLADDRMAX)
+		return -EINVAL;
+	if (copy_from_user(data, buf, count) != 0)
+		return -EFAULT;
+
+	data[count] = '\0';
+
+	rc = sscanf(data, "%hhd.%hhd.%hhd.%hhd/%d %s",
+		&host[0], &host[1], &host[2], &host[3], &m, smack);
+	if (rc != 6) {
+		rc = sscanf(data, "%hhd.%hhd.%hhd.%hhd %s",
+			&host[0], &host[1], &host[2], &host[3], smack);
+		if (rc != 5)
+			return -EINVAL;
+		m = BEBITS;
+	}
+	if (m > BEBITS)
+		return -EINVAL;
+
+	sp = smk_import(smack, 0);
+	if (sp == NULL)
+		return -EINVAL;
+
+	for (mask.s_addr = 0; m > 0; m--) {
+		mask.s_addr |= bebits;
+		bebits <<= 1;
+	}
+	/*
+	 * Only allow one writer at a time. Writes should be
+	 * quite rare and small in any case.
+	 */
+	mutex_lock(&smk_netlbladdr_lock);
+
+	nsa = newname.sin_addr.s_addr;
+	for (skp = smack_netlbladdrs; skp != NULL; skp = skp->smk_next)
+		if (skp->smk_host.sin_addr.s_addr == nsa &&
+		    skp->smk_mask.s_addr == mask.s_addr)
+			break;
+
+	smk_netlabel_audit_set(&audit_info);
+
+	if (skp == NULL) {
+		skp = kzalloc(sizeof(*skp), GFP_KERNEL);
+		if (skp == NULL)
+			rc = -ENOMEM;
+		else {
+			rc = 0;
+			skp->smk_host.sin_addr.s_addr = newname.sin_addr.s_addr;
+			skp->smk_mask.s_addr = mask.s_addr;
+			skp->smk_next = smack_netlbladdrs;
+			skp->smk_label = sp;
+			smack_netlbladdrs = skp;
+		}
+	} else {
+		rc = netlbl_cfg_unlbl_static_del(&init_net, NULL,
+			&skp->smk_host.sin_addr, &skp->smk_mask,
+			PF_INET, &audit_info);
+		skp->smk_label = sp;
+	}
+
+	/*
+	 * Now tell netlabel about the single label nature of
+	 * this host so that incoming packets get labeled.
+	 */
+
+	if (rc == 0)
+		rc = netlbl_cfg_unlbl_static_add(&init_net, NULL,
+			&skp->smk_host.sin_addr, &skp->smk_mask, PF_INET,
+			smack_to_secid(skp->smk_label), &audit_info);
+
+	if (rc == 0)
+		rc = count;
+
+	mutex_unlock(&smk_netlbladdr_lock);
+
+	return rc;
+}
+
+static const struct file_operations smk_netlbladdr_ops = {
+	.open           = smk_open_netlbladdr,
+	.read		= seq_read,
+	.llseek         = seq_lseek,
+	.write		= smk_write_netlbladdr,
+	.release        = seq_release,
+};
+
 /**
  * smk_read_doi - read() for /smack/doi
  * @filp: file pointer, not actually used
@@ -902,110 +1112,6 @@
 	.write		= smk_write_onlycap,
 };
 
-struct option_names {
-	int	o_number;
-	char	*o_name;
-	char	*o_alias;
-};
-
-static struct option_names netlbl_choices[] = {
-	{ NETLBL_NLTYPE_RIPSO,
-		NETLBL_NLTYPE_RIPSO_NAME,	"ripso" },
-	{ NETLBL_NLTYPE_CIPSOV4,
-		NETLBL_NLTYPE_CIPSOV4_NAME,	"cipsov4" },
-	{ NETLBL_NLTYPE_CIPSOV4,
-		NETLBL_NLTYPE_CIPSOV4_NAME,	"cipso" },
-	{ NETLBL_NLTYPE_CIPSOV6,
-		NETLBL_NLTYPE_CIPSOV6_NAME,	"cipsov6" },
-	{ NETLBL_NLTYPE_UNLABELED,
-		NETLBL_NLTYPE_UNLABELED_NAME,	"unlabeled" },
-};
-
-/**
- * smk_read_nltype - read() for /smack/nltype
- * @filp: file pointer, not actually used
- * @buf: where to put the result
- * @count: maximum to send along
- * @ppos: where to start
- *
- * Returns number of bytes read or error code, as appropriate
- */
-static ssize_t smk_read_nltype(struct file *filp, char __user *buf,
-			       size_t count, loff_t *ppos)
-{
-	char bound[40];
-	ssize_t rc;
-	int i;
-
-	if (count < SMK_LABELLEN)
-		return -EINVAL;
-
-	if (*ppos != 0)
-		return 0;
-
-	sprintf(bound, "unknown");
-
-	for (i = 0; i < ARRAY_SIZE(netlbl_choices); i++)
-		if (smack_net_nltype == netlbl_choices[i].o_number) {
-			sprintf(bound, "%s", netlbl_choices[i].o_name);
-			break;
-		}
-
-	rc = simple_read_from_buffer(buf, count, ppos, bound, strlen(bound));
-
-	return rc;
-}
-
-/**
- * smk_write_nltype - write() for /smack/nltype
- * @filp: file pointer, not actually used
- * @buf: where to get the data from
- * @count: bytes sent
- * @ppos: where to start
- *
- * Returns number of bytes written or error code, as appropriate
- */
-static ssize_t smk_write_nltype(struct file *file, const char __user *buf,
-				size_t count, loff_t *ppos)
-{
-	char bound[40];
-	char *cp;
-	int i;
-
-	if (!capable(CAP_MAC_ADMIN))
-		return -EPERM;
-
-	if (count >= 40)
-		return -EINVAL;
-
-	if (copy_from_user(bound, buf, count) != 0)
-		return -EFAULT;
-
-	bound[count] = '\0';
-	cp = strchr(bound, ' ');
-	if (cp != NULL)
-		*cp = '\0';
-	cp = strchr(bound, '\n');
-	if (cp != NULL)
-		*cp = '\0';
-
-	for (i = 0; i < ARRAY_SIZE(netlbl_choices); i++)
-		if (strcmp(bound, netlbl_choices[i].o_name) == 0 ||
-		    strcmp(bound, netlbl_choices[i].o_alias) == 0) {
-			smack_net_nltype = netlbl_choices[i].o_number;
-			return count;
-		}
-	/*
-	 * Not a valid choice.
-	 */
-	return -EINVAL;
-}
-
-static const struct file_operations smk_nltype_ops = {
-	.read		= smk_read_nltype,
-	.write		= smk_write_nltype,
-};
-
 /**
  * smk_fill_super - fill the /smackfs superblock
  * @sb: the empty superblock
@@ -1032,8 +1138,8 @@
 			{"direct", &smk_direct_ops, S_IRUGO|S_IWUSR},
 		[SMK_AMBIENT]	=
 			{"ambient", &smk_ambient_ops, S_IRUGO|S_IWUSR},
-		[SMK_NLTYPE]	=
-			{"nltype", &smk_nltype_ops, S_IRUGO|S_IWUSR},
+		[SMK_NETLBLADDR] =
+			{"netlabel", &smk_netlbladdr_ops, S_IRUGO|S_IWUSR},
 		[SMK_ONLYCAP]	=
 			{"onlycap", &smk_onlycap_ops, S_IRUGO|S_IWUSR},
 		/* last one */ {""}