[PATCH v2 1/1] Tags: Adding tagging feature to security modules

From: José Bollo
Date: Wed Oct 21 2015 - 17:49:55 EST


The Tags security module allows to attach tags to processes.
Tags are accessed through the new files /proc/PID/attr/tags
and /proc/PID/tasks/TID/attr/tags below named "tag file".

Reading a tag file returns all the tags attached to the process
(or thread). The tags are listed one per line, each followed by
exactly one new-line character ('\n').

Writing a tag file allows under condition to change the tags of a
process. When writting the file, it acts like a protocol accepting
lines. The accepted lines are:

- empty: "\n"

only a new-line

- comment: "#...anything...\n"

a sharp followed by anything and a new-line

- remove all tags: "-\n"

a minus followed by a new-line

- remove one tag: "-TAGNAME\n"

a minus followed by the name of the tag and a new-line

- add one tag: "+TAGNAME\n"

a plus followed by the name of the tag and a new-line

- query one tag: "?TAGNAME\n"

a question mark followed by the name of the tag and a new-line

Be aware that the current implementation doesn't trim any extra
characters like spaces or tabs.

Names of tags can have any ASCII printable character (in the
ranges [33...126] that is from '!' to '~'. The minimum length
of a tag's name is 1 and the maximum length is 1000 bytes.

It exist 7 special tags. Their names are:
- tags:add
- tags:sub
- tags:keep
- tags:add-all
- tags:sub-all
- tags:keep-all
- tags:set-others

All other tags prefixed by "tags:" are reserved and can not be used.

Normally a process can not remove tags from itself. A process can
remove a tag from itself only if one or more of the following
conditions is true:
- the process has not the tag (it is not an error)
- the process has the tag "tags:sub" and the tag to remove
is not a special tag
- the process has the tag "tags:sub-all"
- the process is a kernel's thread
- the process has the capability CAP_MAC_ADMIN

Normally a process can not add tags to itself. A process can add
a tag to itself only if one or more of the following conditions
is true:
- the process already has the tag (it is not an error)
- the process has the tag "tags:add" and the tag to add
is not a special tag
- the process has the tag "tags:add-all"
- the process is a kernel's thread
- the process has the capability CAP_MAC_ADMIN

Even if a process has the right to add tags, it can be canceled
(ECANCELED) if the count of tag reached is too high. The current
limit is 1000. This limit is global to the kernel, not by process.

Normally a process can not add or remove tags of other processes.
But it can do that if it has the tag "tags:set-others".

Tags are copied to the children processes during 'clone'.

During execution of 'execve', the following rules apply:
- processes having the tag "tags:keep-all" keep all there tags;
- otherwise, processes having "tags:keep" keep the tags that are
not specials;
- otherwise, processes only keep tags that are prefixed with the
character * (star).

Because changes only occur through tag files accesses, the
notifications might be available to any possible observer.

Signed-off-by: Josà Bollo <jobol@xxxxxxxxxxx>
---
fs/proc/base.c | 3 +
security/Kconfig | 2 +
security/Makefile | 2 +
security/smack/Kconfig | 9 +
security/smack/smack.h | 7 +
security/smack/smack_lsm.c | 50 ++++
security/tags/Kconfig | 8 +
security/tags/Makefile | 6 +
security/tags/tags.c | 731
+++++++++++++++++++++++++++++++++++++++++++++
security/tags/tags.h | 39 +++
10 files changed, 857 insertions(+)
create mode 100644 security/tags/Kconfig
create mode 100644 security/tags/Makefile
create mode 100644 security/tags/tags.c
create mode 100644 security/tags/tags.h

diff --git a/fs/proc/base.c b/fs/proc/base.c
index b25eee4..b1a47daa 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -2406,6 +2406,9 @@ static const struct pid_entry attr_dir_stuff[] = {
REG("fscreate", S_IRUGO|S_IWUGO, proc_pid_attr_operations),
REG("keycreate", S_IRUGO|S_IWUGO, proc_pid_attr_operations),
REG("sockcreate", S_IRUGO|S_IWUGO, proc_pid_attr_operations),
+#ifdef CONFIG_SECURITY_TAGS
+ REG("tags", S_IRUGO|S_IWUGO, proc_pid_attr_operations),
+#endif
};

static int proc_attr_dir_readdir(struct file *file, struct dir_context
*ctx)
diff --git a/security/Kconfig b/security/Kconfig
index e452378..2e628d2 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -118,6 +118,8 @@ config LSM_MMAP_MIN_ADDR
this low address space will need the permission specific to the
systems running LSM.

+source security/tags/Kconfig
+
source security/selinux/Kconfig
source security/smack/Kconfig
source security/tomoyo/Kconfig
diff --git a/security/Makefile b/security/Makefile
index c9bfbc8..9138482 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -8,6 +8,7 @@ subdir-$(CONFIG_SECURITY_SMACK) += smack
subdir-$(CONFIG_SECURITY_TOMOYO) += tomoyo
subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor
subdir-$(CONFIG_SECURITY_YAMA) += yama
+subdir-$(CONFIG_SECURITY_TAGS) += tags

# always enable default capabilities
obj-y += commoncap.o
@@ -23,6 +24,7 @@ obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/
obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/
obj-$(CONFIG_SECURITY_YAMA) += yama/
obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o
+obj-$(CONFIG_SECURITY_TAGS) += tags/

# Object integrity file lists
subdir-$(CONFIG_INTEGRITY) += integrity
diff --git a/security/smack/Kconfig b/security/smack/Kconfig
index 271adae..50a110b 100644
--- a/security/smack/Kconfig
+++ b/security/smack/Kconfig
@@ -40,3 +40,12 @@ config SECURITY_SMACK_NETFILTER
This enables security marking of network packets using
Smack labels.
If you are unsure how to answer this question, answer N.
+
+config SECURITY_SMACK_TAGS
+ bool "Stacking tags module on top of Smack"
+ depends on SECURITY_SMACK
+ depends on SECURITY_TAGS
+ default n
+ help
+ Integrates the tagging module on top of smack.
+
diff --git a/security/smack/smack.h b/security/smack/smack.h
index 6c91156..234b013 100644
--- a/security/smack/smack.h
+++ b/security/smack/smack.h
@@ -110,12 +110,19 @@ struct inode_smack {
int smk_flags; /* smack inode flags */
};

+#ifdef CONFIG_SECURITY_SMACK_TAGS
+struct tags_root; /* This can be removed when extreme stacking is
available */
+#endif
struct task_smack {
struct smack_known *smk_task; /* label for access control */
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 */
+#ifdef CONFIG_SECURITY_SMACK_TAGS
+ /* This can be removed when extreme stacking is available */
+ struct tags_root *smk_tags; /* associated tags */
+#endif
};

#define SMK_INODE_INSTANT 0x01 /* inode is instantiated */
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index ff81026..72593de 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -44,6 +44,11 @@
#include <linux/parser.h>
#include "smack.h"

+#ifdef CONFIG_SECURITY_SMACK_TAGS
+/* This can be removed when extreme stacking is available */
+#include "../tags/tags.h"
+#endif
+
#define TRANS_TRUE "TRUE"
#define TRANS_TRUE_SIZE 4

@@ -323,6 +328,14 @@ static struct task_smack *new_task_smack(struct
smack_known *task,
if (tsp == NULL)
return NULL;

+#ifdef CONFIG_SECURITY_SMACK_TAGS
+/* This can be removed when extreme stacking is available */
+ if (tags_task_alloc_blank(&tsp->smk_tags, gfp)) {
+ kfree(tsp);
+ return NULL;
+ }
+#endif
+
tsp->smk_task = task;
tsp->smk_forked = forked;
INIT_LIST_HEAD(&tsp->smk_rules);
@@ -955,6 +968,11 @@ static void smack_bprm_committing_creds(struct
linux_binprm *bprm)

if (bsp->smk_task != bsp->smk_forked)
current->pdeath_signal = 0;
+
+#ifdef CONFIG_SECURITY_SMACK_TAGS
+/* This can be removed when extreme stacking is available */
+ tags_task_committing(bsp->smk_tags);
+#endif
}

/**
@@ -1959,6 +1977,10 @@ static void smack_cred_free(struct cred *cred)
list_del(&rp->list);
kfree(rp);
}
+#ifdef CONFIG_SECURITY_SMACK_TAGS
+/* This can be removed when extreme stacking is available */
+ tags_task_free(tsp->smk_tags);
+#endif
kfree(tsp);
}

