]> git.proxmox.com Git - mirror_ubuntu-zesty-kernel.git/blobdiff - security/apparmor/policy_unpack.c
UBUNTU: [Config] updateconfigs after rebase to Ubuntu-4.10.0-17.19
[mirror_ubuntu-zesty-kernel.git] / security / apparmor / policy_unpack.c
index 138120698f839e049a5cf2460572559f3529e337..da7b81f636162df2594ed12fc2eba3d4a4ab6402 100644 (file)
 #include <asm/unaligned.h>
 #include <linux/ctype.h>
 #include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/jhash.h>
 
 #include "include/apparmor.h"
 #include "include/audit.h"
 #include "include/context.h"
 #include "include/crypto.h"
 #include "include/match.h"
+#include "include/path.h"
 #include "include/policy.h"
 #include "include/policy_unpack.h"
 
+#define K_ABI_MASK 0x3ff
+#define FORCE_COMPLAIN_FLAG 0x800
+#define VERSION_CMP(OP, X, Y) (((X) & K_ABI_MASK) OP ((Y) & K_ABI_MASK))
+
+#define v5     5       /* base version */
+#define v6     6       /* per entry policydb mediation check */
+#define v7     7       /* full network masking */
+
 /*
  * The AppArmor interface treats data as a type byte followed by the
  * actual data.  The interface has the notion of a a named entry
@@ -70,18 +81,23 @@ struct aa_ext {
 static void audit_cb(struct audit_buffer *ab, void *va)
 {
        struct common_audit_data *sa = va;
-       if (sa->aad->iface.target) {
-               struct aa_profile *name = sa->aad->iface.target;
+
+       if (aad(sa)->iface.ns) {
+               audit_log_format(ab, " ns=");
+               audit_log_untrustedstring(ab, aad(sa)->iface.ns);
+       }
+       if (aad(sa)->name) {
                audit_log_format(ab, " name=");
-               audit_log_untrustedstring(ab, name->base.hname);
+               audit_log_untrustedstring(ab, aad(sa)->name);
        }
-       if (sa->aad->iface.pos)
-               audit_log_format(ab, " offset=%ld", sa->aad->iface.pos);
+       if (aad(sa)->iface.pos)
+               audit_log_format(ab, " offset=%ld", aad(sa)->iface.pos);
 }
 
 /**
  * audit_iface - do audit message for policy unpacking/load/replace/remove
  * @new: profile if it has been allocated (MAYBE NULL)
+ * @ns_name: name of the ns the profile is to be loaded to (MAY BE NULL)
  * @name: name of the profile being manipulated (MAYBE NULL)
  * @info: any extra info about the failure (MAYBE NULL)
  * @e: buffer position info
@@ -89,23 +105,33 @@ static void audit_cb(struct audit_buffer *ab, void *va)
  *
  * Returns: %0 or error
  */
-static int audit_iface(struct aa_profile *new, const char *name,
-                      const char *info, struct aa_ext *e, int error)
+static int audit_iface(struct aa_profile *new, const char *ns_name,
+                      const char *name, const char *info, struct aa_ext *e,
+                      int error)
 {
-       struct aa_profile *profile = __aa_current_profile();
-       struct common_audit_data sa;
-       struct apparmor_audit_data aad = {0,};
-       sa.type = LSM_AUDIT_DATA_NONE;
-       sa.aad = &aad;
+       struct aa_profile *profile = labels_profile(aa_current_raw_label());
+       DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL);
        if (e)
-               aad.iface.pos = e->pos - e->start;
-       aad.iface.target = new;
-       aad.name = name;
-       aad.info = info;
-       aad.error = error;
-
-       return aa_audit(AUDIT_APPARMOR_STATUS, profile, GFP_KERNEL, &sa,
-                       audit_cb);
+               aad(&sa)->iface.pos = e->pos - e->start;
+
+       aad(&sa)->iface.ns = ns_name;
+       if (new)
+               aad(&sa)->name = new->base.hname;
+       else
+               aad(&sa)->name = name;
+       aad(&sa)->info = info;
+       aad(&sa)->error = error;
+
+       return aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa, audit_cb);
+}
+
+void aa_loaddata_kref(struct kref *kref)
+{
+       struct aa_loaddata *d = container_of(kref, struct aa_loaddata, count);
+       if (d) {
+               kzfree(d->hash);
+               kvfree(d);
+       }
 }
 
 /* test if read will be in packed data bounds */
