[PATCH v3 1/1] Introducing domain transition mechanism into Smack.

From: Zbigniew Jasinski
Date: Tue Sep 29 2015 - 10:42:40 EST


This feature consists of two, new kernel interfaces:

- <smack_fs>/relabel-possible - for setting transition privilege

This flag can be set only by process to itself and only with CAP_MAC_ADMIN
capability. With this flag on, process can change it's label without
CAP_MAC_ADMIN but only once. After label changing flag is unset.

- <smack_fs>/relabel-list - for transition-labels list

This list is used to control smack label transition mechanism.
Process can transit to new label only if label is on the list.
Only process with CAP_MAC_ADMIN capability can add label to this list.

Changes in v2:
* use list_for_each_entry instead of _rcu during label write
* added missing description in security/Smack.txt

Changes in v3:
* squashed into one commit

Signed-off-by: Zbigniew Jasinski <z.jasinski@xxxxxxxxxxx>
Signed-off-by: Rafal Krypa <r.krypa@xxxxxxxxxxx>
---
Documentation/security/Smack.txt | 13 +++
security/smack/smack.h | 9 ++
security/smack/smack_lsm.c | 27 ++++-
security/smack/smackfs.c | 221 +++++++++++++++++++++++++++++++++++++++
4 files changed, 268 insertions(+), 2 deletions(-)

diff --git a/Documentation/security/Smack.txt b/Documentation/security/Smack.txt
index 5e6d07f..0ffd194 100644
--- a/Documentation/security/Smack.txt
+++ b/Documentation/security/Smack.txt
@@ -255,6 +255,19 @@ unconfined
the access permitted if it wouldn't be otherwise. Note that this
is dangerous and can ruin the proper labeling of your system.
It should never be used in production.
+relabel-possible
+ This interface is used to set relabel-possible flag. A process
+ with this flag is able to change its label without CAP_MAC_ADMIN,
+ but only once. After label transition this flag is zeroed.
+ 0 - default: process is not allowed to label transition
+ 1 - process is allowed to one-time label tranistion
+relabel-list
+ This interface contains a list of labels, in which process can
+ transition to. The format accepted on write is:
+ "%s"
+ for adding label, and:
+ "-%s"
+ for removing label from list.

If you are using the smackload utility
you can add access rules in /etc/smack/accesses. They take the form:
diff --git a/security/smack/smack.h b/security/smack/smack.h
index fff0c61..db4a1a3 100644
--- a/security/smack/smack.h
+++ b/security/smack/smack.h
@@ -115,6 +115,7 @@ struct task_smack {
struct smack_known *smk_forked; /* label when forked */
struct list_head smk_rules; /* per task access rules */
struct mutex smk_rules_lock; /* lock for the rules */
+ int smk_relabel; /* task can change its label */
};

#define SMK_INODE_INSTANT 0x01 /* inode is instantiated */
@@ -192,6 +193,12 @@ enum {
Opt_fstransmute = 5,
};

+struct smack_relabel {
+ struct rcu_head rcu;
+ struct list_head list;
+ struct smack_known *smk_label;
+};
+
/*
* Mount options
*/
@@ -332,6 +339,8 @@ extern struct list_head smk_net6addr_list;
extern struct mutex smack_onlycap_lock;
extern struct list_head smack_onlycap_list;

+extern struct list_head smack_relabel_list;
+
#define SMACK_HASH_SLOTS 16
extern struct hlist_head smack_known_hash[SMACK_HASH_SLOTS];

diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index 996c889..596f270 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -66,6 +66,8 @@ static const match_table_t smk_mount_tokens = {
{Opt_error, NULL},
};

