root/security/apparmor/policy_unpack.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * AppArmor security module
 *
 * This file contains AppArmor functions for unpacking policy loaded from
 * userspace.
 *
 * Copyright (C) 1998-2008 Novell/SUSE
 * Copyright 2009-2010 Canonical Ltd.
 *
 * AppArmor uses a serialized binary format for loading policy. To find
 * policy format documentation see Documentation/admin-guide/LSM/apparmor.rst
 * All policy is validated before it is used.
 */

#include <linux/unaligned.h>
#include <kunit/visibility.h>
#include <linux/ctype.h>
#include <linux/errno.h>
#include <linux/zstd.h>

#include "include/apparmor.h"
#include "include/audit.h"
#include "include/cred.h"
#include "include/crypto.h"
#include "include/file.h"
#include "include/match.h"
#include "include/path.h"
#include "include/policy.h"
#include "include/policy_unpack.h"
#include "include/policy_compat.h"
#include "include/signal.h"

/* audit callback for unpack fields */
static void audit_cb(struct audit_buffer *ab, void *va)
{
        struct common_audit_data *sa = va;
        struct apparmor_audit_data *ad = aad(sa);

        if (ad->iface.ns) {
                audit_log_format(ab, " ns=");
                audit_log_untrustedstring(ab, ad->iface.ns);
        }
        if (ad->name) {
                audit_log_format(ab, " name=");
                audit_log_untrustedstring(ab, ad->name);
        }
        if (ad->iface.pos)
                audit_log_format(ab, " offset=%ld", ad->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
 * @error: error code
 *
 * Returns: %0 or 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 = labels_profile(aa_current_raw_label());
        DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, NULL);
        if (e)
                ad.iface.pos = e->pos - e->start;
        ad.iface.ns = ns_name;
        if (new)
                ad.name = new->base.hname;
        else
                ad.name = name;
        ad.info = info;
        ad.error = error;

        return aa_audit(AUDIT_APPARMOR_STATUS, profile, &ad, audit_cb);
}

void __aa_loaddata_update(struct aa_loaddata *data, long revision)
{
        AA_BUG(!data);
        AA_BUG(!data->ns);
        AA_BUG(!mutex_is_locked(&data->ns->lock));
        AA_BUG(data->revision > revision);

        data->revision = revision;
        if ((data->dents[AAFS_LOADDATA_REVISION])) {
                struct inode *inode;

                inode = d_inode(data->dents[AAFS_LOADDATA_DIR]);
                inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));

                inode = d_inode(data->dents[AAFS_LOADDATA_REVISION]);
                inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
        }
}

bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r)
{
        if (l->size != r->size)
                return false;
        if (l->compressed_size != r->compressed_size)
                return false;
        if (aa_g_hash_policy && memcmp(l->hash, r->hash, aa_hash_size()) != 0)
                return false;
        return memcmp(l->data, r->data, r->compressed_size ?: r->size) == 0;
}

static void do_loaddata_free(struct aa_loaddata *d)
{
        kfree_sensitive(d->hash);
        kfree_sensitive(d->name);
        kvfree(d->data);
        kfree_sensitive(d);
}

void aa_loaddata_kref(struct kref *kref)
{
        struct aa_loaddata *d = container_of(kref, struct aa_loaddata,
                                             count.count);

        do_loaddata_free(d);
}

/*
 * need to take the ns mutex lock which is NOT safe most places that
 * put_loaddata is called, so we have to delay freeing it
 */
static void do_ploaddata_rmfs(struct work_struct *work)
{
        struct aa_loaddata *d = container_of(work, struct aa_loaddata, work);
        struct aa_ns *ns = aa_get_ns(d->ns);

        if (ns) {
                mutex_lock_nested(&ns->lock, ns->level);
                /* remove fs ref to loaddata */
                __aa_fs_remove_rawdata(d);
                mutex_unlock(&ns->lock);
                aa_put_ns(ns);
        }
        /* called by dropping last pcount, so drop its associated icount */
        aa_put_i_loaddata(d);
}

void aa_ploaddata_kref(struct kref *kref)
{
        struct aa_loaddata *d = container_of(kref, struct aa_loaddata, pcount);

        if (d) {
                INIT_WORK(&d->work, do_ploaddata_rmfs);
                schedule_work(&d->work);
        }
}

struct aa_loaddata *aa_loaddata_alloc(size_t size)
{
        struct aa_loaddata *d;

        d = kzalloc_obj(*d);
        if (d == NULL)
                return ERR_PTR(-ENOMEM);
        d->data = kvzalloc(size, GFP_KERNEL);
        if (!d->data) {
                kfree(d);
                return ERR_PTR(-ENOMEM);
        }
        kref_init(&d->count.count);
        d->count.reftype = REF_RAWDATA;
        kref_init(&d->pcount);
        INIT_LIST_HEAD(&d->list);

        return d;
}

/* test if read will be in packed data bounds */
VISIBLE_IF_KUNIT bool aa_inbounds(struct aa_ext *e, size_t size)
{
        return (size <= e->end - e->pos);
}
EXPORT_SYMBOL_IF_KUNIT(aa_inbounds);

/**
 * aa_unpack_u16_chunk - test and do bounds checking for a u16 size based chunk
 * @e: serialized data read head (NOT NULL)
 * @chunk: start address for chunk of data (NOT NULL)
 *
 * Returns: the size of chunk found with the read head at the end of the chunk.
 */
VISIBLE_IF_KUNIT size_t aa_unpack_u16_chunk(struct aa_ext *e, char **chunk)
{
        size_t size = 0;
        void *pos = e->pos;

        if (!aa_inbounds(e, sizeof(u16)))
                goto fail;
        size = le16_to_cpu(get_unaligned((__le16 *) e->pos));
        e->pos += sizeof(__le16);
        if (!aa_inbounds(e, size))
                goto fail;
        *chunk = e->pos;
        e->pos += size;
        return size;

fail:
        e->pos = pos;
        return 0;
}
EXPORT_SYMBOL_IF_KUNIT(aa_unpack_u16_chunk);

/* unpack control byte */
VISIBLE_IF_KUNIT bool aa_unpack_X(struct aa_ext *e, enum aa_code code)
{
        if (!aa_inbounds(e, 1))
                return false;
        if (*(u8 *) e->pos != code)
                return false;
        e->pos++;
        return true;
}
EXPORT_SYMBOL_IF_KUNIT(aa_unpack_X);

