#include <linux/backing-dev.h>
#include <linux/string.h>
#include <linux/msg.h>
+#include <linux/prctl.h>
#include <net/flow.h>
#include <net/sock.h>
/* Maximum number of letters for an LSM name string */
#define SECURITY_NAME_MAX 10
+#define SECURITY_CHOSEN_NAMES_MAX (SECURITY_NAME_MAX * LSM_MAX_MAJOR)
#define MODULE_STACK "(stacking)"
struct security_hook_heads security_hook_heads __lsm_ro_after_init;
static struct kmem_cache *lsm_inode_cache;
char *lsm_names;
-static struct lsm_blob_sizes blob_sizes;
+
+/*
+ * If stacking is enabled the task blob will always
+ * include an indicator of what security module data
+ * should be displayed. This is set with PR_SET_DISPLAY_LSM.
+ */
+static struct lsm_blob_sizes blob_sizes = {
+#ifdef CONFIG_SECURITY_STACKING
+ .lbs_task = SECURITY_NAME_MAX + 6,
+#endif
+};
/* Boot-time LSM user choice */
-static __initdata char chosen_lsm[SECURITY_NAME_MAX + 1] =
+static __initdata char chosen_lsms[SECURITY_CHOSEN_NAMES_MAX + 1] =
#ifdef CONFIG_SECURITY_STACKING
MODULE_STACK;
#else
CONFIG_DEFAULT_SECURITY;
#endif
+static __initdata char chosen_display_lsm[SECURITY_NAME_MAX + 1];
+static char default_display_lsm[SECURITY_NAME_MAX + 1];
static void __init do_security_initcalls(void)
{
/* Save user chosen LSM */
static int __init choose_lsm(char *str)
{
- strncpy(chosen_lsm, str, SECURITY_NAME_MAX);
+ strncpy(chosen_lsms, str, SECURITY_CHOSEN_NAMES_MAX);
+ pr_info("LSM: command line set '%s' security module(s).\n",
+ chosen_lsms);
return 1;
}
__setup("security=", choose_lsm);
+static int __init choose_display_lsm(char *str)
+{
+ strncpy(chosen_display_lsm, str, SECURITY_NAME_MAX);
+ pr_info("LSM: command line set default display lsm %s'\n",
+ chosen_display_lsm);
+ return 1;
+}
+__setup("security.display=", choose_display_lsm);
+
static bool match_last_lsm(const char *list, const char *lsm)
{
const char *last;
*
* Otherwise, return false.
*/
+#ifdef CONFIG_SECURITY_STACKING
+static bool __init cmp_lsms(const char *lsm)
+{
+ const char *str = chosen_lsms;
+ const char *split;
+ int len = strlen(lsm);
+
+ if (len > SECURITY_NAME_MAX) {
+ pr_info("LSM: security module name '%s' exceeds limit\n", lsm);
+ return false;
+ }
+ for (split = strchr(str, ','); split; split = strchr(str, ',')) {
+ if ((len == split - str) && !strncmp(lsm, str, split - str))
+ return true;
+ str = split + 1;
+ }
+ if ((len == strlen(str)) && !strncmp(lsm, str, strlen(str)))
+ return true;
+ return false;
+}
+#endif
+
bool __init security_module_enable(const char *lsm, const bool stacked)
{
#ifdef CONFIG_SECURITY_STACKING
/*
* Module defined on the command line security=XXXX
*/
- if (strcmp(chosen_lsm, MODULE_STACK)) {
- if (!strcmp(lsm, chosen_lsm)) {
- pr_info("Command line sets the %s security module.\n",
- lsm);
+ if (strcmp(chosen_lsms, MODULE_STACK)) {
+ if (cmp_lsms(lsm)) {
+ /* set to first LSM registered and then override */
+ if (!*default_display_lsm)
+ strcpy(default_display_lsm, lsm);
+ else if (*chosen_display_lsm && !strcmp(chosen_display_lsm, lsm)) {
+ strcpy(default_display_lsm, lsm);
+ pr_info("LSM: default display lsm '%s'\n", default_display_lsm);
+ }
return true;
}
return false;
/*
* Module configured as stacked.
*/
+ if (stacked && !*default_display_lsm)
+ strcpy(default_display_lsm, lsm);
+ else if (stacked && *chosen_display_lsm && !strcmp(chosen_display_lsm, lsm)) {
+ strcpy(default_display_lsm, lsm);
+ pr_info("LSM: default display lsm '%s'\n", default_display_lsm);
+ }
+
return stacked;
#else
- if (strcmp(lsm, chosen_lsm) == 0)
+ if (strcmp(lsm, chosen_lsms) == 0) {
+ strcpy(default_display_lsm, lsm);
return true;
+ }
return false;
#endif
}
+/*
+ * Keep the order of major modules for mapping secids.
+ */
+static int lsm_next_major;
+
/**
* security_add_hooks - Add a modules hooks to the hook lists.
* @hooks: the hooks to add
char *lsm)
{
int i;
+ int lsm_index = lsm_next_major++;
+#ifdef CONFIG_SECURITY_LSM_DEBUG
+ pr_info("LSM: Security module %s gets index %d\n", lsm, lsm_index);
+#endif
for (i = 0; i < count; i++) {
hooks[i].lsm = lsm;
+ hooks[i].lsm_index = lsm_index;
list_add_tail_rcu(&hooks[i].list, hooks[i].head);
}
if (lsm_append(lsm, &lsm_names) < 0)
return 0;
}
+#ifdef CONFIG_SECURITY_STACKING
+static inline char *lsm_of_task(struct task_struct *task)
+{
+#ifdef CONFIG_SECURITY_LSM_DEBUG
+ if (task->security == NULL)
+ pr_info("%s: task has no lsm name.\n", __func__);
+#endif
+ return task->security;
+}
+#endif
+
+#ifdef CONFIG_SECURITY_STACKING
+struct lsm_value {
+ char *lsm;
+ char *data;
+};
+
+/**
+ * lsm_parse_context - break a compound "context" into module data
+ * @cxt: the initial data, which will be modified
+ * @vlist: an array to receive the results
+ *
+ * Returns the number of entries, or -EINVAL if the cxt is unworkable.
+ */
+static int lsm_parse_context(char *cxt, struct lsm_value *vlist)
+{
+ char *lsm;
+ char *data;
+ char *cp;
+ int i;
+
+ lsm = cxt;
+ for (i = 0; i < LSM_MAX_MAJOR; i++) {
+ data = strstr(lsm, "='");
+ if (!data)
+ break;
+ *data = '\0';
+ data += 2;
+ cp = strchr(data, '\'');
+ if (!cp)
+ return -EINVAL;
+ *cp++ = '\0';
+ vlist[i].lsm = lsm;
+ vlist[i].data = data;
+ if (*cp == '\0') {
+ i++;
+ break;
+ }
+ if (*cp == ',')
+ cp++;
+ else
+ return -EINVAL;
+ lsm = cp;
+ }
+ return i;
+}
+#endif /* CONFIG_SECURITY_STACKING */
+
/**
* lsm_task_alloc - allocate a composite task blob
* @task: the task that needs a blob
task->security = kzalloc(blob_sizes.lbs_task, GFP_KERNEL);
if (task->security == NULL)
return -ENOMEM;
+
+ /* inherit current display lsm */
+#ifdef CONFIG_SECURITY_STACKING
+ if (current->security)
+ strcpy(task->security, lsm_of_task(current));
+ else
+ strcpy(task->security, default_display_lsm);
+#endif
return 0;
}
return call_int_hook(task_kill, 0, p, info, sig, secid);
}
+#ifdef CONFIG_SECURITY_STACKING
+static char *nolsm = "-default";
+#define NOLSMLEN 9
+
+static bool is_registered_lsm(const char *str, size_t size)
+{
+ struct security_hook_list *hp;
+
+ list_for_each_entry(hp, &security_hook_heads.getprocattr, list) {
+ if (size == strlen(hp->lsm) && !strncmp(str, hp->lsm, size))
+ return true;
+ }
+
+ return false;
+}
+
+static bool set_lsm_of_current(const char *str, size_t size)
+{
+ char *lsm = lsm_of_task(current);
+
+ if (is_registered_lsm(str, size)) {
+ strncpy(lsm, str, size);
+ lsm[size] = '\0';
+ } else if (size == NOLSMLEN && !strncmp(str, nolsm, size)) {
+ lsm[0] = '\0';
+ } else {
+ return false;
+ }
+ return true;
+}
+
+static int lsm_task_prctl(int option, unsigned long arg2, unsigned long arg3,
+ unsigned long arg4, unsigned long arg5)
+{
+ char *lsm = lsm_of_task(current);
+ char buffer[SECURITY_NAME_MAX + 1];
+ __user char *optval = (__user char *)arg2;
+ __user int *optlen = (__user int *)arg3;
+ int dlen;
+ int len;
+
+ switch (option) {
+ case PR_GET_DISPLAY_LSM:
+ len = arg4;
+ if (lsm[0] == '\0') {
+ lsm = nolsm;
+ dlen = NOLSMLEN;
+ } else
+ dlen = strlen(lsm) + 1;
+ if (dlen > len)
+ return -ERANGE;
+ if (copy_to_user(optval, lsm, dlen))
+ return -EFAULT;
+ if (put_user(dlen, optlen))
+ return -EFAULT;
+ break;
+ case PR_SET_DISPLAY_LSM:
+ len = arg3;
+ if (len > SECURITY_NAME_MAX)
+ return -EINVAL;
+ if (copy_from_user(buffer, optval, len))
+ return -EFAULT;
+ buffer[len] = '\0';
+ /* verify the requested LSM is registered */
+ if (!set_lsm_of_current(buffer, len))
+ return -ENOENT;
+ break;
+ default:
+ return -ENOSYS;
+ }
+ return 0;
+}
+#endif
+
int security_task_prctl(int option, unsigned long arg2, unsigned long arg3,
unsigned long arg4, unsigned long arg5)
{
int rc = -ENOSYS;
struct security_hook_list *hp;
+#ifdef CONFIG_SECURITY_STACKING
+ rc = lsm_task_prctl(option, arg2, arg3, arg4, arg5);
+ if (rc != -ENOSYS)
+ return rc;
+#endif
+
list_for_each_entry(hp, &security_hook_heads.task_prctl, list) {
thisrc = hp->hook.task_prctl(option, arg2, arg3, arg4, arg5);
if (thisrc != -ENOSYS) {
int security_getprocattr(struct task_struct *p, const char *lsm, char *name,
char **value)
{
+#ifdef CONFIG_SECURITY_STACKING
+ char *speclsm = lsm_of_task(p);
+#endif
struct security_hook_list *hp;
+ char *vp;
+ char *cp = NULL;
+ int trc;
+ int rc;
+
+ /*
+ * "context" requires work here in addition to what
+ * the modules provide.
+ */
+ if (strcmp(name, "context") == 0) {
+ *value = NULL;
+ rc = -EINVAL;
+ list_for_each_entry(hp,
+ &security_hook_heads.getprocattr, list) {
+ if (lsm != NULL && strcmp(lsm, hp->lsm))
+ continue;
+ trc = hp->hook.getprocattr(p, "context", &vp);
+ if (trc == -ENOENT)
+ continue;
+ if (trc <= 0) {
+ kfree(*value);
+ return trc;
+ }
+ rc = trc;
+ if (*value == NULL) {
+ *value = vp;
+ } else {
+ cp = kasprintf(GFP_KERNEL, "%s,%s", *value, vp);
+ if (cp == NULL) {
+ kfree(*value);
+ kfree(vp);
+ return -ENOMEM;
+ }
+ kfree(*value);
+ kfree(vp);
+ *value = cp;
+ }
+ }
+ if (rc > 0)
+ return strlen(*value);
+ return rc;
+ } else if (strcmp(name, "display_lsm") == 0) {
+ *value = kstrdup(current->security, GFP_KERNEL);
+ if (*value == NULL)
+ return -ENOMEM;
+ return strlen(*value);
+ }
list_for_each_entry(hp, &security_hook_heads.getprocattr, list) {
if (lsm != NULL && strcmp(lsm, hp->lsm))
continue;
- return hp->hook.getprocattr(p, name, value);
+#ifdef CONFIG_SECURITY_STACKING
+ if (!lsm && speclsm && speclsm[0] && strcmp(speclsm, hp->lsm))
+ continue;
+#endif
+ rc = hp->hook.getprocattr(p, name, value);
+ if (rc != -ENOSYS)
+ return rc;
}
return -EINVAL;
}
int security_setprocattr(const char *lsm, const char *name, void *value,
size_t size)
{
+#ifdef CONFIG_SECURITY_STACKING
+ char *speclsm = lsm_of_task(current);
+ struct lsm_value *lsm_value = NULL;
+ int count;
+#else
+ char *tvalue;
+#endif
struct security_hook_list *hp;
+ int rc;
+ char *temp;
+ char *cp;
+
+ /*
+ * If lsm is NULL look at all the modules to find one
+ * that processes name. If lsm is not NULL only look at
+ * that module.
+ *
+ * "context" is handled directly here.
+ */
+ if (strcmp(name, "context") == 0) {
+ rc = -EINVAL;
+ temp = kmemdup(value, size + 1, GFP_KERNEL);
+ if (!temp)
+ return -ENOMEM;
+
+ temp[size] = '\0';
+ cp = strrchr(temp, '\'');
+ if (!cp)
+ goto free_out;
+
+ cp[1] = '\0';
+#ifdef CONFIG_SECURITY_STACKING
+ lsm_value = kzalloc(sizeof(*lsm_value) * LSM_MAX_MAJOR,
+ GFP_KERNEL);
+ if (!lsm_value) {
+ rc = -ENOMEM;
+ goto free_out;
+ }
+
+ count = lsm_parse_context(temp, lsm_value);
+ if (count <= 0)
+ goto free_out;
+
+ for (count--; count >= 0; count--) {
+ list_for_each_entry(hp,
+ &security_hook_heads.setprocattr, list) {
+
+ if (lsm && strcmp(lsm, hp->lsm))
+ continue;
+ if (!strcmp(hp->lsm, lsm_value[count].lsm)) {
+ rc = hp->hook.setprocattr("context",
+ lsm_value[count].data,
+ strlen(lsm_value[count].data));
+ break;
+ }
+ }
+ if (rc < 0 || (lsm && rc >0))
+ break;
+ }
+#else /* CONFIG_SECURITY_STACKING */
+ cp = strstr(temp, "='");
+ if (!cp)
+ goto free_out;
+ *cp = '\0';
+ tvalue = strchr(cp + 2, '\'');
+ if (!tvalue)
+ goto free_out;
+ list_for_each_entry(hp, &security_hook_heads.setprocattr,
+ list) {
+ if (lsm == NULL || !strcmp(lsm, hp->lsm)) {
+ rc = hp->hook.setprocattr(name, tvalue, size);
+ break;
+ }
+ }
+#endif /* CONFIG_SECURITY_STACKING */
+free_out:
+ kfree(temp);
+#ifdef CONFIG_SECURITY_STACKING
+ kfree(lsm_value);
+#endif
+ if (rc >= 0)
+ return size;
+ return rc;
+ } else if (strcmp(name, "display_lsm") == 0) {
+#ifdef CONFIG_SECURITY_STACKING
+ if (set_lsm_of_current(value, size))
+ return size;
+#endif
+ return -EINVAL;
+ }
list_for_each_entry(hp, &security_hook_heads.setprocattr, list) {
- if (lsm != NULL && strcmp(lsm, hp->lsm))
+#ifdef CONFIG_SECURITY_STACKING
+ if (!lsm && speclsm && speclsm[0] && strcmp(speclsm, hp->lsm))
continue;
- return hp->hook.setprocattr(name, value, size);
+#endif
+ rc = hp->hook.setprocattr(name, value, size);
+ if (rc)
+ return rc;
}
return -EINVAL;
}
void security_release_secctx(char *secdata, u32 seclen)
{
- call_void_hook(release_secctx, secdata, seclen);
+#ifdef CONFIG_SECURITY_STACKING
+ char *speclsm = lsm_of_task(current);
+#endif
+ struct security_hook_list *hp;
+
+ list_for_each_entry(hp, &security_hook_heads.release_secctx, list) {
+#ifdef CONFIG_SECURITY_STACKING
+ if (speclsm[0] && strcmp(hp->lsm, speclsm))
+ continue;
+#endif
+ hp->hook.release_secctx(secdata, seclen);
+ break;
+ }
}
EXPORT_SYMBOL(security_release_secctx);
int security_socket_getpeersec_stream(struct socket *sock, char __user *optval,
int __user *optlen, unsigned len)
{
+#ifdef CONFIG_SECURITY_STACKING
+ struct security_hook_list *hp;
+ char *lsm = lsm_of_task(current);
+
+ list_for_each_entry(hp, &security_hook_heads.socket_getpeersec_stream,
+ list) {
+ if (!lsm || !lsm[0] || !strcmp(lsm, hp->lsm))
+ return hp->hook.socket_getpeersec_stream(sock, optval,
+ optlen, len);
+ }
+ return -ENOPROTOOPT;
+#else
return call_int_hook(socket_getpeersec_stream, -ENOPROTOOPT, sock,
optval, optlen, len);
+#endif
}
int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb, u32 *secid)