[PATCH v4] Smack: limited capability for changing process label

From: Rafal Krypa
Date: Wed Oct 14 2015 - 11:54:56 EST


From: Zbigniew Jasinski <z.jasinski@xxxxxxxxxxx>

This feature introduces new kernel interface:

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

This list is used to control smack label transition mechanism.
List is set by, and per process. Process can transit to new label only if
label is on the list. Only process with CAP_MAC_ADMIN capability can add
labels to this list. With this list, process can change it's label without
CAP_MAC_ADMIN but only once. After label changing, list is unset.

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

Changes in v4:
* switch from global list to per-task list
* since the per-task list is accessed only by the task itself
there is no need to use synchronization mechanisms on it

Signed-off-by: Zbigniew Jasinski <z.jasinski@xxxxxxxxxxx>
Signed-off-by: Rafal Krypa <r.krypa@xxxxxxxxxxx>
---
Documentation/security/Smack.txt | 14 ++++
security/smack/smack.h | 3 +-
security/smack/smack_access.c | 6 +-
security/smack/smack_lsm.c | 73 ++++++++++++++++-
security/smack/smackfs.c | 167 ++++++++++++++++++++++++++++++++++++---
5 files changed, 246 insertions(+), 17 deletions(-)

diff --git a/Documentation/security/Smack.txt b/Documentation/security/Smack.txt
index 5e6d07f..d9ace08 100644
--- a/Documentation/security/Smack.txt
+++ b/Documentation/security/Smack.txt
@@ -255,6 +255,20 @@ 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-self
+ This interface contains a list of labels to which the process can
+ transition to, by writing to /proc/self/attr/current.
+ Normally a process can change its own label to any legal value, but only
+ if it has CAP_MAC_ADMIN. This interface allows a process without
+ CAP_MAC_ADMIN to relabel itself to one of labels from predefined list.
+ A process without CAP_MAC_ADMIN can change its label only once. When it
+ does, this list will be cleared.
+
+ 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..b670321 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 */
+ struct list_head smk_relabel; /* transit allowed labels */
};

#define SMK_INODE_INSTANT 0x01 /* inode is instantiated */
@@ -169,7 +170,7 @@ struct smk_port_label {
};
#endif /* SMACK_IPV6_PORT_LABELING */