/**
 * aa_unpack_nameX - check is the next element is of type X with a name of @name
 * @e: serialized data extent information  (NOT NULL)
 * @code: type code
 * @name: name to match to the serialized element.  (MAYBE NULL)
 *
 * check that the next serialized data element is of type X and has a tag
 * name @name.  If @name is specified then there must be a matching
 * name element in the stream.  If @name is NULL any name element will be
 * skipped and only the typecode will be tested.
 *
 * Returns true on success (both type code and name tests match) and the read
 * head is advanced past the headers
 *
 * Returns: false if either match fails, the read head does not move
 */
VISIBLE_IF_KUNIT bool aa_unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name)
{
        /*
         * May need to reset pos if name or type doesn't match
         */
        void *pos = e->pos;
        /*
         * Check for presence of a tagname, and if present name size
         * AA_NAME tag value is a u16.
         */
        if (aa_unpack_X(e, AA_NAME)) {
                char *tag = NULL;
                size_t size = aa_unpack_u16_chunk(e, &tag);
                /* if a name is specified it must match. otherwise skip tag */
                if (name && (!size || tag[size-1] != '\0' || strcmp(name, tag)))
                        goto fail;
        } else if (name) {
                /* if a name is specified and there is no name tag fail */
                goto fail;
        }

        /* now check if type code matches */
        if (aa_unpack_X(e, code))
                return true;

fail:
        e->pos = pos;
        return false;
}
EXPORT_SYMBOL_IF_KUNIT(aa_unpack_nameX);

static bool unpack_u8(struct aa_ext *e, u8 *data, const char *name)
{
        void *pos = e->pos;

        if (aa_unpack_nameX(e, AA_U8, name)) {
                if (!aa_inbounds(e, sizeof(u8)))
                        goto fail;
                if (data)
                        *data = *((u8 *)e->pos);
                e->pos += sizeof(u8);
                return true;
        }

fail:
        e->pos = pos;
        return false;
}

VISIBLE_IF_KUNIT bool aa_unpack_u32(struct aa_ext *e, u32 *data, const char *name)
{
        void *pos = e->pos;

        if (aa_unpack_nameX(e, AA_U32, name)) {
                if (!aa_inbounds(e, sizeof(u32)))
                        goto fail;
                if (data)
                        *data = le32_to_cpu(get_unaligned((__le32 *) e->pos));
                e->pos += sizeof(u32);
                return true;
        }

fail:
        e->pos = pos;
        return false;
}
EXPORT_SYMBOL_IF_KUNIT(aa_unpack_u32);

VISIBLE_IF_KUNIT bool aa_unpack_u64(struct aa_ext *e, u64 *data, const char *name)
{
        void *pos = e->pos;

        if (aa_unpack_nameX(e, AA_U64, name)) {
                if (!aa_inbounds(e, sizeof(u64)))
                        goto fail;
                if (data)
                        *data = le64_to_cpu(get_unaligned((__le64 *) e->pos));
                e->pos += sizeof(u64);
                return true;
        }

fail:
        e->pos = pos;
        return false;
}
EXPORT_SYMBOL_IF_KUNIT(aa_unpack_u64);

static bool aa_unpack_cap_low(struct aa_ext *e, kernel_cap_t *data, const char *name)
{
        u32 val;

        if (!aa_unpack_u32(e, &val, name))
                return false;
        data->val = val;
        return true;
}

static bool aa_unpack_cap_high(struct aa_ext *e, kernel_cap_t *data, const char *name)
{
        u32 val;

        if (!aa_unpack_u32(e, &val, name))
                return false;
        data->val = (u32)data->val | ((u64)val << 32);
        return true;
}

VISIBLE_IF_KUNIT bool aa_unpack_array(struct aa_ext *e, const char *name, u16 *size)
{
        void *pos = e->pos;

        if (aa_unpack_nameX(e, AA_ARRAY, name)) {
                if (!aa_inbounds(e, sizeof(u16)))
                        goto fail;
                *size = le16_to_cpu(get_unaligned((__le16 *) e->pos));
                e->pos += sizeof(u16);
                return true;
        }

fail:
        e->pos = pos;
        return false;
}
EXPORT_SYMBOL_IF_KUNIT(aa_unpack_array);

VISIBLE_IF_KUNIT size_t aa_unpack_blob(struct aa_ext *e, char **blob, const char *name)
{
        void *pos = e->pos;

        if (aa_unpack_nameX(e, AA_BLOB, name)) {
                u32 size;
                if (!aa_inbounds(e, sizeof(u32)))
                        goto fail;
                size = le32_to_cpu(get_unaligned((__le32 *) e->pos));
                e->pos += sizeof(u32);
                if (aa_inbounds(e, (size_t) size)) {
                        *blob = e->pos;
                        e->pos += size;
                        return size;
                }
        }

fail:
        e->pos = pos;
        return 0;
}
EXPORT_SYMBOL_IF_KUNIT(aa_unpack_blob);

VISIBLE_IF_KUNIT int aa_unpack_str(struct aa_ext *e, const char **string, const char *name)
{
        char *src_str;
        size_t size = 0;
        void *pos = e->pos;
        *string = NULL;
        if (aa_unpack_nameX(e, AA_STRING, name)) {
                size = aa_unpack_u16_chunk(e, &src_str);
                if (size) {
                        /* strings are null terminated, length is size - 1 */
                        if (src_str[size - 1] != 0)
                                goto fail;
                        *string = src_str;

                        return size;
                }
        }

fail:
        e->pos = pos;
        return 0;
}
EXPORT_SYMBOL_IF_KUNIT(aa_unpack_str);

VISIBLE_IF_KUNIT int aa_unpack_strdup(struct aa_ext *e, char **string, const char *name)
{
        const char *tmp;
        void *pos = e->pos;
        int res = aa_unpack_str(e, &tmp, name);
        *string = NULL;

        if (!res)
                return 0;

        *string = kmemdup(tmp, res, GFP_KERNEL);
        if (!*string) {
                e->pos = pos;
                return 0;
        }

        return res;
}
EXPORT_SYMBOL_IF_KUNIT(aa_unpack_strdup);


/**
 * unpack_dfa - unpack a file rule dfa
 * @e: serialized data extent information (NOT NULL)
 * @flags: dfa flags to check
 *
 * returns dfa or ERR_PTR or NULL if no dfa
 */
static struct aa_dfa *unpack_dfa(struct aa_ext *e, int flags)
{
        char *blob = NULL;
        size_t size;
        struct aa_dfa *dfa = NULL;