@@ -1990,6 +2012,13 @@ static int smack_cred_prepare(struct cred *new,
const struct cred *old,
if (rc != 0)
return rc;

+#ifdef CONFIG_SECURITY_SMACK_TAGS
+/* This can be removed when extreme stacking is available */
+ rc = tags_task_prepare(new_tsp->smk_tags, old_tsp->smk_tags, gfp);
+ if (rc != 0)
+ return rc;
+#endif
+
new->security = new_tsp;
return 0;
}
@@ -2010,6 +2039,10 @@ static void smack_cred_transfer(struct cred *new,
const struct cred *old)
new_tsp->smk_forked = old_tsp->smk_task;
mutex_init(&new_tsp->smk_rules_lock);
INIT_LIST_HEAD(&new_tsp->smk_rules);
+#ifdef CONFIG_SECURITY_SMACK_TAGS
+/* This can be removed when extreme stacking is available */
+ tags_task_transfer(new_tsp->smk_tags, old_tsp->smk_tags);
+#endif


/* cbs copy rule list */
@@ -3562,6 +3595,15 @@ static int smack_getprocattr(struct task_struct
*p, char *name, char **value)
char *cp;
int slen;

+#ifdef CONFIG_SECURITY_SMACK_TAGS
+/* This can be removed when extreme stacking is available */
+ if (strcmp(name, "tags") == 0) {
+ struct task_smack *target;
+ target = (struct task_smack *)task_cred_xxx(p, security);
+ return tags_read(target->smk_tags, value);
+ }
+#endif
+
if (strcmp(name, "current") != 0)
return -EINVAL;