-struct smack_onlycap {
+struct smack_known_list_elem {
struct list_head list;
struct smack_known *smk_label;
};
diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c
index bc1053f..a283f9e 100644
--- a/security/smack/smack_access.c
+++ b/security/smack/smack_access.c
@@ -637,7 +637,7 @@ DEFINE_MUTEX(smack_onlycap_lock);
int smack_privileged(int cap)
{
struct smack_known *skp = smk_of_current();
- struct smack_onlycap *sop;
+ struct smack_known_list_elem *sklep;

/*
* All kernel tasks are privileged
@@ -654,8 +654,8 @@ int smack_privileged(int cap)
return 1;
}

- list_for_each_entry_rcu(sop, &smack_onlycap_list, list) {
- if (sop->smk_label == skp) {
+ list_for_each_entry_rcu(sklep, &smack_onlycap_list, list) {
+ if (sklep->smk_label == skp) {
rcu_read_unlock();
return 1;
}
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index 996c889..91fff39 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -326,6 +326,7 @@ static struct task_smack *new_task_smack(struct smack_known *task,
tsp->smk_task = task;
tsp->smk_forked = forked;
INIT_LIST_HEAD(&tsp->smk_rules);
+ INIT_LIST_HEAD(&tsp->smk_relabel);
mutex_init(&tsp->smk_rules_lock);

return tsp;
@@ -361,6 +362,50 @@ static int smk_copy_rules(struct list_head *nhead, struct list_head *ohead,
}

/**
+ * smk_destroy_relabel - destroy relabel list
+ * @head: header pointer of the list to destroy
+ */
+static void smk_destroy_relabel(struct list_head *head)
+{
+ struct smack_known_list_elem *sklep;
+ struct smack_known_list_elem *sklep2;
+
+ list_for_each_entry_safe(sklep, sklep2, head, list)
+ kfree(sklep);
+
+ INIT_LIST_HEAD(head);
+}
+
+/**
+ * smk_copy_relabel - copy smk_relabel labels list
+ * @nhead: new rules header pointer
+ * @ohead: old rules header pointer
+ * @gfp: type of the memory for the allocation
+ *
+ * Returns 0 on success, -ENOMEM on error
+ */
+static int smk_copy_relabel(struct list_head *nhead, struct list_head *ohead,
+ gfp_t gfp)
+{
+ struct smack_known_list_elem *nklep;
+ struct smack_known_list_elem *oklep;
+
+ INIT_LIST_HEAD(nhead);
+
+ list_for_each_entry(oklep, ohead, list) {
+ nklep = kzalloc(sizeof(struct smack_known_list_elem), gfp);
+ if (nklep == NULL) {
+ smk_destroy_relabel(nhead);
+ return -ENOMEM;
+ }
+ nklep->smk_label = oklep->smk_label;
+ list_add(&nklep->list, nhead);
+ }
+
+ return 0;
+}
+
+/**
* smk_ptrace_mode - helper function for converting PTRACE_MODE_* into MAY_*
* @mode - input mode in form of PTRACE_MODE_*
*
@@ -1922,6 +1967,8 @@ static void smack_cred_free(struct cred *cred)
return;
cred->security = NULL;

+ smk_destroy_relabel(&tsp->smk_relabel);
+
list_for_each_safe(l, n, &tsp->smk_rules) {
rp = list_entry(l, struct smack_rule, list);
list_del(&rp->list);
@@ -1953,6 +2000,10 @@ static int smack_cred_prepare(struct cred *new, const struct cred *old,
if (rc != 0)
return rc;

+ rc = smk_copy_relabel(&new_tsp->smk_relabel, &old_tsp->smk_relabel, gfp);
+ if (rc != 0)
+ return rc;
+
new->security = new_tsp;
return 0;
}
@@ -3549,9 +3600,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_known_list_elem *sklep;
+ int rc;

/*
* Changing another process' Smack value is too dangerous
@@ -3560,7 +3613,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) && list_empty(&tsp->smk_relabel))
return -EPERM;

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

+ if (!list_empty(&tsp->smk_relabel) &&
+ !smack_privileged(CAP_MAC_ADMIN)) {
+ rc = -EPERM;
+ list_for_each_entry(sklep, &tsp->smk_relabel, list)
+ if (sklep->smk_label == skp) {
+ rc = 0;
+ break;
+ }
+ 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
+ */
+ smk_destroy_relabel(&tsp->smk_relabel);

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

/*
@@ -1914,10 +1915,10 @@ static void *onlycap_seq_next(struct seq_file *s, void *v, loff_t *pos)
static int onlycap_seq_show(struct seq_file *s, void *v)
{
struct list_head *list = v;
- struct smack_onlycap *sop =
- list_entry_rcu(list, struct smack_onlycap, list);
+ struct smack_known_list_elem *sklep =
+ list_entry_rcu(list, struct smack_known_list_elem, list);

- seq_puts(s, sop->smk_label->smk_known);
+ seq_puts(s, sklep->smk_label->smk_known);
seq_putc(s, ' ');

return 0;
@@ -1989,8 +1990,8 @@ static ssize_t smk_write_onlycap(struct file *file, const char __user *buf,
char *data_parse;
char *tok;
struct smack_known *skp;
- struct smack_onlycap *sop;
- struct smack_onlycap *sop2;
+ struct smack_known_list_elem *sklep;
+ struct smack_known_list_elem *sklep2;
LIST_HEAD(list_tmp);
int rc = count;

@@ -2017,14 +2018,14 @@ static ssize_t smk_write_onlycap(struct file *file, const char __user *buf,
break;
}

- sop = kzalloc(sizeof(*sop), GFP_KERNEL);
- if (sop == NULL) {
+ sklep = kzalloc(sizeof(*sklep), GFP_KERNEL);
+ if (sklep == NULL) {
rc = -ENOMEM;
break;
}

- sop->smk_label = skp;
- list_add_rcu(&sop->list, &list_tmp);
+ sklep->smk_label = skp;
+ list_add_rcu(&sklep->list, &list_tmp);
}
kfree(data);

@@ -2047,8 +2048,8 @@ static ssize_t smk_write_onlycap(struct file *file, const char __user *buf,
mutex_unlock(&smack_onlycap_lock);
}

- list_for_each_entry_safe(sop, sop2, &list_tmp, list)
- kfree(sop);
+ list_for_each_entry_safe(sklep, sklep2, &list_tmp, list)
+ kfree(sklep);

return rc;
}
@@ -2698,6 +2699,147 @@ static const struct file_operations smk_syslog_ops = {
.llseek = default_llseek,
};

+/*
+ * Seq_file read operations for /smack/relabel-self
+ */
+
+static void *relabel_self_seq_start(struct seq_file *s, loff_t *pos)
+{
+ struct task_smack *tsp = current_security();
+
+ return smk_seq_start(s, pos, &tsp->smk_relabel);
+}
+
+static void *relabel_self_seq_next(struct seq_file *s, void *v, loff_t *pos)
+{
+ struct task_smack *tsp = current_security();
+
+ return smk_seq_next(s, v, pos, &tsp->smk_relabel);
+}
+
+static int relabel_self_seq_show(struct seq_file *s, void *v)
+{
+ struct list_head *list = v;
+ struct smack_known_list_elem *sklep =
+ list_entry(list, struct smack_known_list_elem, list);
+
+ seq_printf(s, "%s\n", sklep->smk_label->smk_known);
+
+ return 0;
+}
+
+static const struct seq_operations relabel_self_seq_ops = {
+ .start = relabel_self_seq_start,
+ .next = relabel_self_seq_next,
+ .show = relabel_self_seq_show,
+ .stop = smk_seq_stop,
+};
+
+/**
+ * smk_open_relabel_self - open() for /smack/relabel-self
+ * @inode: inode structure representing file
+ * @file: "relabel-self" file pointer
+ *
+ * Connect our relabel_self_seq_* operations with /smack/relabel-self
+ * file_operations
+ */
+static int smk_open_relabel_self(struct inode *inode, struct file *file)
+{
+ return seq_open(file, &relabel_self_seq_ops);
+}
+
+/**
+ * smk_write_relabel_self - write() for /smack/relabel-self
+ * @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_self(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct smack_known *skp;
+ struct smack_known_list_elem *sklep;
+ struct task_smack *tsp = current_security();
+ 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);
+ kfree(data);
+ if (IS_ERR(label))
+ return PTR_ERR(label);
+
+ skp = smk_find_entry(label);
+ kfree(label);
+ if (skp == NULL)
+ return count;
+ } else {
+ remove = 0;
+
+ skp = smk_import_entry(data, count);
+ kfree(data);
+
+ if (IS_ERR(skp))
+ return PTR_ERR(skp);
+ }
+
+ list_for_each_entry(sklep, &tsp->smk_relabel, list)
+ if (sklep->smk_label == skp) {
+ if (remove) {
+ list_del(&sklep->list);
+ kfree(sklep);
+ }
+ return count;
+ }
+
+ /* Entry not found on smack_relabel list */
+ if (remove)
+ return count;
+
+ sklep = kzalloc(sizeof(*sklep), GFP_KERNEL);
+ if (sklep == NULL)
+ return -ENOMEM;
+
+ sklep->smk_label = skp;
+ list_add(&sklep->list, &tsp->smk_relabel);
+
+ return count;
+}
+
+static const struct file_operations smk_relabel_self_ops = {
+ .open = smk_open_relabel_self,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .write = smk_write_relabel_self,
+ .release = seq_release,
+};

/**
* smk_read_ptrace - read() for /smack/ptrace
@@ -2824,6 +2966,9 @@ 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_SELF] = {
+ "relabel-self", &smk_relabel_self_ops,
+ S_IRUGO|S_IWUGO},
/* last one */
{""}
};
--
2.6.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/