        size = aa_unpack_blob(e, &blob, "aadfa");
        if (size) {
                /*
                 * The dfa is aligned with in the blob to 8 bytes
                 * from the beginning of the stream.
                 * alignment adjust needed by dfa unpack
                 */
                size_t sz = blob - (char *) e->start -
                        ((e->pos - e->start) & 7);
                size_t pad = ALIGN(sz, 8) - sz;
                if (aa_g_paranoid_load)
                        flags |= DFA_FLAG_VERIFY_STATES;
                dfa = aa_dfa_unpack(blob + pad, size - pad, flags);

                if (IS_ERR(dfa))
                        return dfa;

        }

        return dfa;
}

static int process_strs_entry(char *str, int size, bool multi)
{
        int c = 1;

        if (size <= 0)
                return -1;
        if (multi) {
                if (size < 2)
                        return -2;
                /* multi ends with double \0 */
                if (str[size - 2])
                        return -3;
        }

        char *save = str;
        char *pos = str;
        char *end = multi ? str + size - 2 : str + size - 1;
        /* count # of internal \0 */
        while (str < end) {
                if (str == pos) {
                        /* starts with ... */
                        if (!*str) {
                                AA_DEBUG(DEBUG_UNPACK,
                                         "starting with null save=%lu size %d c=%d",
                                         (unsigned long)(str - save), size, c);
                                return -4;
                        }
                        if (isspace(*str))
                                return -5;
                        if (*str == ':') {
                                /* :ns_str\0str\0
                                 * first character after : must be valid
                                 */
                                if (!str[1])
                                        return -6;
                        }
                } else if (!*str) {
                        if (*pos == ':')
                                *str = ':';
                        else
                                c++;
                        pos = str +  1;
                }
                str++;
        } /* while */

        return c;
}

/**
 * unpack_strs_table - unpack a profile transition table
 * @e: serialized data extent information  (NOT NULL)
 * @name: name of table (MAY BE NULL)
 * @multi: allow multiple strings on a single entry
 * @strs: str table to unpack to (NOT NULL)
 *
 * Returns: 0 if table successfully unpacked or not present, else error
 */
static int unpack_strs_table(struct aa_ext *e, const char *name, bool multi,
                              struct aa_str_table *strs)
{
        void *saved_pos = e->pos;
        struct aa_str_table_ent *table = NULL;
        int error = -EPROTO;

        /* exec table is optional */
        if (aa_unpack_nameX(e, AA_STRUCT, name)) {
                u16 size;
                int i;

                if (!aa_unpack_array(e, NULL, &size))
                        /*
                         * Note: index into trans table array is a max
                         * of 2^24, but unpack array can only unpack
                         * an array of 2^16 in size atm so no need
                         * for size check here
                         */
                        goto fail;
                table = kzalloc_objs(struct aa_str_table_ent, size);
                if (!table) {
                        error = -ENOMEM;
                        goto fail;
                }
                strs->table = table;
                strs->size = size;
                for (i = 0; i < size; i++) {
                        char *str;
                        int c, size2 = aa_unpack_strdup(e, &str, NULL);
                        /* aa_unpack_strdup verifies that the last character is
                         * null termination byte.
                         */
                        c = process_strs_entry(str, size2, multi);
                        if (c <= 0) {
                                AA_DEBUG(DEBUG_UNPACK, "process_strs %d i %d pos %ld",
                                         c, i,
                                         (unsigned long)(e->pos - saved_pos));
                                goto fail;
                        }
                        if (!multi && c > 1) {
                                AA_DEBUG(DEBUG_UNPACK, "!multi && c > 1");
                                /* fail - all other cases with embedded \0 */
                                goto fail;
                        }
                        table[i].strs = str;
                        table[i].count = c;
                        table[i].size = size2;
                }
                if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL))
                        goto fail;
                if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
                        goto fail;
        }
        return 0;

fail:
        aa_destroy_str_table(strs);
        e->pos = saved_pos;
        return error;
}

static bool unpack_xattrs(struct aa_ext *e, struct aa_profile *profile)
{
        void *pos = e->pos;

        if (aa_unpack_nameX(e, AA_STRUCT, "xattrs")) {
                u16 size;
                int i;

                if (!aa_unpack_array(e, NULL, &size))
                        goto fail;
                profile->attach.xattr_count = size;
                profile->attach.xattrs = kcalloc(size, sizeof(char *), GFP_KERNEL);
                if (!profile->attach.xattrs)
                        goto fail;
                for (i = 0; i < size; i++) {
                        if (!aa_unpack_strdup(e, &profile->attach.xattrs[i], NULL))
                                goto fail;
                }
                if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL))
                        goto fail;
                if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
                        goto fail;
        }

        return true;

fail:
        e->pos = pos;
        return false;
}

static bool unpack_secmark(struct aa_ext *e, struct aa_ruleset *rules)
{
        void *pos = e->pos;
        u16 size;
        int i;

        if (aa_unpack_nameX(e, AA_STRUCT, "secmark")) {
                if (!aa_unpack_array(e, NULL, &size))
                        goto fail;

                rules->secmark = kzalloc_objs(struct aa_secmark, size);
                if (!rules->secmark)
                        goto fail;

                rules->secmark_count = size;

                for (i = 0; i < size; i++) {
                        if (!unpack_u8(e, &rules->secmark[i].audit, NULL))
                                goto fail;
                        if (!unpack_u8(e, &rules->secmark[i].deny, NULL))
                                goto fail;
                        if (!aa_unpack_strdup(e, &rules->secmark[i].label, NULL))
                                goto fail;
                }
                if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL))
                        goto fail;
                if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
                        goto fail;
        }

        return true;

fail:
        if (rules->secmark) {
                for (i = 0; i < size; i++)
                        kfree_sensitive(rules->secmark[i].label);
                kfree_sensitive(rules->secmark);
                rules->secmark_count = 0;
                rules->secmark = NULL;
        }

        e->pos = pos;
        return false;
}

static bool unpack_rlimits(struct aa_ext *e, struct aa_ruleset *rules)
{
        void *pos = e->pos;

        /* rlimits are optional */
        if (aa_unpack_nameX(e, AA_STRUCT, "rlimits")) {
                u16 size;
                int i;
                u32 tmp = 0;
                if (!aa_unpack_u32(e, &tmp, NULL))
                        goto fail;
                rules->rlimits.mask = tmp;

                if (!aa_unpack_array(e, NULL, &size) ||
                    size > RLIM_NLIMITS)
                        goto fail;
                for (i = 0; i < size; i++) {
                        u64 tmp2 = 0;
                        int a = aa_map_resource(i);
                        if (!aa_unpack_u64(e, &tmp2, NULL))
                                goto fail;
                        rules->rlimits.limits[a].rlim_max = tmp2;
                }
                if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL))
                        goto fail;
                if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
                        goto fail;
        }
        return true;

fail:
        e->pos = pos;
        return false;
}


