diff options
| -rw-r--r-- | security/apparmor/include/match.h | 132 | ||||
| -rw-r--r-- | security/apparmor/match.c | 353 | 
2 files changed, 485 insertions, 0 deletions
diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h new file mode 100644 index 00000000000..734a6d35112 --- /dev/null +++ b/security/apparmor/include/match.h @@ -0,0 +1,132 @@ +/* + * AppArmor security module + * + * This file contains AppArmor policy dfa matching engine definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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. + */ + +#ifndef __AA_MATCH_H +#define __AA_MATCH_H + +#include <linux/workqueue.h> + +#define DFA_NOMATCH			0 +#define DFA_START			1 + +#define DFA_VALID_PERM_MASK		0xffffffff +#define DFA_VALID_PERM2_MASK		0xffffffff + +/** + * 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 +#define YYTH_DEF_RECURSE 0x1			/* DEF Table is recursive */ + +struct table_set_header { +	u32 th_magic;		/* YYTH_MAGIC */ +	u32 th_hsize; +	u32 th_ssize; +	u16 th_flags; +	char th_version[]; +}; + +/* The YYTD_ID are one less than flex table mappings.  The flex id + * has 1 subtracted at table load time, this allows us to directly use the + * ID's as indexes. + */ +#define	YYTD_ID_ACCEPT	0 +#define YYTD_ID_BASE	1 +#define YYTD_ID_CHK	2 +#define YYTD_ID_DEF	3 +#define YYTD_ID_EC	4 +#define YYTD_ID_META	5 +#define YYTD_ID_ACCEPT2 6 +#define YYTD_ID_NXT	7 +#define YYTD_ID_TSIZE	8 + +#define YYTD_DATA8	1 +#define YYTD_DATA16	2 +#define YYTD_DATA32	4 +#define YYTD_DATA64	8 + +/* Each ACCEPT2 table gets 6 dedicated flags, YYTD_DATAX define the + * first flags + */ +#define ACCEPT1_FLAGS(X) ((X) & 0x3f) +#define ACCEPT2_FLAGS(X) ACCEPT1_FLAGS((X) >> YYTD_ID_ACCEPT2) +#define TO_ACCEPT1_FLAG(X) ACCEPT1_FLAGS(X) +#define TO_ACCEPT2_FLAG(X) (ACCEPT1_FLAGS(X) << YYTD_ID_ACCEPT2) +#define DFA_FLAG_VERIFY_STATES 0x1000 + +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]->td_data)) +#define BASE_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_BASE]->td_data)) +#define NEXT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_NXT]->td_data)) +#define CHECK_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_CHK]->td_data)) +#define EQUIV_TABLE(DFA) ((u8 *)((DFA)->tables[YYTD_ID_EC]->td_data)) +#define ACCEPT_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT]->td_data)) +#define ACCEPT_TABLE2(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT2]->td_data)) + +struct aa_dfa { +	struct kref count; +	u16 flags; +	struct table_header *tables[YYTD_ID_TSIZE]; +}; + +#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); +} + +struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags); +unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start, +			      const char *str, int len); +unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start, +			  const char *str); +void aa_dfa_free_kref(struct kref *kref); + +/** + * aa_put_dfa - put a dfa refcount + * @dfa: dfa to put refcount   (MAYBE NULL) + * + * Requires: if @dfa != NULL that a valid refcount be held + */ +static inline void aa_put_dfa(struct aa_dfa *dfa) +{ +	if (dfa) +		kref_put(&dfa->count, aa_dfa_free_kref); +} + +#endif /* __AA_MATCH_H */ diff --git a/security/apparmor/match.c b/security/apparmor/match.c new file mode 100644 index 00000000000..5cb4dc1f699 --- /dev/null +++ b/security/apparmor/match.c @@ -0,0 +1,353 @@ +/* + * AppArmor security module + * + * This file contains AppArmor dfa based regular expression matching engine + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/err.h> +#include <linux/kref.h> + +#include "include/apparmor.h" +#include "include/match.h" + +/** + * unpack_table - unpack a dfa table (one of accept, default, base, next check) + * @blob: data to unpack (NOT NULL) + * @bsize: size of blob + * + * Returns: pointer to table else NULL on failure + * + * NOTE: must be freed by kvfree (not kmalloc) + */ +static struct table_header *unpack_table(char *blob, size_t bsize) +{ +	struct table_header *table = NULL; +	struct table_header th; +	size_t tsize; + +	if (bsize < sizeof(struct table_header)) +		goto out; + +	/* loaded td_id's start at 1, subtract 1 now to avoid doing +	 * it every time we use td_id as an index +	 */ +	th.td_id = be16_to_cpu(*(u16 *) (blob)) - 1; +	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 = kvmalloc(tsize); +	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 if (th.td_flags == YYTD_DATA32) +			UNPACK_ARRAY(table->td_data, blob, th.td_lolen, +				     u32, be32_to_cpu); +		else +			goto fail; +	} + +out: +	/* if table was vmalloced make sure the page tables are synced +	 * before it is used, as it goes live to all cpus. +	 */ +	if (is_vmalloc_addr(table)) +		vm_unmap_aliases(); +	return table; +fail: +	kvfree(table); +	return NULL; +} + +/** + * verify_dfa - verify that transitions and states in the tables are in bounds. + * @dfa: dfa to test  (NOT NULL) + * @flags: flags controlling what type of accept table are acceptable + * + * Assumes dfa has gone through the first pass verification done by unpacking + * NOTE: this does not valid accept table values + * + * Returns: %0 else error code on failure to verify + */ +static int verify_dfa(struct aa_dfa *dfa, int flags) +{ +	size_t i, state_count, trans_count; +	int error = -EPROTO; + +	/* check that required tables exist */ +	if (!(dfa->tables[YYTD_ID_DEF] && +	      dfa->tables[YYTD_ID_BASE] && +	      dfa->tables[YYTD_ID_NXT] && dfa->tables[YYTD_ID_CHK])) +		goto out; + +	/* accept.size == default.size == base.size */ +	state_count = dfa->tables[YYTD_ID_BASE]->td_lolen; +	if (ACCEPT1_FLAGS(flags)) { +		if (!dfa->tables[YYTD_ID_ACCEPT]) +			goto out; +		if (state_count != dfa->tables[YYTD_ID_ACCEPT]->td_lolen) +			goto out; +	} +	if (ACCEPT2_FLAGS(flags)) { +		if (!dfa->tables[YYTD_ID_ACCEPT2]) +			goto out; +		if (state_count != dfa->tables[YYTD_ID_ACCEPT2]->td_lolen) +			goto out; +	} +	if (state_count != dfa->tables[YYTD_ID_DEF]->td_lolen) +		goto out; + +	/* next.size == chk.size */ +	trans_count = dfa->tables[YYTD_ID_NXT]->td_lolen; +	if (trans_count != dfa->tables[YYTD_ID_CHK]->td_lolen) +		goto out; + +	/* if equivalence classes then its table size must be 256 */ +	if (dfa->tables[YYTD_ID_EC] && +	    dfa->tables[YYTD_ID_EC]->td_lolen != 256) +		goto out; + +	if (flags & DFA_FLAG_VERIFY_STATES) { +		for (i = 0; i < state_count; i++) { +			if (DEFAULT_TABLE(dfa)[i] >= state_count) +				goto out; +			/* TODO: do check that DEF state recursion terminates */ +			if (BASE_TABLE(dfa)[i] + 255 >= trans_count) { +				printk(KERN_ERR "AppArmor DFA next/check upper " +				       "bounds error\n"); +				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; +		} +	} + +	error = 0; +out: +	return error; +} + +/** + * dfa_free - free a dfa allocated by aa_dfa_unpack + * @dfa: the dfa to free  (MAYBE NULL) + * + * Requires: reference count to dfa == 0 + */ +static void dfa_free(struct aa_dfa *dfa) +{ +	if (dfa) { +		int i; + +		for (i = 0; i < ARRAY_SIZE(dfa->tables); i++) { +			kvfree(dfa->tables[i]); +			dfa->tables[i] = NULL; +		} +		kfree(dfa); +	} +} + +/** + * aa_dfa_free_kref - free aa_dfa by kref (called by aa_put_dfa) + * @kr: kref callback for freeing of a dfa  (NOT NULL) + */ +void aa_dfa_free_kref(struct kref *kref) +{ +	struct aa_dfa *dfa = container_of(kref, struct aa_dfa, count); +	dfa_free(dfa); +} + +/** + * aa_dfa_unpack - unpack the binary tables of a serialized dfa + * @blob: aligned serialized stream of data to unpack  (NOT NULL) + * @size: size of data to unpack + * @flags: flags controlling what type of accept tables are acceptable + * + * Unpack a dfa that has been serialized.  To find information on the dfa + * format look in Documentation/apparmor.txt + * Assumes the dfa @blob stream has been aligned on a 8 byte boundry + * + * Returns: an unpacked dfa ready for matching or ERR_PTR on failure + */ +struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags) +{ +	int hsize; +	int error = -ENOMEM; +	char *data = blob; +	struct table_header *table = NULL; +	struct aa_dfa *dfa = kzalloc(sizeof(struct aa_dfa), GFP_KERNEL); +	if (!dfa) +		goto fail; + +	kref_init(&dfa->count); + +	error = -EPROTO; + +	/* get dfa table set header */ +	if (size < sizeof(struct table_set_header)) +		goto fail; + +	if (ntohl(*(u32 *) data) != YYTH_MAGIC) +		goto fail; + +	hsize = ntohl(*(u32 *) (data + 4)); +	if (size < hsize) +		goto fail; + +	dfa->flags = ntohs(*(u16 *) (data + 12)); +	data += hsize; +	size -= hsize; + +	while (size > 0) { +		table = unpack_table(data, size); +		if (!table) +			goto fail; + +		switch (table->td_id) { +		case YYTD_ID_ACCEPT: +			if (!(table->td_flags & ACCEPT1_FLAGS(flags))) +				goto fail; +			break; +		case YYTD_ID_ACCEPT2: +			if (!(table->td_flags & ACCEPT2_FLAGS(flags))) +				goto fail; +			break; +		case YYTD_ID_BASE: +			if (table->td_flags != YYTD_DATA32) +				goto fail; +			break; +		case YYTD_ID_DEF: +		case YYTD_ID_NXT: +		case YYTD_ID_CHK: +			if (table->td_flags != YYTD_DATA16) +				goto fail; +			break; +		case YYTD_ID_EC: +			if (table->td_flags != YYTD_DATA8) +				goto fail; +			break; +		default: +			goto fail; +		} +		/* check for duplicate table entry */ +		if (dfa->tables[table->td_id]) +			goto fail; +		dfa->tables[table->td_id] = table; +		data += table_size(table->td_lolen, table->td_flags); +		size -= table_size(table->td_lolen, table->td_flags); +		table = NULL; +	} + +	error = verify_dfa(dfa, flags); +	if (error) +		goto fail; + +	return dfa; + +fail: +	kvfree(table); +	dfa_free(dfa); +	return ERR_PTR(error); +} + +/** + * aa_dfa_match_len - traverse @dfa to find state @str stops at + * @dfa: the dfa to match @str against  (NOT NULL) + * @start: the state of the dfa to start matching in + * @str: the string of bytes to match against the dfa  (NOT NULL) + * @len: length of the string of bytes to match + * + * aa_dfa_match_len 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. + * + * This function will happily match again the 0 byte and only finishes + * when @len input is consumed. + * + * Returns: final state reached after input is consumed + */ +unsigned int aa_dfa_match_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]) { +		/* Equivalence class table defined */ +		u8 *equiv = EQUIV_TABLE(dfa); +		/* default is direct to next state */ +		for (; len; len--) { +			pos = base[state] + equiv[(u8) *str++]; +			if (check[pos] == state) +				state = next[pos]; +			else +				state = def[state]; +		} +	} else { +		/* default is direct to next state */ +		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  (NOT NULL) + * @start: the state of the dfa to start matching in + * @str: the null terminated string of bytes to match against the dfa (NOT NULL) + * + * 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. + * + * Returns: final state reached after input is consumed + */ +unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start, +			  const char *str) +{ +	return aa_dfa_match_len(dfa, start, str, strlen(str)); +}  |