+LIST_HEAD(smack_relabel_list);
+
#ifdef CONFIG_SECURITY_SMACK_BRINGUP
static char *smk_bu_mess[] = {
"Bringup Error", /* Unused */
@@ -325,6 +327,7 @@ static struct task_smack *new_task_smack(struct smack_known *task,

tsp->smk_task = task;
tsp->smk_forked = forked;
+ tsp->smk_relabel = 0;
INIT_LIST_HEAD(&tsp->smk_rules);
mutex_init(&tsp->smk_rules_lock);

@@ -1953,6 +1956,7 @@ static int smack_cred_prepare(struct cred *new, const struct cred *old,
if (rc != 0)
return rc;

+ new_tsp->smk_relabel = old_tsp->smk_relabel;
new->security = new_tsp;
return 0;
}
@@ -3549,9 +3553,11 @@ static int smack_getprocattr(struct task_struct *p, char *name, char **value)
static int smack_setprocattr(struct task_struct *p, char *name,
void *value, size_t size)
{
- struct task_smack *tsp;
+ struct task_smack *tsp = current_security();
struct cred *new;
struct smack_known *skp;
+ struct smack_relabel *srp;
+ int rc;

/*
* Changing another process' Smack value is too dangerous
@@ -3560,7 +3566,7 @@ static int smack_setprocattr(struct task_struct *p, char *name,
if (p != current)
return -EPERM;

- if (!smack_privileged(CAP_MAC_ADMIN))
+ if (!smack_privileged(CAP_MAC_ADMIN) && !tsp->smk_relabel)
return -EPERM;

if (value == NULL || size == 0 || size >= SMK_LONGLABEL)
@@ -3579,12 +3585,29 @@ static int smack_setprocattr(struct task_struct *p, char *name,
if (skp == &smack_known_web)
return -EPERM;

+ if (tsp->smk_relabel) {
+ rc = -EPERM;
+ rcu_read_lock();
+ list_for_each_entry_rcu(srp, &smack_relabel_list, list)
+ if (srp->smk_label == skp) {
+ rc = 0;
+ break;
+ }
+ rcu_read_unlock();
+ if (rc)
+ return rc;
+ }
+
new = prepare_creds();
if (new == NULL)
return -ENOMEM;

tsp = new->security;
tsp->smk_task = skp;
+ /*
+ * process can change its label only once
+ */
+ tsp->smk_relabel = 0;

commit_creds(new);
return size;
diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c
index c20b154..11012f1 100644
--- a/security/smack/smackfs.c
+++ b/security/smack/smackfs.c
@@ -61,6 +61,8 @@ enum smk_inos {
#if IS_ENABLED(CONFIG_IPV6)
SMK_NET6ADDR = 23, /* single label IPv6 hosts */
#endif /* CONFIG_IPV6 */
+ SMK_RELABEL_POSSIBLE = 24, /* relabel possible without CAP_MAC_ADMIN */
+ SMK_RELABEL_LIST = 25,
};

/*
@@ -72,6 +74,7 @@ static DEFINE_MUTEX(smk_net4addr_lock);
#if IS_ENABLED(CONFIG_IPV6)
static DEFINE_MUTEX(smk_net6addr_lock);
#endif /* CONFIG_IPV6 */
+static DEFINE_MUTEX(smack_relabel_list_lock);

/*
* This is the "ambient" label for network traffic.
@@ -2700,6 +2703,218 @@ static const struct file_operations smk_syslog_ops = {


/**
+ * smk_read_relabel - read() for smackfs/relabel
+ * @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_relabel(struct file *filp, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct task_smack *tsp = current_security();
+ char temp[32];
+ ssize_t rc;
+
+ if (*ppos != 0)
+ return 0;
+
+ sprintf(temp, "%d\n", tsp->smk_relabel);
+ rc = simple_read_from_buffer(buf, count, ppos, temp, strlen(temp));
+ return rc;
+}
+
+/**
+ * smk_write_relabel - write() for /smack/relabel
+ * @file: 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_relabel(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct task_smack *tsp = current_security();
+ struct cred *creds;
+ int r;
+
+ if (!smack_privileged(CAP_MAC_ADMIN))
+ return -EPERM;
+
+ if (count == 0 || count > 2)
+ return -EINVAL;
+
+ if (kstrtoint_from_user(buf, count, 0, &r))
+ return -EFAULT;
+
+ if (r != 0 && r != 1)
+ return -EINVAL;
+
+ creds = prepare_creds();
+ if (creds == NULL)
+ return -ENOMEM;
+
+ tsp = creds->security;
+ tsp->smk_relabel = r;
+
+ commit_creds(creds);
+ return count;
+}
+
+
+static const struct file_operations smk_relabel_possible_ops = {
+ .read = smk_read_relabel,
+ .write = smk_write_relabel,
+ .llseek = default_llseek,
+};
+
+/*
+ * Seq_file read operations for /smack/relabel-list
+ */
+
+static void *relabel_list_seq_start(struct seq_file *s, loff_t *pos)
+{
+ return smk_seq_start(s, pos, &smack_relabel_list);
+}
+
+static void *relabel_list_seq_next(struct seq_file *s, void *v, loff_t *pos)
+{
+ return smk_seq_next(s, v, pos, &smack_relabel_list);
+}
+
+static int relabel_list_seq_show(struct seq_file *s, void *v)
+{
+ struct list_head *list = v;
+ struct smack_relabel *srp =
+ list_entry_rcu(list, struct smack_relabel, list);
+
+ seq_printf(s, "%s\n", srp->smk_label->smk_known);
+
+ return 0;
+}
+
+static const struct seq_operations relabel_list_seq_ops = {
+ .start = relabel_list_seq_start,
+ .next = relabel_list_seq_next,
+ .show = relabel_list_seq_show,
+ .stop = smk_seq_stop,
+};
+
+/**
+ * smk_open_relabel_list - open() for /smack/relabel-list
+ * @inode: inode structure representing file
+ * @file: "relabel-list" file pointer
+ *
+ * For reading, use load2_seq_* seq_file reading operations.
+ */
+static int smk_open_relabel_list(struct inode *inode, struct file *file)
+{
+ return seq_open(file, &relabel_list_seq_ops);
+}
+
+/**
+ * smk_write_relabel_list - write() for /smack/relabel-list
+ * @file: file pointer, not actually used
+ * @buf: where to get the data from
+ * @count: bytes sent
+ * @ppos: where to start - must be 0
+ *
+ */
+static ssize_t smk_write_relabel_list(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct smack_known *skp;
+ struct smack_relabel *srp;
+ int rc = count;
+ int remove;
+ char *data;
+ char *label;
+
+ /*
+ * Must have privilege.
+ */
+ if (!smack_privileged(CAP_MAC_ADMIN))
+ return -EPERM;
+
+ /*
+ * Enough data must be present.
+ * One label per line.
+ */
+ if (*ppos != 0 || count >= SMK_LONGLABEL)
+ return -EINVAL;
+
+ data = kzalloc(count + 1, GFP_KERNEL);
+ if (data == NULL)
+ return -ENOMEM;
+
+ if (copy_from_user(data, buf, count) != 0) {
+ kfree(data);
+ return -EFAULT;
+ }
+
+ if (data[0] == '-') {
+ remove = 1;
+ label = smk_parse_smack(data + 1, count - 1);
+ if (IS_ERR(label)) {
+ kfree(data);
+ return PTR_ERR(label);
+ }
+ skp = smk_find_entry(label);
+ kfree(label);
+ } else {
+ remove = 0;
+ skp = smk_import_entry(data, count);
+ }
+ kfree(data);
+
+ if (IS_ERR(skp))
+ return PTR_ERR(skp);
+
+ mutex_lock(&smack_relabel_list_lock);
+ list_for_each_entry(srp, &smack_relabel_list, list)
+ if (srp->smk_label == skp) {
+ if (remove) {
+ list_del_rcu(&srp->list);
+ mutex_unlock(&smack_relabel_list_lock);
+ kfree_rcu(srp, rcu);
+ return rc;
+ } else
+ goto out;
+ }
+
+ /* Entry not found on smack_relabel_list */
+ if (remove) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ srp = kzalloc(sizeof(*srp), GFP_KERNEL);
+ if (srp == NULL) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ srp->smk_label = skp;
+ list_add_rcu(&srp->list, &smack_relabel_list);
+
+out:
+ mutex_unlock(&smack_relabel_list_lock);
+ return rc;
+}
+
+static const struct file_operations smk_relabel_list_ops = {
+ .open = smk_open_relabel_list,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .write = smk_write_relabel_list,
+ .release = seq_release,
+};
+
+/**
* smk_read_ptrace - read() for /smack/ptrace
* @filp: file pointer, not actually used
* @buf: where to put the result
@@ -2824,6 +3039,12 @@ static int smk_fill_super(struct super_block *sb, void *data, int silent)
[SMK_NET6ADDR] = {
"ipv6host", &smk_net6addr_ops, S_IRUGO|S_IWUSR},
#endif /* CONFIG_IPV6 */
+ [SMK_RELABEL_POSSIBLE] = {
+ "relabel-possible", &smk_relabel_possible_ops,
+ S_IRUGO|S_IWUGO},
+ [SMK_RELABEL_LIST] = {
+ "relabel-list", &smk_relabel_list_ops,
+ S_IRUGO|S_IWUGO},
/* last one */
{""}
};
--
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/