static bool verify_tags(struct aa_tags_struct *tags, const char **info)
{
        if ((tags->hdrs.size && !tags->hdrs.table) ||
            (!tags->hdrs.size && tags->hdrs.table)) {
                *info = "failed verification tag.hdrs disagree";
                return false;
        }
        if ((tags->sets.size && !tags->sets.table) ||
            (!tags->sets.size && tags->sets.table)) {
                *info = "failed verification tag.sets disagree";
                return false;
        }
        if ((tags->strs.size && !tags->strs.table) ||
            (!tags->strs.size && tags->strs.table)) {
                *info = "failed verification tags->strs disagree";
                return false;
        }
        /* no data present */
        if (!tags->sets.size && !tags->hdrs.size && !tags->strs.size) {
                return true;
        } else if (!(tags->sets.size && tags->hdrs.size && tags->strs.size)) {
                /* some data present but not all */
                *info = "failed verification tags partial data present";
                return false;
        }

        u32 i;

        for (i = 0; i < tags->sets.size; i++) {
                /* count followed by count indexes into hdrs */
                u32 cnt = tags->sets.table[i];

                if (i+cnt >= tags->sets.size) {
                        AA_DEBUG(DEBUG_UNPACK,
                                 "tagset too large %d+%d > sets.table[%d]",
                                 i, cnt, tags->sets.size);
                        *info = "failed verification tagset too large";
                        return false;
                }
                for (; cnt; cnt--) {
                        if (tags->sets.table[++i] >= tags->hdrs.size) {
                                AA_DEBUG(DEBUG_UNPACK,
                                         "tagsets idx out of bounds cnt %d sets.table[%d] >= %d",
                                         cnt, i-1, tags->hdrs.size);
                                *info = "failed verification tagsets idx out of bounds";
                                return false;
                        }
                }
        }
        for (i = 0; i < tags->hdrs.size; i++) {
                u32 idx = tags->hdrs.table[i].tags;

                if (idx >= tags->strs.size) {
                        AA_DEBUG(DEBUG_UNPACK,
                                 "tag.hdrs idx oob idx %d > tags->strs.size=%d",
                                 idx, tags->strs.size);
                        *info = "failed verification tags.hdrs idx out of bounds";
                        return false;
                }
                if (tags->hdrs.table[i].count != tags->strs.table[idx].count) {
                        AA_DEBUG(DEBUG_UNPACK, "hdrs.table[%d].count=%d != tags->strs.table[%d]=%d",
                                 i, tags->hdrs.table[i].count, idx, tags->strs.table[idx].count);
                        *info = "failed verification tagd.hdrs[idx].count";
                        return false;
                }
                if (tags->hdrs.table[i].size != tags->strs.table[idx].size) {
                        AA_DEBUG(DEBUG_UNPACK, "hdrs.table[%d].size=%d != strs.table[%d].size=%d",
                                 i, tags->hdrs.table[i].size, idx, tags->strs.table[idx].size);
                        *info = "failed verification tagd.hdrs[idx].size";
                        return false;
                }
        }

        return true;
}

static int unpack_tagsets(struct aa_ext *e, struct aa_tags_struct *tags)
{
        u32 *sets;
        u16 i, size;
        int error = -EPROTO;
        void *pos = e->pos;

        if (!aa_unpack_array(e, "sets", &size))
                goto fail_reset;
        sets = kcalloc(size, sizeof(u32), GFP_KERNEL);
        if (!sets) {
                error = -ENOMEM;
                goto fail_reset;
        }
        for (i = 0; i < size; i++) {
                if (!aa_unpack_u32(e, &sets[i], NULL))
                        goto fail;
        }
        if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL))
                goto fail;

        tags->sets.size = size;
        tags->sets.table = sets;

        return 0;

fail:
        kfree_sensitive(sets);
fail_reset:
        e->pos = pos;
        return error;
}

static bool unpack_tag_header_ent(struct aa_ext *e, struct aa_tags_header *h)
{
        return aa_unpack_u32(e, &h->mask, NULL) &&
                aa_unpack_u32(e, &h->count, NULL) &&
                aa_unpack_u32(e, &h->size, NULL) &&
                aa_unpack_u32(e, &h->tags, NULL);
}

static int unpack_tag_headers(struct aa_ext *e, struct aa_tags_struct *tags)
{
        struct aa_tags_header *hdrs;
        u16 i, size;
        int error = -EPROTO;
        void *pos = e->pos;

        if (!aa_unpack_array(e, "hdrs", &size))
                goto fail_reset;
        hdrs = kzalloc_objs(struct aa_tags_header, size);
        if (!hdrs) {
                error = -ENOMEM;
                goto fail_reset;
        }
        for (i = 0; i < size; i++) {
                if (!unpack_tag_header_ent(e, &hdrs[i]))
                        goto fail;
        }
        if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL))
                goto fail;

        tags->hdrs.size = size;
        tags->hdrs.table = hdrs;
        AA_DEBUG(DEBUG_UNPACK, "headers %ld size %d", (long) hdrs, size);
        return true;

fail:
        kfree_sensitive(hdrs);
fail_reset:
        e->pos = pos;
        return error;
}


static int unpack_tags(struct aa_ext *e, struct aa_tags_struct *tags,
        const char **info)
{
        int error = -EPROTO;
        void *pos = e->pos;

        AA_BUG(!tags);
        /* policy tags are optional */
        if (aa_unpack_nameX(e, AA_STRUCT, "tags")) {
                u32 version;

                if (!aa_unpack_u32(e, &version, "version") || version != 1) {
                        *info = "invalid tags version";
                        goto fail_reset;
                }
                error = unpack_strs_table(e, "strs", true, &tags->strs);
                if (error) {
                        *info = "failed to unpack profile tag.strs";
                        goto fail;
                }
                error = unpack_tag_headers(e, tags);
                if (error) {
                        *info = "failed to unpack profile tag.headers";
                        goto fail;
                }
                error = unpack_tagsets(e, tags);
                if (error) {
                        *info = "failed to unpack profile tag.sets";
                        goto fail;
                }
                if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
                        goto fail;

                if (!verify_tags(tags, info))
                        goto fail;
        }

        return 0;

fail:
        aa_destroy_tags(tags);
fail_reset:
        e->pos = pos;
        return error;
}

