/*
* Copyright (C) 2007 Casey Schaufler <casey@schaufler-ca.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 2.
*
* Authors:
* Casey Schaufler <casey@schaufler-ca.com>
* Ahmed S. Darwish <darwish.07@gmail.com>
*
* Special thanks to the authors of selinuxfs.
*
* Karl MacMillan <kmacmillan@tresys.com>
* James Morris <jmorris@redhat.com>
*
*/
#include <linux/kernel.h>
#include <linux/vmalloc.h>
#include <linux/security.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <net/net_namespace.h>
#include <net/netlabel.h>
#include <net/cipso_ipv4.h>
#include <linux/seq_file.h>
#include <linux/ctype.h>
#include <linux/audit.h>
#include "smack.h"
/*
* smackfs pseudo filesystem.
*/
enum smk_inos {
SMK_ROOT_INO = 2,
SMK_LOAD = 3, /* load policy */
SMK_CIPSO = 4, /* load label -> CIPSO mapping */
SMK_DOI = 5, /* CIPSO DOI */
SMK_DIRECT = 6, /* CIPSO level indicating direct label */
SMK_AMBIENT = 7, /* internet ambient label */
SMK_NETLBLADDR = 8, /* single label hosts */
SMK_ONLYCAP = 9, /* the only "capable" label */
SMK_LOGGING = 10, /* logging */
SMK_LOAD_SELF = 11, /* task specific rules */
};
/*
* List locks
*/
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.
* If it isn't somehow marked, use this.
* It can be reset via smackfs/ambient
*/
char *smack_net_ambient = smack_known_floor.smk_known;
/*
* 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
*/
int smack_cipso_direct = SMACK_CIPSO_DIRECT_DEFAULT;
/*
* Unless a process is running with this label even
* having CAP_MAC_OVERRIDE isn't enough to grant
* privilege to violate MAC policy. If no label is
* designated (the NULL case) capabilities apply to
* everyone. It is expected that the hat (^) label
* will be used if any label is used.
*/
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.
*/
LIST_HEAD(smk_netlbladdr_list);
LIST_HEAD(smack_rule_list);
static int smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT;
const char *smack_cipso_option = SMACK_CIPSO_OPTION;
#define SEQ_READ_FINISHED 1
/*
* Values for parsing cipso rules
* SMK_DIGITLEN: Length of a digit field in a rule.
* SMK_CIPSOMIN: Minimum possible cipso rule length.
* SMK_CIPSOMAX: Maximum possible cipso rule length.
*/
#define SMK_DIGITLEN 4
#define SMK_CIPSOMIN (SMK_LABELLEN + 2 * SMK_DIGITLEN)
#define SMK_CIPSOMAX (SMK_CIPSOMIN + SMACK_CIPSO_MAXCATNUM * SMK_DIGITLEN)
/*
* Values for parsing MAC rules
* SMK_ACCESS: Maximum possible combination of access permissions
* SMK_ACCESSLEN: Maximum length for a rule access field
* SMK_LOADLEN: Smack rule length
*/
#define SMK_OACCESS "rwxa"
#define SMK_ACCESS "rwxat"
#define SMK_OACCESSLEN (sizeof(SMK_OACCESS) - 1)
#define SMK_ACCESSLEN (sizeof(SMK_ACCESS) - 1)
#define SMK_OLOADLEN (SMK_LABELLEN + SMK_LABELLEN + SMK_OACCESSLEN)
#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(smk_of_current());
}
/*
* 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
/**
* smk_set_access - add a rule to the rule list
* @srp: the new rule to add
* @rule_list: the list of rules
* @rule_lock: the rule list lock
*
* Looks through the current subject/object/access list for
* the subject/object pair and replaces the access that was
* there. If the pair isn't found add it with the specified
* access.
*
* Returns 1 if a rule was found to exist already, 0 if it is new
* Returns 0 if nothing goes wrong or -ENOMEM if it fails
* during the allocation of the new pair to add.
*/
static int smk_set_access(struct smack_rule *srp, struct list_head *rule_list,
struct mutex *rule_lock)
{
struct smack_rule *sp;
int found = 0;
mutex_lock(rule_lock);
list_for_each_entry_rcu(sp, rule_list, list) {
if (sp->smk_subject == srp->smk_subject &&
sp->smk_object == srp->smk_object) {
found = 1;
sp->smk_access = srp->smk_access;
break;
}
}
if (found == 0)
list_add_rcu(&srp->list, rule_list);
mutex_unlock(rule_lock);
return found;
}
/**
* smk_write_load_list - write() for any /smack/load
* @file: file pointer, not actually used
* @buf: where to get the data from
* @count: bytes sent
* @ppos: where to start - must be 0
* @rule_list: the list of rules to write to
* @rule_lock: lock for the rule list
*
* Get one smack access rule from above.
* The format is exactly:
* char subject[SMK_LABELLEN]
* char object[SMK_LABELLEN]
* char access[SMK_ACCESSLEN]
*
* writes must be SMK_LABELLEN+SMK_LABELLEN+SMK_ACCESSLEN bytes.
*/
static ssize_t smk_write_load_list(struct file *file, const char __user *buf,
size_t count, loff_t *ppos,
struct list_head *rule_list,
struct mutex *rule_lock)
{
struct smack_rule *rule;
char *data;
int rc = -EINVAL;
/*
* No partial writes.
* Enough data must be present.
*/
if (*ppos != 0)
return -EINVAL;
/*
* Minor hack for backward compatibility
*/
if (count < (SMK_OLOADLEN) || count > SMK_LOADLEN)
return -EINVAL;
data = kzalloc(SMK_LOADLEN, GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
if (copy_from_user(data, buf, count) != 0) {
rc = -EFAULT;
goto out;
}
/*
* More on the minor hack for backward compatibility
*/
if (count == (SMK_OLOADLEN))
data[SMK_OLOADLEN] = '-';
rule = kzalloc(sizeof(*rule), GFP_KERNEL);
if (rule == NULL) {
rc = -ENOMEM;
goto out;
}
rule->smk_subject = smk_import(data, 0);
if (rule->smk_subject == NULL)
goto out_free_rule;
rule->smk_object = smk_import(data + SMK_LABELLEN, 0);
if (rule->smk_object == NULL)
goto out_free_rule;
rule->smk_access = 0;
switch (data[SMK_LABELLEN + SMK_LABELLEN]) {
case '-':
break;
case 'r':
case 'R':
rule->smk_access |= MAY_READ;
break;
default:
goto out_free_rule;
}
switch (data[SMK_LABELLEN + SMK_LABELLEN + 1]) {
case '-':
break;
case 'w':
case 'W':
rule->smk_access |= MAY_WRITE;
break;
default:
goto out_free_rule;
}
switch (data[SMK_LABELLEN + SMK_LABELLEN + 2]) {
case '-':
break;
case 'x':
case 'X':
rule->smk_access |= MAY_EXEC;
break;
default:
goto out_free_rule;
}
switch (data[SMK_LABELLEN + SMK_LABELLEN + 3]) {
case '-':
break;
case 'a':
case 'A':
rule->smk_access |= MAY_APPEND;
break;
default:
goto out_free_rule;
}
switch (data[SMK_LABELLEN + SMK_LABELLEN + 4]) {
case '-':
break;
case 't':
case 'T':
rule->smk_access |= MAY_TRANSMUTE;
break;
default:
goto out_free_rule;
}
rc = count;
/*
* smk_set_access returns true if there was already a rule
* for the subject/object pair, and false if it was new.
*/
if (!smk_set_access(rule, rule_list, rule_lock))
goto out;
out_free_rule
|