@@ -193,6 +219,19 @@ fail:
        return 0;
 }
 
+static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name)
+{
+       if (unpack_nameX(e, AA_U16, name)) {
+               if (!inbounds(e, sizeof(u16)))
+                       return 0;
+               if (data)
+                       *data = le16_to_cpu(get_unaligned((u16 *) e->pos));
+               e->pos += sizeof(u16);
+               return 1;
+       }
+       return 0;
+}
+
 static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name)
 {
        if (unpack_nameX(e, AA_U32, name)) {
@@ -340,12 +379,7 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e)
                        ((e->pos - e->start) & 7);
                size_t pad = ALIGN(sz, 8) - sz;
                int flags = TO_ACCEPT1_FLAG(YYTD_DATA32) |
-                       TO_ACCEPT2_FLAG(YYTD_DATA32);
-
-
-               if (aa_g_paranoid_load)
-                       flags |= DFA_FLAG_VERIFY_STATES;
-
+                       TO_ACCEPT2_FLAG(YYTD_DATA32) | DFA_FLAG_VERIFY_STATES;
                dfa = aa_dfa_unpack(blob + pad, size - pad, flags);
 
                if (IS_ERR(dfa))
@@ -389,7 +423,7 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
                profile->file.trans.size = size;
                for (i = 0; i < size; i++) {
                        char *str;
-                       int c, j, size2 = unpack_strdup(e, &str, NULL);
+                       int c, j, pos, size2 = unpack_strdup(e, &str, NULL);
                        /* unpack_strdup verifies that the last character is
                         * null termination byte.
                         */
@@ -401,19 +435,24 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
                                goto fail;
 
                        /* count internal #  of internal \0 */
-                       for (c = j = 0; j < size2 - 2; j++) {
-                               if (!str[j])
+                       for (c = j = 0; j < size2 - 1; j++) {
+                               if (!str[j]) {
+                                       pos = j;
                                        c++;
+                               }
                        }
                        if (*str == ':') {
+                               /* first character after : must be valid */
+                               if (!str[1])
+                                       goto fail;
                                /* beginning with : requires an embedded \0,
                                 * verify that exactly 1 internal \0 exists
                                 * trailing \0 already verified by unpack_strdup
                                 */
-                               if (c != 1)
-                                       goto fail;
-                               /* first character after : must be valid */
-                               if (!str[1])
+                               if (c == 1)
+                                       /* convert \0 back to : for label_parse */
+                                       str[pos] = ':';
+                               else if (c > 1)
                                        goto fail;
                        } else if (c)
                                /* fail - all other cases with embedded \0 */
@@ -466,27 +505,68 @@ fail:
        return 0;
 }
 
+static void *kvmemdup(const void *src, size_t len)
+{
+       void *p = kvmalloc(len);
+
+       if (p)
+               memcpy(p, src, len);
+       return p;
+}
+
+static u32 strhash(const void *data, u32 len, u32 seed)
+{
+       const char * const *key = data;
+
+       return jhash(*key, strlen(*key), seed);
+}
+
+static int datacmp(struct rhashtable_compare_arg *arg, const void *obj)
+{
+       const struct aa_data *data = obj;
+       const char * const *key = arg->key;
+
+       return strcmp(data->key, *key);
+}
+
 /**
  * unpack_profile - unpack a serialized profile
  * @e: serialized data extent information (NOT NULL)
  *
  * NOTE: unpack profile sets audit struct if there is a failure
  */
-static struct aa_profile *unpack_profile(struct aa_ext *e)
+static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
 {
        struct aa_profile *profile = NULL;
-       const char *name = NULL;
+       const char *tmpname, *tmpns = NULL, *name = NULL;
+       const char *info = "failed to unpack profile";
+       size_t size = 0, ns_len;
+       struct rhashtable_params params = { 0 };
+       char *key = NULL;
+       struct aa_data *data;
        int i, error = -EPROTO;
        kernel_cap_t tmpcap;
        u32 tmp;
 
+       *ns_name = NULL;
+
        /* check that we have the right struct being passed */
        if (!unpack_nameX(e, AA_STRUCT, "profile"))
                goto fail;
        if (!unpack_str(e, &name, NULL))
                goto fail;
+       if (*name == '\0')
+               goto fail;
 
-       profile = aa_alloc_profile(name);
+       tmpname = aa_splitn_fqname(name, strlen(name), &tmpns, &ns_len);
+       if (tmpns) {
+         *ns_name = kstrndup(tmpns, ns_len, GFP_KERNEL);
+               if (!*ns_name)
+                       goto fail;
+               name = tmpname;
+       }
+
+       profile = aa_alloc_profile(name, NULL, GFP_KERNEL);
        if (!profile)
                return ERR_PTR(-ENOMEM);
 
@@ -510,16 +590,19 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
                profile->xmatch_len = tmp;
        }
 
+       /* disconnected attachment string is optional */
+       (void) unpack_str(e, &profile->disconnected, "disconnected");
+
        /* per profile debug flags (complain, audit) */
        if (!unpack_nameX(e, AA_STRUCT, "flags"))
                goto fail;
        if (!unpack_u32(e, &tmp, NULL))
                goto fail;
        if (tmp & PACKED_FLAG_HAT)
-               profile->flags |= PFLAG_HAT;
+               profile->label.flags |= FLAG_HAT;
        if (!unpack_u32(e, &tmp, NULL))
                goto fail;
-       if (tmp == PACKED_MODE_COMPLAIN)
+       if (tmp == PACKED_MODE_COMPLAIN || (e->version & FORCE_COMPLAIN_FLAG))
                profile->mode = APPARMOR_COMPLAIN;
        else if (tmp == PACKED_MODE_KILL)
                profile->mode = APPARMOR_KILL;
@@ -534,11 +617,9 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
                goto fail;
 
        /* path_flags is optional */
-       if (unpack_u32(e, &profile->path_flags, "path_flags"))
-               profile->path_flags |= profile->flags & PFLAG_MEDIATE_DELETED;
-       else
+       if (!unpack_u32(e, &profile->path_flags, "path_flags"))
                /* set a default value if path_flags field is not present */
-               profile->path_flags = PFLAG_MEDIATE_DELETED;
+               profile->path_flags = PATH_MEDIATE_DELETED;
 
        if (!unpack_u32(e, &(profile->caps.allow.cap[0]), NULL))
                goto fail;
@@ -576,6 +657,37 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
        if (!unpack_rlimits(e, profile))
                goto fail;
 
+       size = unpack_array(e, "net_allowed_af");
+       if (size) {
+
+               for (i = 0; i < size; i++) {
+                       /* discard extraneous rules that this kernel will
+                        * never request
+                        */
+                       if (i >= AF_MAX) {
+                               u16 tmp;
+                               if (!unpack_u16(e, &tmp, NULL) ||
+                                   !unpack_u16(e, &tmp, NULL) ||
+                                   !unpack_u16(e, &tmp, NULL))
+                                       goto fail;
+                               continue;
+                       }
+                       if (!unpack_u16(e, &profile->net.allow[i], NULL))
+                               goto fail;
+                       if (!unpack_u16(e, &profile->net.audit[i], NULL))
+                               goto fail;
+                       if (!unpack_u16(e, &profile->net.quiet[i], NULL))
+                               goto fail;
+               }
+               if (!unpack_nameX(e, AA_ARRAYEND, NULL))
+                       goto fail;
+       }
+       if (VERSION_CMP(<, e->version, v7)) {
+               /* old policy always allowed these too */
+               profile->net.allow[AF_UNIX] = 0xffff;
+               profile->net.allow[AF_NETLINK] = 0xffff;
+       }
+
        if (unpack_nameX(e, AA_STRUCT, "policydb")) {
                /* generic policy dfa - optional and may be NULL */
                profile->policy.dfa = unpack_dfa(e);
@@ -599,7 +711,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
                }
                if (!unpack_nameX(e, AA_STRUCTEND, NULL))
                        goto fail;
-       }
+       } else
+               profile->policy.dfa = aa_get_dfa(nulldfa);
 
        /* get file rules */
        profile->file.dfa = unpack_dfa(e);