@@ -3595,6 +3637,14 @@ static int smack_setprocattr(struct task_struct
*p, char *name,
struct smack_known_list_elem *sklep;
int rc;

+#ifdef CONFIG_SECURITY_SMACK_TAGS
+/* This can be removed when extreme stacking is available */
+ if (strcmp(name, "tags") == 0)
+ return tags_write(tsp->smk_tags, ((struct task_smack*)
+ task_cred_xxx(p, security))->smk_tags,
+ value, size);
+#endif
+
/*
* Changing another process' Smack value is too dangerous
* and supports no sane use case.
diff --git a/security/tags/Kconfig b/security/tags/Kconfig
new file mode 100644
index 0000000..0e3071f
--- /dev/null
+++ b/security/tags/Kconfig
@@ -0,0 +1,8 @@
+config SECURITY_TAGS
+ bool "Tags Module for tagging processes stacked on LSM"
+ depends on SECURITY
+ default n
+ help
+ Tags module allows tagging processes through files
+ /proc/pid/attr/tags
+
diff --git a/security/tags/Makefile b/security/tags/Makefile
new file mode 100644
index 0000000..b7c18a0
--- /dev/null
+++ b/security/tags/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for the Tag module
+#
+
+obj-$(CONFIG_SECURITY_TAGS) := tags.o
+
diff --git a/security/tags/tags.c b/security/tags/tags.c
new file mode 100644
index 0000000..2ca0e59
--- /dev/null
+++ b/security/tags/tags.c
@@ -0,0 +1,731 @@
+/*
+ * Copyright (C) 2015 Josà Bollo <jobol@xxxxxxxxxxx>
+ *
+ * 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.
+ *
+ * Author:
+ * Josà Bollo <jobol@xxxxxxxxxxx>
+ */
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include "tags.h"
+
+/* maximum count of tags allowed */
+#define MAX_TAG_COUNT 1000
+
+/* maximum lengh for a single tag name */
+#define MAX_TAG_LENGTH 1000
+
+/*
+ * predefined tags: their names, their masking value
+ */
+
+/* prefix of predefined tags */
+#define TAG_PREFIX "tags:"
+
+#define TAG_ADD TAG_PREFIX"add"
+#define TAG_SUB TAG_PREFIX"sub"
+#define TAG_KEEP TAG_PREFIX"keep"
+#define TAG_ADD_ALL TAG_PREFIX"add-all"
+#define TAG_SUB_ALL TAG_PREFIX"sub-all"
+#define TAG_KEEP_ALL TAG_PREFIX"keep-all"
+#define TAG_SET_OTHERS TAG_PREFIX"set-others"
+
+#define FLAG_ADD 1
+#define FLAG_SUB 2
+#define FLAG_KEEP 4
+#define FLAG_ADD_ALL 8
+#define FLAG_SUB_ALL 16
+#define FLAG_KEEP_ALL 32
+#define FLAG_SET_OTHERS 64
+
+#define KEEPCHAR '*'
+
+/*
+ * Definition of the tags
+ */
+struct tags_tag {
+ struct tags_tag *next; /* next tag in hashed list */
+ unsigned int mask; /* mask of the tag */
+ unsigned int length; /* length in byte of the tag's name */
+ unsigned int hash; /* hash code of the name, CAUTION:
+ this field MUST remain the last. */
+};
+
+/* macro to access the tag's name */
+#define WTAGNAME(tag) ((char*)((&((tag)->hash))+1))
+#define TAGNAME(tag) ((const char*)WTAGNAME(tag))
+
+
+/*
+ * The hash table of tags
+ */
+#define TAGHASHSIZE 64 /* size of the hash table */
+
+/* the size of the tag's hash table must be a power of 2 */
+#if (TAGHASHSIZE & (TAGHASHSIZE - 1)) != 0
+#error "TAGHASHSIZE MUST be a power of 2"
+#endif
+
+/* global mutex to access the tag's hash table */
+static DEFINE_MUTEX(tags_htable_lock);
+
+/* the tag's hash table */
+static struct tags_tag *htable_of_tags[TAGHASHSIZE];
+
+/* macro to get/set the head of the hashed list */
+#define TAGHEAD(hash) htable_of_tags[(hash)&((TAGHASHSIZE)-1)]
+
+/* count of tags created */
+static unsigned int count_of_tags;
+
+/*
+ * The tag sets are made of links
+ */
+struct tags_link { /* a tag set is a simple linked list */
+ const struct tags_tag *tag; /* the tag */
+ struct tags_link *next; /* next item of the list */
+};
+
+/* global mutex to access the list of free tag set links */
+static DEFINE_MUTEX(tags_free_links_lock);
+
+/* the list of free links */
+static struct tags_link *free_links;
+
+/* the cache for links used to improve locality */
+static struct kmem_cache *free_links_cache;
+
+/*
+ * Definition of the root.
+ */
+struct tags_root {
+ unsigned int mask; /* or of masks of tags */
+ struct tags_link *tagset; /* head of the tagset */
+};
+
+/* the cache for roots */
+static struct kmem_cache *free_roots_cache;
+
+/**
+ * tags_valid_name - Checks if a tag's name is valid.
+ *
+ * @name: string for the name of the tag
+ * @length: length in bytes of the tag's name
+ *
+ * Returns 1 if the name is valid or otherwise returns 0
+ */
+static int tags_valid_name(const char *name, unsigned int length)
+{
+ unsigned int index;
+
+ /* check the length */
+ if (!length || length > MAX_TAG_LENGTH)
+ return 0;
+
+ /* check the characters */
+ for (index = 0 ; index < length ; index++)
+ if (!('!' <= name[index] && name[index] <= '~'))
+ return 0;
+
+ /* valid if here */
+ return 1;
+}
+
+/**
+ * tags_get - Get an existing or new tag
+ *
+ * @name: string for the name of the tag
+ * @length: length in bytes of the tag's name
+ * @create: boolean indicator that allows creation if true
+ * @flags: flags for kernel allocations
+ *
+ * Returns the tag found.
+ *
+ * If the tag is not found and 'create' == 0, returns NULL.
+ * If the tag is not found and 'create' != 0, tries to create
+ * and return the new tag. Returns NULL on creation failure.
+ * The creation can fail either if the maximum count of tags
+ * was created or if the allocation of memory failed.
+ */
+static struct tags_tag *tags_get(const char *name,
+ unsigned int length,
+ int create,
+ gfp_t flags)
+{
+ struct tags_tag *tag;
+ unsigned int hash;
+ size_t size;
+
+ /* compute the hash code */
+ hash = full_name_hash(name, length);
+
+ /* search the tag */
+ mutex_lock(&tags_htable_lock);
+ tag = TAGHEAD(hash);
+ while (tag) {
+ if (tag->hash == hash && tag->length == tag->length
+ && 0 == memcmp(name, TAGNAME(tag), length))
+ goto found;
+ tag = tag->next;
+ }
+
+ /* not found */
+ if (unlikely(create != 0) && likely(count_of_tags < MAX_TAG_COUNT)) {
+
+ /* compute the size */
+ size = length + (size_t)TAGNAME((struct tags_tag*)0);
+
+ /* allocation */
+ tag = kmalloc(size, flags);
+ if (likely(tag)) {
+ /* initialisation */
+ tag->mask = 0;
+ tag->length = length;
+ tag->hash = hash;
+ memcpy(WTAGNAME(tag), name, length);
+ /* insert ahead */
+ tag->next = TAGHEAD(hash);
+ TAGHEAD(hash) = tag;
+ count_of_tags++;
+ }
+ }
+
+found:
+ mutex_unlock(&tags_htable_lock);
+ return tag;
+}
+
+/**
+ * tags_make_predefined - Creates a predefined tag
+ *
+ * @name: string for the name of the tag
+ * @mask: the mask to set to the tag
+ *
+ * Returns 0 in case of success or -ENOMEM if an allocation failed
+ */
+static int tags_make_predefined(const char *name, unsigned int mask)
+{
+ struct tags_tag *tag;
+
+ tag = tags_get(name, (unsigned int)strlen(name), 1, GFP_KERNEL);
+ if (unlikely(!tag))
+ return -ENOMEM;
+
+ tag->mask = mask;
+ return 0;
+}
+
+/*
+ * tags_init - Initialize the submodule for tags
+ */
+static int tags_init(void)
+{
+ pr_info("Tags: Initialising.\n");
+
+ /* initialise the caches */
+ free_roots_cache = KMEM_CACHE(tags_root, 0);
+ free_links_cache = KMEM_CACHE(tags_link, 0);
+ if (!free_roots_cache || !free_links_cache)
+ goto no_memory;
+
+ /* create the predefined tags */
+ if (tags_make_predefined(TAG_ADD, FLAG_ADD)
+ || tags_make_predefined(TAG_SUB, FLAG_SUB)
+ || tags_make_predefined(TAG_KEEP, FLAG_KEEP)
+ || tags_make_predefined(TAG_ADD_ALL, FLAG_ADD_ALL)
+ || tags_make_predefined(TAG_SUB_ALL, FLAG_SUB_ALL)
+ || tags_make_predefined(TAG_KEEP_ALL, FLAG_KEEP_ALL)
+ || tags_make_predefined(TAG_SET_OTHERS, FLAG_SET_OTHERS))
+ goto no_memory;
+
+ return 0;
+
+no_memory:
+ if (free_links_cache) {
+ kmem_cache_destroy(free_links_cache);
+ free_links_cache = NULL;
+ }
+ if (free_roots_cache) {
+ kmem_cache_destroy(free_roots_cache);
+ free_roots_cache = NULL;
+ }
+ return -ENOMEM;
+}
+
+/**
+ * tags_set_clone - Clones a tag set
+ *
+ * @set: the tag set to clone
+ * @result: a not-null pointer to where store the cloned tag set
+ * @flags: flags for kernel allocations
+ *
+ * Returns 1 if the tag set is cloned or 0 otherwise if an allocation
+ * of memory failed.
+ */
+static int tags_set_clone(struct tags_link *set,
+ struct tags_link **result,
+ gfp_t flags)
+{
+ struct tags_link **prev;
+ struct tags_link *head;
+ struct tags_link *next;
+ struct tags_link *iter;
+
+ iter = set;
+
+ /* first loop: use free allocated links */
+ mutex_lock(&tags_free_links_lock);
+ head = free_links;
+ prev = &head;
+ next = free_links;
+ while (iter && next) {
+ next->tag = iter->tag;
+ iter = iter->next;
+ prev = &next->next;
+ next = next->next;
+ }
+ free_links = next;
+ mutex_unlock(&tags_free_links_lock);
+
+ /* second loop: allocate links */
+ while (iter) {
+ next = kmem_cache_alloc(free_links_cache, flags);
+ if (!next) {
+ mutex_lock(&tags_free_links_lock);
+ *prev = free_links;
+ free_links = head;
+ mutex_unlock(&tags_free_links_lock);
+ return 0;
+ }
+ *prev = next;
+ next->tag = iter->tag;
+ iter = iter->next;
+ prev = &next->next;
+ }
+ *prev = NULL;
+ *result = head;
+ return 1;
+}
+
+/**
+ * tags_set_free - Frees the tag set links
+ *
+ * @head: head of the tag set to free
+ */
+static void tags_set_free(struct tags_link *head)
+{
+ struct tags_link *tail;
+
+ if (head) {
+ for (tail = head ; tail->next ; tail = tail->next);
+ mutex_lock(&tags_free_links_lock);
+ tail->next = free_links;
+ free_links = head;
+ mutex_unlock(&tags_free_links_lock);
+ }
+}
+
+/**
+ * tags_query - Queries one tag
+ *
+ * @root: tags structure of the modified task
+ * @name: string for the name of the tag
+ * @length: length in bytes of the tag's name
+ * @mask: normalized mask of the current process
+ *
+ * Returns 0 in case of success tag present or -ENOENT if the tag is
not found
+ * or invalid.
+ */
+static int tags_query(struct tags_root *root, const char *name,
+ unsigned int length, unsigned int mask)
+{
+ const struct tags_tag *tag;
+ struct tags_link *iter;
+
+ /* search the tag, never create it and silently ignore unkown tags */
+ tag = tags_get(name, length, 0, GFP_KERNEL);
+ if (tag) {
+ iter = root->tagset;
+ while (iter && iter->tag != tag)
+ iter = iter->next;
+ if (iter)
+ return 0; /* tag found */
+ }
+ return -ENOENT;
+}
+
+/**
+ * tags_sub - Removes one tag
+ *
+ * @root: tags structure of the modified task
+ * @name: string for the name of the tag
+ * @length: length in bytes of the tag's name
+ * @mask: normalized mask of the current process
+ *
+ * Returns 0 in case of success or -EPERM if the operation is forbiden
+ */
+static int tags_sub(struct tags_root *root, const char *name,
+ unsigned int length, unsigned int mask)
+{
+ const struct tags_tag *tag;
+ struct tags_link **previous;
+ struct tags_link *iter;
+
+ /* search the tag, never create it and silently ignore unkown tags */
+ tag = tags_get(name, length, 0, GFP_KERNEL);
+ if (tag) {
+ /* search the tag in 'root', silently ignore tags not owned */
+ previous = &root->tagset;
+ iter = root->tagset;
+ while (iter && iter->tag != tag) {
+ previous = &iter->next;
+ iter = iter->next;
+ }
+ if (iter) {
+ /* found in 'root', check right to remove */
+ if (tag->mask)
+ mask &= FLAG_SUB_ALL;
+ else
+ mask &= FLAG_SUB_ALL | FLAG_SUB;
+ if (!mask)
+ return -EPERM;
+
+ /* remove it */
+ root->mask &= ~tag->mask;
+ *previous = iter->next;
+
+ /* free the link */
+ mutex_lock(&tags_free_links_lock);
+ iter->next = free_links;
+ free_links = iter;
+ mutex_unlock(&tags_free_links_lock);
+ }
+ }
+ return 0;
+}
+
+/**
+ * tags_add - Adds one tag
+ *
+ * @root: tags structure of the modified task
+ * @name: string for the name of the tag
+ * @length: length in bytes of the tag's name
+ * @mask: normalized mask of the current process
+ *
+ * Returns 0 in case of success or one of the following error code if
failed:
+ * o -EINVAL if the name is invalid
+ * o -EPERM if the addition is forbidden
+ * o -ENOMEM if the an allocation failed
+ * o -ECANCELED if the maximum count of tag is reached
+ */
+static int tags_add(struct tags_root *root, const char *name,
+ unsigned int length, unsigned int mask)
+{
+ const struct tags_tag *tag;
+ struct tags_link **previous;
+ struct tags_link *iter;
+ int create;
+
+ /* checks validity of the tag's name */
+ if (!tags_valid_name(name, length))
+ return -EINVAL;
+
+ /* get the tag, avoid creation not needed */
+ create = mask & (FLAG_ADD|FLAG_ADD_ALL);
+ if (create && length >= sizeof(TAG_PREFIX)-1)
+ create = memcmp(name, TAG_PREFIX, sizeof(TAG_PREFIX)-1);
+ tag = tags_get(name, length, create, GFP_KERNEL);
+ if (!tag)
+ return !create ? -EPERM :
+ count_of_tags < MAX_TAG_COUNT ? -ENOMEM : -ECANCELED;
+
+ /* search the tag in 'root', silently ignore tags already owned */
+ previous = &root->tagset;
+ iter = root->tagset;
+ while (iter && iter->tag != tag) {
+ previous = &iter->next;
+ iter = iter->next;
+ }
+ if (!iter) {
+ /* not found, check right to add */
+ if (tag->mask)
+ mask &= FLAG_ADD_ALL;
+ else
+ mask &= FLAG_ADD_ALL | FLAG_ADD;
+ if (!mask)
+ return -EPERM;
+
+ /* allocates the link*/
+ mutex_lock(&tags_free_links_lock);
+ iter = free_links;
+ if (iter) {
+ free_links = iter->next;
+ mutex_unlock(&tags_free_links_lock);
+ } else {
+ mutex_unlock(&tags_free_links_lock);
+ iter = kmem_cache_alloc(free_links_cache, GFP_KERNEL);
+ if (!iter)
+ return -ENOMEM;
+ }
+
+ /* adds the tag */
+ iter->tag = tag;
+ iter->next = NULL;
+ *previous = iter;
+ root->mask |= tag->mask;
+ }
+ return 0;
+}
+
+/**
+ * tags_write - Implement the writing of the tags
+ *
+ * @current_root: tags structure of the current task
+ * @root: tags structure of the writen task
+ * @value: a pointer to the written data
+ * @size: the size of the written data
+ *
+ * Returns the positive count of byte written. It can be less than the
+ * count given by size if an error appears after. This count indicates
+ * the count of data treated without errors.
+ * Returns one of the negative error code below if the data begins on
error:
+ * o -EINVAL if the name or the syntax is invalid
+ * o -EPERM if the addition is forbidden
+ * o -ENOMEM if the an allocation failed
+ * o -ECANCELED if the maximum count of tag is reached
+ */
+int tags_write(struct tags_root *current_root, struct tags_root *root,
+ char *value, size_t size)
+{
+ unsigned int start, stop, len, mask;
+ int err;
+
+ /* retrieves the smack structure of the current task */
+ mask = current_root->mask;
+
+ /* compute if privileged */
+ if ((current->flags & PF_KTHREAD) || capable(CAP_MAC_ADMIN))
+ mask |= FLAG_ADD_ALL | FLAG_SUB_ALL;
+
+ /* check if writting itself or has the tag "set-others" */
+ if ((current_root != root) && !(mask & FLAG_SET_OTHERS))
+ return -EPERM; /* can't write others */
+
+ /* begin the parsing */
+ start = 0;
+ while (start < size) {
+ /* scan a line of 'len' */
+ for (stop = start; stop < size && value[stop] != '\n'; stop++);
+ len = stop - start;
+
+ /* ignore empty lines */
+ if (!len)
+ err = 0;
+
+ /* lines not terminated with '\n' */
+ else if (stop == size)
+ err = -EINVAL;
+
+ /* skip comments */
+ else if (value[start] == '#')
+ err = 0;
+
+ /* line starting with '+' */
+ else if (value[start] == '+')
+ err = tags_add(root, value + start + 1,
+ len - 1, mask);
+
+ /* lines not starting with '+' or '-' */
+ else if (value[start] == '?')
+ err = tags_query(root, value + start + 1,
+ len - 1, mask);
+
+ /* lines not starting with '+', '?' or '-' */
+ else if (value[start] != '-')
+ err = -EINVAL;
+
+ /* lines having removing one tag */
+ else if (len > 1)
+ err = tags_sub(root, value + start + 1,
+ len - 1, mask);
+
+ /* lines removing all tags */
+ else {
+ tags_set_free(root->tagset);
+ root->tagset = NULL;
+ root->mask = 0;
+ err = 0;
+ }
+
+ /* treat the error case if any */
+ if (err)
+ return start ? start : err;
+
+ /* parse next line */
+ start = stop + 1;
+ }
+ return start;
+}
+
+/**
+ * tags_read - Implement the reading of the tags
+ *
+ * @root: tags structure of the readen task
+ * @value: a pointer for storing the read result
+ *
+ * Returns the count of byte read or the negative code -ENOMEM
+ * if an allocation failed.
+ */
+int tags_read(struct tags_root *root, char **value)
+{
+ int length, len;
+ struct tags_link *iter;
+ char *buffer;
+
+ /* compute the read length */
+ length = 0;
+ iter = root->tagset;
+ while (iter) {
+ len = (int)(iter->tag->length);
+ length += 1 + len;
+ iter = iter->next;
+ }
+
+ /* allocates storage for the result */
+ buffer = kmalloc(length, GFP_KERNEL);
+ if (buffer == NULL)
+ return -ENOMEM;
+
+ /* store the result */
+ length = 0;
+ iter = root->tagset;
+ while (iter) {
+ len = (int)(iter->tag->length);
+ memcpy(buffer + length, TAGNAME(iter->tag), len);
+ length += len;
+ buffer[length++] = '\n';
+ iter = iter->next;
+ }
+ *value = buffer;
+ return length;
+}
+
+/**
+ * tags_task_free - Frees tag's data
+ *
+ * @root: tags structure of the target task
+ */
+void tags_task_free(struct tags_root *root)
+{
+ tags_set_free(root->tagset);
+ kmem_cache_free(free_roots_cache, root);
+}
+
+/**
+ * tags_task_alloc_blank - Allocates new tag's data
+ *
+ * @proot: address of tags structure pointer of the target
+ * @gfp: flags for kernel allocations
+ *
+ * Returns 0 on succes or -ENOMEM in case of memory error
+ */
+int tags_task_alloc_blank(struct tags_root **proot, gfp_t gfp)
+{
+ int rc;
+
+ /* lazy init */
+ if (!free_roots_cache) {
+ rc = tags_init();
+ if (rc)
+ return rc;
+ }
+
+ /* process */
+ *proot = kmem_cache_alloc(free_roots_cache, gfp | __GFP_ZERO);
+ return *proot ? 0 : -ENOMEM;
+}
+
+/**
+ * tags_task_prepare - Prepares tag set on security_prepare_creds
+ *
+ * @new_root: tags structure of the new task
+ * @old_root: tags structure of the old task
+ * @gfp: flags for kernel allocations
+ *
+ * Returns 0 on success or -ENOMEM on memory allocation failure.
+ */
+int tags_task_prepare(struct tags_root *new_root,
+ struct tags_root *old_root, gfp_t gfp)
+{
+ struct tags_link *tagset;
+
+ if (!tags_set_clone(old_root->tagset, &tagset, gfp))
+ return -ENOMEM;
+ new_root->tagset = tagset;
+ new_root->mask = old_root->mask;
+ return 0;
+}
+
+/**
+ * tags_task_committing - Prunes tag set on
security_bprm_committing_creds
+ *
+ * @root: tags structure of the target task
+ */
+void tags_task_committing(struct tags_root *root)
+{
+ int keep;
+ const struct tags_tag *tag;
+ struct tags_link *iter;
+ struct tags_link *head;
+ struct tags_link **kept;
+ struct tags_link **thrown;
+
+ /* check if the tag set must be pruned of the predefined tags */
+ iter = root->tagset;
+ if (!(root->mask & FLAG_KEEP_ALL) && iter) {
+ /* split in what is kept and what is thrown */
+ keep = (root->mask & FLAG_KEEP) != 0;
+ thrown = &head;
+ kept = &root->tagset;
+ while (iter) {
+ tag = iter->tag;
+ if (!tag->mask && (keep || *TAGNAME(tag) == KEEPCHAR)) {
+ *kept = iter;
+ kept = &iter->next;
+ } else {
+ *thrown = iter;
+ thrown = &iter->next;
+ }
+ iter = iter->next;
+ }
+ /* finalize the kept tag set */
+ *kept = NULL;
+ root->mask = 0;
+ /* free the thrown links */
+ mutex_lock(&tags_free_links_lock);
+ *thrown = free_links;
+ free_links = head;
+ mutex_unlock(&tags_free_links_lock);
+ }
+}
+
+/**
+ * tags_task_transfer - Transfers tag set on security_transfer_creds
+ *
+ * @new_root: tags structure of the new task
+ * @old_root: tags structure of the old task
+ */
+void tags_task_transfer(struct tags_root *new_root, struct tags_root
*old_root)
+{
+ new_root->tagset = old_root->tagset;
+ new_root->mask = old_root->mask;
+ old_root->tagset = NULL;
+ old_root->mask = 0;
+}
+
diff --git a/security/tags/tags.h b/security/tags/tags.h
new file mode 100644
index 0000000..9e29146
--- /dev/null
+++ b/security/tags/tags.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 Josà Bollo <jobol@xxxxxxxxxxx>
+ *
+ * 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.
+ *
+ * Author:
+ * Josà Bollo <jobol@xxxxxxxxxxx>
+ *
+ */
+
+#ifdef CONFIG_SECURITY_SMACK_TAGS
+
+#ifndef _SECURITY_SMACK_TAGS_H
+#define _SECURITY_SMACK_TAGS_H
+
+struct tags_root;
+
+struct task_smack;
+
+void tags_task_free(struct tags_root *);
+
+int tags_task_alloc_blank(struct tags_root **, gfp_t);
+
+int tags_task_prepare(struct tags_root *, struct tags_root *, gfp_t);
+
+void tags_task_transfer(struct tags_root *, struct tags_root *);
+
+void tags_task_committing(struct tags_root *);
+
+int tags_read(struct tags_root *, char **);
+
+int tags_write(struct tags_root *, struct tags_root *, char *, size_t);
+
+#endif
+
+#endif
+
--
2.1.4




--
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/