static bool unpack_perm(struct aa_ext *e, u32 version, struct aa_perms *perm)
{
        u32 reserved;

        if (version != 1)
                return false;

        /* reserved entry is for later expansion, discard for now */
        return  aa_unpack_u32(e, &reserved, NULL) &&
                aa_unpack_u32(e, &perm->allow, NULL) &&
                aa_unpack_u32(e, &perm->deny, NULL) &&
                aa_unpack_u32(e, &perm->subtree, NULL) &&
                aa_unpack_u32(e, &perm->cond, NULL) &&
                aa_unpack_u32(e, &perm->kill, NULL) &&
                aa_unpack_u32(e, &perm->complain, NULL) &&
                aa_unpack_u32(e, &perm->prompt, NULL) &&
                aa_unpack_u32(e, &perm->audit, NULL) &&
                aa_unpack_u32(e, &perm->quiet, NULL) &&
                aa_unpack_u32(e, &perm->hide, NULL) &&
                aa_unpack_u32(e, &perm->xindex, NULL) &&
                aa_unpack_u32(e, &perm->tag, NULL) &&
                aa_unpack_u32(e, &perm->label, NULL);
}

static ssize_t unpack_perms_table(struct aa_ext *e, struct aa_perms **perms)
{
        void *pos = e->pos;
        u16 size = 0;

        AA_BUG(!perms);
        /*
         * policy perms are optional, in which case perms are embedded
         * in the dfa accept table
         */
        if (aa_unpack_nameX(e, AA_STRUCT, "perms")) {
                int i;
                u32 version;

                if (!aa_unpack_u32(e, &version, "version"))
                        goto fail_reset;
                if (!aa_unpack_array(e, NULL, &size))
                        goto fail_reset;
                *perms = kzalloc_objs(struct aa_perms, size);
                if (!*perms) {
                        e->pos = pos;
                        return -ENOMEM;
                }
                for (i = 0; i < size; i++) {
                        if (!unpack_perm(e, version, &(*perms)[i]))
                                goto fail;
                }
                if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL))
                        goto fail;
                if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
                        goto fail;
        } else
                *perms = NULL;

        return size;

fail:
        kfree(*perms);
fail_reset:
        e->pos = pos;
        return -EPROTO;
}

static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy,
                      bool required_dfa, bool required_trans,
                      const char **info)
{
        struct aa_policydb *pdb;
        void *pos = e->pos;
        int i, flags, error = -EPROTO;
        ssize_t size;
        u32 version = 0;

        pdb = aa_alloc_pdb(GFP_KERNEL);
        if (!pdb)
                return -ENOMEM;

        AA_DEBUG(DEBUG_UNPACK, "unpacking tags");
        if (unpack_tags(e, &pdb->tags, info) < 0)
                goto fail;
        AA_DEBUG(DEBUG_UNPACK, "done unpacking tags");

        size = unpack_perms_table(e, &pdb->perms);
        if (size < 0) {
                error = size;
                pdb->perms = NULL;
                *info = "failed to unpack - perms";
                goto fail;
        }
        pdb->size = size;

        if (pdb->perms) {
                /* perms table present accept is index */
                flags = TO_ACCEPT1_FLAG(YYTD_DATA32);
                if (aa_unpack_u32(e, &version, "permsv") && version > 2)
                        /* accept2 used for dfa flags */
                        flags |= TO_ACCEPT2_FLAG(YYTD_DATA32);
        } else {
                /* packed perms in accept1 and accept2 */
                flags = TO_ACCEPT1_FLAG(YYTD_DATA32) |
                        TO_ACCEPT2_FLAG(YYTD_DATA32);
        }

        pdb->dfa = unpack_dfa(e, flags);
        if (IS_ERR(pdb->dfa)) {
                error = PTR_ERR(pdb->dfa);
                pdb->dfa = NULL;
                *info = "failed to unpack - dfa";
                goto fail;
        } else if (!pdb->dfa) {
                if (required_dfa) {
                        *info = "missing required dfa";
                        goto fail;
                }
        } else {
                /*
                 * only unpack the following if a dfa is present
                 *
                 * sadly start was given different names for file and policydb
                 * but since it is optional we can try both
                 */
                if (!aa_unpack_u32(e, &pdb->start[0], "start"))
                        /* default start state */
                        pdb->start[0] = DFA_START;
                if (!aa_unpack_u32(e, &pdb->start[AA_CLASS_FILE], "dfa_start")) {
                        /* default start state for xmatch and file dfa */
                        pdb->start[AA_CLASS_FILE] = DFA_START;
                }

                size_t state_count = pdb->dfa->tables[YYTD_ID_BASE]->td_lolen;

                if (pdb->start[0] >= state_count ||
                    pdb->start[AA_CLASS_FILE] >= state_count) {
                        *info = "invalid dfa start state";
                        goto fail;
                }

                /* setup class index */
                for (i = AA_CLASS_FILE + 1; i <= AA_CLASS_LAST; i++) {
                        pdb->start[i] = aa_dfa_next(pdb->dfa, pdb->start[0],
                                                    i);
                }
        }

        /* accept2 is in some cases being allocated, even with perms */
        if (pdb->perms && !pdb->dfa->tables[YYTD_ID_ACCEPT2]) {
                /* add dfa flags table missing in v2 */
                u32 noents = pdb->dfa->tables[YYTD_ID_ACCEPT]->td_lolen;
                u16 tdflags = pdb->dfa->tables[YYTD_ID_ACCEPT]->td_flags;
                size_t tsize = table_size(noents, tdflags);

                pdb->dfa->tables[YYTD_ID_ACCEPT2] = kvzalloc(tsize, GFP_KERNEL);
                if (!pdb->dfa->tables[YYTD_ID_ACCEPT2]) {
                        *info = "failed to alloc dfa flags table";
                        goto out;
                }
                pdb->dfa->tables[YYTD_ID_ACCEPT2]->td_lolen = noents;
                pdb->dfa->tables[YYTD_ID_ACCEPT2]->td_flags = tdflags;
        }
        /*
         * Unfortunately due to a bug in earlier userspaces, a
         * transition table may be present even when the dfa is
         * not. For compatibility reasons unpack and discard.
         */
        error = unpack_strs_table(e, "xtable", false, &pdb->trans);
        if (error && required_trans) {
                *info = "failed to unpack profile transition table";
                goto fail;
        }

        if (!pdb->dfa && pdb->trans.table)
                aa_destroy_str_table(&pdb->trans);

        /* TODO:
         * - move compat mapping here, requires dfa merging first
         * - move verify here, it has to be done after compat mappings
         * - move free of unneeded trans table here, has to be done
         *   after perm mapping.
         */
out:
        *policy = pdb;
        return 0;

fail:
        aa_put_pdb(pdb);
        e->pos = pos;
        return error;
}

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)
 * @ns_name: pointer of newly allocated copy of %NULL in case of error
 *
 * NOTE: unpack profile sets audit struct if there is a failure
 */
