]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - security/security.c
evm: Don't deadlock if a crypto algorithm is unavailable
[mirror_ubuntu-bionic-kernel.git] / security / security.c
index b517fcb801d171104423039271a1046837b91e68..81b2336b533de0d594bf970933a2f1145aace2e8 100644 (file)
@@ -29,6 +29,7 @@
 #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>
 
@@ -36,6 +37,8 @@
 
 /* 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 ATOMIC_NOTIFIER_HEAD(lsm_notifier_chain);
@@ -44,11 +47,31 @@ static struct kmem_cache *lsm_file_cache;
 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]
+#ifdef CONFIG_SECURITY_STACKING
+       = CONFIG_SECURITY_DEFAULT_DISPLAY_NAME
+#endif
+;
+static char default_display_lsm[SECURITY_NAME_MAX + 1];
 
 static void __init do_security_initcalls(void)
 {
@@ -126,11 +149,22 @@ int __init security_init(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;
@@ -168,6 +202,7 @@ static int lsm_append(char *new, char **result)
 /**
  * security_module_enable - Load given security module on boot ?
  * @module: the name of the module
+ * @stacked: indicates that the module wants to be stacked
  *
  * Each LSM must pass this method before registering its own operations
  * to avoid security registration races. This method may also be used
@@ -183,11 +218,72 @@ static int lsm_append(char *new, char **result)
  *
  * Otherwise, return false.
  */
-int __init security_module_enable(const char *module)
+#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)
 {
-       return !strcmp(module, chosen_lsm);
+#ifdef CONFIG_SECURITY_STACKING
+       /*
+        * Module defined on the command line security=XXXX
+        */
+       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_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
@@ -200,9 +296,14 @@ void __init security_add_hooks(struct security_hook_list *hooks, int count,
                                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)
@@ -329,6 +430,17 @@ int lsm_file_alloc(struct file *file)
        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
+
 /**
  * lsm_task_alloc - allocate a composite task blob
  * @task: the task that needs a blob
@@ -347,6 +459,14 @@ int lsm_task_alloc(struct task_struct *task)
        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;
 }
 
@@ -1520,6 +1640,80 @@ int security_task_kill(struct task_struct *p, struct siginfo *info,
        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)
 {
@@ -1527,6 +1721,12 @@ int security_task_prctl(int option, unsigned long arg2, unsigned long arg3,
        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) {
@@ -1700,12 +1900,29 @@ EXPORT_SYMBOL(security_d_instantiate);
 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;
+       int rc;
+
+       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;
 }
@@ -1713,12 +1930,33 @@ int security_getprocattr(struct task_struct *p, const char *lsm, char *name,
 int security_setprocattr(const char *lsm, const char *name, void *value,
                         size_t size)
 {
+#ifdef CONFIG_SECURITY_STACKING
+       char *speclsm = lsm_of_task(current);
+#else
+       char *tvalue;
+#endif
        struct security_hook_list *hp;
+       int rc;
+
+       if (!size)
+               return -EINVAL;
+
+       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;
 }
@@ -1750,7 +1988,19 @@ EXPORT_SYMBOL(security_secctx_to_secid);
 
 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);
 
@@ -1869,8 +2119,21 @@ EXPORT_SYMBOL(security_sock_rcv_skb);
 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)