@@ -607,15 +720,59 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
                error = PTR_ERR(profile->file.dfa);
                profile->file.dfa = NULL;
                goto fail;
-       }
-
-       if (!unpack_u32(e, &profile->file.start, "dfa_start"))
-               /* default start state */
-               profile->file.start = DFA_START;
+       } else if (profile->file.dfa) {
+               if (!unpack_u32(e, &profile->file.start, "dfa_start"))
+                       /* default start state */
+                       profile->file.start = DFA_START;
+       } else if (profile->policy.dfa &&
+                  profile->policy.start[AA_CLASS_FILE]) {
+               profile->file.dfa = aa_get_dfa(profile->policy.dfa);
+               profile->file.start = profile->policy.start[AA_CLASS_FILE];
+       } else
+               profile->file.dfa = aa_get_dfa(nulldfa);
 
        if (!unpack_trans_table(e, profile))
                goto fail;
 
+       if (unpack_nameX(e, AA_STRUCT, "data")) {
+               profile->data = kzalloc(sizeof(*profile->data), GFP_KERNEL);
+               if (!profile->data)
+                       goto fail;
+
+               params.nelem_hint = 3;
+               params.key_len = sizeof(void *);
+               params.key_offset = offsetof(struct aa_data, key);
+               params.head_offset = offsetof(struct aa_data, head);
+               params.hashfn = strhash;
+               params.obj_cmpfn = datacmp;
+
+               if (rhashtable_init(profile->data, &params))
+                       goto fail;
+
+               while (unpack_strdup(e, &key, NULL)) {
+                       data = kzalloc(sizeof(*data), GFP_KERNEL);
+                       if (!data) {
+                               kzfree(key);
+                               goto fail;
+                       }
+
+                       data->key = key;
+                       data->size = unpack_blob(e, &data->data, NULL);
+                       data->data = kvmemdup(data->data, data->size);
+                       if (data->size && !data->data) {
+                               kzfree(data->key);
+                               kzfree(data);
+                               goto fail;
+                       }
+
+                       rhashtable_insert_fast(profile->data, &data->head,
+                                              profile->data->p);
+               }
+
+               if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+                       goto fail;
+       }
+
        if (!unpack_nameX(e, AA_STRUCTEND, NULL))
                goto fail;
 
