--- /dev/null
+From: John Johansen <jjohansen@suse.de>
+Subject: AppArmor: Profile loading and manipulation, pathname matching
+
+Pathname matching, transition table loading, profile loading and
+manipulation.
+
+Signed-off-by: John Johansen <jjohansen@suse.de>
+Signed-off-by: Andreas Gruenbacher <agruen@suse.de>
+
+---
+ security/apparmor/match.c | 364 ++++++++++++++
+ security/apparmor/match.h | 87 +++
+ security/apparmor/module_interface.c | 875 +++++++++++++++++++++++++++++++++++
+ 3 files changed, 1326 insertions(+)
+
+--- /dev/null
++++ b/security/apparmor/match.c
+@@ -0,0 +1,364 @@
++/*
++ * Copyright (C) 2007 Novell/SUSE
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License as
++ * published by the Free Software Foundation, version 2 of the
++ * License.
++ *
++ * Regular expression transition table matching
++ */
++
++#include <linux/kernel.h>
++#include <linux/slab.h>
++#include <linux/errno.h>
++#include "apparmor.h"
++#include "match.h"
++#include "inline.h"
++
++static struct table_header *unpack_table(void *blob, size_t bsize)
++{
++ struct table_header *table = NULL;
++ struct table_header th;
++ size_t tsize;
++
++ if (bsize < sizeof(struct table_header))
++ goto out;
++
++ th.td_id = be16_to_cpu(*(u16 *) (blob));
++ th.td_flags = be16_to_cpu(*(u16 *) (blob + 2));
++ th.td_lolen = be32_to_cpu(*(u32 *) (blob + 8));
++ blob += sizeof(struct table_header);
++
++ if (!(th.td_flags == YYTD_DATA16 || th.td_flags == YYTD_DATA32 ||
++ th.td_flags == YYTD_DATA8))
++ goto out;
++
++ tsize = table_size(th.td_lolen, th.td_flags);
++ if (bsize < tsize)
++ goto out;
++
++ table = kmalloc(tsize, GFP_KERNEL);
++ if (table) {
++ *table = th;
++ if (th.td_flags == YYTD_DATA8)
++ UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
++ u8, byte_to_byte);
++ else if (th.td_flags == YYTD_DATA16)
++ UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
++ u16, be16_to_cpu);
++ else
++ UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
++ u32, be32_to_cpu);
++ }
++
++out:
++ return table;
++}
++
++int unpack_dfa(struct aa_dfa *dfa, void *blob, size_t size)
++{
++ int hsize, i;
++ int error = -ENOMEM;
++
++ /* get dfa table set header */
++ if (size < sizeof(struct table_set_header))
++ goto fail;
++
++ if (ntohl(*(u32 *)blob) != YYTH_MAGIC)
++ goto fail;
++
++ hsize = ntohl(*(u32 *)(blob + 4));
++ if (size < hsize)
++ goto fail;
++
++ blob += hsize;
++ size -= hsize;
++
++ error = -EPROTO;
++ while (size > 0) {
++ struct table_header *table;
++ table = unpack_table(blob, size);
++ if (!table)
++ goto fail;
++
++ switch(table->td_id) {
++ case YYTD_ID_ACCEPT:
++ case YYTD_ID_ACCEPT2:
++ case YYTD_ID_BASE:
++ dfa->tables[table->td_id - 1] = table;
++ if (table->td_flags != YYTD_DATA32)
++ goto fail;
++ break;
++ case YYTD_ID_DEF:
++ case YYTD_ID_NXT:
++ case YYTD_ID_CHK:
++ dfa->tables[table->td_id - 1] = table;
++ if (table->td_flags != YYTD_DATA16)
++ goto fail;
++ break;
++ case YYTD_ID_EC:
++ dfa->tables[table->td_id - 1] = table;
++ if (table->td_flags != YYTD_DATA8)
++ goto fail;
++ break;
++ default:
++ kfree(table);
++ goto fail;
++ }
++
++ blob += table_size(table->td_lolen, table->td_flags);
++ size -= table_size(table->td_lolen, table->td_flags);
++ }
++
++ return 0;
++
++fail:
++ for (i = 0; i < ARRAY_SIZE(dfa->tables); i++) {
++ if (dfa->tables[i]) {
++ kfree(dfa->tables[i]);
++ dfa->tables[i] = NULL;
++ }
++ }
++ return error;
++}
++
++/**
++ * verify_dfa - verify that all the transitions and states in the dfa tables
++ * are in bounds.
++ * @dfa: dfa to test
++ *
++ * assumes dfa has gone through the verification done by unpacking
++ */
++int verify_dfa(struct aa_dfa *dfa)
++{
++ size_t i, state_count, trans_count;
++ int error = -EPROTO;
++
++ /* check that required tables exist */
++ if (!(dfa->tables[YYTD_ID_ACCEPT - 1] &&
++ dfa->tables[YYTD_ID_ACCEPT2 - 1] &&
++ dfa->tables[YYTD_ID_DEF - 1] &&
++ dfa->tables[YYTD_ID_BASE - 1] &&
++ dfa->tables[YYTD_ID_NXT - 1] &&
++ dfa->tables[YYTD_ID_CHK - 1]))
++ goto out;
++
++ /* accept.size == default.size == base.size */
++ state_count = dfa->tables[YYTD_ID_BASE - 1]->td_lolen;
++ if (!(state_count == dfa->tables[YYTD_ID_DEF - 1]->td_lolen &&
++ state_count == dfa->tables[YYTD_ID_ACCEPT - 1]->td_lolen &&
++ state_count == dfa->tables[YYTD_ID_ACCEPT2 - 1]->td_lolen))
++ goto out;
++
++ /* next.size == chk.size */
++ trans_count = dfa->tables[YYTD_ID_NXT - 1]->td_lolen;
++ if (trans_count != dfa->tables[YYTD_ID_CHK - 1]->td_lolen)
++ goto out;
++
++ /* if equivalence classes then its table size must be 256 */
++ if (dfa->tables[YYTD_ID_EC - 1] &&
++ dfa->tables[YYTD_ID_EC - 1]->td_lolen != 256)
++ goto out;
++
++ for (i = 0; i < state_count; i++) {
++ if (DEFAULT_TABLE(dfa)[i] >= state_count)
++ goto out;
++ if (BASE_TABLE(dfa)[i] >= trans_count + 256)
++ goto out;
++ }
++
++ for (i = 0; i < trans_count ; i++) {
++ if (NEXT_TABLE(dfa)[i] >= state_count)
++ goto out;
++ if (CHECK_TABLE(dfa)[i] >= state_count)
++ goto out;
++ }
++
++ /* verify accept permissions */
++ for (i = 0; i < state_count; i++) {
++ int mode = ACCEPT_TABLE(dfa)[i];
++
++ if (mode & ~AA_VALID_PERM_MASK)
++ goto out;
++ if (ACCEPT_TABLE2(dfa)[i] & ~AA_VALID_PERM2_MASK)
++ goto out;
++
++ /* if any exec modifier is set MAY_EXEC must be set */
++ if ((mode & AA_USER_EXEC_TYPE) && !(mode & AA_USER_EXEC))
++ goto out;
++ if ((mode & AA_OTHER_EXEC_TYPE) && !(mode & AA_OTHER_EXEC))
++ goto out;
++ }
++
++ error = 0;
++out:
++ return error;
++}
++
++struct aa_dfa *aa_match_alloc(void)
++{
++ return kzalloc(sizeof(struct aa_dfa), GFP_KERNEL);
++}
++
++void aa_match_free(struct aa_dfa *dfa)
++{
++ if (dfa) {
++ int i;
++
++ for (i = 0; i < ARRAY_SIZE(dfa->tables); i++)
++ kfree(dfa->tables[i]);
++ }
++ kfree(dfa);
++}
++
++/**
++ * aa_dfa_next_state_len - traverse @dfa to find state @str stops at
++ * @dfa: the dfa to match @str against
++ * @start: the state of the dfa to start matching in
++ * @str: the string of bytes to match against the dfa
++ * @len: length of the string of bytes to match
++ *
++ * aa_dfa_next_state will match @str against the dfa and return the state it
++ * finished matching in. The final state can be used to look up the accepting
++ * label, or as the start state of a continuing match.
++ *
++ * aa_dfa_next_state could be implement using this function by doing
++ * return aa_dfa_next_state_len(dfa, start, str, strlen(str));
++ * but that would require traversing the string twice and be slightly
++ * slower.
++ */
++unsigned int aa_dfa_next_state_len(struct aa_dfa *dfa, unsigned int start,
++ const char *str, int len)
++{
++ u16 *def = DEFAULT_TABLE(dfa);
++ u32 *base = BASE_TABLE(dfa);
++ u16 *next = NEXT_TABLE(dfa);
++ u16 *check = CHECK_TABLE(dfa);
++ unsigned int state = start, pos;
++
++ if (state == 0)
++ return 0;
++
++ /* current state is <state>, matching character *str */
++ if (dfa->tables[YYTD_ID_EC - 1]) {
++ u8 *equiv = EQUIV_TABLE(dfa);
++ for (; len; len--) {
++ pos = base[state] + equiv[(u8)*str++];
++ if (check[pos] == state)
++ state = next[pos];
++ else
++ state = def[state];
++ }
++ } else {
++ for (; len; len--) {
++ pos = base[state] + (u8)*str++;
++ if (check[pos] == state)
++ state = next[pos];
++ else
++ state = def[state];
++ }
++ }
++ return state;
++}
++
++/**
++ * aa_dfa_next_state - traverse @dfa to find state @str stops at
++ * @dfa: the dfa to match @str against
++ * @start: the state of the dfa to start matching in
++ * @str: the null terminated string of bytes to match against the dfa
++ *
++ * aa_dfa_next_state will match @str against the dfa and return the state it
++ * finished matching in. The final state can be used to look up the accepting
++ * label, or as the start state of a continuing match.
++ */
++unsigned int aa_dfa_next_state(struct aa_dfa *dfa, unsigned int start,
++ const char *str)
++{
++ u16 *def = DEFAULT_TABLE(dfa);
++ u32 *base = BASE_TABLE(dfa);
++ u16 *next = NEXT_TABLE(dfa);
++ u16 *check = CHECK_TABLE(dfa);
++ unsigned int state = start, pos;
++
++ if (state == 0)
++ return 0;
++
++ /* current state is <state>, matching character *str */
++ if (dfa->tables[YYTD_ID_EC - 1]) {
++ u8 *equiv = EQUIV_TABLE(dfa);
++ while (*str) {
++ pos = base[state] + equiv[(u8)*str++];
++ if (check[pos] == state)
++ state = next[pos];
++ else
++ state = def[state];
++ }
++ } else {
++ while (*str) {
++ pos = base[state] + (u8)*str++;
++ if (check[pos] == state)
++ state = next[pos];
++ else
++ state = def[state];
++ }
++ }
++ return state;
++}
++
++/**
++ * aa_dfa_null_transition - step to next state after null character
++ * @dfa: the dfa to match against
++ * @start: the state of the dfa to start matching in
++ *
++ * aa_dfa_null_transition transitions to the next state after a null
++ * character which is not used in standard matching and is only
++ * used to seperate pairs.
++ */
++unsigned int aa_dfa_null_transition(struct aa_dfa *dfa, unsigned int start)
++{
++ return aa_dfa_next_state_len(dfa, start, "", 1);
++}
++
++/**
++ * aa_dfa_match - find accept perm for @str in @dfa
++ * @dfa: the dfa to match @str against
++ * @str: the string to match against the dfa
++ * @audit_mask: the audit_mask for the final state
++ *
++ * aa_dfa_match will match @str and return the accept perms for the
++ * final state.
++ */
++unsigned int aa_dfa_match(struct aa_dfa *dfa, const char *str, int *audit_mask)
++{
++ int state = aa_dfa_next_state(dfa, DFA_START, str);
++ if (audit_mask)
++ *audit_mask = dfa_audit_mask(dfa, state);
++ return ACCEPT_TABLE(dfa)[state];
++}
++
++/**
++ * aa_match_state - find accept perm and state for @str in @dfa
++ * @dfa: the dfa to match @str against
++ * @start: the state to start the match from
++ * @str: the string to match against the dfa
++ * @final: the state that the match finished in
++ *
++ * aa_match_state will match @str and return the accept perms, and @final
++ * state, the match occured in.
++ */
++unsigned int aa_match_state(struct aa_dfa *dfa, unsigned int start,
++ const char *str, unsigned int *final)
++{
++ unsigned int state;
++ if (dfa) {
++ state = aa_dfa_next_state(dfa, start, str);
++ if (final)
++ *final = state;
++ return ACCEPT_TABLE(dfa)[state];
++ }
++ if (final)
++ *final = 0;
++ return 0;
++}
++
+--- /dev/null
++++ b/security/apparmor/match.h
+@@ -0,0 +1,87 @@
++/*
++ * Copyright (C) 2007 Novell/SUSE
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License as
++ * published by the Free Software Foundation, version 2 of the
++ * License.
++ *
++ * AppArmor submodule (match) prototypes
++ */
++
++#ifndef __MATCH_H
++#define __MATCH_H
++
++#define DFA_START 1
++
++/**
++ * The format used for transition tables is based on the GNU flex table
++ * file format (--tables-file option; see Table File Format in the flex
++ * info pages and the flex sources for documentation). The magic number
++ * used in the header is 0x1B5E783D insted of 0xF13C57B1 though, because
++ * the YY_ID_CHK (check) and YY_ID_DEF (default) tables are used
++ * slightly differently (see the apparmor-parser package).
++ */
++
++#define YYTH_MAGIC 0x1B5E783D
++
++struct table_set_header {
++ u32 th_magic; /* YYTH_MAGIC */
++ u32 th_hsize;
++ u32 th_ssize;
++ u16 th_flags;
++ char th_version[];
++};
++
++#define YYTD_ID_ACCEPT 1
++#define YYTD_ID_BASE 2
++#define YYTD_ID_CHK 3
++#define YYTD_ID_DEF 4
++#define YYTD_ID_EC 5
++#define YYTD_ID_META 6
++#define YYTD_ID_ACCEPT2 7
++#define YYTD_ID_NXT 8
++
++
++#define YYTD_DATA8 1
++#define YYTD_DATA16 2
++#define YYTD_DATA32 4
++
++struct table_header {
++ u16 td_id;
++ u16 td_flags;
++ u32 td_hilen;
++ u32 td_lolen;
++ char td_data[];
++};
++
++#define DEFAULT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_DEF - 1]->td_data))
++#define BASE_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_BASE - 1]->td_data))
++#define NEXT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_NXT - 1]->td_data))
++#define CHECK_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_CHK - 1]->td_data))
++#define EQUIV_TABLE(DFA) ((u8 *)((DFA)->tables[YYTD_ID_EC - 1]->td_data))
++#define ACCEPT_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT - 1]->td_data))
++#define ACCEPT_TABLE2(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT2 -1]->td_data))
++
++struct aa_dfa {
++ struct table_header *tables[YYTD_ID_NXT];
++};
++
++#define byte_to_byte(X) (X)
++
++#define UNPACK_ARRAY(TABLE, BLOB, LEN, TYPE, NTOHX) \
++ do { \
++ typeof(LEN) __i; \
++ TYPE *__t = (TYPE *) TABLE; \
++ TYPE *__b = (TYPE *) BLOB; \
++ for (__i = 0; __i < LEN; __i++) { \
++ __t[__i] = NTOHX(__b[__i]); \
++ } \
++ } while (0)
++
++static inline size_t table_size(size_t len, size_t el_size)
++{
++ return ALIGN(sizeof(struct table_header) + len * el_size, 8);
++}
++
++#endif /* __MATCH_H */
+--- /dev/null
++++ b/security/apparmor/module_interface.c
+@@ -0,0 +1,875 @@
++/*
++ * Copyright (C) 1998-2007 Novell/SUSE
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License as
++ * published by the Free Software Foundation, version 2 of the
++ * License.
++ *
++ * AppArmor userspace policy interface
++ */
++
++#include <asm/unaligned.h>
++
++#include "apparmor.h"
++#include "inline.h"
++
++/*
++ * This mutex is used to synchronize profile adds, replacements, and
++ * removals: we only allow one of these operations at a time.
++ * We do not use the profile list lock here in order to avoid blocking
++ * exec during those operations. (Exec involves a profile list lookup
++ * for named-profile transitions.)
++ */
++DEFINE_MUTEX(aa_interface_lock);
++
++/*
++ * The AppArmor interface treats data as a type byte followed by the
++ * actual data. The interface has the notion of a a named entry
++ * which has a name (AA_NAME typecode followed by name string) followed by
++ * the entries typecode and data. Named types allow for optional
++ * elements and extensions to be added and tested for without breaking
++ * backwards compatability.
++ */
++
++enum aa_code {
++ AA_U8,
++ AA_U16,
++ AA_U32,
++ AA_U64,
++ AA_NAME, /* same as string except it is items name */
++ AA_STRING,
++ AA_BLOB,
++ AA_STRUCT,
++ AA_STRUCTEND,
++ AA_LIST,
++ AA_LISTEND,
++ AA_ARRAY,
++ AA_ARRAYEND,
++};
++
++/*
++ * aa_ext is the read of the buffer containing the serialized profile. The
++ * data is copied into a kernel buffer in apparmorfs and then handed off to
++ * the unpack routines.
++ */
++struct aa_ext {
++ void *start;
++ void *end;
++ void *pos; /* pointer to current position in the buffer */
++ u32 version;
++ char *ns_name;
++};
++
++static inline int aa_inbounds(struct aa_ext *e, size_t size)
++{
++ return (size <= e->end - e->pos);
++}
++
++/**
++ * aa_u16_chunck - test and do bounds checking for a u16 size based chunk
++ * @e: serialized data read head
++ * @chunk: start address for chunk of data
++ *
++ * return the size of chunk found with the read head at the end of
++ * the chunk.
++ */
++static size_t aa_is_u16_chunk(struct aa_ext *e, char **chunk)
++{
++ void *pos = e->pos;
++ size_t size = 0;
++
++ if (!aa_inbounds(e, sizeof(u16)))
++ goto fail;
++ size = le16_to_cpu(get_unaligned((u16 *)e->pos));
++ e->pos += sizeof(u16);
++ if (!aa_inbounds(e, size))
++ goto fail;
++ *chunk = e->pos;
++ e->pos += size;
++ return size;
++
++fail:
++ e->pos = pos;
++ return 0;
++}
++
++static inline int aa_is_X(struct aa_ext *e, enum aa_code code)
++{
++ if (!aa_inbounds(e, 1))
++ return 0;
++ if (*(u8 *) e->pos != code)
++ return 0;
++ e->pos++;
++ return 1;
++}
++
++/**
++ * aa_is_nameX - check is the next element is of type X with a name of @name
++ * @e: serialized data extent information
++ * @code: type code
++ * @name: name to match to the serialized element.
++ *
++ * 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 1 on success (both type code and name tests match) and the read
++ * head is advanced past the headers
++ * returns %0 if either match failes, the read head does not move
++ */
++static int aa_is_nameX(struct aa_ext *e, enum aa_code code, const char *name)
++{
++ void *pos = e->pos;
++ /*
++ * Check for presence of a tagname, and if present name size
++ * AA_NAME tag value is a u16.
++ */
++ if (aa_is_X(e, AA_NAME)) {
++ char *tag;
++ size_t size = aa_is_u16_chunk(e, &tag);
++ /* if a name is specified it must match. otherwise skip tag */
++ if (name && (!size || 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_is_X(e, code))
++ return 1;
++
++fail:
++ e->pos = pos;
++ return 0;
++}
++
++static int aa_is_u16(struct aa_ext *e, u16 *data, const char *name)
++{
++ void *pos = e->pos;
++ if (aa_is_nameX(e, AA_U16, name)) {
++ if (!aa_inbounds(e, sizeof(u16)))
++ goto fail;
++ if (data)
++ *data = le16_to_cpu(get_unaligned((u16 *)e->pos));
++ e->pos += sizeof(u16);
++ return 1;
++ }
++fail:
++ e->pos = pos;
++ return 0;
++}
++
++static int aa_is_u32(struct aa_ext *e, u32 *data, const char *name)
++{
++ void *pos = e->pos;
++ if (aa_is_nameX(e, AA_U32, name)) {
++ if (!aa_inbounds(e, sizeof(u32)))
++ goto fail;
++ if (data)
++ *data = le32_to_cpu(get_unaligned((u32 *)e->pos));
++ e->pos += sizeof(u32);
++ return 1;
++ }
++fail:
++ e->pos = pos;
++ return 0;
++}
++
++static size_t aa_is_array(struct aa_ext *e, const char *name)
++{
++ void *pos = e->pos;
++ if (aa_is_nameX(e, AA_ARRAY, name)) {
++ int size;
++ if (!aa_inbounds(e, sizeof(u16)))
++ goto fail;
++ size = (int) le16_to_cpu(get_unaligned((u16 *)e->pos));
++ e->pos += sizeof(u16);
++ return size;
++ }
++fail:
++ e->pos = pos;
++ return 0;
++}
++
++static size_t aa_is_blob(struct aa_ext *e, char **blob, const char *name)
++{
++ void *pos = e->pos;
++ if (aa_is_nameX(e, AA_BLOB, name)) {
++ u32 size;
++ if (!aa_inbounds(e, sizeof(u32)))
++ goto fail;
++ size = le32_to_cpu(get_unaligned((u32 *)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;
++}
++
++static int aa_is_dynstring(struct aa_ext *e, char **string, const char *name)
++{
++ char *src_str;
++ size_t size = 0;
++ void *pos = e->pos;
++ *string = NULL;
++ if (aa_is_nameX(e, AA_STRING, name) &&
++ (size = aa_is_u16_chunk(e, &src_str))) {
++ char *str;
++ if (!(str = kmalloc(size, GFP_KERNEL)))
++ goto fail;
++ memcpy(str, src_str, size);
++ *string = str;
++ }
++
++ return size;
++
++fail:
++ e->pos = pos;
++ return 0;
++}
++
++/**
++ * aa_unpack_dfa - unpack a file rule dfa
++ * @e: serialized data extent information
++ *
++ * returns dfa or ERR_PTR
++ */
++static struct aa_dfa *aa_unpack_dfa(struct aa_ext *e)
++{
++ char *blob = NULL;
++ size_t size, error = 0;
++ struct aa_dfa *dfa = NULL;
++
++ size = aa_is_blob(e, &blob, "aadfa");
++ if (size) {
++ dfa = aa_match_alloc();
++ if (dfa) {
++ /*
++ * The dfa is aligned with in the blob to 8 bytes
++ * from the beginning of the stream.
++ */
++ size_t sz = blob - (char *) e->start;
++ size_t pad = ALIGN(sz, 8) - sz;
++ error = unpack_dfa(dfa, blob + pad, size - pad);
++ if (!error)
++ error = verify_dfa(dfa);
++ } else {
++ error = -ENOMEM;
++ }
++
++ if (error) {
++ aa_match_free(dfa);
++ dfa = ERR_PTR(error);
++ }
++ }
++
++ return dfa;
++}
++
++static int aa_unpack_exec_table(struct aa_ext *e, struct aa_profile *profile)
++{
++ void *pos = e->pos;
++
++ /* exec table is optional */
++ if (aa_is_nameX(e, AA_STRUCT, "xtable")) {
++ int i, size;
++
++ size = aa_is_array(e, NULL);
++ /* currently 4 exec bits and entries 0-3 are reserved iupcx */
++ if (size > 16 - 4)
++ goto fail;
++ profile->exec_table = kzalloc(sizeof(char *) * size,
++ GFP_KERNEL);
++ if (!profile->exec_table)
++ goto fail;
++
++ for (i = 0; i < size; i++) {
++ char *tmp;
++ if (!aa_is_dynstring(e, &tmp, NULL))
++ goto fail;
++ /* note: strings beginning with a : have an embedded
++ \0 seperating the profile ns name from the profile
++ name */
++ profile->exec_table[i] = tmp;
++ }
++ if (!aa_is_nameX(e, AA_ARRAYEND, NULL))
++ goto fail;
++ if (!aa_is_nameX(e, AA_STRUCTEND, NULL))
++ goto fail;
++ profile->exec_table_size = size;
++ }
++ return 1;
++
++fail:
++ e->pos = pos;
++ return 0;
++}
++
++/**
++ * aa_unpack_profile - unpack a serialized profile
++ * @e: serialized data extent information
++ * @sa: audit struct for the operation
++ */
++static struct aa_profile *aa_unpack_profile(struct aa_ext *e,
++ struct aa_audit *sa)
++{
++ struct aa_profile *profile = NULL;
++
++ int error = -EPROTO;
++
++ profile = alloc_aa_profile();
++ if (!profile)
++ return ERR_PTR(-ENOMEM);
++
++ /* check that we have the right struct being passed */
++ if (!aa_is_nameX(e, AA_STRUCT, "profile"))
++ goto fail;
++ if (!aa_is_dynstring(e, &profile->name, NULL))
++ goto fail;
++
++ /* per profile debug flags (complain, audit) */
++ if (!aa_is_nameX(e, AA_STRUCT, "flags"))
++ goto fail;
++ if (!aa_is_u32(e, &(profile->flags.hat), NULL))
++ goto fail;
++ if (!aa_is_u32(e, &(profile->flags.complain), NULL))
++ goto fail;
++ if (!aa_is_u32(e, &(profile->flags.audit), NULL))
++ goto fail;
++ if (!aa_is_nameX(e, AA_STRUCTEND, NULL))
++ goto fail;
++
++ if (!aa_is_u32(e, &(profile->capabilities), NULL))
++ goto fail;
++ if (!aa_is_u32(e, &(profile->audit_caps), NULL))
++ goto fail;
++ if (!aa_is_u32(e, &(profile->quiet_caps), NULL))
++ goto fail;
++ if (!aa_is_u32(e, &(profile->set_caps), NULL))
++ goto fail;
++
++ /* get file rules */
++ profile->file_rules = aa_unpack_dfa(e);
++ if (IS_ERR(profile->file_rules)) {
++ error = PTR_ERR(profile->file_rules);
++ profile->file_rules = NULL;
++ goto fail;
++ }
++
++ if (!aa_unpack_exec_table(e, profile))
++ goto fail;
++
++ if (!aa_is_nameX(e, AA_STRUCTEND, NULL))
++ goto fail;
++
++ return profile;
++
++fail:
++ sa->name = profile && profile->name ? profile->name : "unknown";
++ if (!sa->info)
++ sa->info = "failed to unpack profile";
++ aa_audit_status(NULL, sa);
++
++ if (profile)
++ free_aa_profile(profile);
++
++ return ERR_PTR(error);
++}
++
++/**
++ * aa_verify_head - unpack serialized stream header
++ * @e: serialized data read head
++ * @operation: operation header is being verified for
++ *
++ * returns error or 0 if header is good
++ */
++static int aa_verify_header(struct aa_ext *e, struct aa_audit *sa)
++{
++ /* get the interface version */
++ if (!aa_is_u32(e, &e->version, "version")) {
++ sa->info = "invalid profile format";
++ aa_audit_status(NULL, sa);
++ return -EPROTONOSUPPORT;
++ }
++
++ /* check that the interface version is currently supported */
++ if (e->version != 5) {
++ sa->info = "unsupported interface version";
++ aa_audit_status(NULL, sa);
++ return -EPROTONOSUPPORT;
++ }
++
++ /* read the namespace if present */
++ if (!aa_is_dynstring(e, &e->ns_name, "namespace")) {
++ e->ns_name = NULL;
++ }
++
++ return 0;
++}
++
++/**
++ * aa_add_profile - Unpack and add a new profile to the profile list
++ * @data: serialized data stream
++ * @size: size of the serialized data stream
++ */
++ssize_t aa_add_profile(void *data, size_t size)
++{
++ struct aa_profile *profile = NULL;
++ struct aa_namespace *ns = NULL;
++ struct aa_ext e = {
++ .start = data,
++ .end = data + size,
++ .pos = data,
++ .ns_name = NULL
++ };
++ ssize_t error;
++ struct aa_audit sa;
++ memset(&sa, 0, sizeof(sa));
++ sa.operation = "profile_load";
++ sa.gfp_mask = GFP_KERNEL;
++
++ error = aa_verify_header(&e, &sa);
++ if (error)
++ return error;
++
++ profile = aa_unpack_profile(&e, &sa);
++ if (IS_ERR(profile))
++ return PTR_ERR(profile);
++
++ mutex_lock(&aa_interface_lock);
++ write_lock(&profile_ns_list_lock);
++ if (e.ns_name)
++ ns = __aa_find_namespace(e.ns_name, &profile_ns_list);
++ else
++ ns = default_namespace;
++ if (!ns) {
++ struct aa_namespace *new_ns;
++ write_unlock(&profile_ns_list_lock);
++ new_ns = alloc_aa_namespace(e.ns_name);
++ if (!new_ns) {
++ mutex_unlock(&aa_interface_lock);
++ return -ENOMEM;
++ }
++ write_lock(&profile_ns_list_lock);
++ ns = __aa_find_namespace(e.ns_name, &profile_ns_list);
++ if (!ns) {
++ list_add(&new_ns->list, &profile_ns_list);
++ ns = new_ns;
++ } else
++ free_aa_namespace(new_ns);
++ }
++
++ write_lock(&ns->lock);
++ if (__aa_find_profile(profile->name, &ns->profiles)) {
++ /* A profile with this name exists already. */
++ write_unlock(&ns->lock);
++ write_unlock(&profile_ns_list_lock);
++ sa.name = profile->name;
++ sa.name2 = ns->name;
++ sa.info = "failed: profile already loaded";
++ aa_audit_status(NULL, &sa);
++ mutex_unlock(&aa_interface_lock);
++ aa_put_profile(profile);
++ return -EEXIST;
++ }
++ profile->ns = aa_get_namespace(ns);
++ ns->profile_count++;
++ list_add(&profile->list, &ns->profiles);
++ write_unlock(&ns->lock);
++ write_unlock(&profile_ns_list_lock);
++
++ sa.name = profile->name;
++ sa.name2 = ns->name;
++ aa_audit_status(NULL, &sa);
++ mutex_unlock(&aa_interface_lock);
++ return size;
++}
++
++/**
++ * task_replace - replace a task's profile
++ * @task: task to replace profile on
++ * @new_cxt: new aa_task_context to do replacement with
++ * @new_profile: new profile
++ */
++static inline void task_replace(struct task_struct *task,
++ struct aa_task_context *new_cxt,
++ struct aa_profile *new_profile)
++{
++ struct aa_task_context *cxt = aa_task_context(task);
++
++ AA_DEBUG("%s: replacing profile for task %d "
++ "profile=%s (%p)\n",
++ __FUNCTION__,
++ cxt->task->pid,
++ cxt->profile->name, cxt->profile);
++
++ aa_change_task_context(task, new_cxt, new_profile, cxt->cookie,
++ cxt->previous_profile);
++}
++
++/**
++ * aa_replace_profile - replace a profile on the profile list
++ * @udata: serialized data stream
++ * @size: size of the serialized data stream
++ *
++ * unpack and replace a profile on the profile list and uses of that profile
++ * by any aa_task_context. If the profile does not exist on the profile list
++ * it is added. Return %0 or error.
++ */
++ssize_t aa_replace_profile(void *udata, size_t size)
++{
++ struct aa_profile *old_profile, *new_profile;
++ struct aa_namespace *ns;
++ struct aa_task_context *new_cxt;
++ struct aa_ext e = {
++ .start = udata,
++ .end = udata + size,
++ .pos = udata,
++ .ns_name = NULL
++ };
++ ssize_t error;
++ struct aa_audit sa;
++ memset(&sa, 0, sizeof(sa));
++ sa.operation = "profile_replace";
++ sa.gfp_mask = GFP_KERNEL;
++
++ error = aa_verify_header(&e, &sa);
++ if (error)
++ return error;
++
++ new_profile = aa_unpack_profile(&e, &sa);
++ if (IS_ERR(new_profile))
++ return PTR_ERR(new_profile);
++
++ mutex_lock(&aa_interface_lock);
++ write_lock(&profile_ns_list_lock);
++ if (e.ns_name)
++ ns = __aa_find_namespace(e.ns_name, &profile_ns_list);
++ else
++ ns = default_namespace;
++ if (!ns) {
++ struct aa_namespace *new_ns;
++ write_unlock(&profile_ns_list_lock);
++ new_ns = alloc_aa_namespace(e.ns_name);
++ if (!new_ns) {
++ mutex_unlock(&aa_interface_lock);
++ return -ENOMEM;
++ }
++ write_lock(&profile_ns_list_lock);
++ ns = __aa_find_namespace(e.ns_name, &profile_ns_list);
++ if (!ns) {
++ list_add(&new_ns->list, &profile_ns_list);
++ ns = new_ns;
++ } else
++ free_aa_namespace(new_ns);
++ }
++
++ write_lock(&ns->lock);
++ old_profile = __aa_find_profile(new_profile->name, &ns->profiles);
++ if (old_profile) {
++ lock_profile(old_profile);
++ old_profile->isstale = 1;
++ list_del_init(&old_profile->list);
++ unlock_profile(old_profile);
++ ns->profile_count--;
++ }
++ new_profile->ns = aa_get_namespace(ns);
++ ns->profile_count++;
++ /* not don't need an extra ref count to keep new_profile as
++ * it is protect by the interface mutex */
++ list_add(&new_profile->list, &ns->profiles);
++ write_unlock(&ns->lock);
++ write_unlock(&profile_ns_list_lock);
++
++ if (!old_profile) {
++ sa.operation = "profile_load";
++ goto out;
++ }
++ /*
++ * Replacement needs to allocate a new aa_task_context for each
++ * task confined by old_profile. To do this the profile locks
++ * are only held when the actual switch is done per task. While
++ * looping to allocate a new aa_task_context the old_task list
++ * may get shorter if tasks exit/change their profile but will
++ * not get longer as new task will not use old_profile detecting
++ * that is stale.
++ */
++ do {
++ new_cxt = aa_alloc_task_context(GFP_KERNEL | __GFP_NOFAIL);
++
++ lock_both_profiles(old_profile, new_profile);
++ if (!list_empty(&old_profile->task_contexts)) {
++ struct task_struct *task =
++ list_entry(old_profile->task_contexts.next,
++ struct aa_task_context, list)->task;
++ task_lock(task);
++ task_replace(task, new_cxt, new_profile);
++ task_unlock(task);
++ new_cxt = NULL;
++ }
++ unlock_both_profiles(old_profile, new_profile);
++ } while (!new_cxt);
++ aa_free_task_context(new_cxt);
++ aa_put_profile(old_profile);
++
++out:
++ sa.name = new_profile->name;
++ sa.name2 = ns->name;
++ aa_audit_status(NULL, &sa);
++ mutex_unlock(&aa_interface_lock);
++ return size;
++}
++
++/**
++ * aa_remove_profile - remove a profile from the system
++ * @name: name of the profile to remove
++ * @size: size of the name
++ *
++ * remove a profile from the profile list and all aa_task_context references
++ * to said profile.
++ */
++ssize_t aa_remove_profile(char *name, size_t size)
++{
++ struct aa_namespace *ns;
++ struct aa_profile *profile;
++ struct aa_audit sa;
++ memset(&sa, 0, sizeof(sa));
++ sa.operation = "profile_remove";
++ sa.gfp_mask = GFP_KERNEL;
++
++ mutex_lock(&aa_interface_lock);
++ write_lock(&profile_ns_list_lock);
++
++ if (name[0] == ':') {
++ char *split = strchr(name + 1, ':');
++ if (!split)
++ goto noent;
++ *split = 0;
++ ns = __aa_find_namespace(name + 1, &profile_ns_list);
++ name = split + 1;
++ } else {
++ ns = default_namespace;
++ }
++
++ if (!ns)
++ goto noent;
++ sa.name2 = ns->name;
++ write_lock(&ns->lock);
++ profile = __aa_find_profile(name, &ns->profiles);
++ if (!profile) {
++ write_unlock(&ns->lock);
++ goto noent;
++ }
++ sa.name = profile->name;
++
++ /* Remove the profile from each task context it is on. */
++ lock_profile(profile);
++ profile->isstale = 1;
++ aa_unconfine_tasks(profile);
++ list_del_init(&profile->list);
++ ns->profile_count--;
++ unlock_profile(profile);
++ /* Release the profile itself. */
++ write_unlock(&ns->lock);
++ /* check to see if the namespace has become stale */
++ if (ns != default_namespace && ns->profile_count == 0) {
++ list_del_init(&ns->list);
++ aa_put_namespace(ns);
++ }
++ write_unlock(&profile_ns_list_lock);
++
++ aa_audit_status(NULL, &sa);
++ mutex_unlock(&aa_interface_lock);
++ aa_put_profile(profile);
++
++ return size;
++
++noent:
++ write_unlock(&profile_ns_list_lock);
++ sa.info = "failed: profile does not exist";
++ aa_audit_status(NULL, &sa);
++ mutex_unlock(&aa_interface_lock);
++ return -ENOENT;
++}
++
++/**
++ * free_aa_namespace_kref - free aa_namespace by kref (see aa_put_namespace)
++ * @kr: kref callback for freeing of a namespace
++ */
++void free_aa_namespace_kref(struct kref *kref)
++{
++ struct aa_namespace *ns=container_of(kref, struct aa_namespace, count);
++
++ free_aa_namespace(ns);
++}
++
++/**
++ * alloc_aa_namespace - allocate, initialize and return a new namespace
++ * @name: a preallocated name
++ * Returns NULL on failure.
++ */
++struct aa_namespace *alloc_aa_namespace(char *name)
++{
++ struct aa_namespace *ns;
++
++ ns = kzalloc(sizeof(*ns), GFP_KERNEL);
++ AA_DEBUG("%s(%p)\n", __FUNCTION__, ns);
++ if (ns) {
++ ns->name = name;
++ INIT_LIST_HEAD(&ns->list);
++ INIT_LIST_HEAD(&ns->profiles);
++ kref_init(&ns->count);
++ rwlock_init(&ns->lock);
++
++ ns->null_complain_profile = alloc_aa_profile();
++ if (!ns->null_complain_profile) {
++ if (!name)
++ kfree(ns->name);
++ kfree(ns);
++ return NULL;
++ }
++ ns->null_complain_profile->name =
++ kstrdup("null-complain-profile", GFP_KERNEL);
++ if (!ns->null_complain_profile->name) {
++ free_aa_profile(ns->null_complain_profile);
++ if (!name)
++ kfree(ns->name);
++ kfree(ns);
++ return NULL;
++ }
++ ns->null_complain_profile->flags.complain = 1;
++ /* null_complain_profile doesn't contribute to ns ref count */
++ ns->null_complain_profile->ns = ns;
++ }
++ return ns;
++}
++
++/**
++ * free_aa_namespace - free a profile namespace
++ * @namespace: the namespace to free
++ *
++ * Free a namespace. All references to the namespace must have been put.
++ * If the namespace was referenced by a profile confining a task,
++ * free_aa_namespace will be called indirectly (through free_aa_profile)
++ * from an rcu callback routine, so we must not sleep here.
++ */
++void free_aa_namespace(struct aa_namespace *ns)
++{
++ AA_DEBUG("%s(%p)\n", __FUNCTION__, ns);
++
++ if (!ns)
++ return;
++
++ /* namespace still contains profiles -- invalid */
++ if (!list_empty(&ns->profiles)) {
++ AA_ERROR("%s: internal error, "
++ "namespace '%s' still contains profiles\n",
++ __FUNCTION__,
++ ns->name);
++ BUG();
++ }
++ if (!list_empty(&ns->list)) {
++ AA_ERROR("%s: internal error, "
++ "namespace '%s' still on list\n",
++ __FUNCTION__,
++ ns->name);
++ BUG();
++ }
++ /* null_complain_profile doesn't contribute to ns ref counting */
++ ns->null_complain_profile->ns = NULL;
++ aa_put_profile(ns->null_complain_profile);
++ kfree(ns->name);
++ kfree(ns);
++}
++
++/**
++ * free_aa_profile_kref - free aa_profile by kref (called by aa_put_profile)
++ * @kr: kref callback for freeing of a profile
++ */
++void free_aa_profile_kref(struct kref *kref)
++{
++ struct aa_profile *p=container_of(kref, struct aa_profile, count);
++
++ free_aa_profile(p);
++}
++
++/**
++ * alloc_aa_profile - allocate, initialize and return a new profile
++ * Returns NULL on failure.
++ */
++struct aa_profile *alloc_aa_profile(void)
++{
++ struct aa_profile *profile;
++
++ profile = kzalloc(sizeof(*profile), GFP_KERNEL);
++ AA_DEBUG("%s(%p)\n", __FUNCTION__, profile);
++ if (profile) {
++ INIT_LIST_HEAD(&profile->list);
++ kref_init(&profile->count);
++ INIT_LIST_HEAD(&profile->task_contexts);
++ spin_lock_init(&profile->lock);
++ }
++ return profile;
++}
++
++/**
++ * free_aa_profile - free a profile
++ * @profile: the profile to free
++ *
++ * Free a profile, its hats and null_profile. All references to the profile,
++ * its hats and null_profile must have been put.
++ *
++ * If the profile was referenced from a task context, free_aa_profile() will
++ * be called from an rcu callback routine, so we must not sleep here.
++ */
++void free_aa_profile(struct aa_profile *profile)
++{
++ AA_DEBUG("%s(%p)\n", __FUNCTION__, profile);
++
++ if (!profile)
++ return;
++
++ /* profile is still on profile namespace list -- invalid */
++ if (!list_empty(&profile->list)) {
++ AA_ERROR("%s: internal error, "
++ "profile '%s' still on global list\n",
++ __FUNCTION__,
++ profile->name);
++ BUG();
++ }
++ aa_put_namespace(profile->ns);
++
++ aa_match_free(profile->file_rules);
++
++ if (profile->name) {
++ AA_DEBUG("%s: %s\n", __FUNCTION__, profile->name);
++ kfree(profile->name);
++ }
++
++ kfree(profile);
++}
++
++/**
++ * aa_unconfine_tasks - remove tasks on a profile's task context list
++ * @profile: profile to remove tasks from
++ *
++ * Assumes that @profile lock is held.
++ */
++void aa_unconfine_tasks(struct aa_profile *profile)
++{
++ while (!list_empty(&profile->task_contexts)) {
++ struct task_struct *task =
++ list_entry(profile->task_contexts.next,
++ struct aa_task_context, list)->task;
++ task_lock(task);
++ aa_change_task_context(task, NULL, NULL, 0, NULL);
++ task_unlock(task);
++ }
++}