static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
{
        struct aa_ruleset *rules;
        struct aa_profile *profile = NULL;
        const char *tmpname, *tmpns = NULL, *name = NULL;
        const char *info = "failed to unpack profile";
        size_t ns_len;
        struct rhashtable_params params = { 0 };
        char *key = NULL, *disconnected = NULL;
        struct aa_data *data;
        int error = -EPROTO;
        kernel_cap_t tmpcap;
        u32 tmp;

        *ns_name = NULL;

        /* check that we have the right struct being passed */
        if (!aa_unpack_nameX(e, AA_STRUCT, "profile"))
                goto fail;
        if (!aa_unpack_str(e, &name, NULL))
                goto fail;
        if (*name == '\0')
                goto fail;

        tmpname = aa_splitn_fqname(name, strlen(name), &tmpns, &ns_len);
        if (tmpns) {
                if (!tmpname) {
                        info = "empty profile name";
                        goto fail;
                }
                *ns_name = kstrndup(tmpns, ns_len, GFP_KERNEL);
                if (!*ns_name) {
                        info = "out of memory";
                        error = -ENOMEM;
                        goto fail;
                }
                name = tmpname;
        }

        profile = aa_alloc_profile(name, NULL, GFP_KERNEL);
        if (!profile) {
                info = "out of memory";
                error = -ENOMEM;
                goto fail;
        }
        rules = profile->label.rules[0];

        /* profile renaming is optional */
        (void) aa_unpack_str(e, &profile->rename, "rename");

        /* attachment string is optional */
        (void) aa_unpack_str(e, &profile->attach.xmatch_str, "attach");

        /* xmatch is optional and may be NULL */
        error = unpack_pdb(e, &profile->attach.xmatch, false, false, &info);
        if (error) {
                info = "bad xmatch";
                goto fail;
        }

        /* neither xmatch_len not xmatch_perms are optional if xmatch is set */
        if (profile->attach.xmatch->dfa) {
                if (!aa_unpack_u32(e, &tmp, NULL)) {
                        info = "missing xmatch len";
                        goto fail;
                }
                profile->attach.xmatch_len = tmp;
                profile->attach.xmatch->start[AA_CLASS_XMATCH] = DFA_START;
                if (!profile->attach.xmatch->perms) {
                        error = aa_compat_map_xmatch(profile->attach.xmatch);
                        if (error) {
                                info = "failed to convert xmatch permission table";
                                goto fail;
                        }
                }
        }

        /* disconnected attachment string is optional */
        (void) aa_unpack_strdup(e, &disconnected, "disconnected");
        profile->disconnected = disconnected;

        /* optional */
        (void) aa_unpack_u32(e, &profile->signal, "kill");
        if (profile->signal < 1 || profile->signal > MAXMAPPED_SIG) {
                info = "profile kill.signal invalid value";
                goto fail;
        }
        /* per profile debug flags (complain, audit) */
        if (!aa_unpack_nameX(e, AA_STRUCT, "flags")) {
                info = "profile missing flags";
                goto fail;
        }
        info = "failed to unpack profile flags";
        if (!aa_unpack_u32(e, &tmp, NULL))
                goto fail;
        if (tmp & PACKED_FLAG_HAT)
                profile->label.flags |= FLAG_HAT;
        if (tmp & PACKED_FLAG_DEBUG1)
                profile->label.flags |= FLAG_DEBUG1;
        if (tmp & PACKED_FLAG_DEBUG2)
                profile->label.flags |= FLAG_DEBUG2;
        if (!aa_unpack_u32(e, &tmp, NULL))
                goto fail;
        if (tmp == PACKED_MODE_COMPLAIN || (e->version & FORCE_COMPLAIN_FLAG)) {
                profile->mode = APPARMOR_COMPLAIN;
        } else if (tmp == PACKED_MODE_ENFORCE) {
                profile->mode = APPARMOR_ENFORCE;
        } else if (tmp == PACKED_MODE_KILL) {
                profile->mode = APPARMOR_KILL;
        } else if (tmp == PACKED_MODE_UNCONFINED) {
                profile->mode = APPARMOR_UNCONFINED;
                profile->label.flags |= FLAG_UNCONFINED;
        } else if (tmp == PACKED_MODE_USER) {
                profile->mode = APPARMOR_USER;
        } else {
                goto fail;
        }
        if (!aa_unpack_u32(e, &tmp, NULL))
                goto fail;
        if (tmp)
                profile->audit = AUDIT_ALL;

        if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
                goto fail;

        /* path_flags is optional */
        if (aa_unpack_u32(e, &profile->path_flags, "path_flags"))
                profile->path_flags |= profile->label.flags &
                        PATH_MEDIATE_DELETED;
        else
                /* set a default value if path_flags field is not present */
                profile->path_flags = PATH_MEDIATE_DELETED;

        info = "failed to unpack profile capabilities";
        if (!aa_unpack_cap_low(e, &rules->caps.allow, NULL))
                goto fail;
        if (!aa_unpack_cap_low(e, &rules->caps.audit, NULL))
                goto fail;
        if (!aa_unpack_cap_low(e, &rules->caps.quiet, NULL))
                goto fail;
        if (!aa_unpack_cap_low(e, &tmpcap, NULL))
                goto fail;

        info = "failed to unpack upper profile capabilities";
        if (aa_unpack_nameX(e, AA_STRUCT, "caps64")) {
                /* optional upper half of 64 bit caps */
                if (!aa_unpack_cap_high(e, &rules->caps.allow, NULL))
                        goto fail;
                if (!aa_unpack_cap_high(e, &rules->caps.audit, NULL))
                        goto fail;
                if (!aa_unpack_cap_high(e, &rules->caps.quiet, NULL))
                        goto fail;
                if (!aa_unpack_cap_high(e, &tmpcap, NULL))
                        goto fail;
                if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
                        goto fail;
        }

        info = "failed to unpack extended profile capabilities";
        if (aa_unpack_nameX(e, AA_STRUCT, "capsx")) {
                /* optional extended caps mediation mask */
                if (!aa_unpack_cap_low(e, &rules->caps.extended, NULL))
                        goto fail;
                if (!aa_unpack_cap_high(e, &rules->caps.extended, NULL))
                        goto fail;
                if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
                        goto fail;
        }

        if (!unpack_xattrs(e, profile)) {
                info = "failed to unpack profile xattrs";
                goto fail;
        }

        if (!unpack_rlimits(e, rules)) {
                info = "failed to unpack profile rlimits";
                goto fail;
        }

        if (!unpack_secmark(e, rules)) {
                info = "failed to unpack profile secmark rules";
                goto fail;
        }

        if (aa_unpack_nameX(e, AA_STRUCT, "policydb")) {
                /* generic policy dfa - optional and may be NULL */
                info = "failed to unpack policydb";
                error = unpack_pdb(e, &rules->policy, true, false,
                                   &info);
                if (error)
                        goto fail;
                /* Fixup: drop when we get rid of start array */
                if (aa_dfa_next(rules->policy->dfa, rules->policy->start[0],
                                AA_CLASS_FILE))
                        rules->policy->start[AA_CLASS_FILE] =
                          aa_dfa_next(rules->policy->dfa,
                                      rules->policy->start[0],
                                      AA_CLASS_FILE);
                if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
                        goto fail;
                if (!rules->policy->perms) {
                        error = aa_compat_map_policy(rules->policy,
                                                     e->version);
                        if (error) {
                                info = "failed to remap policydb permission table";
                                goto fail;
                        }
                }
        } else {
                rules->policy = aa_get_pdb(nullpdb);
        }
        /* get file rules */
        error = unpack_pdb(e, &rules->file, false, true, &info);
        if (error) {
                goto fail;
        } else if (rules->file->dfa) {
                if (!rules->file->perms) {
                        AA_DEBUG(DEBUG_UNPACK, "compat mapping perms");
                        error = aa_compat_map_file(rules->file);
                        if (error) {
                                info = "failed to remap file permission table";
                                goto fail;
                        }
                }
        } else if (rules->policy->dfa &&
                   rules->policy->start[AA_CLASS_FILE]) {
                aa_put_pdb(rules->file);
                rules->file = aa_get_pdb(rules->policy);
        } else {
                aa_put_pdb(rules->file);
                rules->file = aa_get_pdb(nullpdb);
        }
        error = -EPROTO;
        if (aa_unpack_nameX(e, AA_STRUCT, "data")) {
                info = "out of memory";
                profile->data = kzalloc_obj(*profile->data);
                if (!profile->data) {
                        error = -ENOMEM;
                        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)) {
                        info = "failed to init key, value hash table";
                        goto fail;
                }

                while (aa_unpack_strdup(e, &key, NULL)) {
                        data = kzalloc_obj(*data);
                        if (!data) {
                                kfree_sensitive(key);
                                error = -ENOMEM;
                                goto fail;
                        }

                        data->key = key;
                        data->size = aa_unpack_blob(e, &data->data, NULL);
                        data->data = kvmemdup(data->data, data->size, GFP_KERNEL);
                        if (data->size && !data->data) {
                                kfree_sensitive(data->key);
                                kfree_sensitive(data);
                                error = -ENOMEM;
                                goto fail;
                        }

                        if (rhashtable_insert_fast(profile->data, &data->head,
                                                   profile->data->p)) {
                                kvfree_sensitive(data->data, data->size);
                                kfree_sensitive(data->key);
                                kfree_sensitive(data);
                                info = "failed to insert data to table";
                                goto fail;
                        }
                }

                if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) {
                        info = "failed to unpack end of key, value data table";
                        goto fail;
                }
        }

        if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) {
                info = "failed to unpack end of profile";
                goto fail;
        }

        aa_compute_profile_mediates(profile);

        return profile;

fail:
        if (error == 0)
                /* default error covers most cases */
                error = -EPROTO;
        if (*ns_name) {
                kfree(*ns_name);
                *ns_name = NULL;
        }
        if (profile)
                name = NULL;
        else if (!name)
                name = "unknown";
        audit_iface(profile, NULL, name, info, e, error);
        aa_free_profile(profile);

        return ERR_PTR(error);
}

/**
 * verify_header - unpack serialized stream header
 * @e: serialized data read head (NOT NULL)
 * @required: whether the header is required or optional
 * @ns: Returns - namespace if one is specified else NULL (NOT NULL)
 *
 * Returns: error or 0 if header is good
 */
static int verify_header(struct aa_ext *e, int required, const char **ns)
{
        int error = -EPROTONOSUPPORT;
        const char *name = NULL;

        /* get the interface version */
        if (!aa_unpack_u32(e, &e->version, "version")) {
                if (required) {
                        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_LT(e->version, v5) || VERSION_GT(e->version, v9)) {
                audit_iface(NULL, NULL, NULL, "unsupported interface version",
                            e, error);
                return error;
        }

        /* read the namespace if present */
        if (aa_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, NULL, "invalid ns change", e,
                                    error);
                } else if (!*ns) {
                        *ns = kstrdup(name, GFP_KERNEL);
                        if (!*ns)
                                return -ENOMEM;
                }
        }

        return 0;
}

/**
 * verify_dfa_accept_index - verify accept indexes are in range of perms table
 * @dfa: the dfa to check accept indexes are in range
 * @table_size: the permission table size the indexes should be within
 */
static bool verify_dfa_accept_index(struct aa_dfa *dfa, int table_size)
{
        int i;
        for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) {
                if (ACCEPT_TABLE(dfa)[i] >= table_size)
                        return false;
        }
        return true;
}

static bool verify_perm(struct aa_perms *perm)
{
        /* TODO: allow option to just force the perms into a valid state */
        if (perm->allow & perm->deny)
                return false;
        if (perm->subtree & ~perm->allow)
                return false;
        if (perm->cond & (perm->allow | perm->deny))
                return false;
        if (perm->kill & perm->allow)
                return false;
        if (perm->complain & (perm->allow | perm->deny))
                return false;
        if (perm->prompt & (perm->allow | perm->deny))
                return false;
        if (perm->complain & perm->prompt)
                return false;
        if (perm->hide & perm->allow)
                return false;

        return true;
}

static bool verify_perms(struct aa_policydb *pdb)
{
        int i;
        int xidx, xmax = -1;

        for (i = 0; i < pdb->size; i++) {
                if (!verify_perm(&pdb->perms[i]))
                        return false;
                /* verify indexes into str table */
                if ((pdb->perms[i].xindex & AA_X_TYPE_MASK) == AA_X_TABLE) {
                        xidx = pdb->perms[i].xindex & AA_X_INDEX_MASK;
                        if (xidx >= pdb->trans.size)
                                return false;
                        if (xmax < xidx)
                                xmax = xidx;
                }
                if (pdb->perms[i].tag && pdb->perms[i].tag >= pdb->tags.sets.size)
                        return false;
                if (pdb->perms[i].label &&
                    pdb->perms[i].label >= pdb->trans.size)
                        return false;
        }
        /* deal with incorrectly constructed string tables */
        if (xmax == -1) {
                aa_destroy_str_table(&pdb->trans);
        } else if (pdb->trans.size > xmax + 1) {
                if (!aa_resize_str_table(&pdb->trans, xmax + 1, GFP_KERNEL))
                        return false;
        }
        return true;
}

/**
 * verify_profile - Do post unpack analysis to verify profile consistency
 * @profile: profile to verify (NOT NULL)
 *
 * Returns: 0 if passes verification else error
 *
 * This verification is post any unpack mapping or changes
 */
static int verify_profile(struct aa_profile *profile)
{
        struct aa_ruleset *rules = profile->label.rules[0];

        if (!rules)
                return 0;

        if (rules->file->dfa && !verify_dfa_accept_index(rules->file->dfa,
                                                        rules->file->size)) {
                audit_iface(profile, NULL, NULL,
                            "Unpack: file Invalid named transition", NULL,
                            -EPROTO);
                return -EPROTO;
        }
        if (rules->policy->dfa &&
            !verify_dfa_accept_index(rules->policy->dfa, rules->policy->size)) {
                audit_iface(profile, NULL, NULL,
                            "Unpack: policy Invalid named transition", NULL,
                            -EPROTO);
                return -EPROTO;
        }

        if (!verify_perms(rules->file)) {
                audit_iface(profile, NULL, NULL,
                            "Unpack: Invalid perm index", NULL, -EPROTO);
                return -EPROTO;
        }
        if (!verify_perms(rules->policy)) {
                audit_iface(profile, NULL, NULL,
                            "Unpack: Invalid perm index", NULL, -EPROTO);
                return -EPROTO;
        }
        if (!verify_perms(profile->attach.xmatch)) {
                audit_iface(profile, NULL, NULL,
                            "Unpack: Invalid perm index", NULL, -EPROTO);
                return -EPROTO;
        }

        return 0;
}

void aa_load_ent_free(struct aa_load_ent *ent)
{
        if (ent) {
                aa_put_profile(ent->rename);
                aa_put_profile(ent->old);
                aa_put_profile(ent->new);
                kfree(ent->ns_name);
                kfree_sensitive(ent);
        }
}

struct aa_load_ent *aa_load_ent_alloc(void)
{
        struct aa_load_ent *ent = kzalloc_obj(*ent);
        if (ent)
                INIT_LIST_HEAD(&ent->list);
        return ent;
}

static int compress_zstd(const char *src, size_t slen, char **dst, size_t *dlen)
{
#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
        const zstd_parameters params =
                zstd_get_params(aa_g_rawdata_compression_level, slen);
        const size_t wksp_len = zstd_cctx_workspace_bound(&params.cParams);
        void *wksp = NULL;
        zstd_cctx *ctx = NULL;
        size_t out_len = zstd_compress_bound(slen);
        void *out = NULL;
        int ret = 0;

        out = kvzalloc(out_len, GFP_KERNEL);
        if (!out) {
                ret = -ENOMEM;
                goto cleanup;
        }

        wksp = kvzalloc(wksp_len, GFP_KERNEL);
        if (!wksp) {
                ret = -ENOMEM;
                goto cleanup;
        }

        ctx = zstd_init_cctx(wksp, wksp_len);
        if (!ctx) {
                ret = -EINVAL;
                goto cleanup;
        }

        out_len = zstd_compress_cctx(ctx, out, out_len, src, slen, &params);
        if (zstd_is_error(out_len) || out_len >= slen) {
                ret = -EINVAL;
                goto cleanup;
        }

        if (is_vmalloc_addr(out)) {
                *dst = kvzalloc(out_len, GFP_KERNEL);
                if (*dst) {
                        memcpy(*dst, out, out_len);
                        kvfree(out);
                        out = NULL;
                }
        } else {
                /*
                 * If the staging buffer was kmalloc'd, then using krealloc is
                 * probably going to be faster. The destination buffer will
                 * always be smaller, so it's just shrunk, avoiding a memcpy
                 */
                *dst = krealloc(out, out_len, GFP_KERNEL);
        }

        if (!*dst) {
                ret = -ENOMEM;
                goto cleanup;
        }

        *dlen = out_len;

cleanup:
        if (ret) {
                kvfree(out);
                *dst = NULL;
        }

        kvfree(wksp);
        return ret;
#else
        *dlen = slen;
        return 0;
#endif
}

static int compress_loaddata(struct aa_loaddata *data)
{
        AA_BUG(data->compressed_size > 0);

        /*
         * Shortcut the no compression case, else we increase the amount of
         * storage required by a small amount
         */
        if (aa_g_rawdata_compression_level != 0) {
                void *udata = data->data;
                int error = compress_zstd(udata, data->size, &data->data,
                                          &data->compressed_size);
                if (error) {
                        data->compressed_size = data->size;
                        return error;
                }
                if (udata != data->data)
                        kvfree(udata);
        } else
                data->compressed_size = data->size;

        return 0;
}

/**
 * aa_unpack - unpack packed binary profile(s) data loaded from user space
 * @udata: user data copied to kmem  (NOT NULL)
 * @lh: list to place unpacked profiles in a aa_repl_ws
 * @ns: Returns namespace profile is in if specified else NULL (NOT NULL)
 *
 * Unpack user data and return refcounted allocated profile(s) stored in
 * @lh in order of discovery, with the list chain stored in base.list
 * or error
 *
 * Returns: profile(s) on @lh else error pointer if fails to unpack
 */
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;
        char *ns_name = NULL;
        int error;
        struct aa_ext e = {
                .start = udata->data,
                .end = udata->data + udata->size,
                .pos = udata->data,
        };

        *ns = NULL;
        while (e.pos < e.end) {
                void *start;
                error = verify_header(&e, e.pos == e.start, ns);
                if (error)
                        goto fail;

                start = e.pos;
                profile = unpack_profile(&e, &ns_name);
                if (IS_ERR(profile)) {
                        error = PTR_ERR(profile);
                        goto fail;
                }

                error = verify_profile(profile);
                if (error)
                        goto fail_profile;

                if (aa_g_hash_policy)
                        error = aa_calc_profile_hash(profile, e.version, start,
                                                     e.pos - start);
                if (error)
                        goto fail_profile;

                ent = aa_load_ent_alloc();
                if (!ent) {
                        error = -ENOMEM;
                        goto fail_profile;
                }

                ent->new = profile;
                ent->ns_name = ns_name;
                ns_name = NULL;
                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;
                }
        }

        if (aa_g_export_binary) {
                error = compress_loaddata(udata);
                if (error)
                        goto fail;
        }
        return 0;

fail_profile:
        kfree(ns_name);
        aa_put_profile(profile);

fail:
        list_for_each_entry_safe(ent, tmp, lh, list) {
                list_del_init(&ent->list);
                aa_load_ent_free(ent);
        }

        return error;
}