@@ -626,7 +783,7 @@ fail:
                name = NULL;
        else if (!name)
                name = "unknown";
-       audit_iface(profile, name, "failed to unpack profile", e, error);
+       audit_iface(profile, NULL, name, info, e, error);
        aa_free_profile(profile);
 
        return ERR_PTR(error);
@@ -649,24 +806,32 @@ static int verify_header(struct aa_ext *e, int required, const char **ns)
        /* get the interface version */
        if (!unpack_u32(e, &e->version, "version")) {
                if (required) {
-                       audit_iface(NULL, NULL, "invalid profile format", e,
-                                   error);
-                       return error;
-               }
-
-               /* check that the interface version is currently supported */
-               if (e->version != 5) {
-                       audit_iface(NULL, NULL, "unsupported interface version",
+                       audit_iface(NULL, NULL, NULL, "invalid profile format",
                                    e, error);
                        return error;
                }
        }
 
+       /* Check that the interface version is currently supported.
+        * if not specified use previous version
+        * Mask off everything that is not kernel abi version
+        */
+       if (VERSION_CMP(<, e->version, v5) && VERSION_CMP(>, e->version, v7)) {
+               audit_iface(NULL, NULL, NULL, "unsupported interface version",
+                           e, error);
+               return error;
+       }
 
        /* read the namespace if present */
        if (unpack_str(e, &name, "namespace")) {
+               if (*name == '\0') {
+                       audit_iface(NULL, NULL, NULL, "invalid namespace name",
+                                   e, error);
+                       return error;
+               }
                if (*ns && strcmp(*ns, name))
-                       audit_iface(NULL, NULL, "invalid ns change", e, error);
+                       audit_iface(NULL, NULL, NULL, "invalid ns change", e,
+                                   error);
                else if (!*ns)
                        *ns = name;
        }
@@ -705,14 +870,12 @@ static bool verify_dfa_xindex(struct aa_dfa *dfa, int table_size)
  */
 static int verify_profile(struct aa_profile *profile)
 {
-       if (aa_g_paranoid_load) {
-               if (profile->file.dfa &&
-                   !verify_dfa_xindex(profile->file.dfa,
-                                      profile->file.trans.size)) {
-                       audit_iface(profile, NULL, "Invalid named transition",
-                                   NULL, -EPROTO);
-                       return -EPROTO;
-               }
+       if (profile->file.dfa &&
+           !verify_dfa_xindex(profile->file.dfa,
+                              profile->file.trans.size)) {
+               audit_iface(profile, NULL, NULL,
+                           "Invalid named transition", NULL, -EPROTO);
+               return -EPROTO;
        }
 
        return 0;
@@ -724,6 +887,7 @@ void aa_load_ent_free(struct aa_load_ent *ent)
                aa_put_profile(ent->rename);
                aa_put_profile(ent->old);
                aa_put_profile(ent->new);
+               kfree(ent->ns_name);
                kzfree(ent);
        }
 }
@@ -739,7 +903,6 @@ struct aa_load_ent *aa_load_ent_alloc(void)
 /**
  * aa_unpack - unpack packed binary profile(s) data loaded from user space
  * @udata: user data copied to kmem  (NOT NULL)
- * @size: the size of the user data
  * @lh: list to place unpacked profiles in a aa_repl_ws
  * @ns: Returns namespace profile is in if specified else NULL (NOT NULL)
  *
@@ -749,26 +912,26 @@ struct aa_load_ent *aa_load_ent_alloc(void)
  *
  * Returns: profile(s) on @lh else error pointer if fails to unpack
  */
-int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns)
+int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns)
 {
        struct aa_load_ent *tmp, *ent;
        struct aa_profile *profile = NULL;
        int error;
        struct aa_ext e = {
-               .start = udata,
-               .end = udata + size,
-               .pos = udata,
+               .start = udata->data,
+               .end = udata->data + udata->size,
+               .pos = udata->data,
        };
 
        *ns = NULL;
        while (e.pos < e.end) {
+               char *ns_name = NULL;
                void *start;
                error = verify_header(&e, e.pos == e.start, ns);
                if (error)
                        goto fail;
-
                start = e.pos;
-               profile = unpack_profile(&e);
+               profile = unpack_profile(&e, &ns_name);
                if (IS_ERR(profile)) {
                        error = PTR_ERR(profile);
                        goto fail;
@@ -778,7 +941,8 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns)
                if (error)
                        goto fail_profile;
 
-               error = aa_calc_profile_hash(profile, e.version, start,
+               if (aa_g_hash_policy)
+                       error = aa_calc_profile_hash(profile, e.version, start,
                                                     e.pos - start);
                if (error)
                        goto fail_profile;
@@ -790,9 +954,18 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns)
                }
 
                ent->new = profile;
+               ent->ns_name = ns_name;
                list_add_tail(&ent->list, lh);
        }
-
+       udata->abi = e.version & K_ABI_MASK;
+       if (aa_g_hash_policy) {
+               udata->hash = aa_calc_hash(udata->data, udata->size);
+               if (IS_ERR(udata->hash)) {
+                       error = PTR_ERR(udata->hash);
+                       udata->hash = NULL;
+                       goto fail;
+               }
+       }
        return 0;
 
 fail_profile: