fs/namespace.c | 3 include/linux/audit.h | 5 include/linux/namespace.h | 3 kernel/audit.c | 12 security/Kconfig | 1 security/Makefile | 1 security/apparmor/Kbuild | 10 security/apparmor/Kconfig | 9 security/apparmor/Makefile | 25 security/apparmor/aamatch/Kbuild | 6 security/apparmor/aamatch/match.h | 137 ++ security/apparmor/aamatch/match_pcre.c | 169 ++ security/apparmor/aamatch/pcre_exec.c | 1945 ++++++++++++++++++++++++++++++++ security/apparmor/aamatch/pcre_exec.h | 308 +++++ security/apparmor/aamatch/pcre_tables.h | 184 +++ security/apparmor/apparmor.h | 302 ++++ security/apparmor/apparmor_version.c | 42 security/apparmor/apparmorfs.c | 440 +++++++ security/apparmor/capabilities.c | 54 security/apparmor/inline.h | 364 +++++ security/apparmor/list.c | 271 ++++ security/apparmor/lsm.c | 959 +++++++++++++++ security/apparmor/main.c | 1691 +++++++++++++++++++++++++++ security/apparmor/module_interface.c | 712 +++++++++++ security/apparmor/module_interface.h | 37 security/apparmor/procattr.c | 329 +++++ security/apparmor/shared.h | 47 27 files changed, 8063 insertions(+), 3 deletions(-) --- linux-2.6.16.29.orig/fs/namespace.c 2006-10-12 21:11:17.000000000 -0700 +++ linux-2.6.16.29/fs/namespace.c 2006-10-12 21:11:14.000000000 -0700 @@ -46,7 +46,8 @@ static int event; static struct list_head *mount_hashtable; static int hash_mask __read_mostly, hash_bits __read_mostly; static kmem_cache_t *mnt_cache; -static struct rw_semaphore namespace_sem; +struct rw_semaphore namespace_sem; +EXPORT_SYMBOL_GPL(namespace_sem); /* /sys/fs */ decl_subsys(fs, NULL, NULL); --- linux-2.6.16.29.orig/include/linux/audit.h 2006-10-12 21:11:17.000000000 -0700 +++ linux-2.6.16.29/include/linux/audit.h 2006-10-12 21:11:14.000000000 -0700 @@ -73,6 +73,8 @@ #define AUDIT_SELINUX_ERR 1401 /* Internal SE Linux Errors */ #define AUDIT_AVC_PATH 1402 /* dentry, vfsmount pair from avc */ +#define AUDIT_SD 1500 /* AppArmor (SubDomain) audit */ + #define AUDIT_KERNEL 2000 /* Asynchronous audit record. NOT A REQUEST. */ /* Rule flags */ @@ -265,6 +267,9 @@ extern void audit_log(struct audit_ __attribute__((format(printf,4,5))); extern struct audit_buffer *audit_log_start(struct audit_context *ctx, gfp_t gfp_mask, int type); +extern void audit_log_vformat(struct audit_buffer *ab, + const char *fmt, va_list args) + __attribute__((format(printf,2,0))); extern void audit_log_format(struct audit_buffer *ab, const char *fmt, ...) __attribute__((format(printf,2,3))); --- linux-2.6.16.29.orig/include/linux/namespace.h 2006-10-12 21:11:17.000000000 -0700 +++ linux-2.6.16.29/include/linux/namespace.h 2006-10-12 21:11:14.000000000 -0700 @@ -5,6 +5,9 @@ #include #include +/* exported for AppArmor (SubDomain) */ +extern struct rw_semaphore namespace_sem; + struct namespace { atomic_t count; struct vfsmount * root; --- linux-2.6.16.29.orig/kernel/audit.c 2006-10-12 21:11:17.000000000 -0700 +++ linux-2.6.16.29/kernel/audit.c 2006-10-12 21:11:14.000000000 -0700 @@ -733,8 +733,8 @@ static inline int audit_expand(struct au * room in the audit buffer, more room will be allocated and vsnprint * will be called a second time. Currently, we assume that a printk * can't format message larger than 1024 bytes, so we don't either. */ -static void audit_log_vformat(struct audit_buffer *ab, const char *fmt, - va_list args) +void audit_log_vformat(struct audit_buffer *ab, const char *fmt, + va_list args) { int len, avail; struct sk_buff *skb; @@ -895,3 +895,11 @@ void audit_log(struct audit_context *ctx audit_log_end(ab); } } + +EXPORT_SYMBOL_GPL(audit_log_start); +EXPORT_SYMBOL_GPL(audit_log_vformat); +EXPORT_SYMBOL_GPL(audit_log_format); +EXPORT_SYMBOL_GPL(audit_log_untrustedstring); +EXPORT_SYMBOL_GPL(audit_log_d_path); +EXPORT_SYMBOL_GPL(audit_log_end); +EXPORT_SYMBOL_GPL(audit_log); --- linux-2.6.16.29.orig/security/apparmor/aamatch/Kbuild 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.16.29/security/apparmor/aamatch/Kbuild 2006-10-12 21:11:15.000000000 -0700 @@ -0,0 +1,6 @@ +# Makefile for AppArmor aamatch submodule +# + +obj-$(CONFIG_SECURITY_APPARMOR) += aamatch_pcre.o + +aamatch_pcre-y := match_pcre.o pcre_exec.o --- linux-2.6.16.29.orig/security/apparmor/aamatch/match.h 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.16.29/security/apparmor/aamatch/match.h 2006-10-12 21:11:15.000000000 -0700 @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2002-2005 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 + +#include "../module_interface.h" +#include "../apparmor.h" + +/* The following functions implement an interface used by the primary + * AppArmor module to perform name matching (n.b. "AppArmor" was previously + * called "SubDomain"). + + * sdmatch_alloc + * sdmatch_free + * sdmatch_features + * sdmatch_serialize + * sdmatch_match + * + * The intent is for the primary module to export (via virtual fs entries) + * the features provided by the submodule (sdmatch_features) so that the + * parser may only load policy that can be supported. + * + * The primary module will call sdmatch_serialize to allow the submodule + * to consume submodule specific data from parser data stream and will call + * sdmatch_match to determine if a pathname matches an sd_entry. + */ + +typedef int (*sdmatch_serializecb) + (struct sd_ext *, enum sd_code, void *, const char *); + +/** + * sdmatch_alloc: allocate extradata (if necessary) + * @entry_type: type of entry being allocated + * Return value: NULL indicates no data was allocated (ERR_PTR(x) on error) + */ +extern void* sdmatch_alloc(enum entry_t entry_type); + +/** + * sdmatch_free: release data allocated by sdmatch_alloc + * @entry_extradata: data previously allocated by sdmatch_alloc + */ +extern void sdmatch_free(void *entry_extradata); + +/** + * sdmatch_features: return match types supported + * Return value: space seperated string (of types supported - use type=value + * to indicate variants of a type) + */ +extern const char* sdmatch_features(void); + +/** + * sdmatch_serialize: serialize extradata + * @entry_extradata: data previously allocated by sdmatch_alloc + * @e: input stream + * @cb: callback fn (consume incoming data stream) + * Return value: 0 success, -ve error + */ +extern int sdmatch_serialize(void *entry_extradata, struct sd_ext *e, + sdmatch_serializecb cb); + +/** + * sdmatch_match: determine if pathname matches entry + * @pathname: pathname to verify + * @entry_name: entry name + * @entry_type: type of entry + * @entry_extradata: data previously allocated by sdmatch_alloc + * Return value: 1 match, 0 othersise + */ +extern unsigned int sdmatch_match(const char *pathname, const char *entry_name, + enum entry_t entry_type, + void *entry_extradata); + + +/** + * sd_getentry_type - return string representation of entry_t + * @etype: entry type + */ +static inline const char *sd_getentry_type(enum entry_t etype) +{ + const char *etype_names[] = { + "sd_entry_literal", + "sd_entry_tailglob", + "sd_entry_pattern", + "sd_entry_invalid" + }; + + if (etype >= sd_entry_invalid) { + etype = sd_entry_invalid; + } + + return etype_names[etype]; +} + +/** + * sdmatch_match_common - helper function to check if a pathname matches + * a literal/tailglob + * @path: path requested to search for + * @entry_name: name from sd_entry + * @etype: type of entry + */ +static inline int sdmatch_match_common(const char *path, + const char *entry_name, + enum entry_t etype) +{ + int retval; + + /* literal, no pattern matching characters */ + if (etype == sd_entry_literal) { + retval = (strcmp(entry_name, path) == 0); + /* trailing ** glob pattern */ + } else if (etype == sd_entry_tailglob) { + retval = (strncmp(entry_name, path, + strlen(entry_name) - 2) == 0); + } else { + SD_WARN("%s: Invalid entry_t %d\n", __FUNCTION__, etype); + retval = 0; + } + +#if 0 + SD_DEBUG("%s(%d): %s %s [%s]\n", + __FUNCTION__, retval, path, entry_name, + sd_getentry_type(etype)); +#endif + + return retval; +} + +#endif /* __MATCH_H */ --- linux-2.6.16.29.orig/security/apparmor/aamatch/match_pcre.c 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.16.29/security/apparmor/aamatch/match_pcre.c 2006-10-12 21:11:15.000000000 -0700 @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2002-2005 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. + * + * http://forge.novell.com/modules/xfmod/project/?apparmor + * + * AppArmor aamatch submodule (w/ pattern expansion). + * + * This module makes use of a slightly modified version of the PCRE + * library developed by Philip Hazel . See the files + * pcre_* in this directory. + */ + +#include +#include "match.h" +#include "pcre_exec.h" +#include "pcre_tables.h" + +static const char *features="literal tailglob pattern=pcre"; + +struct sdmatch_entry +{ + char *pattern; + pcre *compiled; +}; + +void* sdmatch_alloc(enum entry_t entry_type) +{ +void *ptr=NULL; + + if (entry_type == sd_entry_pattern) { + ptr = kmalloc(sizeof(struct sdmatch_entry), GFP_KERNEL); + if (ptr) + memset(ptr, 0, sizeof(struct sdmatch_entry)); + else + ptr=ERR_PTR(-ENOMEM); + } else if (entry_type != sd_entry_literal && + entry_type != sd_entry_tailglob) { + ptr = ERR_PTR(-EINVAL); + } + + return ptr; +} + +void sdmatch_free(void *ptr) +{ + if (ptr) { + struct sdmatch_entry *ed = (struct sdmatch_entry *) ptr; + kfree(ed->pattern); + kfree(ed->compiled); /* allocated by SD_READ_X */ + } + kfree(ptr); +} + +const char *sdmatch_features(void) +{ + return features; +} + +int sdmatch_serialize(void *entry_extradata, struct sd_ext *e, + sdmatch_serializecb cb) +{ +#define SD_READ_X(E, C, D, N) \ + do { \ + if (!cb((E), (C), (D), (N))) { \ + error = -EINVAL; \ + goto done; \ + }\ + } while (0) + + int error = 0; + u32 size, magic, opts; + u8 t_char; + struct sdmatch_entry *ed = (struct sdmatch_entry *) entry_extradata; + + if (ed == NULL) + goto done; + + SD_READ_X(e, SD_DYN_STRING, &ed->pattern, NULL); + + /* size determines the real size of the pcre struct, + it is size_t - sizeof(pcre) on user side. + uschar must be the same in user and kernel space */ + /* check that we are processing the correct structure */ + SD_READ_X(e, SD_STRUCT, NULL, "pcre"); + SD_READ_X(e, SD_U32, &size, "pattern.size"); + SD_READ_X(e, SD_U32, &magic, "pattern.magic"); + + /* the allocation of pcre is delayed because it depends on the size + * of the pattern */ + ed->compiled = (pcre *) kmalloc(size + sizeof(pcre), GFP_KERNEL); + if (!ed->compiled) { + error = -ENOMEM; + goto done; + } + + memset(ed->compiled, 0, size + sizeof(pcre)); + ed->compiled->magic_number = magic; + ed->compiled->size = size + sizeof(pcre); + + SD_READ_X(e, SD_U32, &opts, "pattern.options"); + ed->compiled->options = opts; + SD_READ_X(e, SD_U16, &ed->compiled->top_bracket, "pattern.top_bracket"); + SD_READ_X(e, SD_U16, &ed->compiled->top_backref, "pattern.top_backref"); + SD_READ_X(e, SD_U8, &t_char, "pattern.first_char"); + ed->compiled->first_char = t_char; + SD_READ_X(e, SD_U8, &t_char, "pattern.req_char"); + ed->compiled->req_char = t_char; + SD_READ_X(e, SD_U8, &t_char, "pattern.code[0]"); + ed->compiled->code[0] = t_char; + + SD_READ_X(e, SD_STATIC_BLOB, &ed->compiled->code[1], NULL); + + SD_READ_X(e, SD_STRUCTEND, NULL, NULL); + + /* stitch in pcre patterns, it was NULLed out by parser + * pcre_default_tables defined in pcre_tables.h */ + ed->compiled->tables = pcre_default_tables; + +done: + if (error != 0 && ed) { + kfree(ed->pattern); /* allocated by SD_READ_X */ + kfree(ed->compiled); + ed->pattern = NULL; + ed->compiled = NULL; + } + + return error; +} + +unsigned int sdmatch_match(const char *pathname, const char *entry_name, + enum entry_t entry_type, void *entry_extradata) +{ + int ret; + + if (entry_type == sd_entry_pattern) { + int pcreret; + struct sdmatch_entry *ed = + (struct sdmatch_entry *) entry_extradata; + + pcreret = pcre_exec(ed->compiled, NULL, + pathname, strlen(pathname), + 0, 0, NULL, 0); + + ret = (pcreret >= 0); + + // XXX - this needs access to subdomain_debug, hmmm + //SD_DEBUG("%s(%d): %s %s %d\n", __FUNCTION__, + // ret, pathname, ed->pattern, pcreret); + } else { + ret = sdmatch_match_common(pathname, entry_name, entry_type); + } + + return ret; +} + +EXPORT_SYMBOL_GPL(sdmatch_alloc); +EXPORT_SYMBOL_GPL(sdmatch_free); +EXPORT_SYMBOL_GPL(sdmatch_features); +EXPORT_SYMBOL_GPL(sdmatch_serialize); +EXPORT_SYMBOL_GPL(sdmatch_match); + +MODULE_DESCRIPTION("AppArmor aa_match module [pcre]"); +MODULE_AUTHOR("Tony Jones "); +MODULE_LICENSE("GPL"); --- linux-2.6.16.29.orig/security/apparmor/aamatch/pcre_exec.c 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.16.29/security/apparmor/aamatch/pcre_exec.c 2006-10-12 21:11:15.000000000 -0700 @@ -0,0 +1,1945 @@ +/* + * This is a modified version of pcre.c containing only the code/data + * required to support pcre_exec() + */ + + +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* +This is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. See +the file Tech.Notes for some information on the internals. + +Written by: Philip Hazel + + Copyright (c) 1997-2001 University of Cambridge + +----------------------------------------------------------------------------- +Permission is granted to anyone to use this software for any purpose on any +computer system, and to redistribute it freely, subject to the following +restrictions: + +1. This software is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +2. The origin of this software must not be misrepresented, either by + explicit claim or by omission. + +3. Altered versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +4. If PCRE is embedded in any software that is released under the GNU + General Purpose Licence (GPL), then the terms of that licence shall + supersede any condition above with which it is incompatible. +----------------------------------------------------------------------------- +*/ + + +/* Define DEBUG to get debugging output on stdout. */ + +/* #define DEBUG */ + +/* Use a macro for debugging printing, 'cause that eliminates the use of #ifdef +inline, and there are *still* stupid compilers about that don't like indented +pre-processor statements. I suppose it's only been 10 years... */ + +#ifdef DEBUG +#define DPRINTF(p) PCRE_PRINTF p +#else +#define DPRINTF(p) /*nothing*/ +#endif + +/* Include the internals header, which itself includes Standard C headers plus +the external pcre header. */ + +#include "pcre_exec.h" + + +/* ---- CODE DELETED ---- */ + + +/* Min and max values for the common repeats; for the maxima, 0 => infinity */ + +static const char rep_min[] = { 0, 0, 1, 1, 0, 0 }; +static const char rep_max[] = { 0, 0, 0, 0, 1, 1 }; + + +/* ---- CODE DELETED ---- */ + + +/* Structure for building a chain of data that actually lives on the + * stack, for holding the values of the subject pointer at the start of each + * subpattern, so as to detect when an empty string has been matched by a + * subpattern - to break infinite loops. */ + +typedef struct eptrblock { + struct eptrblock *prev; + const uschar *saved_eptr; +} eptrblock; + +/* Flag bits for the match() function */ + +#define match_condassert 0x01 /* Called to check a condition assertion */ +#define match_isgroup 0x02 /* Set if start of bracketed group */ + + +/* ---- CODE DELETED ---- */ + + +/************************************************* + * * Global variables * + * *************************************************/ + +/* PCRE is thread-clean and doesn't use any global variables in the normal + * sense. However, it calls memory allocation and free functions via the two + * indirections below, which are can be changed by the caller, but are shared + * between all threads. */ + +#ifdef __KERNEL__ +static void *kern_malloc(size_t sz) +{ + return kmalloc(sz, GFP_KERNEL); +} +void *(*pcre_malloc)(size_t) = kern_malloc; +void (*pcre_free)(const void *) = kfree; +#else +void *(*pcre_malloc)(size_t) = malloc; +void (*pcre_free)(const void *) = free; +#endif + + +/************************************************* + * * Macros and tables for character handling * + * *************************************************/ + +/* When UTF-8 encoding is being used, a character is no longer just a single + * byte. The macros for character handling generate simple sequences when used in + * byte-mode, and more complicated ones for UTF-8 characters. */ + +#ifndef SUPPORT_UTF8 +#define GETCHARINC(c, eptr) c = *eptr++; +#define GETCHARLEN(c, eptr, len) c = *eptr; +#define BACKCHAR(eptr) +#endif + +/* ---- CODE DELETED ---- */ + +#ifdef DEBUG +/************************************************* +* Debugging function to print chars * +*************************************************/ + +/* Print a sequence of chars in printable format, stopping at the end of the +subject if the requested. + +Arguments: + p points to characters + length number to print + is_subject TRUE if printing from within md->start_subject + md pointer to matching data block, if is_subject is TRUE + +Returns: nothing +*/ + +static void +pchars(const uschar *p, int length, BOOL is_subject, match_data *md) +{ +int c; +if (is_subject && length > md->end_subject - p) length = md->end_subject - p; +while (length-- > 0) + if (isprint(c = *(p++))) PCRE_PRINTF("%c", c); else PCRE_PRINTF("\\x%02x", c); +} +#endif /* DEBUG */ + +/* ---- CODE DELETED ---- */ + + +/************************************************* +* Match a back-reference * +*************************************************/ + +/* If a back reference hasn't been set, the length that is passed is greater +than the number of characters left in the string, so the match fails. + +Arguments: + offset index into the offset vector + eptr points into the subject + length length to be matched + md points to match data block + ims the ims flags + +Returns: TRUE if matched +*/ + +static BOOL +match_ref(int offset, register const uschar *eptr, int length, match_data *md, + unsigned long int ims) +{ +const uschar *p = md->start_subject + md->offset_vector[offset]; + +#ifdef DEBUG +if (eptr >= md->end_subject) + PCRE_PRINTF("matching subject "); +else + { + PCRE_PRINTF("matching subject "); + pchars(eptr, length, TRUE, md); + } +PCRE_PRINTF(" against backref "); +pchars(p, length, FALSE, md); +PCRE_PRINTF("\n"); +#endif + +/* Always fail if not enough characters left */ + +if (length > md->end_subject - eptr) return FALSE; + +/* Separate the caselesss case for speed */ + +if ((ims & PCRE_CASELESS) != 0) + { + while (length-- > 0) + if (md->lcc[*p++] != md->lcc[*eptr++]) return FALSE; + } +else + { while (length-- > 0) if (*p++ != *eptr++) return FALSE; } + +return TRUE; +} + + +/************************************************* +* Match from current position * +*************************************************/ + +/* On entry ecode points to the first opcode, and eptr to the first character +in the subject string, while eptrb holds the value of eptr at the start of the +last bracketed group - used for breaking infinite loops matching zero-length +strings. + +Arguments: + eptr pointer in subject + ecode position in code + offset_top current top pointer + md pointer to "static" info for the match + ims current /i, /m, and /s options + eptrb pointer to chain of blocks containing eptr at start of + brackets - for testing for empty matches + flags can contain + match_condassert - this is an assertion condition + match_isgroup - this is the start of a bracketed group + +Returns: TRUE if matched +*/ + +static BOOL +match(register const uschar *eptr, register const uschar *ecode, + int offset_top, match_data *md, unsigned long int ims, eptrblock *eptrb, + int flags) +{ +unsigned long int original_ims = ims; /* Save for resetting on ')' */ +eptrblock newptrb; + +/* At the start of a bracketed group, add the current subject pointer to the +stack of such pointers, to be re-instated at the end of the group when we hit +the closing ket. When match() is called in other circumstances, we don't add to +the stack. */ + +if ((flags & match_isgroup) != 0) + { + newptrb.prev = eptrb; + newptrb.saved_eptr = eptr; + eptrb = &newptrb; + } + +/* Now start processing the operations. */ + +for (;;) + { + int op = (int)*ecode; + int min, max, ctype; + register int i; + register int c; + BOOL minimize = FALSE; + + /* Opening capturing bracket. If there is space in the offset vector, save + the current subject position in the working slot at the top of the vector. We + mustn't change the current values of the data slot, because they may be set + from a previous iteration of this group, and be referred to by a reference + inside the group. + + If the bracket fails to match, we need to restore this value and also the + values of the final offsets, in case they were set by a previous iteration of + the same bracket. + + If there isn't enough space in the offset vector, treat this as if it were a + non-capturing bracket. Don't worry about setting the flag for the error case + here; that is handled in the code for KET. */ + + if (op > OP_BRA) + { + int offset; + int number = op - OP_BRA; + + /* For extended extraction brackets (large number), we have to fish out the + number from a dummy opcode at the start. */ + + if (number > EXTRACT_BASIC_MAX) number = (ecode[4] << 8) | ecode[5]; + offset = number << 1; + +#ifdef DEBUG + PCRE_PRINTF("start bracket %d subject=", number); + pchars(eptr, 16, TRUE, md); + PCRE_PRINTF("\n"); +#endif + + if (offset < md->offset_max) + { + int save_offset1 = md->offset_vector[offset]; + int save_offset2 = md->offset_vector[offset+1]; + int save_offset3 = md->offset_vector[md->offset_end - number]; + + DPRINTF(("saving %d %d %d\n", save_offset1, save_offset2, save_offset3)); + md->offset_vector[md->offset_end - number] = eptr - md->start_subject; + + do + { + if (match(eptr, ecode+3, offset_top, md, ims, eptrb, match_isgroup)) + return TRUE; + ecode += (ecode[1] << 8) + ecode[2]; + } + while (*ecode == OP_ALT); + + DPRINTF(("bracket %d failed\n", number)); + + md->offset_vector[offset] = save_offset1; + md->offset_vector[offset+1] = save_offset2; + md->offset_vector[md->offset_end - number] = save_offset3; + + return FALSE; + } + + /* Insufficient room for saving captured contents */ + + else op = OP_BRA; + } + + /* Other types of node can be handled by a switch */ + + switch(op) + { + case OP_BRA: /* Non-capturing bracket: optimized */ + DPRINTF(("start bracket 0\n")); + do + { + if (match(eptr, ecode+3, offset_top, md, ims, eptrb, match_isgroup)) + return TRUE; + ecode += (ecode[1] << 8) + ecode[2]; + } + while (*ecode == OP_ALT); + DPRINTF(("bracket 0 failed\n")); + return FALSE; + + /* Conditional group: compilation checked that there are no more than + two branches. If the condition is false, skipping the first branch takes us + past the end if there is only one branch, but that's OK because that is + exactly what going to the ket would do. */ + + case OP_COND: + if (ecode[3] == OP_CREF) /* Condition is extraction test */ + { + int offset = (ecode[4] << 9) | (ecode[5] << 1); /* Doubled ref number */ + return match(eptr, + ecode + ((offset < offset_top && md->offset_vector[offset] >= 0)? + 6 : 3 + (ecode[1] << 8) + ecode[2]), + offset_top, md, ims, eptrb, match_isgroup); + } + + /* The condition is an assertion. Call match() to evaluate it - setting + the final argument TRUE causes it to stop at the end of an assertion. */ + + else + { + if (match(eptr, ecode+3, offset_top, md, ims, NULL, + match_condassert | match_isgroup)) + { + ecode += 3 + (ecode[4] << 8) + ecode[5]; + while (*ecode == OP_ALT) ecode += (ecode[1] << 8) + ecode[2]; + } + else ecode += (ecode[1] << 8) + ecode[2]; + return match(eptr, ecode+3, offset_top, md, ims, eptrb, match_isgroup); + } + /* Control never reaches here */ + + /* Skip over conditional reference or large extraction number data if + encountered. */ + + case OP_CREF: + case OP_BRANUMBER: + ecode += 3; + break; + + /* End of the pattern. If PCRE_NOTEMPTY is set, fail if we have matched + an empty string - recursion will then try other alternatives, if any. */ + + case OP_END: + if (md->notempty && eptr == md->start_match) return FALSE; + md->end_match_ptr = eptr; /* Record where we ended */ + md->end_offset_top = offset_top; /* and how many extracts were taken */ + return TRUE; + + /* Change option settings */ + + case OP_OPT: + ims = ecode[1]; + ecode += 2; + DPRINTF(("ims set to %02lx\n", ims)); + break; + + /* Assertion brackets. Check the alternative branches in turn - the + matching won't pass the KET for an assertion. If any one branch matches, + the assertion is true. Lookbehind assertions have an OP_REVERSE item at the + start of each branch to move the current point backwards, so the code at + this level is identical to the lookahead case. */ + + case OP_ASSERT: + case OP_ASSERTBACK: + do + { + if (match(eptr, ecode+3, offset_top, md, ims, NULL, match_isgroup)) break; + ecode += (ecode[1] << 8) + ecode[2]; + } + while (*ecode == OP_ALT); + if (*ecode == OP_KET) return FALSE; + + /* If checking an assertion for a condition, return TRUE. */ + + if ((flags & match_condassert) != 0) return TRUE; + + /* Continue from after the assertion, updating the offsets high water + mark, since extracts may have been taken during the assertion. */ + + do ecode += (ecode[1] << 8) + ecode[2]; while (*ecode == OP_ALT); + ecode += 3; + offset_top = md->end_offset_top; + continue; + + /* Negative assertion: all branches must fail to match */ + + case OP_ASSERT_NOT: + case OP_ASSERTBACK_NOT: + do + { + if (match(eptr, ecode+3, offset_top, md, ims, NULL, match_isgroup)) + return FALSE; + ecode += (ecode[1] << 8) + ecode[2]; + } + while (*ecode == OP_ALT); + + if ((flags & match_condassert) != 0) return TRUE; + + ecode += 3; + continue; + + /* Move the subject pointer back. This occurs only at the start of + each branch of a lookbehind assertion. If we are too close to the start to + move back, this match function fails. When working with UTF-8 we move + back a number of characters, not bytes. */ + + case OP_REVERSE: +#ifdef SUPPORT_UTF8 + c = (ecode[1] << 8) + ecode[2]; + for (i = 0; i < c; i++) + { + eptr--; + BACKCHAR(eptr) + } +#else + eptr -= (ecode[1] << 8) + ecode[2]; +#endif + + if (eptr < md->start_subject) return FALSE; + ecode += 3; + break; + + /* Recursion matches the current regex, nested. If there are any capturing + brackets started but not finished, we have to save their starting points + and reinstate them after the recursion. However, we don't know how many + such there are (offset_top records the completed total) so we just have + to save all the potential data. There may be up to 99 such values, which + is a bit large to put on the stack, but using malloc for small numbers + seems expensive. As a compromise, the stack is used when there are fewer + than 16 values to store; otherwise malloc is used. A problem is what to do + if the malloc fails ... there is no way of returning to the top level with + an error. Save the top 15 values on the stack, and accept that the rest + may be wrong. */ + + case OP_RECURSE: + { + BOOL rc; + int *save; + int stacksave[15]; + + c = md->offset_max; + + if (c < 16) save = stacksave; else + { + save = (int *)(pcre_malloc)((c+1) * sizeof(int)); + if (save == NULL) + { + save = stacksave; + c = 15; + } + } + + for (i = 1; i <= c; i++) + save[i] = md->offset_vector[md->offset_end - i]; + rc = match(eptr, md->start_pattern, offset_top, md, ims, eptrb, + match_isgroup); + for (i = 1; i <= c; i++) + md->offset_vector[md->offset_end - i] = save[i]; + if (save != stacksave) (pcre_free)(save); + if (!rc) return FALSE; + + /* In case the recursion has set more capturing values, save the final + number, then move along the subject till after the recursive match, + and advance one byte in the pattern code. */ + + offset_top = md->end_offset_top; + eptr = md->end_match_ptr; + ecode++; + } + break; + + /* "Once" brackets are like assertion brackets except that after a match, + the point in the subject string is not moved back. Thus there can never be + a move back into the brackets. Check the alternative branches in turn - the + matching won't pass the KET for this kind of subpattern. If any one branch + matches, we carry on as at the end of a normal bracket, leaving the subject + pointer. */ + + case OP_ONCE: + { + const uschar *prev = ecode; + const uschar *saved_eptr = eptr; + + do + { + if (match(eptr, ecode+3, offset_top, md, ims, eptrb, match_isgroup)) + break; + ecode += (ecode[1] << 8) + ecode[2]; + } + while (*ecode == OP_ALT); + + /* If hit the end of the group (which could be repeated), fail */ + + if (*ecode != OP_ONCE && *ecode != OP_ALT) return FALSE; + + /* Continue as from after the assertion, updating the offsets high water + mark, since extracts may have been taken. */ + + do ecode += (ecode[1] << 8) + ecode[2]; while (*ecode == OP_ALT); + + offset_top = md->end_offset_top; + eptr = md->end_match_ptr; + + /* For a non-repeating ket, just continue at this level. This also + happens for a repeating ket if no characters were matched in the group. + This is the forcible breaking of infinite loops as implemented in Perl + 5.005. If there is an options reset, it will get obeyed in the normal + course of events. */ + + if (*ecode == OP_KET || eptr == saved_eptr) + { + ecode += 3; + break; + } + + /* The repeating kets try the rest of the pattern or restart from the + preceding bracket, in the appropriate order. We need to reset any options + that changed within the bracket before re-running it, so check the next + opcode. */ + + if (ecode[3] == OP_OPT) + { + ims = (ims & ~PCRE_IMS) | ecode[4]; + DPRINTF(("ims set to %02lx at group repeat\n", ims)); + } + + if (*ecode == OP_KETRMIN) + { + if (match(eptr, ecode+3, offset_top, md, ims, eptrb, 0) || + match(eptr, prev, offset_top, md, ims, eptrb, match_isgroup)) + return TRUE; + } + else /* OP_KETRMAX */ + { + if (match(eptr, prev, offset_top, md, ims, eptrb, match_isgroup) || + match(eptr, ecode+3, offset_top, md, ims, eptrb, 0)) return TRUE; + } + } + return FALSE; + + /* An alternation is the end of a branch; scan along to find the end of the + bracketed group and go to there. */ + + case OP_ALT: + do ecode += (ecode[1] << 8) + ecode[2]; while (*ecode == OP_ALT); + break; + + /* BRAZERO and BRAMINZERO occur just before a bracket group, indicating + that it may occur zero times. It may repeat infinitely, or not at all - + i.e. it could be ()* or ()? in the pattern. Brackets with fixed upper + repeat limits are compiled as a number of copies, with the optional ones + preceded by BRAZERO or BRAMINZERO. */ + + case OP_BRAZERO: + { + const uschar *next = ecode+1; + if (match(eptr, next, offset_top, md, ims, eptrb, match_isgroup)) + return TRUE; + do next += (next[1] << 8) + next[2]; while (*next == OP_ALT); + ecode = next + 3; + } + break; + + case OP_BRAMINZERO: + { + const uschar *next = ecode+1; + do next += (next[1] << 8) + next[2]; while (*next == OP_ALT); + if (match(eptr, next+3, offset_top, md, ims, eptrb, match_isgroup)) + return TRUE; + ecode++; + } + break; + + /* End of a group, repeated or non-repeating. If we are at the end of + an assertion "group", stop matching and return TRUE, but record the + current high water mark for use by positive assertions. Do this also + for the "once" (not-backup up) groups. */ + + case OP_KET: + case OP_KETRMIN: + case OP_KETRMAX: + { + const uschar *prev = ecode - (ecode[1] << 8) - ecode[2]; + const uschar *saved_eptr = eptrb->saved_eptr; + + eptrb = eptrb->prev; /* Back up the stack of bracket start pointers */ + + if (*prev == OP_ASSERT || *prev == OP_ASSERT_NOT || + *prev == OP_ASSERTBACK || *prev == OP_ASSERTBACK_NOT || + *prev == OP_ONCE) + { + md->end_match_ptr = eptr; /* For ONCE */ + md->end_offset_top = offset_top; + return TRUE; + } + + /* In all other cases except a conditional group we have to check the + group number back at the start and if necessary complete handling an + extraction by setting the offsets and bumping the high water mark. */ + + if (*prev != OP_COND) + { + int offset; + int number = *prev - OP_BRA; + + /* For extended extraction brackets (large number), we have to fish out + the number from a dummy opcode at the start. */ + + if (number > EXTRACT_BASIC_MAX) number = (prev[4] << 8) | prev[5]; + offset = number << 1; + +#ifdef DEBUG + PCRE_PRINTF("end bracket %d", number); + PCRE_PRINTF("\n"); +#endif + + if (number > 0) + { + if (offset >= md->offset_max) md->offset_overflow = TRUE; else + { + md->offset_vector[offset] = + md->offset_vector[md->offset_end - number]; + md->offset_vector[offset+1] = eptr - md->start_subject; + if (offset_top <= offset) offset_top = offset + 2; + } + } + } + + /* Reset the value of the ims flags, in case they got changed during + the group. */ + + ims = original_ims; + DPRINTF(("ims reset to %02lx\n", ims)); + + /* For a non-repeating ket, just continue at this level. This also + happens for a repeating ket if no characters were matched in the group. + This is the forcible breaking of infinite loops as implemented in Perl + 5.005. If there is an options reset, it will get obeyed in the normal + course of events. */ + + if (*ecode == OP_KET || eptr == saved_eptr) + { + ecode += 3; + break; + } + + /* The repeating kets try the rest of the pattern or restart from the + preceding bracket, in the appropriate order. */ + + if (*ecode == OP_KETRMIN) + { + if (match(eptr, ecode+3, offset_top, md, ims, eptrb, 0) || + match(eptr, prev, offset_top, md, ims, eptrb, match_isgroup)) + return TRUE; + } + else /* OP_KETRMAX */ + { + if (match(eptr, prev, offset_top, md, ims, eptrb, match_isgroup) || + match(eptr, ecode+3, offset_top, md, ims, eptrb, 0)) return TRUE; + } + } + return FALSE; + + /* Start of subject unless notbol, or after internal newline if multiline */ + + case OP_CIRC: + if (md->notbol && eptr == md->start_subject) return FALSE; + if ((ims & PCRE_MULTILINE) != 0) + { + if (eptr != md->start_subject && eptr[-1] != NEWLINE) return FALSE; + ecode++; + break; + } + /* ... else fall through */ + + /* Start of subject assertion */ + + case OP_SOD: + if (eptr != md->start_subject) return FALSE; + ecode++; + break; + + /* Assert before internal newline if multiline, or before a terminating + newline unless endonly is set, else end of subject unless noteol is set. */ + + case OP_DOLL: + if ((ims & PCRE_MULTILINE) != 0) + { + if (eptr < md->end_subject) { if (*eptr != NEWLINE) return FALSE; } + else { if (md->noteol) return FALSE; } + ecode++; + break; + } + else + { + if (md->noteol) return FALSE; + if (!md->endonly) + { + if (eptr < md->end_subject - 1 || + (eptr == md->end_subject - 1 && *eptr != NEWLINE)) return FALSE; + + ecode++; + break; + } + } + /* ... else fall through */ + + /* End of subject assertion (\z) */ + + case OP_EOD: + if (eptr < md->end_subject) return FALSE; + ecode++; + break; + + /* End of subject or ending \n assertion (\Z) */ + + case OP_EODN: + if (eptr < md->end_subject - 1 || + (eptr == md->end_subject - 1 && *eptr != NEWLINE)) return FALSE; + ecode++; + break; + + /* Word boundary assertions */ + + case OP_NOT_WORD_BOUNDARY: + case OP_WORD_BOUNDARY: + { + BOOL prev_is_word = (eptr != md->start_subject) && + ((md->ctypes[eptr[-1]] & ctype_word) != 0); + BOOL cur_is_word = (eptr < md->end_subject) && + ((md->ctypes[*eptr] & ctype_word) != 0); + if ((*ecode++ == OP_WORD_BOUNDARY)? + cur_is_word == prev_is_word : cur_is_word != prev_is_word) + return FALSE; + } + break; + + /* Match a single character type; inline for speed */ + + case OP_ANY: + if ((ims & PCRE_DOTALL) == 0 && eptr < md->end_subject && *eptr == NEWLINE) + return FALSE; + if (eptr++ >= md->end_subject) return FALSE; +#ifdef SUPPORT_UTF8 + if (md->utf8) + while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++; +#endif + ecode++; + break; + + case OP_NOT_DIGIT: + if (eptr >= md->end_subject || + (md->ctypes[*eptr++] & ctype_digit) != 0) + return FALSE; + ecode++; + break; + + case OP_DIGIT: + if (eptr >= md->end_subject || + (md->ctypes[*eptr++] & ctype_digit) == 0) + return FALSE; + ecode++; + break; + + case OP_NOT_WHITESPACE: + if (eptr >= md->end_subject || + (md->ctypes[*eptr++] & ctype_space) != 0) + return FALSE; + ecode++; + break; + + case OP_WHITESPACE: + if (eptr >= md->end_subject || + (md->ctypes[*eptr++] & ctype_space) == 0) + return FALSE; + ecode++; + break; + + case OP_NOT_WORDCHAR: + if (eptr >= md->end_subject || + (md->ctypes[*eptr++] & ctype_word) != 0) + return FALSE; + ecode++; + break; + + case OP_WORDCHAR: + if (eptr >= md->end_subject || + (md->ctypes[*eptr++] & ctype_word) == 0) + return FALSE; + ecode++; + break; + + /* Match a back reference, possibly repeatedly. Look past the end of the + item to see if there is repeat information following. The code is similar + to that for character classes, but repeated for efficiency. Then obey + similar code to character type repeats - written out again for speed. + However, if the referenced string is the empty string, always treat + it as matched, any number of times (otherwise there could be infinite + loops). */ + + case OP_REF: + { + int length; + int offset = (ecode[1] << 9) | (ecode[2] << 1); /* Doubled ref number */ + ecode += 3; /* Advance past item */ + + /* If the reference is unset, set the length to be longer than the amount + of subject left; this ensures that every attempt at a match fails. We + can't just fail here, because of the possibility of quantifiers with zero + minima. */ + + length = (offset >= offset_top || md->offset_vector[offset] < 0)? + md->end_subject - eptr + 1 : + md->offset_vector[offset+1] - md->offset_vector[offset]; + + /* Set up for repetition, or handle the non-repeated case */ + + switch (*ecode) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRPLUS: + case OP_CRMINPLUS: + case OP_CRQUERY: + case OP_CRMINQUERY: + c = *ecode++ - OP_CRSTAR; + minimize = (c & 1) != 0; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + break; + + case OP_CRRANGE: + case OP_CRMINRANGE: + minimize = (*ecode == OP_CRMINRANGE); + min = (ecode[1] << 8) + ecode[2]; + max = (ecode[3] << 8) + ecode[4]; + if (max == 0) max = INT_MAX; + ecode += 5; + break; + + default: /* No repeat follows */ + if (!match_ref(offset, eptr, length, md, ims)) return FALSE; + eptr += length; + continue; /* With the main loop */ + } + + /* If the length of the reference is zero, just continue with the + main loop. */ + + if (length == 0) continue; + + /* First, ensure the minimum number of matches are present. We get back + the length of the reference string explicitly rather than passing the + address of eptr, so that eptr can be a register variable. */ + + for (i = 1; i <= min; i++) + { + if (!match_ref(offset, eptr, length, md, ims)) return FALSE; + eptr += length; + } + + /* If min = max, continue at the same level without recursion. + They are not both allowed to be zero. */ + + if (min == max) continue; + + /* If minimizing, keep trying and advancing the pointer */ + + if (minimize) + { + for (i = min;; i++) + { + if (match(eptr, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + if (i >= max || !match_ref(offset, eptr, length, md, ims)) + return FALSE; + eptr += length; + } + /* Control never gets here */ + } + + /* If maximizing, find the longest string and work backwards */ + + else + { + const uschar *pp = eptr; + for (i = min; i < max; i++) + { + if (!match_ref(offset, eptr, length, md, ims)) break; + eptr += length; + } + while (eptr >= pp) + { + if (match(eptr, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + eptr -= length; + } + return FALSE; + } + } + /* Control never gets here */ + + + + /* Match a character class, possibly repeatedly. Look past the end of the + item to see if there is repeat information following. Then obey similar + code to character type repeats - written out again for speed. */ + + case OP_CLASS: + { + const uschar *data = ecode + 1; /* Save for matching */ + ecode += 33; /* Advance past the item */ + + switch (*ecode) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRPLUS: + case OP_CRMINPLUS: + case OP_CRQUERY: + case OP_CRMINQUERY: + c = *ecode++ - OP_CRSTAR; + minimize = (c & 1) != 0; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + break; + + case OP_CRRANGE: + case OP_CRMINRANGE: + minimize = (*ecode == OP_CRMINRANGE); + min = (ecode[1] << 8) + ecode[2]; + max = (ecode[3] << 8) + ecode[4]; + if (max == 0) max = INT_MAX; + ecode += 5; + break; + + default: /* No repeat follows */ + min = max = 1; + break; + } + + /* First, ensure the minimum number of matches are present. */ + + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject) return FALSE; + GETCHARINC(c, eptr) /* Get character; increment eptr */ + +#ifdef SUPPORT_UTF8 + /* We do not yet support class members > 255 */ + if (c > 255) return FALSE; +#endif + + if ((data[c/8] & (1 << (c&7))) != 0) continue; + return FALSE; + } + + /* If max == min we can continue with the main loop without the + need to recurse. */ + + if (min == max) continue; + + /* If minimizing, keep testing the rest of the expression and advancing + the pointer while it matches the class. */ + + if (minimize) + { + for (i = min;; i++) + { + if (match(eptr, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + if (i >= max || eptr >= md->end_subject) return FALSE; + GETCHARINC(c, eptr) /* Get character; increment eptr */ + +#ifdef SUPPORT_UTF8 + /* We do not yet support class members > 255 */ + if (c > 255) return FALSE; +#endif + if ((data[c/8] & (1 << (c&7))) != 0) continue; + return FALSE; + } + /* Control never gets here */ + } + + /* If maximizing, find the longest possible run, then work backwards. */ + + else + { + const uschar *pp = eptr; + int len = 1; + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject) break; + GETCHARLEN(c, eptr, len) /* Get character, set length if UTF-8 */ + +#ifdef SUPPORT_UTF8 + /* We do not yet support class members > 255 */ + if (c > 255) break; +#endif + if ((data[c/8] & (1 << (c&7))) == 0) break; + eptr += len; + } + + while (eptr >= pp) + { + if (match(eptr--, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + +#ifdef SUPPORT_UTF8 + BACKCHAR(eptr) +#endif + } + return FALSE; + } + } + /* Control never gets here */ + + /* Match a run of characters */ + + case OP_CHARS: + { + register int length = ecode[1]; + ecode += 2; + +#ifdef DEBUG /* Sigh. Some compilers never learn. */ + if (eptr >= md->end_subject) + PCRE_PRINTF("matching subject against pattern "); + else + { + PCRE_PRINTF("matching subject "); + pchars(eptr, length, TRUE, md); + PCRE_PRINTF(" against pattern "); + } + pchars(ecode, length, FALSE, md); + PCRE_PRINTF("\n"); +#endif + + if (length > md->end_subject - eptr) return FALSE; + if ((ims & PCRE_CASELESS) != 0) + { + while (length-- > 0) + if (md->lcc[*ecode++] != md->lcc[*eptr++]) + return FALSE; + } + else + { + while (length-- > 0) if (*ecode++ != *eptr++) return FALSE; + } + } + break; + + /* Match a single character repeatedly; different opcodes share code. */ + + case OP_EXACT: + min = max = (ecode[1] << 8) + ecode[2]; + ecode += 3; + goto REPEATCHAR; + + case OP_UPTO: + case OP_MINUPTO: + min = 0; + max = (ecode[1] << 8) + ecode[2]; + minimize = *ecode == OP_MINUPTO; + ecode += 3; + goto REPEATCHAR; + + case OP_STAR: + case OP_MINSTAR: + case OP_PLUS: + case OP_MINPLUS: + case OP_QUERY: + case OP_MINQUERY: + c = *ecode++ - OP_STAR; + minimize = (c & 1) != 0; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + + /* Common code for all repeated single-character matches. We can give + up quickly if there are fewer than the minimum number of characters left in + the subject. */ + + REPEATCHAR: + if (min > md->end_subject - eptr) return FALSE; + c = *ecode++; + + /* The code is duplicated for the caseless and caseful cases, for speed, + since matching characters is likely to be quite common. First, ensure the + minimum number of matches are present. If min = max, continue at the same + level without recursing. Otherwise, if minimizing, keep trying the rest of + the expression and advancing one matching character if failing, up to the + maximum. Alternatively, if maximizing, find the maximum number of + characters and work backwards. */ + + DPRINTF(("matching %c{%d,%d} against subject %.*s\n", c, min, max, + max, eptr)); + + if ((ims & PCRE_CASELESS) != 0) + { + c = md->lcc[c]; + for (i = 1; i <= min; i++) + if (c != md->lcc[*eptr++]) return FALSE; + if (min == max) continue; + if (minimize) + { + for (i = min;; i++) + { + if (match(eptr, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + if (i >= max || eptr >= md->end_subject || + c != md->lcc[*eptr++]) + return FALSE; + } + /* Control never gets here */ + } + else + { + const uschar *pp = eptr; + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || c != md->lcc[*eptr]) break; + eptr++; + } + while (eptr >= pp) + if (match(eptr--, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + return FALSE; + } + /* Control never gets here */ + } + + /* Caseful comparisons */ + + else + { + for (i = 1; i <= min; i++) if (c != *eptr++) return FALSE; + if (min == max) continue; + if (minimize) + { + for (i = min;; i++) + { + if (match(eptr, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + if (i >= max || eptr >= md->end_subject || c != *eptr++) return FALSE; + } + /* Control never gets here */ + } + else + { + const uschar *pp = eptr; + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || c != *eptr) break; + eptr++; + } + while (eptr >= pp) + if (match(eptr--, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + return FALSE; + } + } + /* Control never gets here */ + + /* Match a negated single character */ + + case OP_NOT: + if (eptr >= md->end_subject) return FALSE; + ecode++; + if ((ims & PCRE_CASELESS) != 0) + { + if (md->lcc[*ecode++] == md->lcc[*eptr++]) return FALSE; + } + else + { + if (*ecode++ == *eptr++) return FALSE; + } + break; + + /* Match a negated single character repeatedly. This is almost a repeat of + the code for a repeated single character, but I haven't found a nice way of + commoning these up that doesn't require a test of the positive/negative + option for each character match. Maybe that wouldn't add very much to the + time taken, but character matching *is* what this is all about... */ + + case OP_NOTEXACT: + min = max = (ecode[1] << 8) + ecode[2]; + ecode += 3; + goto REPEATNOTCHAR; + + case OP_NOTUPTO: + case OP_NOTMINUPTO: + min = 0; + max = (ecode[1] << 8) + ecode[2]; + minimize = *ecode == OP_NOTMINUPTO; + ecode += 3; + goto REPEATNOTCHAR; + + case OP_NOTSTAR: + case OP_NOTMINSTAR: + case OP_NOTPLUS: + case OP_NOTMINPLUS: + case OP_NOTQUERY: + case OP_NOTMINQUERY: + c = *ecode++ - OP_NOTSTAR; + minimize = (c & 1) != 0; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + + /* Common code for all repeated single-character matches. We can give + up quickly if there are fewer than the minimum number of characters left in + the subject. */ + + REPEATNOTCHAR: + if (min > md->end_subject - eptr) return FALSE; + c = *ecode++; + + /* The code is duplicated for the caseless and caseful cases, for speed, + since matching characters is likely to be quite common. First, ensure the + minimum number of matches are present. If min = max, continue at the same + level without recursing. Otherwise, if minimizing, keep trying the rest of + the expression and advancing one matching character if failing, up to the + maximum. Alternatively, if maximizing, find the maximum number of + characters and work backwards. */ + + DPRINTF(("negative matching %c{%d,%d} against subject %.*s\n", c, min, max, + max, eptr)); + + if ((ims & PCRE_CASELESS) != 0) + { + c = md->lcc[c]; + for (i = 1; i <= min; i++) + if (c == md->lcc[*eptr++]) return FALSE; + if (min == max) continue; + if (minimize) + { + for (i = min;; i++) + { + if (match(eptr, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + if (i >= max || eptr >= md->end_subject || + c == md->lcc[*eptr++]) + return FALSE; + } + /* Control never gets here */ + } + else + { + const uschar *pp = eptr; + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || c == md->lcc[*eptr]) break; + eptr++; + } + while (eptr >= pp) + if (match(eptr--, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + return FALSE; + } + /* Control never gets here */ + } + + /* Caseful comparisons */ + + else + { + for (i = 1; i <= min; i++) if (c == *eptr++) return FALSE; + if (min == max) continue; + if (minimize) + { + for (i = min;; i++) + { + if (match(eptr, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + if (i >= max || eptr >= md->end_subject || c == *eptr++) return FALSE; + } + /* Control never gets here */ + } + else + { + const uschar *pp = eptr; + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || c == *eptr) break; + eptr++; + } + while (eptr >= pp) + if (match(eptr--, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + return FALSE; + } + } + /* Control never gets here */ + + /* Match a single character type repeatedly; several different opcodes + share code. This is very similar to the code for single characters, but we + repeat it in the interests of efficiency. */ + + case OP_TYPEEXACT: + min = max = (ecode[1] << 8) + ecode[2]; + minimize = TRUE; + ecode += 3; + goto REPEATTYPE; + + case OP_TYPEUPTO: + case OP_TYPEMINUPTO: + min = 0; + max = (ecode[1] << 8) + ecode[2]; + minimize = *ecode == OP_TYPEMINUPTO; + ecode += 3; + goto REPEATTYPE; + + case OP_TYPESTAR: + case OP_TYPEMINSTAR: + case OP_TYPEPLUS: + case OP_TYPEMINPLUS: + case OP_TYPEQUERY: + case OP_TYPEMINQUERY: + c = *ecode++ - OP_TYPESTAR; + minimize = (c & 1) != 0; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + + /* Common code for all repeated single character type matches */ + + REPEATTYPE: + ctype = *ecode++; /* Code for the character type */ + + /* First, ensure the minimum number of matches are present. Use inline + code for maximizing the speed, and do the type test once at the start + (i.e. keep it out of the loop). Also we can test that there are at least + the minimum number of bytes before we start, except when doing '.' in + UTF8 mode. Leave the test in in all cases; in the special case we have + to test after each character. */ + + if (min > md->end_subject - eptr) return FALSE; + if (min > 0) switch(ctype) + { + case OP_ANY: +#ifdef SUPPORT_UTF8 + if (md->utf8) + { + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject || + (*eptr++ == NEWLINE && (ims & PCRE_DOTALL) == 0)) + return FALSE; + while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++; + } + break; + } +#endif + /* Non-UTF8 can be faster */ + if ((ims & PCRE_DOTALL) == 0) + { for (i = 1; i <= min; i++) if (*eptr++ == NEWLINE) return FALSE; } + else eptr += min; + break; + + case OP_NOT_DIGIT: + for (i = 1; i <= min; i++) + if ((md->ctypes[*eptr++] & ctype_digit) != 0) return FALSE; + break; + + case OP_DIGIT: + for (i = 1; i <= min; i++) + if ((md->ctypes[*eptr++] & ctype_digit) == 0) return FALSE; + break; + + case OP_NOT_WHITESPACE: + for (i = 1; i <= min; i++) + if ((md->ctypes[*eptr++] & ctype_space) != 0) return FALSE; + break; + + case OP_WHITESPACE: + for (i = 1; i <= min; i++) + if ((md->ctypes[*eptr++] & ctype_space) == 0) return FALSE; + break; + + case OP_NOT_WORDCHAR: + for (i = 1; i <= min; i++) + if ((md->ctypes[*eptr++] & ctype_word) != 0) + return FALSE; + break; + + case OP_WORDCHAR: + for (i = 1; i <= min; i++) + if ((md->ctypes[*eptr++] & ctype_word) == 0) + return FALSE; + break; + } + + /* If min = max, continue at the same level without recursing */ + + if (min == max) continue; + + /* If minimizing, we have to test the rest of the pattern before each + subsequent match. */ + + if (minimize) + { + for (i = min;; i++) + { + if (match(eptr, ecode, offset_top, md, ims, eptrb, 0)) return TRUE; + if (i >= max || eptr >= md->end_subject) return FALSE; + + c = *eptr++; + switch(ctype) + { + case OP_ANY: + if ((ims & PCRE_DOTALL) == 0 && c == NEWLINE) return FALSE; +#ifdef SUPPORT_UTF8 + if (md->utf8) + while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++; +#endif + break; + + case OP_NOT_DIGIT: + if ((md->ctypes[c] & ctype_digit) != 0) return FALSE; + break; + + case OP_DIGIT: + if ((md->ctypes[c] & ctype_digit) == 0) return FALSE; + break; + + case OP_NOT_WHITESPACE: + if ((md->ctypes[c] & ctype_space) != 0) return FALSE; + break; + + case OP_WHITESPACE: + if ((md->ctypes[c] & ctype_space) == 0) return FALSE; + break; + + case OP_NOT_WORDCHAR: + if ((md->ctypes[c] & ctype_word) != 0) return FALSE; + break; + + case OP_WORDCHAR: + if ((md->ctypes[c] & ctype_word) == 0) return FALSE; + break; + } + } + /* Control never gets here */ + } + + /* If maximizing it is worth using inline code for speed, doing the type + test once at the start (i.e. keep it out of the loop). */ + + else + { + const uschar *pp = eptr; + switch(ctype) + { + case OP_ANY: + + /* Special code is required for UTF8, but when the maximum is unlimited + we don't need it. */ + +#ifdef SUPPORT_UTF8 + if (md->utf8 && max < INT_MAX) + { + if ((ims & PCRE_DOTALL) == 0) + { + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || *eptr++ == NEWLINE) break; + while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++; + } + } + else + { + for (i = min; i < max; i++) + { + eptr++; + while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++; + } + } + break; + } +#endif + /* Non-UTF8 can be faster */ + if ((ims & PCRE_DOTALL) == 0) + { + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || *eptr == NEWLINE) break; + eptr++; + } + } + else + { + c = max - min; + if (c > md->end_subject - eptr) c = md->end_subject - eptr; + eptr += c; + } + break; + + case OP_NOT_DIGIT: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_digit) != 0) + break; + eptr++; + } + break; + + case OP_DIGIT: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_digit) == 0) + break; + eptr++; + } + break; + + case OP_NOT_WHITESPACE: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_space) != 0) + break; + eptr++; + } + break; + + case OP_WHITESPACE: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_space) == 0) + break; + eptr++; + } + break; + + case OP_NOT_WORDCHAR: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_word) != 0) + break; + eptr++; + } + break; + + case OP_WORDCHAR: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_word) == 0) + break; + eptr++; + } + break; + } + + while (eptr >= pp) + { + if (match(eptr--, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; +#ifdef SUPPORT_UTF8 + if (md->utf8) + while (eptr > pp && (*eptr & 0xc0) == 0x80) eptr--; +#endif + } + return FALSE; + } + /* Control never gets here */ + + /* There's been some horrible disaster. */ + + default: + DPRINTF(("Unknown opcode %d\n", *ecode)); + md->errorcode = PCRE_ERROR_UNKNOWN_NODE; + return FALSE; + } + + /* Do not stick any code in here without much thought; it is assumed + that "continue" in the code above comes out to here to repeat the main + loop. */ + + } /* End of main loop */ +/* Control never reaches here */ +} + + +/************************************************* +* Execute a Regular Expression * +*************************************************/ + +/* This function applies a compiled re to a subject string and picks out +portions of the string if it matches. Two elements in the vector are set for +each substring: the offsets to the start and end of the substring. + +Arguments: + external_re points to the compiled expression + external_extra points to "hints" from pcre_study() or is NULL + subject points to the subject string + length length of subject string (may contain binary zeros) + start_offset where to start in the subject string + options option bits + offsets points to a vector of ints to be filled in with offsets + offsetcount the number of elements in the vector + +Returns: > 0 => success; value is the number of elements filled in + = 0 => success, but offsets is not big enough + -1 => failed to match + < -1 => some kind of unexpected problem +*/ + +int +pcre_exec(const pcre *external_re, const pcre_extra *external_extra, + const char *subject, int length, int start_offset, int options, int *offsets, + int offsetcount) +{ +int resetcount, ocount; +int first_char = -1; +int req_char = -1; +int req_char2 = -1; +unsigned long int ims = 0; +match_data match_block; +const uschar *start_bits = NULL; +const uschar *start_match = (const uschar *)subject + start_offset; +const uschar *end_subject; +const uschar *req_char_ptr = start_match - 1; +const real_pcre *re = (const real_pcre *)external_re; +const real_pcre_extra *extra = (const real_pcre_extra *)external_extra; +BOOL using_temporary_offsets = FALSE; +BOOL anchored; +BOOL startline; + +if ((options & ~PUBLIC_EXEC_OPTIONS) != 0) return PCRE_ERROR_BADOPTION; + +if (re == NULL || subject == NULL || + (offsets == NULL && offsetcount > 0)) return PCRE_ERROR_NULL; +if (re->magic_number != MAGIC_NUMBER) return PCRE_ERROR_BADMAGIC; + +anchored = ((re->options | options) & PCRE_ANCHORED) != 0; +startline = (re->options & PCRE_STARTLINE) != 0; + +match_block.start_pattern = re->code; +match_block.start_subject = (const uschar *)subject; +match_block.end_subject = match_block.start_subject + length; +end_subject = match_block.end_subject; + +match_block.endonly = (re->options & PCRE_DOLLAR_ENDONLY) != 0; +match_block.utf8 = (re->options & PCRE_UTF8) != 0; + +match_block.notbol = (options & PCRE_NOTBOL) != 0; +match_block.noteol = (options & PCRE_NOTEOL) != 0; +match_block.notempty = (options & PCRE_NOTEMPTY) != 0; + +match_block.errorcode = PCRE_ERROR_NOMATCH; /* Default error */ + +match_block.lcc = re->tables + lcc_offset; +match_block.ctypes = re->tables + ctypes_offset; + +/* The ims options can vary during the matching as a result of the presence +of (?ims) items in the pattern. They are kept in a local variable so that +restoring at the exit of a group is easy. */ + +ims = re->options & (PCRE_CASELESS|PCRE_MULTILINE|PCRE_DOTALL); + +/* If the expression has got more back references than the offsets supplied can +hold, we get a temporary bit of working store to use during the matching. +Otherwise, we can use the vector supplied, rounding down its size to a multiple +of 3. */ + +ocount = offsetcount - (offsetcount % 3); + +if (re->top_backref > 0 && re->top_backref >= ocount/3) + { + ocount = re->top_backref * 3 + 3; + match_block.offset_vector = (int *)(pcre_malloc)(ocount * sizeof(int)); + if (match_block.offset_vector == NULL) return PCRE_ERROR_NOMEMORY; + using_temporary_offsets = TRUE; + DPRINTF(("Got memory to hold back references\n")); + } +else match_block.offset_vector = offsets; + +match_block.offset_end = ocount; +match_block.offset_max = (2*ocount)/3; +match_block.offset_overflow = FALSE; + +/* Compute the minimum number of offsets that we need to reset each time. Doing +this makes a huge difference to execution time when there aren't many brackets +in the pattern. */ + +resetcount = 2 + re->top_bracket * 2; +if (resetcount > offsetcount) resetcount = ocount; + +/* Reset the working variable associated with each extraction. These should +never be used unless previously set, but they get saved and restored, and so we +initialize them to avoid reading uninitialized locations. */ + +if (match_block.offset_vector != NULL) + { + register int *iptr = match_block.offset_vector + ocount; + register int *iend = iptr - resetcount/2 + 1; + while (--iptr >= iend) *iptr = -1; + } + +/* Set up the first character to match, if available. The first_char value is +never set for an anchored regular expression, but the anchoring may be forced +at run time, so we have to test for anchoring. The first char may be unset for +an unanchored pattern, of course. If there's no first char and the pattern was +studied, there may be a bitmap of possible first characters. */ + +if (!anchored) + { + if ((re->options & PCRE_FIRSTSET) != 0) + { + first_char = re->first_char; + if ((ims & PCRE_CASELESS) != 0) first_char = match_block.lcc[first_char]; + } + else + if (!startline && extra != NULL && + (extra->options & PCRE_STUDY_MAPPED) != 0) + start_bits = extra->start_bits; + } + +/* For anchored or unanchored matches, there may be a "last known required +character" set. If the PCRE_CASELESS is set, implying that the match starts +caselessly, or if there are any changes of this flag within the regex, set up +both cases of the character. Otherwise set the two values the same, which will +avoid duplicate testing (which takes significant time). This covers the vast +majority of cases. It will be suboptimal when the case flag changes in a regex +and the required character in fact is caseful. */ + +if ((re->options & PCRE_REQCHSET) != 0) + { + req_char = re->req_char; + req_char2 = ((re->options & (PCRE_CASELESS | PCRE_ICHANGED)) != 0)? + (re->tables + fcc_offset)[req_char] : req_char; + } + +/* Loop for handling unanchored repeated matching attempts; for anchored regexs +the loop runs just once. */ + +do + { + int rc; + register int *iptr = match_block.offset_vector; + register int *iend = iptr + resetcount; + + /* Reset the maximum number of extractions we might see. */ + + while (iptr < iend) *iptr++ = -1; + + /* Advance to a unique first char if possible */ + + if (first_char >= 0) + { + if ((ims & PCRE_CASELESS) != 0) + while (start_match < end_subject && + match_block.lcc[*start_match] != first_char) + start_match++; + else + while (start_match < end_subject && *start_match != first_char) + start_match++; + } + + /* Or to just after \n for a multiline match if possible */ + + else if (startline) + { + if (start_match > match_block.start_subject + start_offset) + { + while (start_match < end_subject && start_match[-1] != NEWLINE) + start_match++; + } + } + + /* Or to a non-unique first char after study */ + + else if (start_bits != NULL) + { + while (start_match < end_subject) + { + register int c = *start_match; + if ((start_bits[c/8] & (1 << (c&7))) == 0) start_match++; else break; + } + } + +#ifdef DEBUG /* Sigh. Some compilers never learn. */ + PCRE_PRINTF(">>>> Match against: "); + pchars(start_match, end_subject - start_match, TRUE, &match_block); + PCRE_PRINTF("\n"); +#endif + + /* If req_char is set, we know that that character must appear in the subject + for the match to succeed. If the first character is set, req_char must be + later in the subject; otherwise the test starts at the match point. This + optimization can save a huge amount of backtracking in patterns with nested + unlimited repeats that aren't going to match. We don't know what the state of + case matching may be when this character is hit, so test for it in both its + cases if necessary. However, the different cased versions will not be set up + unless PCRE_CASELESS was given or the casing state changes within the regex. + Writing separate code makes it go faster, as does using an autoincrement and + backing off on a match. */ + + if (req_char >= 0) + { + register const uschar *p = start_match + ((first_char >= 0)? 1 : 0); + + /* We don't need to repeat the search if we haven't yet reached the + place we found it at last time. */ + + if (p > req_char_ptr) + { + /* Do a single test if no case difference is set up */ + + if (req_char == req_char2) + { + while (p < end_subject) + { + if (*p++ == req_char) { p--; break; } + } + } + + /* Otherwise test for either case */ + + else + { + while (p < end_subject) + { + register int pp = *p++; + if (pp == req_char || pp == req_char2) { p--; break; } + } + } + + /* If we can't find the required character, break the matching loop */ + + if (p >= end_subject) break; + + /* If we have found the required character, save the point where we + found it, so that we don't search again next time round the loop if + the start hasn't passed this character yet. */ + + req_char_ptr = p; + } + } + + /* When a match occurs, substrings will be set for all internal extractions; + we just need to set up the whole thing as substring 0 before returning. If + there were too many extractions, set the return code to zero. In the case + where we had to get some local store to hold offsets for backreferences, copy + those back references that we can. In this case there need not be overflow + if certain parts of the pattern were not used. */ + + match_block.start_match = start_match; + if (!match(start_match, re->code, 2, &match_block, ims, NULL, match_isgroup)) + continue; + + /* Copy the offset information from temporary store if necessary */ + + if (using_temporary_offsets) + { + if (offsetcount >= 4) + { + memcpy(offsets + 2, match_block.offset_vector + 2, + (offsetcount - 2) * sizeof(int)); + DPRINTF(("Copied offsets from temporary memory\n")); + } + if (match_block.end_offset_top > offsetcount) + match_block.offset_overflow = TRUE; + + DPRINTF(("Freeing temporary memory\n")); + (pcre_free)(match_block.offset_vector); + } + + rc = match_block.offset_overflow? 0 : match_block.end_offset_top/2; + + if (offsetcount < 2) rc = 0; else + { + offsets[0] = start_match - match_block.start_subject; + offsets[1] = match_block.end_match_ptr - match_block.start_subject; + } + + DPRINTF((">>>> returning %d\n", rc)); + return rc; + } + +/* This "while" is the end of the "do" above */ + +while (!anchored && + match_block.errorcode == PCRE_ERROR_NOMATCH && + start_match++ < end_subject); + +if (using_temporary_offsets) + { + DPRINTF(("Freeing temporary memory\n")); + (pcre_free)(match_block.offset_vector); + } + +DPRINTF((">>>> returning %d\n", match_block.errorcode)); + +return match_block.errorcode; +} + +/* End of pcre.c */ --- linux-2.6.16.29.orig/security/apparmor/aamatch/pcre_exec.h 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.16.29/security/apparmor/aamatch/pcre_exec.h 2006-10-12 21:11:15.000000000 -0700 @@ -0,0 +1,308 @@ +/* + * This is a modified header file containing the definitions from + * pcre.h and internal.h required to support pcre_exec() + */ + + +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* Copyright (c) 1997-2001 University of Cambridge */ + +#ifndef _PCRE_H +#define _PCRE_H + +/* ----- CODE ADDED ---- */ + +#ifdef __KERNEL__ +#include // for kmalloc/kfree +#endif + +#ifdef __KERNEL__ +#define PCRE_PRINTF printk +#define isprint(x) ((unsigned char)(x) >= 128 && (unsigned char)(x) <= 255) +#else +#define PCRE_PRINTF printf +#endif + +/* The value of NEWLINE determines the newline character. The default is to + * leave it up to the compiler, but some sites want to force a particular value. + * On Unix systems, "configure" can be used to override this default. */ + +#ifndef NEWLINE +#define NEWLINE '\n' +#endif + +/* ---- CODE DELETED ---- */ + +/* Options */ + +#define PCRE_CASELESS 0x0001 +#define PCRE_MULTILINE 0x0002 +#define PCRE_DOTALL 0x0004 +#define PCRE_EXTENDED 0x0008 +#define PCRE_ANCHORED 0x0010 +#define PCRE_DOLLAR_ENDONLY 0x0020 +#define PCRE_EXTRA 0x0040 +#define PCRE_NOTBOL 0x0080 +#define PCRE_NOTEOL 0x0100 +#define PCRE_UNGREEDY 0x0200 +#define PCRE_NOTEMPTY 0x0400 +#define PCRE_UTF8 0x0800 + +/* Exec-time and get-time error codes */ + +#define PCRE_ERROR_NOMATCH (-1) +#define PCRE_ERROR_NULL (-2) +#define PCRE_ERROR_BADOPTION (-3) +#define PCRE_ERROR_BADMAGIC (-4) +#define PCRE_ERROR_UNKNOWN_NODE (-5) +#define PCRE_ERROR_NOMEMORY (-6) +#define PCRE_ERROR_NOSUBSTRING (-7) + +/* ---- CODE DELETED ---- */ + +/* Types */ + +struct real_pcre; /* declaration; the definition is private */ +struct real_pcre_extra; /* declaration; the definition is private */ + +typedef struct real_pcre pcre; +typedef struct real_pcre_extra pcre_extra; + +/* ---- CODE DELETED ---- */ + +extern int pcre_exec(const pcre *, const pcre_extra *, + const char *, int, int, int, int *, + int); + +/* ---- CODE ADDED (from internal.h) ---- */ + +/* These are the public options that can change during matching. */ + +#define PCRE_IMS (PCRE_CASELESS|PCRE_MULTILINE|PCRE_DOTALL) + +/* Private options flags start at the most significant end of the four bytes, +but skip the top bit so we can use ints for convenience without getting tangled +with negative values. The public options defined in pcre.h start at the least +significant end. Make sure they don't overlap, though now that we have expanded +to four bytes there is plenty of space. */ + +#define PCRE_FIRSTSET 0x40000000 /* first_char is set */ +#define PCRE_REQCHSET 0x20000000 /* req_char is set */ +#define PCRE_STARTLINE 0x10000000 /* start after \n for multiline */ +#define PCRE_ICHANGED 0x04000000 /* i option changes within regex */ + +/* Options for the "extra" block produced by pcre_study(). */ + +#define PCRE_STUDY_MAPPED 0x01 /* a map of starting chars exists */ + +/* Masks for identifying the public options which are permitted at compile +time, run time or study time, respectively. */ + +#define PUBLIC_EXEC_OPTIONS \ + (PCRE_ANCHORED|PCRE_NOTBOL|PCRE_NOTEOL|PCRE_NOTEMPTY) + +/* Magic number to provide a small check against being handed junk. */ + +#define MAGIC_NUMBER 0x50435245UL /* 'PCRE' */ + +typedef int BOOL; + +#define FALSE 0 +#define TRUE 1 + +/* Opcode table: OP_BRA must be last, as all values >= it are used for brackets +that extract substrings. Starting from 1 (i.e. after OP_END), the values up to +OP_EOD must correspond in order to the list of escapes immediately above. */ + +enum { + OP_END, /* End of pattern */ + + /* Values corresponding to backslashed metacharacters */ + + OP_SOD, /* Start of data: \A */ + OP_NOT_WORD_BOUNDARY, /* \B */ + OP_WORD_BOUNDARY, /* \b */ + OP_NOT_DIGIT, /* \D */ + OP_DIGIT, /* \d */ + OP_NOT_WHITESPACE, /* \S */ + OP_WHITESPACE, /* \s */ + OP_NOT_WORDCHAR, /* \W */ + OP_WORDCHAR, /* \w */ + OP_EODN, /* End of data or \n at end of data: \Z. */ + OP_EOD, /* End of data: \z */ + + OP_OPT, /* Set runtime options */ + OP_CIRC, /* Start of line - varies with multiline switch */ + OP_DOLL, /* End of line - varies with multiline switch */ + OP_ANY, /* Match any character */ + OP_CHARS, /* Match string of characters */ + OP_NOT, /* Match anything but the following char */ + + OP_STAR, /* The maximizing and minimizing versions of */ + OP_MINSTAR, /* all these opcodes must come in pairs, with */ + OP_PLUS, /* the minimizing one second. */ + OP_MINPLUS, /* This first set applies to single characters */ + OP_QUERY, + OP_MINQUERY, + OP_UPTO, /* From 0 to n matches */ + OP_MINUPTO, + OP_EXACT, /* Exactly n matches */ + + OP_NOTSTAR, /* The maximizing and minimizing versions of */ + OP_NOTMINSTAR, /* all these opcodes must come in pairs, with */ + OP_NOTPLUS, /* the minimizing one second. */ + OP_NOTMINPLUS, /* This first set applies to "not" single characters */ + OP_NOTQUERY, + OP_NOTMINQUERY, + OP_NOTUPTO, /* From 0 to n matches */ + OP_NOTMINUPTO, + OP_NOTEXACT, /* Exactly n matches */ + + OP_TYPESTAR, /* The maximizing and minimizing versions of */ + OP_TYPEMINSTAR, /* all these opcodes must come in pairs, with */ + OP_TYPEPLUS, /* the minimizing one second. These codes must */ + OP_TYPEMINPLUS, /* be in exactly the same order as those above. */ + OP_TYPEQUERY, /* This set applies to character types such as \d */ + OP_TYPEMINQUERY, + OP_TYPEUPTO, /* From 0 to n matches */ + OP_TYPEMINUPTO, + OP_TYPEEXACT, /* Exactly n matches */ + + OP_CRSTAR, /* The maximizing and minimizing versions of */ + OP_CRMINSTAR, /* all these opcodes must come in pairs, with */ + OP_CRPLUS, /* the minimizing one second. These codes must */ + OP_CRMINPLUS, /* be in exactly the same order as those above. */ + OP_CRQUERY, /* These are for character classes and back refs */ + OP_CRMINQUERY, + OP_CRRANGE, /* These are different to the three seta above. */ + OP_CRMINRANGE, + + OP_CLASS, /* Match a character class */ + OP_REF, /* Match a back reference */ + OP_RECURSE, /* Match this pattern recursively */ + + OP_ALT, /* Start of alternation */ + OP_KET, /* End of group that doesn't have an unbounded repeat */ + OP_KETRMAX, /* These two must remain together and in this */ + OP_KETRMIN, /* order. They are for groups the repeat for ever. */ + + /* The assertions must come before ONCE and COND */ + + OP_ASSERT, /* Positive lookahead */ + OP_ASSERT_NOT, /* Negative lookahead */ + OP_ASSERTBACK, /* Positive lookbehind */ + OP_ASSERTBACK_NOT, /* Negative lookbehind */ + OP_REVERSE, /* Move pointer back - used in lookbehind assertions */ + + /* ONCE and COND must come after the assertions, with ONCE first, as there's + a test for >= ONCE for a subpattern that isn't an assertion. */ + + OP_ONCE, /* Once matched, don't back up into the subpattern */ + OP_COND, /* Conditional group */ + OP_CREF, /* Used to hold an extraction string number (cond ref) */ + + OP_BRAZERO, /* These two must remain together and in this */ + OP_BRAMINZERO, /* order. */ + + OP_BRANUMBER, /* Used for extracting brackets whose number is greater + than can fit into an opcode. */ + + OP_BRA /* This and greater values are used for brackets that + extract substrings up to a basic limit. After that, + use is made of OP_BRANUMBER. */ +}; + +/* The highest extraction number before we have to start using additional +bytes. (Originally PCRE didn't have support for extraction counts highter than +this number.) The value is limited by the number of opcodes left after OP_BRA, +i.e. 255 - OP_BRA. We actually set it a bit lower to leave room for additional +opcodes. */ + +#define EXTRACT_BASIC_MAX 150 + +/* All character handling must be done as unsigned characters. Otherwise there +are problems with top-bit-set characters and functions such as isspace(). +However, we leave the interface to the outside world as char *, because that +should make things easier for callers. We define a short type for unsigned char +to save lots of typing. I tried "uchar", but it causes problems on Digital +Unix, where it is defined in sys/types, so use "uschar" instead. */ + +typedef unsigned char uschar; + +/* The real format of the start of the pcre block; the actual code vector +runs on as long as necessary after the end. */ + +typedef struct real_pcre { + unsigned long int magic_number; + size_t size; + const unsigned char *tables; + unsigned long int options; + unsigned short int top_bracket; + unsigned short int top_backref; + uschar first_char; + uschar req_char; + uschar code[1]; +} real_pcre; + +/* The real format of the extra block returned by pcre_study(). */ + +typedef struct real_pcre_extra { + uschar options; + uschar start_bits[32]; +} real_pcre_extra; + +/* Structure for passing "static" information around between the functions +doing the matching, so that they are thread-safe. */ + +typedef struct match_data { + int errorcode; /* As it says */ + int *offset_vector; /* Offset vector */ + int offset_end; /* One past the end */ + int offset_max; /* The maximum usable for return data */ + const uschar *lcc; /* Points to lower casing table */ + const uschar *ctypes; /* Points to table of type maps */ + BOOL offset_overflow; /* Set if too many extractions */ + BOOL notbol; /* NOTBOL flag */ + BOOL noteol; /* NOTEOL flag */ + BOOL utf8; /* UTF8 flag */ + BOOL endonly; /* Dollar not before final \n */ + BOOL notempty; /* Empty string match not wanted */ + const uschar *start_pattern; /* For use when recursing */ + const uschar *start_subject; /* Start of the subject string */ + const uschar *end_subject; /* End of the subject string */ + const uschar *start_match; /* Start of this match attempt */ + const uschar *end_match_ptr; /* Subject position at end match */ + int end_offset_top; /* Highwater mark at end of match */ +} match_data; + +/* Bit definitions for entries in the pcre_ctypes table. */ + +#define ctype_space 0x01 +#define ctype_letter 0x02 +#define ctype_digit 0x04 +#define ctype_xdigit 0x08 +#define ctype_word 0x10 /* alphameric or '_' */ +#define ctype_meta 0x80 /* regexp meta char or zero (end pattern) */ + +/* Offsets for the bitmap tables in pcre_cbits. Each table contains a set +of bits for a class map. Some classes are built by combining these tables. */ + +#define cbit_length 320 /* Length of the cbits table */ + +/* Offsets of the various tables from the base tables pointer, and +total length. */ + +#define lcc_offset 0 +#define fcc_offset 256 + +#define fcc_offset 256 +#define cbits_offset 512 +#define ctypes_offset (cbits_offset + cbit_length) + +/* ----- CODE ADDED ---- */ + +#endif // _PCRE_H + /* End of pcre.h */ --- linux-2.6.16.29.orig/security/apparmor/aamatch/pcre_tables.h 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.16.29/security/apparmor/aamatch/pcre_tables.h 2006-10-12 21:11:15.000000000 -0700 @@ -0,0 +1,184 @@ + +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* This file is automatically written by the dftables auxiliary +program. If you edit it by hand, you might like to edit the Makefile to +prevent its ever being regenerated. + +This file is #included in the compilation of pcre.c to build the default +character tables which are used when no tables are passed to the compile +function. */ + +static unsigned char pcre_default_tables[] = { + +/* This table is a lower casing table. */ + + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 64, 97, 98, 99,100,101,102,103, + 104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119, + 120,121,122, 91, 92, 93, 94, 95, + 96, 97, 98, 99,100,101,102,103, + 104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119, + 120,121,122,123,124,125,126,127, + 128,129,130,131,132,133,134,135, + 136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151, + 152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167, + 168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183, + 184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199, + 200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215, + 216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231, + 232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247, + 248,249,250,251,252,253,254,255, + +/* This table is a case flipping table. */ + + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 64, 97, 98, 99,100,101,102,103, + 104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119, + 120,121,122, 91, 92, 93, 94, 95, + 96, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90,123,124,125,126,127, + 128,129,130,131,132,133,134,135, + 136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151, + 152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167, + 168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183, + 184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199, + 200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215, + 216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231, + 232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247, + 248,249,250,251,252,253,254,255, + +/* This table contains bit maps for various character classes. +Each map is 32 bytes long and the bits run from the least +significant end of each byte. The classes that have their own +maps are: space, xdigit, digit, upper, lower, word, graph +print, punct, and cntrl. Other classes are built from combinations. */ + + 0x00,0x3e,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03, + 0x7e,0x00,0x00,0x00,0x7e,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfe,0xff,0xff,0x07,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03, + 0xfe,0xff,0xff,0x87,0xfe,0xff,0xff,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0xfe,0xff,0x00,0xfc, + 0x01,0x00,0x00,0xf8,0x01,0x00,0x00,0x78, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + +/* This table identifies various classes of character by individual bits: + 0x01 white space character + 0x02 letter + 0x04 decimal digit + 0x08 hexadecimal digit + 0x10 alphanumeric or '_' + 0x80 regular expression metacharacter or binary zero +*/ + + 0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 0- 7 */ + 0x00,0x01,0x01,0x01,0x01,0x01,0x00,0x00, /* 8- 15 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 16- 23 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 24- 31 */ + 0x01,0x00,0x00,0x00,0x80,0x00,0x00,0x00, /* - ' */ + 0x80,0x80,0x80,0x80,0x00,0x00,0x80,0x00, /* ( - / */ + 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c, /* 0 - 7 */ + 0x1c,0x1c,0x00,0x00,0x00,0x00,0x00,0x80, /* 8 - ? */ + 0x00,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* @ - G */ + 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* H - O */ + 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* P - W */ + 0x12,0x12,0x12,0x80,0x00,0x00,0x80,0x10, /* X - _ */ + 0x00,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* ` - g */ + 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* h - o */ + 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* p - w */ + 0x12,0x12,0x12,0x80,0x80,0x00,0x00,0x00, /* x -127 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 128-135 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 136-143 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 144-151 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 152-159 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 160-167 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 168-175 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 176-183 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 184-191 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 192-199 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 200-207 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 208-215 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 216-223 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 224-231 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 232-239 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 240-247 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};/* 248-255 */ + +/* End of chartables.c */ --- linux-2.6.16.29.orig/security/apparmor/apparmorfs.c 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.16.29/security/apparmor/apparmorfs.c 2006-10-12 21:11:15.000000000 -0700 @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2005 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 filesystem (part of securityfs) + */ + +#include +#include +#include +#include +#include + +#include "apparmor.h" +#include "inline.h" +#include "aamatch/match.h" + +#define SECFS_SD "apparmor" +static struct dentry *sdfs_dentry = NULL; + +/* profile */ +extern struct seq_operations subdomainfs_profiles_op; +static int sd_prof_open(struct inode *inode, struct file *file); +static int sd_prof_release(struct inode *inode, struct file *file); + +static struct file_operations subdomainfs_profiles_fops = { + .open = sd_prof_open, + .read = seq_read, + .llseek = seq_lseek, + .release = sd_prof_release, +}; + +/* version */ +static ssize_t sd_version_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos); + +static struct file_operations subdomainfs_version_fops = { + .read = sd_version_read, +}; + +/* matching */ +static ssize_t sd_matching_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos); + +static struct file_operations subdomainfs_matching_fops = { + .read = sd_matching_read, +}; + + +/* interface */ +extern ssize_t sd_file_prof_add(void *, size_t); +extern ssize_t sd_file_prof_repl(void *, size_t); +extern ssize_t sd_file_prof_remove(const char *, int); + +static ssize_t sd_profile_load(struct file *f, const char __user *buf, + size_t size, loff_t *pos); +static ssize_t sd_profile_replace(struct file *f, const char __user *buf, + size_t size, loff_t *pos); +static ssize_t sd_profile_remove(struct file *f, const char __user *buf, + size_t size, loff_t *pos); + +static struct file_operations subdomainfs_profile_load = { + .write = sd_profile_load +}; + +static struct file_operations subdomainfs_profile_replace = { + .write = sd_profile_replace +}; + +static struct file_operations subdomainfs_profile_remove = { + .write = sd_profile_remove +}; + + +/* control */ +static u64 sd_control_get(void *data); +static void sd_control_set(void *data, u64 val); + +DEFINE_SIMPLE_ATTRIBUTE(subdomainfs_control_fops, sd_control_get, + sd_control_set, "%lld\n"); + + + +/* table of static entries */ + +static struct root_entry { + const char *name; + int mode; + int access; + struct file_operations *fops; + void *data; + + /* internal fields */ + struct dentry *dentry; + int parent_index; +} root_entries[] = { + /* our root, normally /sys/kernel/security/subdomain */ + {SECFS_SD, S_IFDIR, 0550}, /* DO NOT EDIT/MOVE */ + + /* interface for obtaining list of profiles currently loaded */ + {"profiles", S_IFREG, 0440, &subdomainfs_profiles_fops, + NULL}, + + /* interface for obtaining version# of subdomain */ + {"version", S_IFREG, 0440, &subdomainfs_version_fops, + NULL}, + + /* interface for obtaining matching features supported */ + {"matching", S_IFREG, 0440, &subdomainfs_matching_fops, + NULL}, + + /* interface for loading/removing/replacing profiles */ + {".load", S_IFREG, 0640, &subdomainfs_profile_load, + NULL}, + {".replace", S_IFREG, 0640, &subdomainfs_profile_replace, + NULL}, + {".remove", S_IFREG, 0640, &subdomainfs_profile_remove, + NULL}, + + /* interface for setting binary config values */ + {"control", S_IFDIR, 0550}, + {"complain", S_IFREG, 0640, &subdomainfs_control_fops, + &subdomain_complain}, + {"audit", S_IFREG, 0640, &subdomainfs_control_fops, + &subdomain_audit}, + {"debug", S_IFREG, 0640, &subdomainfs_control_fops, + &subdomain_debug}, + {"logsyscall", S_IFREG, 0640, &subdomainfs_control_fops, + &subdomain_logsyscall}, + {NULL, S_IFDIR, 0}, + + /* root end */ + {NULL, S_IFDIR, 0} +}; + +#define SDFS_DENTRY root_entries[0].dentry + +static const unsigned int num_entries = + sizeof(root_entries) / sizeof(struct root_entry); + + + +static int sd_prof_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &subdomainfs_profiles_op); +} + + +static int sd_prof_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +static ssize_t sd_version_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + const char *version = apparmor_version_nl(); + + return simple_read_from_buffer(buf, size, ppos, version, + strlen(version)); +} + +static ssize_t sd_matching_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + const char *matching = sdmatch_features(); + + return simple_read_from_buffer(buf, size, ppos, matching, + strlen(matching)); +} + +static char *sd_simple_write_to_buffer(const char __user *userbuf, + size_t alloc_size, size_t copy_size, + loff_t *pos, const char *msg) +{ + char *data; + + if (*pos != 0) { + /* only writes from pos 0, that is complete writes */ + data = ERR_PTR(-ESPIPE); + goto out; + } + + /* Don't allow confined processes to load/replace/remove profiles. + * No sane person would add rules allowing this to a profile + * but we enforce the restriction anyways. + */ + if (sd_is_confined()) { + struct subdomain *sd = SD_SUBDOMAIN(current->security); + + SD_WARN("REJECTING access to profile %s (%s(%d) " + "profile %s active %s)\n", + msg, current->comm, current->pid, + sd->profile->name, sd->active->name); + + data = ERR_PTR(-EPERM); + goto out; + } + + data = vmalloc(alloc_size); + if (data == NULL) { + data = ERR_PTR(-ENOMEM); + goto out; + } + + if (copy_from_user(data, userbuf, copy_size)) { + vfree(data); + data = ERR_PTR(-EFAULT); + goto out; + } + +out: + return data; +} + +static ssize_t sd_profile_load(struct file *f, const char __user *buf, + size_t size, loff_t *pos) +{ + char *data; + ssize_t error; + + data = sd_simple_write_to_buffer(buf, size, size, pos, "load"); + + if (!IS_ERR(data)) { + error = sd_file_prof_add(data, size); + vfree(data); + } else { + error = PTR_ERR(data); + } + + return error; +} + +static ssize_t sd_profile_replace(struct file *f, const char __user *buf, + size_t size, loff_t *pos) +{ + char *data; + ssize_t error; + + data = sd_simple_write_to_buffer(buf, size, size, pos, "replacement"); + + if (!IS_ERR(data)) { + error = sd_file_prof_repl(data, size); + vfree(data); + } else { + error = PTR_ERR(data); + } + + return error; +} + +static ssize_t sd_profile_remove(struct file *f, const char __user *buf, + size_t size, loff_t *pos) +{ + char *data; + ssize_t error; + + /* sd_file_prof_remove needs a null terminated string so 1 extra + * byte is allocated and null the copied data is then null terminated + */ + data = sd_simple_write_to_buffer(buf, size+1, size, pos, "removal"); + + if (!IS_ERR(data)) { + data[size] = 0; + error = sd_file_prof_remove(data, size); + vfree(data); + } else { + error = PTR_ERR(data); + } + + return error; +} + +static u64 sd_control_get(void *data) +{ + return *(int *)data; +} + +static void sd_control_set(void *data, u64 val) +{ + if (val > 1) + val = 1; + + *(int*)data = (int)val; +} + +static void clear_subdomainfs(void) +{ + unsigned int i; + + for (i=0; i < num_entries;i++) { + unsigned int index; + + if (root_entries[i].mode == S_IFDIR) { + if (root_entries[i].name) + /* defer dir free till all sub-entries freed */ + continue; + else + /* cleanup parent */ + index = root_entries[i].parent_index; + } else { + index = i; + } + + if (root_entries[index].dentry) { + securityfs_remove(root_entries[index].dentry); + + SD_DEBUG("%s: deleted subdomainfs entry name=%s " + "dentry=%p\n", + __FUNCTION__, + root_entries[index].name, + root_entries[index].dentry); + + root_entries[index].dentry = NULL; + root_entries[index].parent_index = 0; + } + } +} + +static int populate_subdomainfs(struct dentry *root) +{ + unsigned int i, parent_index, depth; + +#define ENT root_entries[i] + + for (i = 0; i < num_entries; i++) { + root_entries[i].dentry = NULL; + root_entries[i].parent_index = 0; + } + + /* 1. Verify entry 0 is valid [sanity check] */ + if (num_entries == 0 || + !root_entries[0].name || + strcmp(root_entries[0].name, SECFS_SD) != 0 || + root_entries[0].mode != S_IFDIR) { + SD_ERROR("%s: root entry 0 is not SECFS_SD/dir\n", + __FUNCTION__); + goto error; + } + + /* 2. Verify table structure */ + parent_index = 0; + depth = 1; + + for (i = 1; i < num_entries; i++) { + ENT.parent_index = parent_index; + + if (ENT.name && ENT.mode == S_IFDIR) { + depth++; + parent_index = i; + } else if (!ENT.name) { + if (ENT.mode != S_IFDIR || depth == 0) { + SD_ERROR("%s: root_entry %d invalid (%u %d)", + __FUNCTION__, i, + ENT.mode, ENT.parent_index); + goto error; + } + + depth--; + parent_index = root_entries[parent_index].parent_index; + } + } + + if (depth != 0) { + SD_ERROR("%s: root_entry table not correctly terminated\n", + __FUNCTION__); + goto error; + } + + /* 3. Create root (parent=NULL) */ + i=0; + + ENT.dentry = securityfs_create_file(ENT.name, + ENT.mode | ENT.access, + NULL, NULL, NULL); + + if (ENT.dentry) + SD_DEBUG("%s: created securityfs/subdomain [dentry=%p]\n", + __FUNCTION__, ENT.dentry); + else + goto error; + + + /* 4. create remaining nodes */ + for (i = 1; i < num_entries; i++) { + struct dentry *parent; + + /* end of directory ? */ + if (!ENT.name) + continue; + + parent = root_entries[ENT.parent_index].dentry; + + ENT.dentry = securityfs_create_file(ENT.name, + ENT.mode | ENT.access, + parent, + ENT.mode != S_IFDIR ? ENT.data : NULL, + ENT.mode != S_IFDIR ? ENT.fops : NULL); + + if (!ENT.dentry) + goto cleanup_error; + + SD_DEBUG("%s: added subdomainfs entry " + "name=%s mode=%x dentry=%p [parent %p]\n", + __FUNCTION__, ENT.name, ENT.mode|ENT.access, + ENT.dentry, parent); + } + + return 1; + +cleanup_error: + clear_subdomainfs(); + +error: + return 0; +} + +int create_subdomainfs(void) +{ + if (SDFS_DENTRY) + SD_ERROR("%s: Subdomain securityfs already exists\n", + __FUNCTION__); + else if (!populate_subdomainfs(sdfs_dentry)) + SD_ERROR("%s: Error populating Subdomain securityfs\n", + __FUNCTION__); + + return (SDFS_DENTRY != NULL); +} + +int destroy_subdomainfs(void) +{ + if (SDFS_DENTRY) + clear_subdomainfs(); + + return 1; +} --- linux-2.6.16.29.orig/security/apparmor/apparmor.h 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.16.29/security/apparmor/apparmor.h 2006-10-12 21:11:15.000000000 -0700 @@ -0,0 +1,302 @@ +/* + * Copyright (C) 1998-2005 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 internal prototypes + */ + +#ifndef __SUBDOMAIN_H +#define __SUBDOMAIN_H + +/* defn of iattr */ +#include + +/* defn of linux_binprm */ +#include + +#include "shared.h" + +/* Control parameters (0 or 1), settable thru module/boot flags or + * via /sys/kernel/security/subdomain/control */ +extern int subdomain_complain; +extern int subdomain_debug; +extern int subdomain_audit; +extern int subdomain_logsyscall; + +#define SD_UNCONSTRAINED "unconstrained" + +/* $ echo -n subdomain.o | md5sum | cut -c -8 */ +#define SD_ID_MAGIC 0x8c235e38 + +#define PROFILE_COMPLAIN(_profile) \ + (subdomain_complain == 1 || ((_profile) && (_profile)->flags.complain)) + +#define SUBDOMAIN_COMPLAIN(_sd) \ + (subdomain_complain == 1 || \ + ((_sd) && (_sd)->active && (_sd)->active->flags.complain)) + +#define SUBDOMAIN_AUDIT(_sd) \ + (subdomain_audit == 1 || \ + ((_sd) && (_sd)->active && (_sd)->active->flags.audit)) + +/* + * DEBUG remains global (no per profile flag) since it is mostly used in sysctl + * which is not related to profile accesses. + */ + +#define SD_DEBUG(fmt, args...) \ + do { \ + if (subdomain_debug) \ + printk(KERN_DEBUG "AppArmor: " fmt, ##args); \ + } while (0) +#define SD_INFO(fmt, args...) printk(KERN_INFO "AppArmor: " fmt, ##args) +#define SD_WARN(fmt, args...) printk(KERN_WARNING "AppArmor: " fmt, ##args) +#define SD_ERROR(fmt, args...) printk(KERN_ERR "AppArmor: " fmt, ##args) + +/* basic AppArmor data structures */ + +struct flagval { + int debug; + int complain; + int audit; +}; + +enum entry_t { + sd_entry_literal, + sd_entry_tailglob, + sd_entry_pattern, + sd_entry_invalid +}; + +/** + * sd_entry - file ACL * + * Each entry describes a file and an allowed access mode. + */ +struct sd_entry { + char *filename; + int mode; /* mode is 'or' of READ, WRITE, EXECUTE, + * INHERIT, UNCONSTRAINED, and LIBRARY + * (meaning don't prefetch). */ + + enum entry_t entry_type; + void *extradata; + + struct list_head list; + struct list_head listp[POS_SD_FILE_MAX + 1]; +}; + +#define SD_SECURE_EXEC_NEEDED 0x00000001 + +#define SD_EXEC_MODIFIER_MASK(mask) ((mask) & SD_EXEC_MODIFIERS) + +#define SD_EXEC_MASK(mask) ((mask) & (SD_MAY_EXEC | SD_EXEC_MODIFIERS)) + +#define SD_EXEC_UNSAFE_MASK(mask) ((mask) & (SD_MAY_EXEC |\ + SD_EXEC_MODIFIERS |\ + SD_EXEC_UNSAFE)) + +/** + * sdprofile - basic confinement data + * + * The AppArmor profile contains the basic confinement data. Each profile + * has a name and potentially a list of subdomain entries. The profiles are + * connected in a list + */ +struct sdprofile { + char *name; /* profile name */ + + struct list_head file_entry; /* file ACL */ + struct list_head file_entryp[POS_SD_FILE_MAX + 1]; + struct list_head list; /* list of profiles */ + struct list_head sub; /* sub profiles, for change_hat */ + struct flagval flags; /* per profile debug flags */ + + int isstale; /* is profile stale */ + + int num_file_entries; + int num_file_pentries[POS_SD_FILE_MAX + 1]; + + kernel_cap_t capabilities; + + atomic_t count; /* reference count */ +}; + +enum sdfile_type { + sd_file_default, + sd_file_shmem +}; + +/** + * sdfile - file pointer confinement data + * + * Data structure assigned to each open file (by subdomain_file_alloc_security) + */ +struct sdfile { + enum sdfile_type type; + struct sdprofile *profile; +}; + +/** + * subdomain - a task's subdomain + * + * Contains the original profile obtained from execve() as well as the + * current active profile (which could change due to change_hat). Plus + * the hat_magic needed during change_hat. + */ +struct subdomain { + __u32 sd_magic; /* magic value to distinguish blobs */ + struct sdprofile *profile; /* The profile obtained from execve() */ + struct sdprofile *active; /* The current active profile */ + __u32 sd_hat_magic; /* used with change_hat */ + struct list_head list; /* list of subdomains */ + struct task_struct *task; +}; + +typedef int (*sd_iter) (struct subdomain *, void *); + +/* sd_path_data + * temp (cookie) data used by sd_path_* functions, see inline.h + */ +struct sd_path_data { + struct dentry *root, *dentry; + struct namespace *namespace; + struct list_head *head, *pos; + int errno; +}; + +#define SD_SUBDOMAIN(sec) ((struct subdomain*)(sec)) +#define SD_PROFILE(sec) ((struct sdprofile*)(sec)) + +/* Lock protecting access to 'struct subdomain' accesses */ +extern rwlock_t sd_lock; + +extern struct sdprofile *null_profile; +extern struct sdprofile *null_complain_profile; + +/** sd_audit + * + * Auditing structure + */ + +struct sd_audit { + unsigned short type, flags; + unsigned int result; + unsigned int gfp_mask; + int errorcode; + + const char *name; + unsigned int ival; + union{ + const void *pval; + va_list vaval; + }; +}; + +/* audit types */ +#define SD_AUDITTYPE_FILE 1 +#define SD_AUDITTYPE_DIR 2 +#define SD_AUDITTYPE_ATTR 3 +#define SD_AUDITTYPE_XATTR 4 +#define SD_AUDITTYPE_LINK 5 +#define SD_AUDITTYPE_CAP 6 +#define SD_AUDITTYPE_MSG 7 +#define SD_AUDITTYPE_SYSCALL 8 +#define SD_AUDITTYPE__END 9 + +/* audit flags */ +#define SD_AUDITFLAG_AUDITSS_SYSCALL 1 /* log syscall context */ +#define SD_AUDITFLAG_LOGERR 2 /* log operations that failed due to + non permission errors */ + +#define HINT_UNKNOWN_HAT "unknown_hat" +#define HINT_FORK "fork" +#define HINT_MANDPROF "missing_mandatory_profile" +#define HINT_CHGPROF "changing_profile" + +#define LOG_HINT(sd, gfp, hint, fmt, args...) \ + do {\ + sd_audit_message(sd, gfp, 0, \ + "LOGPROF-HINT " hint " " fmt, ##args);\ + } while(0) + +/* diroptype */ +#define SD_DIR_MKDIR 0 +#define SD_DIR_RMDIR 1 + +/* xattroptype */ +#define SD_XATTR_GET 0 +#define SD_XATTR_SET 1 +#define SD_XATTR_LIST 2 +#define SD_XATTR_REMOVE 3 + +/* main.c */ +extern int alloc_nullprofiles(void); +extern void free_nullprofiles(void); +extern int sd_audit_message(struct subdomain *, unsigned int gfp, int, + const char *, ...); +extern int sd_audit_syscallreject(struct subdomain *, unsigned int gfp, + const char *); +extern int sd_audit(struct subdomain *, const struct sd_audit *); +extern char *sd_get_name(struct dentry *dentry, struct vfsmount *mnt); + +extern int sd_attr(struct subdomain *sd, struct dentry *dentry, + struct iattr *iattr); +extern int sd_xattr(struct subdomain *sd, struct dentry *dentry, + const char *xattr, int xattroptype); +extern int sd_capability(struct subdomain *sd, int cap); +extern int sd_perm(struct subdomain *sd, struct dentry *dentry, + struct vfsmount *mnt, int mask); +extern int sd_perm_nameidata(struct subdomain *sd, struct nameidata *nd, + int mask); +extern int sd_perm_dentry(struct subdomain *sd, struct dentry *dentry, + int mask); +extern int sd_perm_dir(struct subdomain *sd, struct dentry *dentry, + int diroptype); +extern int sd_link(struct subdomain *sd, + struct dentry *link, struct dentry *target); +extern int sd_fork(struct task_struct *p); +extern int sd_register(struct linux_binprm *bprm); +extern void sd_release(struct task_struct *p); +extern int sd_change_hat(const char *id, __u32 hat_magic); +extern int sd_associate_filp(struct file *filp); + +/* list.c */ +extern struct sdprofile *sd_profilelist_find(const char *name); +extern int sd_profilelist_add(struct sdprofile *profile); +extern struct sdprofile *sd_profilelist_remove(const char *name); +extern void sd_profilelist_release(void); +extern struct sdprofile *sd_profilelist_replace(struct sdprofile *profile); +extern void sd_profile_dump(struct sdprofile *); +extern void sd_profilelist_dump(void); +extern void sd_subdomainlist_add(struct subdomain *); +extern void sd_subdomainlist_remove(struct subdomain *); +extern void sd_subdomainlist_iterate(sd_iter, void *); +extern void sd_subdomainlist_iterateremove(sd_iter, void *); +extern void sd_subdomainlist_release(void); + +/* subdomain_interface.c */ +extern void free_sdprofile(struct sdprofile *profile); +extern int sd_sys_security(unsigned int id, unsigned call, unsigned long *args); + +/* procattr.c */ +extern size_t sd_getprocattr(struct subdomain *sd, char *str, size_t size); +extern int sd_setprocattr_changehat(char *hatinfo, size_t infosize); +extern int sd_setprocattr_setprofile(struct task_struct *p, char *profilename, + size_t profilesize); + +/* apparmorfs.c */ +extern int create_subdomainfs(void); +extern int destroy_subdomainfs(void); + +/* capabilities.c */ +extern const char *capability_to_name(unsigned int cap); + +/* apparmor_version.c */ +extern const char *apparmor_version(void); +extern const char *apparmor_version_nl(void); + +#endif /* __SUBDOMAIN_H */ --- linux-2.6.16.29.orig/security/apparmor/apparmor_version.c 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.16.29/security/apparmor/apparmor_version.c 2006-10-12 21:11:15.000000000 -0700 @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2005 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 version definition + */ + +#ifndef APPARMOR_VERSION +#error "-DAPPARMOR_VERSION must be specified when compiling this file" +#endif + +#define APPARMOR_VERSION_STR_PFX "APPARMOR_VERSION=" + +#include +MODULE_VERSION(APPARMOR_VERSION); + +/* apparmor_version_str exists to allow a strings on module to + * see APPARMOR_VERSION= prefix + */ +static const char *apparmor_version_str = + APPARMOR_VERSION_STR_PFX APPARMOR_VERSION; + +/* apparmor_version_str_nl exists to allow an easy way to get a newline + * terminated string without having to do dynamic memory allocation + */ +static const char *apparmor_version_str_nl = APPARMOR_VERSION "\n"; + +const char *apparmor_version(void) +{ + const int len = sizeof(APPARMOR_VERSION_STR_PFX) - 1; + + return apparmor_version_str + len; +} + +const char *apparmor_version_nl(void) +{ + return apparmor_version_str_nl; +} --- linux-2.6.16.29.orig/security/apparmor/capabilities.c 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.16.29/security/apparmor/capabilities.c 2006-10-12 21:11:15.000000000 -0700 @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2005 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 capability definitions + */ + +#include "apparmor.h" + +static const char *capnames[] = { + "chown", + "dac_override", + "dac_read_search", + "fowner", + "fsetid", + "kill", + "setgid", + "setuid", + "setpcap", + "linux_immutable", + "net_bind_service", + "net_broadcast", + "net_admin", + "net_raw", + "ipc_lock", + "ipc_owner", + "sys_module", + "sys_rawio", + "sys_chroot", + "sys_ptrace", + "sys_pacct", + "sys_admin", + "sys_boot", + "sys_nice", + "sys_resource", + "sys_time", + "sys_tty_config", + "mknod", + "lease" +}; + +const char *capability_to_name(unsigned int cap) +{ + const char *capname; + + capname = (cap < (sizeof(capnames) / sizeof(char *)) + ? capnames[cap] : "invalid-capability"); + + return capname; +} --- linux-2.6.16.29.orig/security/apparmor/inline.h 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.16.29/security/apparmor/inline.h 2006-10-12 21:11:15.000000000 -0700 @@ -0,0 +1,364 @@ +/* + * Copyright (C) 2005 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. + */ + +#ifndef __INLINE_H +#define __INLINE_H + +#include + +static inline int __sd_is_confined(struct subdomain *sd) +{ + int rc = 0; + + if (sd && sd->sd_magic == SD_ID_MAGIC && sd->profile) { + BUG_ON(!sd->active); + rc = 1; + } + + return rc; +} + +/** + * sd_is_confined + * @sd: subdomain + * + * Check if @sd is confined (contains a valid profile) + * Return 1 if confined, 0 otherwise. + */ +static inline int sd_is_confined(void) +{ + struct subdomain *sd = SD_SUBDOMAIN(current->security); + return __sd_is_confined(sd); +} + +static inline int __sd_sub_defined(struct subdomain *sd) +{ + return __sd_is_confined(sd) && !list_empty(&sd->profile->sub); +} + +/** + * sd_sub_defined + * @sd: subdomain + * + * Check if @sd has at least one subprofile + * Return 1 if true, 0 otherwise + */ +static inline int sd_sub_defined(void) +{ + struct subdomain *sd = SD_SUBDOMAIN(current->security); + return __sd_sub_defined(sd); +} + +/** + * get_sdprofile + * @p: profile + * + * Increment refcount on profile + */ +static inline struct sdprofile *get_sdprofile(struct sdprofile *p) +{ + if (p) + atomic_inc(&p->count); + return p; +} + +/** + * put_sdprofile + * @p: profile + * + * Decrement refcount on profile + */ +static inline void put_sdprofile(struct sdprofile *p) +{ + if (p) + if (atomic_dec_and_test(&p->count)) + free_sdprofile(p); +} + +/** + * sd_switch + * @sd: subdomain to switch + * @profile: new profile + * @active: new active + * + * Change subdomain to use new profiles. + */ +static inline void sd_switch(struct subdomain *sd, + struct sdprofile *profile, + struct sdprofile *active) +{ + /* noop if NULL */ + put_sdprofile(sd->profile); + put_sdprofile(sd->active); + + sd->profile = get_sdprofile(profile); + sd->active = get_sdprofile(active); +} + +/** + * sd_switch_unconfined + * @sd: subdomain to switch + * + * Change subdomain to unconfined + */ +static inline void sd_switch_unconfined(struct subdomain *sd) +{ + sd_switch(sd, NULL, NULL); + + /* reset magic in case we were in a subhat before */ + sd->sd_hat_magic = 0; +} + +/** + * alloc_subdomain + * @tsk: task struct + * + * Allocate a new subdomain including a backpointer to it's referring task. + */ +static inline struct subdomain *alloc_subdomain(struct task_struct *tsk) +{ + struct subdomain *sd; + + sd = kmalloc(sizeof(struct subdomain), GFP_KERNEL); + if (!sd) + goto out; + + /* zero it first */ + memset(sd, 0, sizeof(struct subdomain)); + sd->sd_magic = SD_ID_MAGIC; + + /* back pointer to task */ + sd->task = tsk; + + /* any readers of the list must make sure that they can handle + * case where sd->profile and sd->active are not yet set (null) + */ + sd_subdomainlist_add(sd); + +out: + return sd; +} + +/** + * free_subdomain + * @sd: subdomain + * + * Free a subdomain previously allocated by alloc_subdomain + */ +static inline void free_subdomain(struct subdomain *sd) +{ + sd_subdomainlist_remove(sd); + kfree(sd); +} + +/** + * alloc_sdprofile + * + * Allocate, initialize and return a new zeroed profile. + * Returns NULL on failure. + */ +static inline struct sdprofile *alloc_sdprofile(void) +{ + struct sdprofile *profile; + + profile = (struct sdprofile *)kmalloc(sizeof(struct sdprofile), + GFP_KERNEL); + SD_DEBUG("%s(%p)\n", __FUNCTION__, profile); + if (profile) { + int i; + memset(profile, 0, sizeof(struct sdprofile)); + INIT_LIST_HEAD(&profile->list); + INIT_LIST_HEAD(&profile->sub); + INIT_LIST_HEAD(&profile->file_entry); + for (i = 0; i <= POS_SD_FILE_MAX; i++) { + INIT_LIST_HEAD(&profile->file_entryp[i]); + } + } + return profile; +} + +/** + * sd_put_name + * @name: name to release. + * + * Release space (free_page) allocated to hold pathname + * name may be NULL (checked for by free_page) + */ +static inline void sd_put_name(const char *name) +{ + free_page((unsigned long)name); +} + +/** __sd_find_profile + * @name: name of profile to find + * @head: list to search + * + * Return reference counted copy of profile. NULL if not found + * Caller must hold any necessary locks + */ +static inline struct sdprofile *__sd_find_profile(const char *name, + struct list_head *head) +{ + struct sdprofile *p; + + if (!name || !head) + return NULL; + + SD_DEBUG("%s: finding profile %s\n", __FUNCTION__, name); + list_for_each_entry(p, head, list) { + if (!strcmp(p->name, name)) { + /* return refcounted object */ + p = get_sdprofile(p); + return p; + } else { + SD_DEBUG("%s: skipping %s\n", __FUNCTION__, p->name); + } + } + return NULL; +} + +static inline struct subdomain *__get_sdcopy(struct subdomain *new, + struct task_struct *tsk) +{ + struct subdomain *old, *temp = NULL; + + old = SD_SUBDOMAIN(tsk->security); + + if (old) { + new->sd_magic = old->sd_magic; + new->sd_hat_magic = old->sd_hat_magic; + + new->active = get_sdprofile(old->active); + + if (old->profile == old->active) + new->profile = new->active; + else + new->profile = get_sdprofile(old->profile); + + temp = new; + } + + return temp; +} + +/** get_sdcopy + * @new: subdomain to hold copy + * + * Make copy of current subdomain containing refcounted profile and active + * Used to protect readers against racing writers (changehat and profile + * replacement). + */ +static inline struct subdomain *get_sdcopy(struct subdomain *new) +{ + struct subdomain *temp; + unsigned long flags; + + read_lock_irqsave(&sd_lock, flags); + + temp = __get_sdcopy(new, current); + + read_unlock_irqrestore(&sd_lock, flags); + + return temp; +} + +/** get_sdcopy + * @temp: subdomain to drop refcounts on + * + * Drop refcounted profile/active in copy of subdomain made by get_sdcopy + */ +static inline void put_sdcopy(struct subdomain *temp) +{ + if (temp) { + put_sdprofile(temp->active); + if (temp->active != temp->profile) + (void)put_sdprofile(temp->profile); + } +} + +/** sd_path_begin2 + * @rdentry: filesystem root dentry (searching for vfsmnts matching this) + * @dentry: dentry object to obtain pathname from (relative to matched vfsmnt) + * + * Setup data for iterating over vfsmounts (in current tasks namespace). + */ +static inline void sd_path_begin2(struct dentry *rdentry, + struct dentry *dentry, + struct sd_path_data *data) +{ + data->dentry = dentry; + data->root = dget(rdentry->d_sb->s_root); + data->namespace = current->namespace; + data->head = &data->namespace->list; + data->pos = data->head->next; + prefetch(data->pos->next); + data->errno = 0; + + down_read(&namespace_sem); +} + +/** sd_path_begin + * @dentry filesystem root dentry and object to obtain pathname from + * + * Utility function for calling _sd_path_begin for when the dentry we are + * looking for and the root are the same (this is the usual case). + */ +static inline void sd_path_begin(struct dentry *dentry, + struct sd_path_data *data) +{ + sd_path_begin2(dentry, dentry, data); +} + +/** sd_path_end + * @data: data object previously initialized by sd_path_begin + * + * End iterating over vfsmounts. + * If an error occured in begin or get, it is returned. Otherwise 0. + */ +static inline int sd_path_end(struct sd_path_data *data) +{ + up_read(&namespace_sem); + dput(data->root); + + return data->errno; +} + +/** sd_path_getname + * @data: data object previously initialized by sd_path_begin + * + * Return the next mountpoint which has the same root dentry as data->root. + * If no more mount points exist (or in case of error) NULL is returned + * (caller should call sd_path_end() and inspect return code to differentiate) + */ +static inline char *sd_path_getname(struct sd_path_data *data) +{ + char *name = NULL; + struct vfsmount *mnt; + + while (data->pos != data->head) { + mnt = list_entry(data->pos, struct vfsmount, mnt_list); + + /* advance to next -- so that it is done before we break */ + data->pos = data->pos->next; + prefetch(data->pos->next); + + if (mnt->mnt_root == data->root) { + name = sd_get_name(data->dentry, mnt); + if (IS_ERR(name)) { + data->errno = PTR_ERR(name); + name = NULL; + } + break; + } + } + + return name; +} + +#endif /* __INLINE_H__ */ --- linux-2.6.16.29.orig/security/apparmor/Kbuild 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.16.29/security/apparmor/Kbuild 2006-10-12 21:11:15.000000000 -0700 @@ -0,0 +1,10 @@ +# Makefile for AppArmor Linux Security Module +# +EXTRA_CFLAGS += -DAPPARMOR_VERSION=\"${APPARMOR_VER}\" + +obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o + +apparmor-y := main.o list.o procattr.o lsm.o apparmorfs.o capabilities.o \ + module_interface.o apparmor_version.o + +obj-$(CONFIG_SECURITY_APPARMOR) += aamatch/ --- linux-2.6.16.29.orig/security/apparmor/Kconfig 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.16.29/security/apparmor/Kconfig 2006-10-12 21:11:15.000000000 -0700 @@ -0,0 +1,9 @@ +config SECURITY_APPARMOR + tristate "AppArmor support" + depends on SECURITY!=n + help + This enables the AppArmor security module. + Required userspace tools (if they are not included in your + distribution) and further information may be found at + + If you are unsure how to answer this question, answer N. --- linux-2.6.16.29.orig/security/apparmor/list.c 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.16.29/security/apparmor/list.c 2006-10-12 21:11:15.000000000 -0700 @@ -0,0 +1,271 @@ +/* + * Copyright (C) 1998-2005 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 Profile List Management + */ + +#include +#include "apparmor.h" +#include "inline.h" + +/* list of all profiles and lock */ +static LIST_HEAD(profile_list); +static rwlock_t profile_lock = RW_LOCK_UNLOCKED; + +/* list of all subdomains and lock */ +static LIST_HEAD(subdomain_list); +static rwlock_t subdomain_lock = RW_LOCK_UNLOCKED; + +/** + * sd_profilelist_find + * @name: profile name (program name) + * + * Search the profile list for profile @name. Return refcounted profile on + * success, NULL on failure. + */ +struct sdprofile *sd_profilelist_find(const char *name) +{ + struct sdprofile *p = NULL; + if (name) { + read_lock(&profile_lock); + p = __sd_find_profile(name, &profile_list); + read_unlock(&profile_lock); + } + return p; +} + +/** + * sd_profilelist_add + * @profile: new profile to add to list + * + * Add new profile to list. Reference count on profile is incremented. + * Return 1 on success, 0 on failure (bad profile or already exists) + */ +int sd_profilelist_add(struct sdprofile *profile) +{ + struct sdprofile *old_profile; + int ret = 0; + + if (!profile) + goto out; + + write_lock(&profile_lock); + old_profile = __sd_find_profile(profile->name, &profile_list); + if (old_profile) { + put_sdprofile(old_profile); + goto out; + } + profile = get_sdprofile(profile); + + list_add(&profile->list, &profile_list); + ret = 1; + out: + write_unlock(&profile_lock); + return ret; +} + +/** + * sd_profilelist_remove + * @name: name of profile to be removed + * + * If the profile exists remove profile from list and return its reference. + * The reference count on profile is not decremented and should be decremented + * when the profile is no longer needed + */ +struct sdprofile *sd_profilelist_remove(const char *name) +{ + struct sdprofile *profile = NULL; + struct sdprofile *p, *tmp; + + if (!name) + goto out; + + write_lock(&profile_lock); + list_for_each_entry_safe(p, tmp, &profile_list, list) { + if (!strcmp(p->name, name)) { + list_del_init(&p->list); + /* mark old profile as stale */ + p->isstale = 1; + profile = p; + break; + } + } + write_unlock(&profile_lock); + +out: + return profile; +} + +/** + * sd_profilelist_replace + * @profile - new profile + * + * Replace a profile on the profile list. Find the old profile by name in + * the list, and replace it with the new profile. This is an atomic + * list operation. Returns the old profile (which is still refcounted) if + * there was one, or NULL. + */ +struct sdprofile *sd_profilelist_replace(struct sdprofile *profile) +{ + struct sdprofile *oldprofile; + + write_lock(&profile_lock); + oldprofile = __sd_find_profile(profile->name, &profile_list); + if (oldprofile) { + list_del_init(&oldprofile->list); + /* mark old profile as stale */ + oldprofile->isstale = 1; + + /* __sd_find_profile incremented count, so adjust down */ + put_sdprofile(oldprofile); + } + profile = get_sdprofile(profile); + list_add(&profile->list, &profile_list); + write_unlock(&profile_lock); + + return oldprofile; +} + +/** + * sd_profilelist_release + * + * Remove all profiles from profile_list + */ +void sd_profilelist_release(void) +{ + struct sdprofile *p, *tmp; + + write_lock(&profile_lock); + list_for_each_entry_safe(p, tmp, &profile_list, list) { + list_del_init(&p->list); + put_sdprofile(p); + } + write_unlock(&profile_lock); +} + +/** + * sd_subdomainlist_add + * @sd: new subdomain + * + * Add subdomain to subdomain_list + */ +void sd_subdomainlist_add(struct subdomain *sd) +{ + unsigned long flags; + + if (!sd) { + SD_INFO("%s: bad subdomain\n", __FUNCTION__); + return; + } + + write_lock_irqsave(&subdomain_lock, flags); + /* new subdomains must be added to the end of the list due to a + * subtle interaction between fork and profile replacement. + */ + list_add_tail(&sd->list, &subdomain_list); + write_unlock_irqrestore(&subdomain_lock, flags); +} + +/** + * sd_subdomainlist_remove + * @sd: subdomain to be removed + * + * Remove subdomain from subdomain_list + */ +void sd_subdomainlist_remove(struct subdomain *sd) +{ + unsigned long flags; + + if (sd) { + write_lock_irqsave(&subdomain_lock, flags); + list_del_init(&sd->list); + write_unlock_irqrestore(&subdomain_lock, flags); + } +} + +/** + * sd_subdomainlist_iterate + * @func: method to be called for each element + * @cookie: user passed data + * + * Iterate over subdomain list, stop when sd_iter func returns non zero + */ +void sd_subdomainlist_iterate(sd_iter func, void *cookie) +{ + struct subdomain *node; + int ret = 0; + unsigned long flags; + + read_lock_irqsave(&subdomain_lock, flags); + list_for_each_entry(node, &subdomain_list, list) { + ret = (*func) (node, cookie); + if (ret != 0) + break; + } + read_unlock_irqrestore(&subdomain_lock, flags); +} + +/** + * sd_subdomainlist_release + * + * Remove all subdomains from subdomain_list + */ +void sd_subdomainlist_release() +{ + struct subdomain *node, *tmp; + unsigned long flags; + + write_lock_irqsave(&subdomain_lock, flags); + list_for_each_entry_safe(node, tmp, &subdomain_list, list) { + list_del_init(&node->list); + } + write_unlock_irqrestore(&subdomain_lock, flags); +} + +/* seq_file helper routines + * Used by subdomainfs.c to iterate over profile_list + */ +static void *p_start(struct seq_file *f, loff_t *pos) +{ + struct sdprofile *node; + loff_t l = *pos; + + read_lock(&profile_lock); + list_for_each_entry(node, &profile_list, list) + if (!l--) + return node; + return NULL; +} + +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct list_head *lh = ((struct sdprofile *)p)->list.next; + (*pos)++; + return lh == &profile_list ? + NULL : list_entry(lh, struct sdprofile, list); +} + +static void p_stop(struct seq_file *f, void *v) +{ + read_unlock(&profile_lock); +} + +static int seq_show_profile(struct seq_file *f, void *v) +{ + struct sdprofile *profile = (struct sdprofile *)v; + seq_printf(f, "%s (%s)\n", profile->name, + PROFILE_COMPLAIN(profile) ? "complain" : "enforce"); + return 0; +} + +struct seq_operations subdomainfs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; --- linux-2.6.16.29.orig/security/apparmor/lsm.c 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.16.29/security/apparmor/lsm.c 2006-10-12 21:11:15.000000000 -0700 @@ -0,0 +1,959 @@ +/* + * Copyright (C) 2002-2005 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. + * + * http://forge.novell.com/modules/xfmod/project/?apparmor + * + * Immunix AppArmor LSM interface (previously called "SubDomain") + */ + +#include +#include +#include + +/* superblock types */ + +/* PIPEFS_MAGIC */ +#include +/* from net/socket.c */ +#define SOCKFS_MAGIC 0x534F434B +/* from inotify.c */ +#define INOTIFYFS_MAGIC 0xBAD1DEA + +#define VALID_FSTYPE(inode) ((inode)->i_sb->s_magic != PIPEFS_MAGIC && \ + (inode)->i_sb->s_magic != SOCKFS_MAGIC && \ + (inode)->i_sb->s_magic != INOTIFYFS_MAGIC) + +#include + +#include "apparmor.h" +#include "inline.h" + +/* main SD lock [see get_sdcopy and put_sdcopy] */ +rwlock_t sd_lock = RW_LOCK_UNLOCKED; + +/* Flag values, also controllable via subdomainfs/control. + * We explicitly do not allow these to be modifiable when exported via + * /sys/modules/parameters, as we want to do additional mediation and + * don't want to add special path code. */ + +/* Complain mode (used to be 'bitch' mode) */ +int subdomain_complain = 0; +module_param_named(complain, subdomain_complain, int, S_IRUSR); +MODULE_PARM_DESC(subdomain_complain, "Toggle AppArmor complain mode"); + +/* Debug mode */ +int subdomain_debug = 0; +module_param_named(debug, subdomain_debug, int, S_IRUSR); +MODULE_PARM_DESC(subdomain_debug, "Toggle AppArmor debug mode"); + +/* Audit mode */ +int subdomain_audit = 0; +module_param_named(audit, subdomain_audit, int, S_IRUSR); +MODULE_PARM_DESC(subdomain_audit, "Toggle AppArmor audit mode"); + +/* Syscall logging mode */ +int subdomain_logsyscall = 0; +module_param_named(logsyscall, subdomain_logsyscall, int, S_IRUSR); +MODULE_PARM_DESC(subdomain_logsyscall, "Toggle AppArmor logsyscall mode"); + +#ifndef MODULE +static int __init sd_getopt_complain(char *str) +{ + get_option(&str, &subdomain_complain); + return 1; +} +__setup("subdomain_complain=", sd_getopt_complain); + +static int __init sd_getopt_debug(char *str) +{ + get_option(&str, &subdomain_debug); + return 1; +} +__setup("subdomain_debug=", sd_getopt_debug); + +static int __init sd_getopt_audit(char *str) +{ + get_option(&str, &subdomain_audit); + return 1; +} +__setup("subdomain_audit=", sd_getopt_audit); + +static int __init sd_getopt_logsyscall(char *str) +{ + get_option(&str, &subdomain_logsyscall); + return 1; +} +__setup("subdomain_logsyscall=", sd_getopt_logsyscall); +#endif + +static int subdomain_ptrace(struct task_struct *parent, + struct task_struct *child) +{ + int error; + struct subdomain *sd; + unsigned long flags; + + error = cap_ptrace(parent, child); + + if (error == 0 && parent->security) { + read_lock_irqsave(&sd_lock, flags); + + sd = SD_SUBDOMAIN(parent->security); + + if (__sd_is_confined(sd)) { + error = sd_audit_syscallreject(sd, GFP_ATOMIC, + "ptrace"); + WARN_ON(error != -EPERM); + } + + read_unlock_irqrestore(&sd_lock, flags); + } + + return error; +} + +static int subdomain_capget(struct task_struct *target, + kernel_cap_t * effective, + kernel_cap_t * inheritable, + kernel_cap_t * permitted) +{ + return cap_capget(target, effective, inheritable, permitted); +} + +static int subdomain_capset_check(struct task_struct *target, + kernel_cap_t *effective, + kernel_cap_t *inheritable, + kernel_cap_t *permitted) +{ + return cap_capset_check(target, effective, inheritable, permitted); +} + +static void subdomain_capset_set(struct task_struct *target, + kernel_cap_t *effective, + kernel_cap_t *inheritable, + kernel_cap_t *permitted) +{ + cap_capset_set(target, effective, inheritable, permitted); + return; +} + +static int subdomain_capable(struct task_struct *tsk, int cap) +{ + int error; + + /* cap_capable returns 0 on success, else -EPERM */ + error = cap_capable(tsk, cap); + + if (error == 0 && current->security) { + struct subdomain *sd, sdcopy; + unsigned long flags; + + read_lock_irqsave(&sd_lock, flags); + sd = __get_sdcopy(&sdcopy, tsk); + read_unlock_irqrestore(&sd_lock, flags); + + error = sd_capability(sd, cap); + + put_sdcopy(sd); + } + + return error; +} + +static int subdomain_sysctl(struct ctl_table *table, int op) +{ + int error = 0; + struct subdomain *sd; + unsigned long flags; + + if (!current->security) + return 0; + + read_lock_irqsave(&sd_lock, flags); + + sd = SD_SUBDOMAIN(current->security); + + if ((op & 002) && __sd_is_confined(sd) && !capable(CAP_SYS_ADMIN)) { + error = sd_audit_syscallreject(sd, GFP_ATOMIC, + "sysctl (write)"); + WARN_ON(error != -EPERM); + } + + read_unlock_irqrestore(&sd_lock, flags); + + return error; +} + +static int subdomain_syslog(int type) +{ + return cap_syslog(type); +} + +static int subdomain_netlink_send(struct sock *sk, struct sk_buff *skb) +{ + return cap_netlink_send(sk, skb); +} + +static int subdomain_netlink_recv(struct sk_buff *skb) +{ + return cap_netlink_recv(skb); +} + +static void subdomain_bprm_apply_creds(struct linux_binprm *bprm, int unsafe) +{ + cap_bprm_apply_creds(bprm, unsafe); + return; +} + +static int subdomain_bprm_set_security(struct linux_binprm *bprm) +{ + /* handle capability bits with setuid, etc */ + cap_bprm_set_security(bprm); + /* already set based on script name */ + if (bprm->sh_bang) + return 0; + return sd_register(bprm); +} + +static int subdomain_bprm_secureexec(struct linux_binprm *bprm) +{ + int ret = cap_bprm_secureexec(bprm); + + if (ret == 0 && (unsigned long)bprm->security & SD_SECURE_EXEC_NEEDED) { + SD_DEBUG("%s: secureexec required for %s\n", + __FUNCTION__, bprm->filename); + ret = 1; + } + + return ret; +} + +static int subdomain_sb_mount(char *dev_name, struct nameidata *nd, char *type, + unsigned long flags, void *data) +{ + int error = 0; + struct subdomain *sd; + unsigned long lockflags; + + if (!current->security) + return 0; + + read_lock_irqsave(&sd_lock, lockflags); + + sd = SD_SUBDOMAIN(current->security); + + if (__sd_is_confined(sd)) { + error = sd_audit_syscallreject(sd, GFP_ATOMIC, "mount"); + WARN_ON(error != -EPERM); + } + + read_unlock_irqrestore(&sd_lock, lockflags); + + return error; +} + +static int subdomain_umount(struct vfsmount *mnt, int flags) +{ + int error = 0; + struct subdomain *sd; + unsigned long lockflags; + + if (!current->security) + return 0; + + read_lock_irqsave(&sd_lock, lockflags); + + sd = SD_SUBDOMAIN(current->security); + + if (__sd_is_confined(sd)) { + error = sd_audit_syscallreject(sd, GFP_ATOMIC, "umount"); + WARN_ON(error != -EPERM); + } + + read_unlock_irqrestore(&sd_lock, lockflags); + + return error; +} + +static int subdomain_inode_mkdir(struct inode *inode, struct dentry *dentry, + int mask) +{ + struct subdomain sdcopy, *sd; + int error; + + if (!current->security) + return 0; + + sd = get_sdcopy(&sdcopy); + + error = sd_perm_dir(sd, dentry, SD_DIR_MKDIR); + + put_sdcopy(sd); + + return error; +} + +static int subdomain_inode_rmdir(struct inode *inode, struct dentry *dentry) +{ + struct subdomain sdcopy, *sd; + int error; + + if (!current->security) + return 0; + + sd = get_sdcopy(&sdcopy); + + error = sd_perm_dir(sd, dentry, SD_DIR_RMDIR); + + put_sdcopy(sd); + + return error; +} + +static int subdomain_inode_create(struct inode *inode, struct dentry *dentry, + int mask) +{ + struct subdomain sdcopy, *sd; + int error; + + if (!current->security) + return 0; + + sd = get_sdcopy(&sdcopy); + + /* At a minimum, need write perm to create */ + error = sd_perm_dentry(sd, dentry, MAY_WRITE); + + put_sdcopy(sd); + + return error; +} + +static int subdomain_inode_link(struct dentry *old_dentry, struct inode *inode, + struct dentry *new_dentry) +{ + int error = 0; + struct subdomain sdcopy, *sd; + + if (!current->security) + return 0; + + sd = get_sdcopy(&sdcopy); + error = sd_link(sd, new_dentry, old_dentry); + put_sdcopy(sd); + + return error; +} + +static int subdomain_inode_unlink(struct inode *inode, struct dentry *dentry) +{ + struct subdomain sdcopy, *sd; + int error; + + if (!current->security) + return 0; + + sd = get_sdcopy(&sdcopy); + + error = sd_perm_dentry(sd, dentry, MAY_WRITE); + + put_sdcopy(sd); + + return error; +} + +static int subdomain_inode_mknod(struct inode *inode, struct dentry *dentry, + int mode, dev_t dev) +{ + struct subdomain sdcopy, *sd; + int error = 0; + + if (!current->security) + return 0; + + sd = get_sdcopy(&sdcopy); + + error = sd_perm_dentry(sd, dentry, MAY_WRITE); + + put_sdcopy(sd); + + return error; +} + +static int subdomain_inode_rename(struct inode *old_inode, + struct dentry *old_dentry, + struct inode *new_inode, + struct dentry *new_dentry) +{ + struct subdomain sdcopy, *sd; + int error = 0; + + if (!current->security) + return 0; + + sd = get_sdcopy(&sdcopy); + + error = sd_perm_dentry(sd, old_dentry, + MAY_READ | MAY_WRITE); + + if (!error) + error = sd_perm_dentry(sd, new_dentry, MAY_WRITE); + + put_sdcopy(sd); + + return error; +} + +static int subdomain_inode_permission(struct inode *inode, int mask, + struct nameidata *nd) +{ + int error = 0; + + /* Do not perform check on pipes or sockets + * Same as subdomain_file_permission + */ + if (current->security && VALID_FSTYPE(inode)) { + struct subdomain sdcopy, *sd; + + sd = get_sdcopy(&sdcopy); + error = sd_perm_nameidata(sd, nd, mask); + put_sdcopy(sd); + } + + return error; +} + +static int subdomain_inode_setattr(struct dentry *dentry, struct iattr *iattr) +{ + struct subdomain sdcopy, *sd; + int error = 0; + + if (current->security && VALID_FSTYPE(dentry->d_inode)) { + + sd = get_sdcopy(&sdcopy); + + /* + * Mediate any attempt to change attributes of a file + * (chmod, chown, chgrp, etc) + */ + error = sd_attr(sd, dentry, iattr); + + put_sdcopy(sd); + } + + return error; +} + +static int subdomain_inode_setxattr(struct dentry *dentry, char *name, + void *value, size_t size, int flags) +{ + int error = 0; + + if (current->security && VALID_FSTYPE(dentry->d_inode)) { + struct subdomain sdcopy, *sd; + + sd = get_sdcopy(&sdcopy); + error = sd_xattr(sd, dentry, name, SD_XATTR_SET); + put_sdcopy(sd); + } + + return error; +} + +static int subdomain_inode_getxattr(struct dentry *dentry, char *name) +{ + int error = 0; + + if (current->security && VALID_FSTYPE(dentry->d_inode)) { + struct subdomain sdcopy, *sd; + + sd = get_sdcopy(&sdcopy); + error = sd_xattr(sd, dentry, name, SD_XATTR_GET); + put_sdcopy(sd); + } + + return error; +} +static int subdomain_inode_listxattr(struct dentry *dentry) +{ + int error = 0; + + if (current->security && VALID_FSTYPE(dentry->d_inode)) { + struct subdomain sdcopy, *sd; + + sd = get_sdcopy(&sdcopy); + error = sd_xattr(sd, dentry, NULL, SD_XATTR_LIST); + put_sdcopy(sd); + } + + return error; +} + +static int subdomain_inode_removexattr(struct dentry *dentry, char *name) +{ + int error = 0; + + if (current->security && VALID_FSTYPE(dentry->d_inode)) { + struct subdomain sdcopy, *sd; + + sd = get_sdcopy(&sdcopy); + error = sd_xattr(sd, dentry, name, SD_XATTR_REMOVE); + put_sdcopy(sd); + } + + return error; +} + +static int subdomain_file_permission(struct file *file, int mask) +{ + struct subdomain sdcopy, *sd; + struct sdfile *sdf; + int error = 0; + + if (!current->security || + !(sdf = (struct sdfile *)file->f_security) || + !VALID_FSTYPE(file->f_dentry->d_inode)) + return 0; + + sd = get_sdcopy(&sdcopy); + + if (__sd_is_confined(sd) && sdf->profile != sd->active) + error = sd_perm(sd, file->f_dentry, file->f_vfsmnt, + mask & (MAY_EXEC | MAY_WRITE | MAY_READ)); + + put_sdcopy(sd); + + return error; +} + +static int subdomain_file_alloc_security(struct file *file) +{ + struct subdomain sdcopy, *sd; + int error = 0; + + if (!current->security) + return 0; + + sd = get_sdcopy(&sdcopy); + + if (__sd_is_confined(sd)) { + struct sdfile *sdf; + + sdf = kmalloc(sizeof(struct sdfile), GFP_KERNEL); + + if (sdf) { + sdf->type = sd_file_default; + sdf->profile = get_sdprofile(sd->active); + } else { + error = -ENOMEM; + } + + file->f_security = sdf; + } + + put_sdcopy(sd); + + return error; +} + +static void subdomain_file_free_security(struct file *file) +{ + struct sdfile *sdf = (struct sdfile *)file->f_security; + + if (sdf) { + put_sdprofile(sdf->profile); + kfree(sdf); + } +} + +static inline int sd_mmap(struct file *file, unsigned long prot, + unsigned long flags) +{ + int error = 0, mask = 0; + struct subdomain sdcopy, *sd; + struct sdfile *sdf; + + if (!current->security || !file || + !(sdf = (struct sdfile *)file->f_security) || + sdf->type == sd_file_shmem) + return 0; + + sd = get_sdcopy(&sdcopy); + + if (prot & PROT_READ) + mask |= MAY_READ; + + /* Private mappings don't require write perms since they don't + * write back to the files */ + if (prot & PROT_WRITE && !(flags & MAP_PRIVATE)) + mask |= MAY_WRITE; + + if (prot & PROT_EXEC) + mask |= SD_EXEC_MMAP; + + SD_DEBUG("%s: 0x%x\n", __FUNCTION__, mask); + + if (mask) + error = sd_perm(sd, file->f_dentry, file->f_vfsmnt, mask); + + put_sdcopy(sd); + + return error; +} + +static int subdomain_file_mmap(struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags) +{ + return sd_mmap(file, prot, flags); +} + +static int subdomain_file_mprotect(struct vm_area_struct* vma, + unsigned long reqprot, unsigned long prot) +{ + return sd_mmap(vma->vm_file, prot, + !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); +} + +static int subdomain_task_alloc_security(struct task_struct *p) +{ + return sd_fork(p); +} + +static void subdomain_task_free_security(struct task_struct *p) +{ + if (p->security) + sd_release(p); +} + +static int subdomain_task_post_setuid(uid_t id0, uid_t id1, uid_t id2, + int flags) +{ + return cap_task_post_setuid(id0, id1, id2, flags); +} + +static void subdomain_task_reparent_to_init(struct task_struct *p) +{ + cap_task_reparent_to_init(p); + return; +} + +static int subdomain_shm_shmat(struct shmid_kernel* shp, char __user *shmaddr, + int shmflg) +{ + struct sdfile *sdf = (struct sdfile *)shp->shm_file->f_security; + + if (sdf) + sdf->type = sd_file_shmem; + + return 0; +} + +static int subdomain_getprocattr(struct task_struct *p, char *name, void *value, + size_t size) +{ + int error; + struct subdomain sdcopy, *sd; + char *str = value; + unsigned long flags; + + /* Subdomain only supports the "current" process attribute */ + if (strcmp(name, "current") != 0) { + error = -EINVAL; + goto out; + } + + if (!size) { + error = -ERANGE; + goto out; + } + + /* must be task querying itself or admin */ + if (current != p && !capable(CAP_SYS_ADMIN)) { + error = -EPERM; + goto out; + } + + read_lock_irqsave(&sd_lock, flags); + + sd = __get_sdcopy(&sdcopy, p); + + read_unlock_irqrestore(&sd_lock, flags); + + error = sd_getprocattr(sd, str, size); + put_sdcopy(sd); + +out: + return error; +} + +static int subdomain_setprocattr(struct task_struct *p, char *name, void *value, + size_t size) +{ + const char *cmd_changehat = "changehat ", + *cmd_setprofile = "setprofile "; + + int error = -EACCES; /* default to a perm denied */ + char *cmd = (char *)value; + + /* only support messages to current */ + if (strcmp(name, "current") != 0) { + error = -EINVAL; + goto out; + } + + if (!size) { + error = -ERANGE; + goto out; + } + + /* CHANGE HAT */ + if (size > strlen(cmd_changehat) && + strncmp(cmd, cmd_changehat, strlen(cmd_changehat)) == 0) { + char *hatinfo = cmd + strlen(cmd_changehat); + size_t infosize = size - strlen(cmd_changehat); + + /* Only the current process may change it's hat */ + if (current != p) { + SD_WARN("%s: Attempt by foreign task %s(%d) " + "[user %d] to changehat of task %s(%d)\n", + __FUNCTION__, + current->comm, + current->pid, + current->uid, + p->comm, + p->pid); + + error = -EACCES; + goto out; + } + + error = sd_setprocattr_changehat(hatinfo, infosize); + if (error == 0) + /* success, set return to #bytes in orig request */ + error = size; + + /* SET NEW PROFILE */ + } else if (size > strlen(cmd_setprofile) && + strncmp(cmd, cmd_setprofile, strlen(cmd_setprofile)) == 0) { + int confined; + unsigned long flags; + + /* only an unconfined process with admin capabilities + * may change the profile of another task + */ + + if (!capable(CAP_SYS_ADMIN)) { + SD_WARN("%s: Unprivileged attempt by task %s(%d) " + "[user %d] to assign profile to task %s(%d)\n", + __FUNCTION__, + current->comm, + current->pid, + current->uid, + p->comm, + p->pid); + error = -EACCES; + goto out; + } + + read_lock_irqsave(&sd_lock, flags); + confined = sd_is_confined(); + read_unlock_irqrestore(&sd_lock, flags); + + if (!confined) { + char *profile = cmd + strlen(cmd_setprofile); + size_t profilesize = size - strlen(cmd_setprofile); + + error = sd_setprocattr_setprofile(p, profile, profilesize); + if (error == 0) + /* success, + * set return to #bytes in orig request + */ + error = size; + } else { + SD_WARN("%s: Attempt by confined task %s(%d) " + "[user %d] to assign profile to task %s(%d)\n", + __FUNCTION__, + current->comm, + current->pid, + current->uid, + p->comm, + p->pid); + + error = -EACCES; + } + } else { + /* unknown operation */ + SD_WARN("%s: Unknown setprocattr command '%.*s' by task %s(%d) " + "[user %d] for task %s(%d)\n", + __FUNCTION__, + size < 16 ? (int)size : 16, + cmd, + current->comm, + current->pid, + current->uid, + p->comm, + p->pid); + + error = -EINVAL; + } + +out: + return error; +} + +struct security_operations subdomain_ops = { + .ptrace = subdomain_ptrace, + .capget = subdomain_capget, + .capset_check = subdomain_capset_check, + .capset_set = subdomain_capset_set, + .sysctl = subdomain_sysctl, + .capable = subdomain_capable, + .syslog = subdomain_syslog, + + .netlink_send = subdomain_netlink_send, + .netlink_recv = subdomain_netlink_recv, + + .bprm_apply_creds = subdomain_bprm_apply_creds, + .bprm_set_security = subdomain_bprm_set_security, + .bprm_secureexec = subdomain_bprm_secureexec, + + .sb_mount = subdomain_sb_mount, + .sb_umount = subdomain_umount, + + .inode_mkdir = subdomain_inode_mkdir, + .inode_rmdir = subdomain_inode_rmdir, + .inode_create = subdomain_inode_create, + .inode_link = subdomain_inode_link, + .inode_unlink = subdomain_inode_unlink, + .inode_mknod = subdomain_inode_mknod, + .inode_rename = subdomain_inode_rename, + .inode_permission = subdomain_inode_permission, + .inode_setattr = subdomain_inode_setattr, + .inode_setxattr = subdomain_inode_setxattr, + .inode_getxattr = subdomain_inode_getxattr, + .inode_listxattr = subdomain_inode_listxattr, + .inode_removexattr = subdomain_inode_removexattr, + .file_permission = subdomain_file_permission, + .file_alloc_security = subdomain_file_alloc_security, + .file_free_security = subdomain_file_free_security, + .file_mmap = subdomain_file_mmap, + .file_mprotect = subdomain_file_mprotect, + + .task_alloc_security = subdomain_task_alloc_security, + .task_free_security = subdomain_task_free_security, + .task_post_setuid = subdomain_task_post_setuid, + .task_reparent_to_init = subdomain_task_reparent_to_init, + + .shm_shmat = subdomain_shm_shmat, + + .getprocattr = subdomain_getprocattr, + .setprocattr = subdomain_setprocattr, +}; + +static int __init subdomain_init(void) +{ + int error = 0; + const char *complainmsg = ": complainmode enabled"; + + if (!create_subdomainfs()) { + SD_ERROR("Unable to activate AppArmor filesystem\n"); + error = -ENOENT; + goto createfs_out; + } + + if (!alloc_nullprofiles()){ + SD_ERROR("Unable to allocate null profiles\n"); + error = -ENOMEM; + goto alloc_out; + } + + if ((error = register_security(&subdomain_ops))) { + SD_WARN("Unable to load AppArmor\n"); + goto register_security_out; + } + + SD_INFO("AppArmor (version %s) initialized%s\n", + apparmor_version(), + subdomain_complain ? complainmsg : ""); + sd_audit_message(NULL, GFP_KERNEL, 0, + "AppArmor (version %s) initialized%s\n", + apparmor_version(), + subdomain_complain ? complainmsg : ""); + + return error; + +register_security_out: + free_nullprofiles(); + +alloc_out: + (void)destroy_subdomainfs(); + +createfs_out: + return error; + +} + +static int subdomain_exit_removeall_iter(struct subdomain *sd, void *cookie) +{ + /* write_lock(&sd_lock) held here */ + + if (__sd_is_confined(sd)) { + SD_DEBUG("%s: Dropping profiles %s(%d) " + "profile %s(%p) active %s(%p)\n", + __FUNCTION__, + sd->task->comm, sd->task->pid, + sd->profile->name, sd->profile, + sd->active->name, sd->active); + sd_switch_unconfined(sd); + } + + return 0; +} + +static void __exit subdomain_exit(void) +{ + unsigned long flags; + + /* Remove profiles from the global profile list. + * This is just for tidyness as there is no way to reference this + * list once the AppArmor lsm hooks are detached (below) + */ + sd_profilelist_release(); + + /* Remove profiles from active tasks + * If this is not done, if module is reloaded after being removed, + * old profiles (still refcounted in memory) will become 'magically' + * reattached + */ + + write_lock_irqsave(&sd_lock, flags); + sd_subdomainlist_iterate(subdomain_exit_removeall_iter, NULL); + write_unlock_irqrestore(&sd_lock, flags); + + /* Free up list of active subdomain */ + sd_subdomainlist_release(); + + free_nullprofiles(); + + if (!destroy_subdomainfs()) + SD_WARN("Unable to properly deactivate AppArmor fs\n"); + + if (unregister_security(&subdomain_ops)) + SD_WARN("Unable to properly unregister AppArmor\n"); + + SD_INFO("AppArmor protection removed\n"); + sd_audit_message(NULL, GFP_KERNEL, 0, + "AppArmor protection removed\n"); +} + +module_init(subdomain_init); +module_exit(subdomain_exit); + +MODULE_DESCRIPTION("AppArmor process confinement"); +MODULE_AUTHOR("Tony Jones "); +MODULE_LICENSE("GPL"); --- linux-2.6.16.29.orig/security/apparmor/main.c 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.16.29/security/apparmor/main.c 2006-10-12 21:11:15.000000000 -0700 @@ -0,0 +1,1691 @@ +/* + * Copyright (C) 2002-2005 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 Core + */ + +#include +#include +#include + +#include "apparmor.h" +#include "aamatch/match.h" + +#include "inline.h" + +/* NULL profile + * + * Used when an attempt is made to changehat into a non-existant + * subhat. In the NULL profile, no file access is allowed + * (currently full network access is allowed). Using a NULL + * profile ensures that active is always non zero. + * + * Leaving the NULL profile is by either successfully changehatting + * into a sibling hat, or changehatting back to the parent (NULL hat). + */ +struct sdprofile *null_profile; + +/* NULL complain profile + * + * Used when in complain mode, to emit Permitting messages for non-existant + * profiles and hats. This is necessary because of selective mode, in which + * case we need a complain null_profile and enforce null_profile + * + * The null_complain_profile cannot be statically allocated, because it + * can be associated to files which keep their reference even if subdomain is + * unloaded + */ +struct sdprofile *null_complain_profile; + +/*************************** + * PRIVATE UTILITY FUNCTIONS + **************************/ + +/** + * dentry_xlate_error + * @dentry: pointer to dentry + * @error: error number + * @dtype: type of dentry + * + * Display error message when a dentry translation error occured + */ +static void dentry_xlate_error(struct dentry *dentry, int error, char *dtype) +{ + const unsigned int len = 16; + char buf[len]; + + if (dentry->d_inode) { + snprintf(buf, len, "%lu", dentry->d_inode->i_ino); + } else { + strncpy(buf, "", len); + buf[len-1]=0; + } + + SD_ERROR("An error occured while translating %s %p " + "inode# %s to a pathname. Error %d\n", + dtype, + dentry, + buf, + error); +} + +/** + * sd_taskattr_access: + * @name: name of file to check permission + * @mask: permission mask requested for file + * + * Determine if request is for write access to /proc/self/attr/current + */ +static inline int sd_taskattr_access(const char *procrelname) +{ +/* + * assumes a 32bit pid, which requires max 10 decimal digits to represent + * sizeof includes trailing \0 + */ + char buf[sizeof("/attr/current") + 10]; + const int maxbuflen = sizeof(buf); + + snprintf(buf, maxbuflen, "%d/attr/current", current->pid); + buf[maxbuflen - 1] = 0; + + return strcmp(buf, procrelname) == 0; +} + +/** + * sd_file_mode - get full mode for file entry from profile + * @profile: profile + * @name: filename + */ +static inline int sd_file_mode(struct sdprofile *profile, const char *name) +{ + struct sd_entry *entry; + int mode = 0; + + SD_DEBUG("%s: %s\n", __FUNCTION__, name); + if (!name) { + SD_DEBUG("%s: no name\n", __FUNCTION__); + goto out; + } + + if (!profile) { + SD_DEBUG("%s: no profile\n", __FUNCTION__); + goto out; + } + list_for_each_entry(entry, &profile->file_entry, list) { + if (sdmatch_match(name, entry->filename, + entry->entry_type, entry->extradata)) + mode |= entry->mode; + } +out: + return mode; +} + +/** + * sd_get_execmode - calculate what qualifier to apply to an exec + * @sd: subdomain to search + * @name: name of file to exec + * @xmod: pointer to a execution mode bit for the rule that was matched + * if the rule has no execuition qualifier {pui} then + * SD_MAY_EXEC is returned indicating a naked x + * if the has an exec qualifier then only the qualifier bit {pui} + * is returned (SD_MAY_EXEC) is not set. + * @unsafe: true if secure_exec should be overridden + * + * Returns 0 (false): + * if unable to find profile or there are conflicting pattern matches. + * *xmod - is not modified + * *unsafe - is not modified + * + * Returns 1 (true): + * if not confined + * *xmod = SD_MAY_EXEC + * *unsafe = 0 + * if exec rule matched + * if the rule has an execution mode qualifier {pui} then + * *xmod = the execution qualifier of the rule {pui} + * else + * *xmod = SD_MAY_EXEC + * unsafe = presence of unsafe flag + */ +static inline int sd_get_execmode(struct subdomain *sd, const char *name, + int *xmod, int *unsafe) +{ + struct sdprofile *profile; + struct sd_entry *entry; + struct sd_entry *match = NULL; + + int pattern_match_invalid = 0, rc = 0; + + /* not confined */ + if (!__sd_is_confined(sd)) { + SD_DEBUG("%s: not confined\n", __FUNCTION__); + goto not_confined; + } + + profile = sd->active; + + /* search list of profiles with 'x' permission + * this will also include entries with 'p', 'u' and 'i' + * qualifiers. + * + * If we find a pattern match we will keep looking for an exact match + * If we find conflicting pattern matches we will flag (while still + * looking for an exact match). If all we have is a conflict, FALSE + * is returned. + */ + + list_for_each_entry(entry, &profile->file_entryp[POS_SD_MAY_EXEC], + listp[POS_SD_MAY_EXEC]) { + if (!pattern_match_invalid && + entry->entry_type == sd_entry_pattern && + sdmatch_match(name, entry->filename, + entry->entry_type, entry->extradata)) { + if (match && + SD_EXEC_UNSAFE_MASK(entry->mode) != + SD_EXEC_UNSAFE_MASK(match->mode)) + pattern_match_invalid = 1; + else + /* keep searching for an exact match */ + match = entry; + } else if ((entry->entry_type == sd_entry_literal || + (!pattern_match_invalid && + entry->entry_type == sd_entry_tailglob)) && + sdmatch_match(name, entry->filename, + entry->entry_type, + entry->extradata)) { + if (entry->entry_type == sd_entry_literal) { + /* got an exact match -- there can be only + * one, asserted at profile load time + */ + match = entry; + pattern_match_invalid = 0; + break; + } else { + if (match && + SD_EXEC_UNSAFE_MASK(entry->mode) != + SD_EXEC_UNSAFE_MASK(match->mode)) + pattern_match_invalid = 1; + else + /* got a tailglob match, keep searching + * for an exact match + */ + match = entry; + } + } + + } + + rc = match && !pattern_match_invalid; + + if (rc) { + int mode = SD_EXEC_MASK(match->mode); + + /* check for qualifiers, if present + * we just return the qualifier + */ + if (mode & ~SD_MAY_EXEC) + mode = mode & ~SD_MAY_EXEC; + + *xmod = mode; + *unsafe = (match->mode & SD_EXEC_UNSAFE); + } else if (!match) { + SD_DEBUG("%s: Unable to find execute entry in profile " + "for image '%s'\n", + __FUNCTION__, + name); + } else if (pattern_match_invalid) { + SD_WARN("%s: Inconsistency in profile %s. " + "Two (or more) patterns specify conflicting exec " + "qualifiers ('u', 'i' or 'p') for image %s\n", + __FUNCTION__, + sd->active->name, + name); + } + + return rc; + +not_confined: + *xmod = SD_MAY_EXEC; + *unsafe = 0; + return 1; +} + +/** + * sd_filter_mask + * @mask: requested mask + * @inode: potential directory inode + * + * This fn performs pre-verification of the requested mask + * We ignore append. Previously we required 'w' on a dir to add a file. + * No longer. Now we require 'w' on just the file itself. Traversal 'x' is + * also ignored for directories. + * + * Returned value of 0 indicates no need to perform a perm check. + */ +static inline int sd_filter_mask(int mask, struct inode *inode) +{ + if (mask) { + int elim = MAY_APPEND; + + if (inode && S_ISDIR(inode->i_mode)) + elim |= (MAY_EXEC | MAY_WRITE); + + mask &= ~elim; + } + + return mask; +} + +static inline void sd_permerror2result(int perm_result, struct sd_audit *sa) +{ + if (perm_result == 0) { /* success */ + sa->result = 1; + sa->errorcode = 0; + } else { /* -ve internal error code or +ve mask of denied perms */ + sa->result = 0; + sa->errorcode = perm_result; + } +} + +/************************* + * MAIN INTERNAL FUNCTIONS + ************************/ + +/** + * sd_file_perm - calculate access mode for file + * @subdomain: current subdomain + * @name: name of file to calculate mode for + * @mask: permission mask requested for file + * + * Search the sd_entry list in @profile. + * Search looking to verify all permissions passed in mask. + * Perform the search by looking at the partitioned list of entries, one + * partition per permission bit. + * + * Return 0 on success, else mask of non-allowed permissions + */ +static unsigned int sd_file_perm(struct subdomain *sd, const char *name, + int mask) +{ + struct sdprofile *profile; + int i, error = 0, mode; + +#define PROCPFX "/proc/" +#define PROCLEN sizeof(PROCPFX) - 1 + + SD_DEBUG("%s: %s 0x%x\n", __FUNCTION__, name, mask); + + /* should not enter with other than R/W/M/X/L */ + BUG_ON(mask & + ~(SD_MAY_READ | SD_MAY_WRITE | SD_MAY_EXEC | + SD_EXEC_MMAP | SD_MAY_LINK)); + + /* not confined */ + if (!__sd_is_confined(sd)) { + /* exit with access allowed */ + SD_DEBUG("%s: not confined\n", __FUNCTION__); + goto done; + } + + /* Special case access to /proc/self/attr/current + * Currently we only allow access if opened O_WRONLY + */ + if (mask == MAY_WRITE && strncmp(PROCPFX, name, PROCLEN) == 0 && + (!list_empty(&sd->profile->sub) || SUBDOMAIN_COMPLAIN(sd)) && + sd_taskattr_access(name + PROCLEN)) + goto done; + + profile = sd->active; + + mode = 0; + + /* iterate over partition, one permission bit at a time */ + for (i = 0; i <= POS_SD_FILE_MAX; i++) { + struct sd_entry *entry; + + /* do we have to accumulate this bit? + * or have we already accumulated it (shortcut below)? */ + if (!(mask & (1 << i)) || mode & (1 << i)) + continue; + + list_for_each_entry(entry, &profile->file_entryp[i], + listp[i]) { + if (sdmatch_match(name, entry->filename, + entry->entry_type, entry->extradata)) { + /* Shortcut, accumulate all bits present */ + mode |= entry->mode; + + /* + * Mask bits are overloaded + * MAY_{EXEC,WRITE,READ,APPEND} are used by + * kernel, other values are used locally only. + */ + if ((mode & mask) == mask) { + SD_DEBUG("MATCH! %s=0x%x [total mode=0x%x]\n", + name, mask, mode); + + goto done; + } + } + } + } + + /* return permissions not satisfied */ + error = mask & ~mode; + +done: + return error; +} + +/** + * sd_link_perm - test permission to link to a file + * @sd: current subdomain + * @link: name of link being created + * @target: name of target to be linked to + * + * Look up permission mode on both @link and @target. @link must have same + * permission mode as @target. At least @link must have the link bit enabled. + * Return 0 on success, error otherwise. + */ +static int sd_link_perm(struct subdomain *sd, + const char *link, const char *target) +{ + int l_mode, t_mode, ret; + struct sdprofile *profile = sd->active; + + l_mode = sd_file_mode(profile, link); + if (l_mode & SD_MAY_LINK) { + /* mask off link bit */ + l_mode &= ~SD_MAY_LINK; + + t_mode = sd_file_mode(profile, target); + t_mode &= ~SD_MAY_LINK; + + ret = (l_mode == t_mode); + } else { + ret = 0; + } + + return ret; +} + +/** + * _sd_perm_dentry + * @sd: current subdomain + * @dentry: requested dentry + * @mask: mask of requested operations + * @pname: pointer to hold matched pathname (if any) + * + * Helper function. Obtain pathname for specified dentry. Verify if profile + * authorizes mask operations on pathname (due to lack of vfsmnt it is sadly + * necessary to search mountpoints in namespace -- when nameidata is passed + * more fully, this code can go away). If more than one mountpoint matches + * but none satisfy the profile, only the first pathname (mountpoint) is + * returned for subsequent logging. + * + * Return 0 (success), +ve (mask of permissions not satisfied) or -ve (system + * error, most likely -ENOMEM). + */ +static int _sd_perm_dentry(struct subdomain *sd, struct dentry *dentry, + int mask, const char **pname) +{ + char *name = NULL, *failed_name = NULL; + struct sd_path_data data; + int error = 0, failed_error = 0, sdpath_error, + sdcomplain = SUBDOMAIN_COMPLAIN(sd); + + /* search all paths to dentry */ + + sd_path_begin(dentry, &data); + do { + name = sd_path_getname(&data); + if (name) { + /* error here is 0 (success) or +ve (mask of perms) */ + error = sd_file_perm(sd, name, mask); + + /* access via any path is enough */ + if (sdcomplain || error == 0) + break; /* Caller must free name */ + + /* Already have an path that failed? */ + if (failed_name) { + sd_put_name(name); + } else { + failed_name = name; + failed_error = error; + } + } + } while (name); + + if ((sdpath_error = sd_path_end(&data)) != 0) { + dentry_xlate_error(dentry, sdpath_error, "dentry"); + + WARN_ON(name); /* name should not be set if error */ + error = sdpath_error; + name = NULL; + } else if (name) { + if (failed_name) + sd_put_name(failed_name); + } else { + name = failed_name; + error = failed_error; + } + + *pname = name; + + return error; +} + +/************************** + * GLOBAL UTILITY FUNCTIONS + *************************/ + +/** + * alloc_nullprofiles - Allocate null profiles + */ +int alloc_nullprofiles(void) +{ + null_profile = alloc_sdprofile(); + null_complain_profile = alloc_sdprofile(); + + if (!null_profile || !null_complain_profile) + goto fail; + + null_profile->name = kstrdup("null-profile", GFP_KERNEL); + null_complain_profile->name = + kstrdup("null-complain-profile", GFP_KERNEL); + + if (!null_profile->name || + !null_complain_profile->name) + goto fail; + + get_sdprofile(null_profile); + get_sdprofile(null_complain_profile); + null_complain_profile->flags.complain = 1; + + return 1; + +fail: + /* free_sdprofile is safe for freeing partially constructed objects */ + free_sdprofile(null_profile); + free_sdprofile(null_complain_profile); + null_profile = null_complain_profile = NULL; + return 0; +} + +/** + * free_nullprofiles - Free null profiles + */ +void free_nullprofiles(void) +{ + put_sdprofile(null_complain_profile); + put_sdprofile(null_profile); + null_profile = null_complain_profile = NULL; +} + +/** + * sd_audit_message - Log a message to the audit subsystem + * @sd: current subdomain + * @gfp: allocation flags + * @flags: audit flags + * @fmt: varargs fmt + */ +int sd_audit_message(struct subdomain *sd, unsigned int gfp, int flags, + const char *fmt, ...) +{ + int ret; + struct sd_audit sa; + + sa.type = SD_AUDITTYPE_MSG; + sa.name = fmt; + va_start(sa.vaval, fmt); + sa.flags = flags; + sa.gfp_mask = gfp; + sa.errorcode = 0; + sa.result = 0; /* fake failure: force message to be logged */ + + ret = sd_audit(sd, &sa); + + va_end(sa.vaval); + + return ret; +} + +/** + * sd_audit_syscallreject - Log a syscall rejection to the audit subsystem + * @sd: current subdomain + * @msg: string describing syscall being rejected + * @gfp: memory allocation flags + */ +int sd_audit_syscallreject(struct subdomain *sd, unsigned int gfp, + const char *msg) +{ + struct sd_audit sa; + + sa.type = SD_AUDITTYPE_SYSCALL; + sa.name = msg; + sa.flags = 0; + sa.gfp_mask = gfp; + sa.errorcode = 0; + sa.result = 0; /* failure */ + + return sd_audit(sd, &sa); +} + +/** + * sd_audit - Log an audit event to the audit subsystem + * @sd: current subdomain + * @sa: audit event + */ +int sd_audit(struct subdomain *sd, const struct sd_audit *sa) +{ + struct audit_buffer *ab = NULL; + struct audit_context *ctx; + + const char *logcls; + unsigned int flags; + int sdaudit = 0, + sdcomplain = 0, + error = -EINVAL, + opspec_error = -EACCES; + + const unsigned int gfp_mask = sa->gfp_mask; + + WARN_ON(sa->type >= SD_AUDITTYPE__END); + + /* + * sa->result: 1 success, 0 failure + * sa->errorcode: success: 0 + * failure: +ve mask of failed permissions or -ve + * system error + */ + + if (likely(sa->result)) { + if (likely(!SUBDOMAIN_AUDIT(sd))) { + /* nothing to log */ + error = 0; + goto out; + } else { + sdaudit = 1; + logcls = "AUDITING"; + } + } else if (sa->errorcode < 0) { + audit_log(current->audit_context, gfp_mask, AUDIT_SD, + "Internal error auditing event type %d (error %d)", + sa->type, sa->errorcode); + SD_ERROR("Internal error auditing event type %d (error %d)\n", + sa->type, sa->errorcode); + error = sa->errorcode; + goto out; + } else if (sa->type == SD_AUDITTYPE_SYSCALL) { + /* Currently SD_AUDITTYPE_SYSCALL is for rejects only. + * Values set by sd_audit_syscallreject will get us here. + */ + logcls = "REJECTING"; + } else { + sdcomplain = SUBDOMAIN_COMPLAIN(sd); + logcls = sdcomplain ? "PERMITTING" : "REJECTING"; + } + + /* In future extend w/ per-profile flags + * (flags |= sa->active->flags) + */ + flags = sa->flags; + if (subdomain_logsyscall) + flags |= SD_AUDITFLAG_AUDITSS_SYSCALL; + + + /* Force full audit syscall logging regardless of global setting if + * we are rejecting a syscall + */ + if (sa->type == SD_AUDITTYPE_SYSCALL) { + ctx = current->audit_context; + } else { + ctx = (flags & SD_AUDITFLAG_AUDITSS_SYSCALL) ? + current->audit_context : NULL; + } + + ab = audit_log_start(ctx, gfp_mask, AUDIT_SD); + + if (!ab) { + SD_ERROR("Unable to log event (%d) to audit subsys\n", + sa->type); + if (sdcomplain) + error = 0; + goto out; + } + + /* messages get special handling */ + if (sa->type == SD_AUDITTYPE_MSG) { + audit_log_vformat(ab, sa->name, sa->vaval); + audit_log_end(ab); + error = 0; + goto out; + } + + /* log operation */ + + audit_log_format(ab, "%s ", logcls); /* REJECTING/ALLOWING/etc */ + + if (sa->type == SD_AUDITTYPE_FILE) { + int perm = sdaudit ? sa->ival : sa->errorcode; + + audit_log_format(ab, "%s%s%s%s%s access to %s ", + perm & SD_EXEC_MMAP ? "m" : "", + perm & SD_MAY_READ ? "r" : "", + perm & SD_MAY_WRITE ? "w" : "", + perm & SD_MAY_EXEC ? "x" : "", + perm & SD_MAY_LINK ? "l" : "", + sa->name); + + opspec_error = -EPERM; + + } else if (sa->type == SD_AUDITTYPE_DIR) { + audit_log_format(ab, "%s on %s ", + sa->ival == SD_DIR_MKDIR ? "mkdir" : "rmdir", + sa->name); + + } else if (sa->type == SD_AUDITTYPE_ATTR) { + struct iattr *iattr = (struct iattr*)sa->pval; + + audit_log_format(ab, + "attribute (%s%s%s%s%s%s%s) change to %s ", + iattr->ia_valid & ATTR_MODE ? "mode," : "", + iattr->ia_valid & ATTR_UID ? "uid," : "", + iattr->ia_valid & ATTR_GID ? "gid," : "", + iattr->ia_valid & ATTR_SIZE ? "size," : "", + ((iattr->ia_valid & ATTR_ATIME_SET) || + (iattr->ia_valid & ATTR_ATIME)) ? "atime," : "", + ((iattr->ia_valid & ATTR_MTIME_SET) || + (iattr->ia_valid & ATTR_MTIME)) ? "mtime," : "", + iattr->ia_valid & ATTR_CTIME ? "ctime," : "", + sa->name); + + } else if (sa->type == SD_AUDITTYPE_XATTR) { + const char *fmt; + switch (sa->ival) { + case SD_XATTR_GET: + fmt = "xattr get"; + break; + case SD_XATTR_SET: + fmt = "xattr set"; + break; + case SD_XATTR_LIST: + fmt = "xattr list"; + break; + case SD_XATTR_REMOVE: + fmt = "xattr remove"; + break; + default: + fmt = "xattr "; + break; + } + + audit_log_format(ab, "%s on %s ", fmt, sa->name); + + } else if (sa->type == SD_AUDITTYPE_LINK) { + audit_log_format(ab, + "link access from %s to %s ", + sa->name, + (char*)sa->pval); + + } else if (sa->type == SD_AUDITTYPE_CAP) { + audit_log_format(ab, + "access to capability '%s' ", + capability_to_name(sa->ival)); + + opspec_error = -EPERM; + } else if (sa->type == SD_AUDITTYPE_SYSCALL) { + audit_log_format(ab, "access to syscall '%s' ", sa->name); + + opspec_error = -EPERM; + } else { + /* -EINVAL -- will WARN_ON above */ + goto out; + } + + audit_log_format(ab, "(%s(%d) profile %s active %s)", + current->comm, current->pid, + sd->profile->name, sd->active->name); + + audit_log_end(ab); + + if (sdcomplain) + error = 0; + else + error = sa->result ? 0 : opspec_error; + +out: + return error; +} + +/** + * sd_get_name - retrieve fully qualified path name + * @dentry: relative path element + * @mnt: where in tree + * + * Returns fully qualified path name on sucess, NULL on failure. + * sd_put_name must be used to free allocated buffer. + */ +char *sd_get_name(struct dentry *dentry, struct vfsmount *mnt) +{ + char *page, *name; + + page = (char *)__get_free_page(GFP_KERNEL); + if (!page) { + name = ERR_PTR(-ENOMEM); + goto out; + } + + name = d_path(dentry, mnt, page, PAGE_SIZE); + + /* check for (deleted) that d_path appends to pathnames if the dentry + * has been removed from the cache. + * The size > deleted_size and strcmp checks are redundant safe guards. + */ + if (IS_ERR(name)) { + free_page((unsigned long)page); + } else { + const char deleted_str[] = " (deleted)"; + const size_t deleted_size = sizeof(deleted_str) - 1; + size_t size; + size = strlen(name); + if (!IS_ROOT(dentry) && d_unhashed(dentry) && + size > deleted_size && + strcmp(name + size - deleted_size, deleted_str) == 0) + name[size - deleted_size] = '\0'; + + SD_DEBUG("%s: full_path=%s\n", __FUNCTION__, name); + } + +out: + return name; +} + +/*********************************** + * GLOBAL PERMISSION CHECK FUNCTIONS + ***********************************/ + +/** + * sd_attr - check whether attribute change allowed + * @sd: subdomain to check against to check against + * @dentry: file to check + * @iattr: attribute changes requested + */ +int sd_attr(struct subdomain *sd, struct dentry *dentry, struct iattr *iattr) +{ + int error = 0, permerror; + struct sd_audit sa; + + if (!__sd_is_confined(sd)) + goto out; + + sa.type = SD_AUDITTYPE_ATTR; + sa.pval = iattr; + sa.flags = 0; + sa.gfp_mask = GFP_KERNEL; + + permerror = _sd_perm_dentry(sd, dentry, MAY_WRITE, &sa.name); + sd_permerror2result(permerror, &sa); + + error = sd_audit(sd, &sa); + + sd_put_name(sa.name); + +out: + return error; +} + +int sd_xattr(struct subdomain *sd, struct dentry *dentry, const char *xattr, + int xattroptype) +{ + int error = 0, permerror, mask = 0; + struct sd_audit sa; + + /* if not confined or empty mask permission granted */ + if (!__sd_is_confined(sd)) + goto out; + + if (xattroptype == SD_XATTR_GET || xattroptype == SD_XATTR_LIST) + mask = MAY_READ; + else if (xattroptype == SD_XATTR_SET || xattroptype == SD_XATTR_REMOVE) + mask = MAY_WRITE; + + sa.type = SD_AUDITTYPE_XATTR; + sa.ival = xattroptype; + sa.pval = xattr; + sa.flags = 0; + sa.gfp_mask = GFP_KERNEL; + + permerror = _sd_perm_dentry(sd, dentry, mask, &sa.name); + sd_permerror2result(permerror, &sa); + + error = sd_audit(sd, &sa); + + sd_put_name(sa.name); + +out: + return error; +} + +/** + * sd_perm - basic subdomain permissions check + * @sd: subdomain to check against + * @dentry: dentry + * @mnt: mountpoint + * @mask: access mode requested + * + * Determine if access (mask) for dentry is authorized by subdomain sd. + * Result, 0 (success), -ve (error) + */ +int sd_perm(struct subdomain *sd, struct dentry *dentry, struct vfsmount *mnt, + int mask) +{ + int error = 0, permerror; + struct sd_audit sa; + + if (!__sd_is_confined(sd)) + goto out; + + if ((mask = sd_filter_mask(mask, dentry->d_inode)) == 0) + goto out; + + sa.type = SD_AUDITTYPE_FILE; + sa.name = sd_get_name(dentry, mnt); + sa.ival = mask; + sa.flags = 0; + sa.gfp_mask = GFP_KERNEL; + + if (IS_ERR(sa.name)) { + permerror = PTR_ERR(sa.name); + sa.name = NULL; + } else { + permerror = sd_file_perm(sd, sa.name, mask); + } + + sd_permerror2result(permerror, &sa); + + error = sd_audit(sd, &sa); + + sd_put_name(sa.name); + +out: + return error; +} + +/** + * sd_perm_nameidata: interface to sd_perm accepting nameidata + * @sd: subdomain to check against + * @nd: namespace data (for vfsmnt and dentry) + * @mask: access mode requested + */ +int sd_perm_nameidata(struct subdomain *sd, struct nameidata *nd, int mask) +{ + int error = 0; + + if (nd) + error = sd_perm(sd, nd->dentry, nd->mnt, mask); + + return error; +} + +/** + * sd_perm_dentry - file permissions interface when no vfsmnt available + * @sd: current subdomain + * @dentry: requested dentry + * @mask: access mode requested + * + * Determine if access (mask) for dentry is authorized by subdomain sd. + * Result, 0 (success), -ve (error) + */ +int sd_perm_dentry(struct subdomain *sd, struct dentry *dentry, int mask) +{ + int error = 0, permerror; + struct sd_audit sa; + + if (!__sd_is_confined(sd)) + goto out; + + if ((mask = sd_filter_mask(mask, dentry->d_inode)) == 0) + goto out; + + sa.type = SD_AUDITTYPE_FILE; + sa.ival = mask; + sa.flags = 0; + sa.gfp_mask = GFP_KERNEL; + + permerror = _sd_perm_dentry(sd, dentry, mask, &sa.name); + sd_permerror2result(permerror, &sa); + + error = sd_audit(sd, &sa); + + sd_put_name(sa.name); + +out: + return error; +} + +/** + * sd_perm_dir + * @sd: current subdomain + * @dentry: requested dentry + * @mode: SD_DIR_MKDIR or SD_DIR_RMDIR + * + * Determine if directory operation (make/remove) for dentry is authorized + * by subdomain sd. + * Result, 0 (success), -ve (error) + */ +int sd_perm_dir(struct subdomain *sd, struct dentry *dentry, int diroptype) +{ + int error = 0, permerror, mask; + struct sd_audit sa; + + BUG_ON(diroptype != SD_DIR_MKDIR && diroptype != SD_DIR_RMDIR); + + if (!__sd_is_confined(sd)) + goto out; + + mask = MAY_WRITE; + + sa.type = SD_AUDITTYPE_DIR; + sa.ival = diroptype; + sa.flags = 0; + sa.gfp_mask = GFP_KERNEL; + + permerror = _sd_perm_dentry(sd, dentry, mask, &sa.name); + sd_permerror2result(permerror, &sa); + + error = sd_audit(sd, &sa); + + sd_put_name(sa.name); + +out: + return error; +} + +/** + * sd_capability - test permission to use capability + * @sd: subdomain to check against + * @cap: capability to be tested + * + * Look up capability in active profile capability set. + * Return 0 (success), -EPERM (error) + */ +int sd_capability(struct subdomain *sd, int cap) +{ + int error = 0; + + if (__sd_is_confined(sd)) { + struct sd_audit sa; + + sa.type = SD_AUDITTYPE_CAP; + sa.name = NULL; + sa.ival = cap; + sa.flags = 0; + sa.errorcode = 0; + sa.result = cap_raised(sd->active->capabilities, cap); + sa.gfp_mask = GFP_ATOMIC; + + error = sd_audit(sd, &sa); + } + + return error; +} + +/** + * sd_link - hard link check + * @link: dentry for link being created + * @target: dentry for link target + * @sd: subdomain to check against + * + * Checks link permissions for all possible name combinations. This is + * particularly ugly. Returns 0 on sucess, error otherwise. + */ +int sd_link(struct subdomain *sd, struct dentry *link, struct dentry *target) +{ + char *iname = NULL, *oname = NULL, + *failed_iname = NULL, *failed_oname = NULL; + unsigned int result = 0; + int error, sdpath_error, errorcode = 0, match = 0, + sdcomplain = SUBDOMAIN_COMPLAIN(sd); + struct sd_path_data idata, odata; + struct sd_audit sa; + + if (!__sd_is_confined(sd)) + return 0; + + /* Perform nested lookup for names. + * This is necessary in the case where /dev/block is mounted + * multiple times, i.e /dev/block->/a and /dev/block->/b + * This allows us to detect links where src/dest are on different + * mounts. N.B no support yet for links across bind mounts of + * the form mount -bind /mnt/subpath /mnt2 + * + * Getting direct access to vfsmounts (via nameidata) for link and + * target would allow all this uglyness to go away. + * + * If more than one mountpoint matches but none satisfy the profile, + * only the first pathname (mountpoint) is logged. + */ + + sd_path_begin2(target, link, &odata); + do { + oname = sd_path_getname(&odata); + if (oname) { + sd_path_begin(target, &idata); + do { + iname = sd_path_getname(&idata); + if (iname) { + result = sd_link_perm(sd, oname, iname); + + /* access via any path is enough */ + if (result || sdcomplain) { + match = 1; + break; + } + + /* Already have an path that failed? */ + if (failed_iname) { + sd_put_name(iname); + } else { + failed_iname = iname; + failed_oname = oname; + } + } + } while (iname && !match); + + /* should not be possible if we matched */ + if ((sdpath_error = sd_path_end(&idata)) != 0) { + dentry_xlate_error(target, sdpath_error, + "inner dentry [link]"); + + /* name should not be set if error */ + WARN_ON(iname); + + errorcode = sdpath_error; + } + + /* don't release if we're saving it */ + if (!match && failed_oname != oname) + sd_put_name(oname); + } + } while (oname && !match); + + if (errorcode != 0) { + /* inner error */ + (void)sd_path_end(&odata); + } else if ((sdpath_error = sd_path_end(&odata)) != 0) { + dentry_xlate_error(link, sdpath_error, "outer dentry [link]"); + + errorcode = sdpath_error; + } + + if (errorcode != 0) { + /* inner or outer error */ + result = 0; + } else if (match) { + result = 1; + } else { + /* failed to match */ + WARN_ON(iname); + WARN_ON(oname); + + result = 0; + iname = failed_iname; + oname = failed_oname; + } + + sa.type = SD_AUDITTYPE_LINK; + sa.name = oname; /* link */ + sa.pval = iname; /* target */ + sa.flags = 0; + sa.errorcode = errorcode; + sa.result = result; + sa.gfp_mask = GFP_KERNEL; + + error = sd_audit(sd, &sa); + + if (failed_oname != oname) + sd_put_name(failed_oname); + if (failed_iname != iname) + sd_put_name(failed_iname); + + sd_put_name(oname); + sd_put_name(iname); + + return error; +} + +/********************************** + * GLOBAL PROCESS RELATED FUNCTIONS + *********************************/ + +/** + * sd_fork - create a new subdomain + * @p: new process + * + * Create a new subdomain for newly created process @p if it's parent + * is already confined. Otherwise a subdomain will be lazily allocated + * for the child if it subsequently execs (in sd_register). + * Return 0 on sucess. + */ + +int sd_fork(struct task_struct *p) +{ + struct subdomain *sd = SD_SUBDOMAIN(current->security); + struct subdomain *newsd = NULL; + + SD_DEBUG("%s\n", __FUNCTION__); + + if (__sd_is_confined(sd)) { + unsigned long flags; + + newsd = alloc_subdomain(p); + + if (!newsd) + return -ENOMEM; + + /* Can get away with a read rather than write lock here + * as we just allocated newsd above, so we can guarantee + * that it's active/profile are null and therefore a replace + * cannot happen. + */ + read_lock_irqsave(&sd_lock, flags); + sd_switch(newsd, sd->profile, sd->active); + newsd->sd_hat_magic = sd->sd_hat_magic; + read_unlock_irqrestore(&sd_lock, flags); + + if (SUBDOMAIN_COMPLAIN(sd) && + sd->active == null_complain_profile) + LOG_HINT(sd, GFP_KERNEL, HINT_FORK, + "pid=%d child=%d\n", + current->pid, p->pid); + } + p->security = newsd; + return 0; +} + +/** + * sd_register - register a new program + * @filp: file of program being registered + * + * Try to register a new program during execve(). This should give the + * new program a valid subdomain. + * + * This _used_ to be a really simple piece of code :-( + * + */ +int sd_register(struct linux_binprm *bprm) +{ + char *filename; + struct file *filp = bprm->file; + struct subdomain *sd, sdcopy; + struct sdprofile *newprofile = NULL, unconstrained_flag; + int error = -ENOMEM, + exec_mode = 0, + findprofile = 0, + findprofile_mandatory = 0, + unsafe_exec = 0, + complain = 0; + + SD_DEBUG("%s\n", __FUNCTION__); + + sd = get_sdcopy(&sdcopy); + + filename = sd_get_name(filp->f_dentry, filp->f_vfsmnt); + if (IS_ERR(filename)) { + SD_WARN("%s: Failed to get filename\n", __FUNCTION__); + goto out; + } + + error = 0; + + if (!__sd_is_confined(sd)) { + /* Unconfined task, load profile if it exists */ + findprofile = 1; + goto find_profile; + } + + complain = SUBDOMAIN_COMPLAIN(sd); + + /* Confined task, determine what mode inherit, unconstrained or + * mandatory to load new profile + */ + if (sd_get_execmode(sd, filename, &exec_mode, &unsafe_exec)) { + switch (exec_mode) { + case SD_EXEC_INHERIT: + /* do nothing - setting of profile + * already handed in sd_fork + */ + SD_DEBUG("%s: INHERIT %s\n", + __FUNCTION__, + filename); + break; + + case SD_EXEC_UNCONSTRAINED: + SD_DEBUG("%s: UNCONSTRAINED %s\n", + __FUNCTION__, + filename); + + /* unload profile */ + newprofile = &unconstrained_flag; + break; + + case SD_EXEC_PROFILE: + SD_DEBUG("%s: PROFILE %s\n", + __FUNCTION__, + filename); + + findprofile = 1; + findprofile_mandatory = 1; + break; + + case SD_MAY_EXEC: + /* this should not happen, entries + * with just EXEC only should be + * rejected at profile load time + */ + SD_ERROR("%s: Rejecting exec(2) of image '%s'. " + "SD_MAY_EXEC without exec qualifier invalid " + "(%s(%d) profile %s active %s\n", + __FUNCTION__, + filename, + current->comm, current->pid, + sd->profile->name, sd->active->name); + error = -EPERM; + break; + + default: + SD_ERROR("%s: Rejecting exec(2) of image '%s'. " + "Unknown exec qualifier %x " + "(%s (pid %d) profile %s active %s)\n", + __FUNCTION__, + filename, + exec_mode, + current->comm, current->pid, + sd->profile->name, sd->active->name); + error = -EPERM; + break; + } + + } else if (complain) { + /* There was no entry in calling profile + * describing mode to execute image in. + * Drop into null-profile (disabling secure exec). + */ + newprofile = get_sdprofile(null_complain_profile); + unsafe_exec = 1; + } else { + SD_WARN("%s: Rejecting exec(2) of image '%s'. " + "Unable to determine exec qualifier " + "(%s (pid %d) profile %s active %s)\n", + __FUNCTION__, + filename, + current->comm, current->pid, + sd->profile->name, sd->active->name); + error = -EPERM; + } + + +find_profile: + if (!findprofile) + goto apply_profile; + + /* Locate new profile */ + newprofile = sd_profilelist_find(filename); + if (newprofile) { + SD_DEBUG("%s: setting profile %s\n", + __FUNCTION__, newprofile->name); + } else if (findprofile_mandatory) { + /* Profile (mandatory) could not be found */ + + if (complain) { + LOG_HINT(sd, GFP_KERNEL, HINT_MANDPROF, + "image=%s pid=%d profile=%s active=%s\n", + filename, + current->pid, + sd->profile->name, + sd->active->name); + + newprofile = get_sdprofile(null_complain_profile); + } else { + SD_WARN("REJECTING exec(2) of image '%s'. " + "Profile mandatory and not found " + "(%s(%d) profile %s active %s)\n", + filename, + current->comm, current->pid, + sd->profile->name, sd->active->name); + error = -EPERM; + } + } else { + /* Profile (non-mandatory) could not be found */ + + /* Only way we can get into this code is if task + * is unconstrained. + */ + + BUG_ON(__sd_is_confined(sd)); + + SD_DEBUG("%s: No profile found for exec image %s\n", + __FUNCTION__, + filename); + } /* newprofile */ + + +apply_profile: + /* Apply profile if necessary */ + if (newprofile) { + struct subdomain *latest_sd, *lazy_sd = NULL; + unsigned long flags; + + if (newprofile == &unconstrained_flag) + newprofile = NULL; + + /* grab a write lock + * + * - Task may be presently unconfined (have no sd). In which + * case we have to lazily allocate one. Note we may be raced + * to this allocation by a setprofile. + * + * - sd is a refcounted copy of the subdomain (get_sdcopy) and + * not the actual subdomain. This allows us to not have to + * hold a read lock around all this code. However, we need to + * change the actual subdomain, not the copy. + * + * - If newprofile points to an actual profile (result of + * sd_profilelist_find above), this profile may have been + * replaced. We need to fix it up. Doing this to avoid + * having to hold a write lock around all this code. + */ + + if (!sd) { + lazy_sd = alloc_subdomain(current); + } + + write_lock_irqsave(&sd_lock, flags); + + latest_sd = SD_SUBDOMAIN(current->security); + + if (latest_sd) { + if (lazy_sd) { + /* raced by setprofile (created latest_sd) */ + free_subdomain(lazy_sd); + lazy_sd = NULL; + } + } else { + if (lazy_sd) { + latest_sd = lazy_sd; + current->security = lazy_sd; + } else { + SD_ERROR("%s: Failed to allocate subdomain\n", + __FUNCTION__); + + error = -ENOMEM; + write_unlock_irqrestore(&sd_lock, flags); + goto done; + } + } + + /* Determine if profile we found earlier is stale. + * If so, reobtain it. N.B stale flag should never be + * set on null_complain profile. + */ + if (newprofile && unlikely(newprofile->isstale)) { + BUG_ON(newprofile == null_complain_profile); + + /* drop refcnt obtained from earlier get_sdprofile */ + put_sdprofile(newprofile); + + newprofile = sd_profilelist_find(filename); + + if (!newprofile) { + /* Race, profile was removed, not replaced. + * Redo with error checking + */ + write_unlock_irqrestore(&sd_lock, flags); + goto find_profile; + } + } + + /* Handle confined exec. + * Can be at this point for the following reasons: + * 1. unconfined switching to confined + * 2. confined switching to different confinement + * 3. confined switching to unconfined + * + * Cases 2 and 3 are marked as requiring secure exec + * (unless policy specified "unsafe exec") + */ + if (__sd_is_confined(latest_sd) && !unsafe_exec) { + unsigned long bprm_flags; + + bprm_flags = SD_SECURE_EXEC_NEEDED; + bprm->security = (void*) + ((unsigned long)bprm->security | bprm_flags); + } + + sd_switch(latest_sd, newprofile, newprofile); + put_sdprofile(newprofile); + + if (complain && newprofile == null_complain_profile) + LOG_HINT(latest_sd, GFP_ATOMIC, HINT_CHGPROF, + "pid=%d\n", + current->pid); + + write_unlock_irqrestore(&sd_lock, flags); + } + +done: + sd_put_name(filename); + + if (sd) + put_sdcopy(sd); + +out: + return error; +} + +/** + * sd_release - release the task's subdomain + * @p: task being released + * + * This is called after a task has exited and the parent has reaped it. + * p->security must be !NULL. The @p->security blob is freed. + */ +void sd_release(struct task_struct *p) +{ + struct subdomain *sd = SD_SUBDOMAIN(p->security); + p->security = NULL; + + sd_subdomainlist_remove(sd); + + /* release profiles */ + put_sdprofile(sd->profile); + put_sdprofile(sd->active); + + kfree(sd); +} + +/***************************** + * GLOBAL SUBPROFILE FUNCTIONS + ****************************/ + +/** + * do_change_hat - actually switch hats + * @name: name of hat to swtich to + * @sd: current subdomain + * + * Switch to a new hat. Return 0 on success, error otherwise. + */ +static inline int do_change_hat(const char *hat_name, struct subdomain *sd) +{ + struct sdprofile *sub; + struct sdprofile *p = sd->active; + int error = 0; + + sub = __sd_find_profile(hat_name, &sd->profile->sub); + + if (sub) { + /* change hat */ + sd->active = sub; + } else { + /* There is no such subprofile change to a NULL profile. + * The NULL profile grants no file access. + * + * This feature is used by changehat_apache. + * + * N.B from the null-profile the task can still changehat back + * out to the parent profile (assuming magic != NULL) + */ + if (SUBDOMAIN_COMPLAIN(sd)) { + LOG_HINT(sd, GFP_ATOMIC, HINT_UNKNOWN_HAT, + "%s pid=%d " + "profile=%s active=%s\n", + hat_name, + current->pid, + sd->profile->name, + sd->active->name); + sd->active = get_sdprofile(null_complain_profile); + } else { + SD_DEBUG("%s: Unknown hatname '%s'. " + "Changing to NULL profile " + "(%s(%d) profile %s active %s)\n", + __FUNCTION__, + hat_name, + current->comm, current->pid, + sd->profile->name, sd->active->name); + + sd->active = get_sdprofile(null_profile); + error = -EACCES; + } + } + put_sdprofile(p); + + return error; +} + +/** + * sd_change_hat - change hat to/from subprofile + * @hat_name: specifies hat to change to + * @hat_magic: token to validate hat change + * + * Change to new @hat_name when current hat is top level profile, and store + * the @hat_magic in the current subdomain. If the new @hat_name is + * NULL, and the @hat_magic matches that stored in the current subdomain + * return to original top level profile. Returns 0 on success, error + * otherwise. + */ +#define IN_SUBPROFILE(sd) ((sd)->profile != (sd)->active) +int sd_change_hat(const char *hat_name, __u32 hat_magic) +{ + struct subdomain *sd = SD_SUBDOMAIN(current->security); + int error = 0; + + SD_DEBUG("%s: %p, 0x%x (pid %d)\n", + __FUNCTION__, + hat_name, hat_magic, + current->pid); + + /* Dump out above debugging in WARN mode if we are in AUDIT mode */ + if (SUBDOMAIN_AUDIT(sd)) { + SD_WARN("%s: %s, 0x%x (pid %d)\n", + __FUNCTION__, hat_name ? hat_name : "NULL", + hat_magic, current->pid); + } + + /* no subdomain: changehat into the null_profile, since the process + has no subdomain do_change_hat won't find a match which will cause + a changehat to null_profile. We could short circuit this but since + the subdprofile (hat) list is empty we would save very little. */ + + /* check to see if an unconfined process is doing a changehat. */ + if (!__sd_is_confined(sd)) { + error = -EACCES; + goto out; + } + + /* Check whether current domain is parent + * or one of the sibling children + */ + if (sd->profile == sd->active) { + /* + * parent + */ + if (hat_name) { + SD_DEBUG("%s: switching to %s, 0x%x\n", + __FUNCTION__, + hat_name, + hat_magic); + + /* + * N.B hat_magic == 0 has a special meaning + * this indicates that the task may never changehat + * back to it's parent, it will stay in this subhat + * (or null-profile, if the hat doesn't exist) until + * the task terminates + */ + sd->sd_hat_magic = hat_magic; + error = do_change_hat(hat_name, sd); + } else { + /* Got here via changehat(NULL, magic) + * + * We used to simply update the magic cookie. + * That's an odd behaviour, so just do nothing. + */ + } + } else { + /* + * child -- check to make sure magic is same as what was + * passed when we switched into this profile, + * Handle special casing of NULL magic which confines task + * to subprofile and prohibits further changehats + */ + if (hat_magic == sd->sd_hat_magic && sd->sd_hat_magic) { + if (!hat_name) { + /* + * Got here via changehat(NULL, magic) + * Return from subprofile, back to parent + */ + put_sdprofile(sd->active); + sd->active = get_sdprofile(sd->profile); + + /* Reset hat_magic to zero. + * New value will be passed on next changehat + */ + sd->sd_hat_magic = 0; + } else { + /* change to another (sibling) profile */ + error = do_change_hat(hat_name, sd); + } + } else if (sd->sd_hat_magic) { + SD_ERROR("KILLING process %s(%d) " + "Invalid change_hat() magic# 0x%x " + "(hatname %s profile %s active %s)\n", + current->comm, current->pid, + hat_magic, + hat_name ? hat_name : "NULL", + sd->profile->name, sd->active->name); + + /* terminate current process */ + (void)send_sig_info(SIGKILL, NULL, current); + } else { /* sd->sd_hat_magic == NULL */ + SD_ERROR("KILLING process %s(%d) " + "Task was confined to current subprofile " + "(profile %s active %s)\n", + current->comm, current->pid, + sd->profile->name, sd->active->name); + + /* terminate current process */ + (void)send_sig_info(SIGKILL, NULL, current); + } + + } + +out: + return error; +} --- linux-2.6.16.29.orig/security/apparmor/Makefile 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.16.29/security/apparmor/Makefile 2006-10-12 21:11:15.000000000 -0700 @@ -0,0 +1,25 @@ +# Makefile for AppArmor Linux Security Module (previously called "SubDomain") +# +# kernel build Makefile is the Kbuild file + +REPO_VERSION := $(shell if [ -x /usr/bin/svn ] ; then \ + /usr/bin/svn info . 2> /dev/null | grep "^Last Changed Rev:" | sed "s/^Last Changed Rev: //" ; \ + fi) + +ifeq ("${REPO_VERSION}", "") +REPO_VERSION := "unknown" +endif + +KERNELVER := $(shell uname -r) + +KERNELDIR := /lib/modules/${KERNELVER}/build + +all: + $(MAKE) -C $(KERNELDIR) M=`pwd` APPARMOR_VER=${REPO_VERSION} $@ + mv apparmor.ko apparmor-${KERNELVER}.ko + mv aamatch/aamatch_pcre.ko aamatch_pcre-${KERNELVER}.ko + +clean: + rm -f *.o *.ko *.mod.c .*.cmd Modules.symvers \ + aamatch/*.o aamatch/*.ko aamatch/.*.cmd aamatch/*.mod.c + rm -rf .tmp_versions --- linux-2.6.16.29.orig/security/apparmor/module_interface.c 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.16.29/security/apparmor/module_interface.c 2006-10-12 21:11:15.000000000 -0700 @@ -0,0 +1,712 @@ +/* + * Copyright (C) 1998-2005 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 + +#include "apparmor.h" +#include "inline.h" +#include "module_interface.h" +#include "aamatch/match.h" + +/* sd_code defined in module_interface.h */ + +const int sdcode_datasize[] = { 1, 2, 4, 8, 2, 2, 4, 0, 0, 0, 0, 0, 0 }; + +struct sd_taskreplace_data { + struct sdprofile *old_profile; + struct sdprofile *new_profile; +}; + +/* inlines must be forward of there use in newer version of gcc, + just forward declaring with a prototype won't work anymore */ + +static inline void free_sd_entry(struct sd_entry *entry) +{ + if (entry) { + kfree(entry->filename); + sdmatch_free(entry->extradata); + kfree(entry); + } +} + +/** + * alloc_sd_entry - create new empty sd_entry + * + * This routine allocates, initializes, and returns a new subdomain + * file entry structure. Structure is zeroed. Returns new structure on + * success, NULL on failure. + */ +static inline struct sd_entry *alloc_sd_entry(void) +{ + struct sd_entry *entry; + + SD_DEBUG("%s\n", __FUNCTION__); + entry = kmalloc(sizeof(struct sd_entry), GFP_KERNEL); + if (entry) { + int i; + memset(entry, 0, sizeof(struct sd_entry)); + INIT_LIST_HEAD(&entry->list); + for (i = 0; i <= POS_SD_FILE_MAX; i++) { + INIT_LIST_HEAD(&entry->listp[i]); + } + } + return entry; +} + +/** + * free_sdprofile - free sdprofile structure + */ +void free_sdprofile(struct sdprofile *profile) +{ + struct sd_entry *sdent, *tmp; + struct sdprofile *p, *ptmp; + + SD_DEBUG("%s(%p)\n", __FUNCTION__, profile); + + if (!profile) + return; + + /* profile is still on global profile list -- invalid */ + if (!list_empty(&profile->list)) { + SD_ERROR("%s: internal error, " + "profile '%s' still on global list\n", + __FUNCTION__, + profile->name); + BUG(); + } + + list_for_each_entry_safe(sdent, tmp, &profile->file_entry, list) { + if (sdent->filename) + SD_DEBUG("freeing sd_entry: %p %s\n", + sdent->filename, sdent->filename); + list_del_init(&sdent->list); + free_sd_entry(sdent); + } + + list_for_each_entry_safe(p, ptmp, &profile->sub, list) { + list_del_init(&p->list); + put_sdprofile(p); + } + + if (profile->name) { + SD_DEBUG("%s: %s\n", __FUNCTION__, profile->name); + kfree(profile->name); + } + + kfree(profile); +} + +/** task_remove + * + * remove profile in a task's subdomain leaving the task unconfined + * + * @sd: task's subdomain + */ +static inline void task_remove(struct subdomain *sd) +{ + /* write_lock(&sd_lock) held here */ + SD_DEBUG("%s: removing profile from task %s(%d) profile %s active %s\n", + __FUNCTION__, + sd->task->comm, + sd->task->pid, + sd->profile->name, + sd->active->name); + + sd_switch_unconfined(sd); +} + +/** taskremove_iter + * + * Iterate over all subdomains. + * + * If any matches old_profile, then call task_remove to remove it. + * This leaves the task (subdomain) unconfined. + */ +static int taskremove_iter(struct subdomain *sd, void *cookie) +{ + struct sdprofile *old_profile = (struct sdprofile *)cookie; + unsigned long flags; + + write_lock_irqsave(&sd_lock, flags); + + if (__sd_is_confined(sd) && sd->profile == old_profile) + task_remove(sd); + + write_unlock_irqrestore(&sd_lock, flags); + + return 0; +} + +/** task_replace + * + * replace profile in a task's subdomain with newly loaded profile + * + * @sd: task's subdomain + * @new: old profile + */ +static inline void task_replace(struct subdomain *sd, struct sdprofile *new) +{ + struct sdprofile *nactive = NULL; + + SD_DEBUG("%s: replacing profile for task %s(%d) " + "profile=%s (%p) active=%s (%p)\n", + __FUNCTION__, + sd->task->comm, sd->task->pid, + sd->profile->name, sd->profile, + sd->active->name, sd->active); + + if (sd->profile == sd->active) + nactive = get_sdprofile(new); + else if (sd->active) { + /* old in hat, new profile has hats */ + nactive = __sd_find_profile(sd->active->name, &new->sub); + + if (!nactive) { + if (new->flags.complain) + nactive = get_sdprofile(null_complain_profile); + else + nactive = get_sdprofile(null_profile); + } + } + sd_switch(sd, new, nactive); + + put_sdprofile(nactive); +} + +/** taskreplace_iter + * + * Iterate over all subdomains. + * + * If any matches old_profile, then call task_replace to replace with + * new_profile + */ +static int taskreplace_iter(struct subdomain *sd, void *cookie) +{ + struct sd_taskreplace_data *data = (struct sd_taskreplace_data *)cookie; + unsigned long flags; + + write_lock_irqsave(&sd_lock, flags); + + if (__sd_is_confined(sd) && sd->profile == data->old_profile) + task_replace(sd, data->new_profile); + + write_unlock_irqrestore(&sd_lock, flags); + + return 0; +} + +static inline int sd_inbounds(struct sd_ext *e, size_t size) +{ + return (e->pos + size <= e->end); +} + +/** + * sdconvert - for codes that have a trailing value, convert that value + * and put it in dest. + * if a code does not have a trailing value nop + * @code: type code + * @dest: pointer to object to receive the converted value + * @src: pointer to value to convert + */ +static void sdconvert(enum sd_code code, void *dest, void *src) +{ + switch (code) { + case SD_U8: + *(u8 *)dest = *(u8 *) src; + break; + case SD_U16: + case SD_NAME: + case SD_DYN_STRING: + *(u16 *)dest = le16_to_cpu(get_unaligned((u16 *)src)); + break; + case SD_U32: + case SD_STATIC_BLOB: + *(u32 *)dest = le32_to_cpu(get_unaligned((u32 *)src)); + break; + case SD_U64: + *(u64 *)dest = le64_to_cpu(get_unaligned((u64 *)src)); + break; + default: + /* nop - all other type codes do not have a trailing value */ + ; + } +} + +/** + * sd_is_X - check if the next element is of type X and if it is within + * bounds. If it is put the associated value in data. + * @e: extent information + * @code: type code + * @data: object located at @e->pos (of type @code) is written into @data + * if @data is non-null. if data is null it means skip this + * entry + * return the size of bytes associated with the returned data + * for complex object like blob and string a pointer to the allocated + * data is returned in data, but the size of the blob or string is + * returned. + */ +static u32 sd_is_X(struct sd_ext *e, enum sd_code code, void *data) +{ + void *pos = e->pos; + int ret = 0; + if (!sd_inbounds(e, SD_CODE_BYTE + sdcode_datasize[code])) + goto fail; + if (code != *(u8 *)e->pos) + goto out; + e->pos += SD_CODE_BYTE; + if (code == SD_NAME) { + u16 size; + /* name codes are followed by X bytes */ + size = le16_to_cpu(get_unaligned((u16 *)e->pos)); + if (!sd_inbounds(e, (size_t) size)) + goto fail; + if (data) + *(u16 *)data = size; + e->pos += sdcode_datasize[code]; + ret = 1 + sdcode_datasize[code]; + } else if (code == SD_DYN_STRING) { + u16 size; + char *str; + /* strings codes are followed by X bytes */ + size = le16_to_cpu(get_unaligned((u16 *)e->pos)); + e->pos += sdcode_datasize[code]; + if (!sd_inbounds(e, (size_t) size)) + goto fail; + if (data) { + * (char **)data = NULL; + str = kmalloc(size, GFP_KERNEL); + if (!str) + goto fail; + memcpy(str, e->pos, (size_t) size); + str[size-1] = '\0'; + * (char **)data = str; + } + e->pos += size; + ret = size; + } else if (code == SD_STATIC_BLOB) { + u32 size; + /* blobs are followed by X bytes, that can be 2^32 */ + size = le32_to_cpu(get_unaligned((u32 *)e->pos)); + e->pos += sdcode_datasize[code]; + if (!sd_inbounds(e, (size_t) size)) + goto fail; + if (data) + memcpy(data, e->pos, (size_t) size); + e->pos += size; + ret = size; + } else { + if (data) + sdconvert(code, data, e->pos); + e->pos += sdcode_datasize[code]; + ret = 1 + sdcode_datasize[code]; + } +out: + return ret; +fail: + e->pos = pos; + return 0; +} + +/* sd_is_nameX - check is the next element is X, and its tag is name. + * if the code matches and name (if specified) matches then the packed data + * is unpacked into *data. (Note for strings this is the size, and the next + * data in the stream is the string data) + * returns 0 if either match failes + */ +static int sd_is_nameX(struct sd_ext *e, enum sd_code code, void *data, + const char *name) +{ + void *pos = e->pos; + u16 size; + u32 ret; + /* check for presence of a tagname, and if present name size + * SD_NAME tag value is a u16 */ + if (sd_is_X(e, SD_NAME, &size)) { + /* if a name is specified it must match. otherwise skip tag */ + if (name && ((strlen(name) != size-1) || + strncmp(name, (char *)e->pos, (size_t)size-1))) + goto fail; + e->pos += size; + } + /* now check if data actually matches */ + ret = sd_is_X(e, code, data); + if (!ret) + goto fail; + return ret; + +fail: + e->pos = pos; + return 0; +} + +/* macro to wrap error case to make a block of reads look nicer */ +#define SD_READ_X(E, C, D, N) \ + do { \ + u32 __ret; \ + __ret = sd_is_nameX((E), (C), (D), (N)); \ + if (!__ret) \ + goto fail; \ + } while (0) + +/** + * sd_activate_net_entry - ignores/skips net entries if the they are present + * in the data stream. + * @e: extent information + */ +static inline int sd_activate_net_entry(struct sd_ext *e) +{ + SD_READ_X(e, SD_STRUCT, NULL, "ne"); + SD_READ_X(e, SD_U32, NULL, NULL); + SD_READ_X(e, SD_U32, NULL, NULL); + SD_READ_X(e, SD_U32, NULL, NULL); + SD_READ_X(e, SD_U16, NULL, NULL); + SD_READ_X(e, SD_U16, NULL, NULL); + SD_READ_X(e, SD_U32, NULL, NULL); + SD_READ_X(e, SD_U32, NULL, NULL); + SD_READ_X(e, SD_U16, NULL, NULL); + SD_READ_X(e, SD_U16, NULL, NULL); + /* interface name is optional so just ignore return code */ + sd_is_nameX(e, SD_DYN_STRING, NULL, NULL); + SD_READ_X(e, SD_STRUCTEND, NULL, NULL); + + return 1; +fail: + return 0; +} + +static inline struct sd_entry *sd_activate_file_entry(struct sd_ext *e) +{ + struct sd_entry *entry = NULL; + + if (!(entry = alloc_sd_entry())) + goto fail; + + SD_READ_X(e, SD_STRUCT, NULL, "fe"); + SD_READ_X(e, SD_DYN_STRING, &entry->filename, NULL); + SD_READ_X(e, SD_U32, &entry->mode, "file.mode"); + SD_READ_X(e, SD_U32, &entry->entry_type, "file.pattern_type"); + + entry->extradata = sdmatch_alloc(entry->entry_type); + if (IS_ERR(entry->extradata)) { + entry->extradata = NULL; + goto fail; + } + + if (entry->extradata && + sdmatch_serialize(entry->extradata, e, sd_is_nameX) != 0) { + goto fail; + } + SD_READ_X(e, SD_STRUCTEND, NULL, NULL); + + switch (entry->entry_type) { + case sd_entry_literal: + SD_DEBUG("%s: %s [no pattern] mode=0x%x\n", + __FUNCTION__, + entry->filename, + entry->mode); + break; + case sd_entry_tailglob: + SD_DEBUG("%s: %s [tailglob] mode=0x%x\n", + __FUNCTION__, + entry->filename, + entry->mode); + break; + case sd_entry_pattern: + SD_DEBUG("%s: %s mode=0x%x\n", + __FUNCTION__, + entry->filename, + entry->mode); + break; + default: + SD_WARN("%s: INVALID entry_type %d\n", + __FUNCTION__, + (int)entry->entry_type); + goto fail; + } + + return entry; + +fail: + sdmatch_free(entry->extradata); + free_sd_entry(entry); + return NULL; +} + +static inline int check_rule_and_add(struct sd_entry *file_entry, + struct sdprofile *profile, + const char **message) +{ + /* verify consistency of x, px, ix, ux for entry against + possible duplicates for this entry */ + int mode = SD_EXEC_MODIFIER_MASK(file_entry->mode); + int i; + + if (mode && !(SD_MAY_EXEC & file_entry->mode)) { + *message = "inconsistent rule, x modifiers without x"; + goto out; + } + + /* check that only 1 of the modifiers is set */ + if (mode && (mode & (mode - 1))) { + *message = "inconsistent rule, multiple x modifiers"; + goto out; + } + + /* ix -> m (required so that target exec binary may map itself) */ + if (mode & SD_EXEC_INHERIT) + file_entry->mode |= SD_EXEC_MMAP; + + list_add(&file_entry->list, &profile->file_entry); + profile->num_file_entries++; + + mode = file_entry->mode; + + /* Handle partitioned lists + * Chain entries onto sublists based on individual + * permission bits. This allows more rapid searching. + */ + for (i = 0; i <= POS_SD_FILE_MAX; i++) { + if (mode & (1 << i)) + /* profile->file_entryp[i] initially set to + * NULL in alloc_sdprofile() */ + list_add(&file_entry->listp[i], + &profile->file_entryp[i]); + } + + return 1; + +out: + free_sd_entry(file_entry); + return 0; +} + +#define SD_ENTRY_LIST(NAME) \ + do { \ + if (sd_is_nameX(e, SD_LIST, NULL, (NAME))) { \ + rulename = ""; \ + error_string = "Invalid file entry"; \ + while (!sd_is_nameX(e, SD_LISTEND, NULL, NULL)) { \ + struct sd_entry *file_entry; \ + file_entry = sd_activate_file_entry(e); \ + if (!file_entry) \ + goto fail; \ + if (!check_rule_and_add(file_entry, profile, \ + &error_string)) { \ + rulename = file_entry->filename; \ + goto fail; \ + } \ + } \ + } \ + } while (0) + +struct sdprofile *sd_activate_profile(struct sd_ext *e, ssize_t *error) +{ + struct sdprofile *profile = NULL; + const char *rulename = ""; + const char *error_string = "Invalid Profile"; + + *error = -EPROTO; + + profile = alloc_sdprofile(); + if (!profile) { + error_string = "Could not allocate profile"; + *error = -ENOMEM; + goto fail; + } + + /* check that we have the right struct being passed */ + SD_READ_X(e, SD_STRUCT, NULL, "profile"); + SD_READ_X(e, SD_DYN_STRING, &profile->name, NULL); + + error_string = "Invalid flags"; + /* per profile debug flags (debug, complain, audit) */ + SD_READ_X(e, SD_STRUCT, NULL, "flags"); + SD_READ_X(e, SD_U32, &(profile->flags.debug), "profile.flags.debug"); + SD_READ_X(e, SD_U32, &(profile->flags.complain), + "profile.flags.complain"); + SD_READ_X(e, SD_U32, &(profile->flags.audit), "profile.flags.audit"); + SD_READ_X(e, SD_STRUCTEND, NULL, NULL); + + error_string = "Invalid capabilities"; + SD_READ_X(e, SD_U32, &(profile->capabilities), "profile.capabilities"); + + /* get the file entries. */ + SD_ENTRY_LIST("pgent"); /* pcre rules */ + SD_ENTRY_LIST("sgent"); /* simple globs */ + SD_ENTRY_LIST("fent"); /* regular file entries */ + + /* get the net entries */ + if (sd_is_nameX(e, SD_LIST, NULL, "net")) { + error_string = "Invalid net entry"; + while (!sd_is_nameX(e, SD_LISTEND, NULL, NULL)) { + if (!sd_activate_net_entry(e)) + goto fail; + } + } + rulename = ""; + + /* get subprofiles */ + if (sd_is_nameX(e, SD_LIST, NULL, "hats")) { + error_string = "Invalid profile hat"; + while (!sd_is_nameX(e, SD_LISTEND, NULL, NULL)) { + struct sdprofile *subprofile; + subprofile = sd_activate_profile(e, error); + if (!subprofile) + goto fail; + get_sdprofile(subprofile); + list_add(&subprofile->list, &profile->sub); + } + } + + error_string = "Invalid end of profile"; + SD_READ_X(e, SD_STRUCTEND, NULL, NULL); + + return profile; + +fail: + SD_WARN("%s: %s %s in profile %s\n", INTERFACE_ID, rulename, + error_string, profile && profile->name ? profile->name + : "unknown"); + + if (profile) { + free_sdprofile(profile); + profile = NULL; + } + + return NULL; +} + +void *sd_activate_top_profile(struct sd_ext *e, ssize_t *error) +{ + /* get the interface version */ + if (!sd_is_nameX(e, SD_U32, &e->version, "version")) { + SD_WARN("%s: version missing\n", INTERFACE_ID); + *error = -EPROTONOSUPPORT; + goto out; + } + + /* check that the interface version is currently supported */ + if (e->version != 2) { + SD_WARN("%s: unsupported interface version (%d)\n", + INTERFACE_ID, e->version); + *error = -EPROTONOSUPPORT; + goto out; + } + + return sd_activate_profile(e, error); +out: + return NULL; +} + +ssize_t sd_file_prof_add(void *data, size_t size) +{ + struct sdprofile *profile = NULL; + + struct sd_ext e = { data, data + size, data }; + ssize_t error; + + profile = sd_activate_top_profile(&e, &error); + if (!profile) { + SD_DEBUG("couldn't activate profile\n"); + return error; + } + + if (!sd_profilelist_add(profile)) { + SD_WARN("trying to add profile (%s) that already exists.\n", + profile->name); + free_sdprofile(profile); + return -EEXIST; + } + + return size; +} + +ssize_t sd_file_prof_repl(void *udata, size_t size) +{ + struct sd_taskreplace_data data; + struct sd_ext e = { udata, udata + size, udata }; + ssize_t error; + + data.new_profile = sd_activate_top_profile(&e, &error); + if (!data.new_profile) { + SD_DEBUG("couldn't activate profile\n"); + return error; + } + /* Grab reference to close race window (see comment below) */ + get_sdprofile(data.new_profile); + + /* Replace the profile on the global profile list. + * This list is used by all new exec's to find the correct profile. + * If there was a previous profile, it is returned, else NULL. + * + * N.B sd_profilelist_replace does not drop the refcnt on + * old_profile when removing it from the global list, otherwise it + * could reach zero and be automatically free'd. We nust manually + * drop it at the end of this function when we are finished with it. + */ + data.old_profile = sd_profilelist_replace(data.new_profile); + + /* RACE window here. + * At this point another task could preempt us trying to replace + * the SAME profile. If it makes it to this point, it has removed + * the original tasks new_profile from the global list and holds a + * reference of 1 to it in it's old_profile. If the new task + * reaches the end of the function it will put old_profile causing + * the profile to be deleted. + * When the original task is rescheduled it will continue calling + * sd_subdomainlist_iterate relabelling tasks with a profile + * which points to free'd memory. + */ + + /* If there was an old profile, find all currently executing tasks + * using this profile and replace the old profile with the new. + */ + if (data.old_profile) { + SD_DEBUG("%s: try to replace profile (%p)%s\n", + __FUNCTION__, + data.old_profile, + data.old_profile->name); + + sd_subdomainlist_iterate(taskreplace_iter, (void *)&data); + + /* it's off global list, and we are done replacing */ + put_sdprofile(data.old_profile); + } + + /* Free reference obtained above */ + put_sdprofile(data.new_profile); + + return size; +} + +ssize_t sd_file_prof_remove(const char *name, size_t size) +{ + struct sdprofile *old_profile; + + /* if the old profile exists it will be removed from the list and + * a reference is returned. + */ + old_profile = sd_profilelist_remove(name); + + if (old_profile) { + /* remove profile from any tasks using it */ + sd_subdomainlist_iterate(taskremove_iter, (void *)old_profile); + + /* drop reference obtained by sd_profilelist_remove */ + put_sdprofile(old_profile); + } else { + SD_WARN("%s: trying to remove profile (%s) that " + "doesn't exist - skipping.\n", __FUNCTION__, name); + return -ENOENT; + } + + return size; +} --- linux-2.6.16.29.orig/security/apparmor/module_interface.h 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.16.29/security/apparmor/module_interface.h 2006-10-12 21:11:15.000000000 -0700 @@ -0,0 +1,37 @@ +#ifndef __MODULEINTERFACE_H +#define __MODULEINTERFACE_H + +/* Codes of the types of basic structures that are understood */ +#define SD_CODE_BYTE (sizeof(u8)) +#define INTERFACE_ID "INTERFACE" + +#define SUBDOMAIN_INTERFACE_VERSION 2 + +enum sd_code { + SD_U8, + SD_U16, + SD_U32, + SD_U64, + SD_NAME, /* same as string except it is items name */ + SD_DYN_STRING, + SD_STATIC_BLOB, + SD_STRUCT, + SD_STRUCTEND, + SD_LIST, + SD_LISTEND, + SD_OFFSET, + SD_BAD +}; + +/* sd_ext tracks the kernel buffer and read position in it. The interface + * data is copied into a kernel buffer in subdomainfs and then handed off to + * the activate routines. + */ +struct sd_ext { + void *start; + void *end; + void *pos; /* pointer to current position in the buffer */ + u32 version; +}; + +#endif /* __MODULEINTERFACE_H */ --- linux-2.6.16.29.orig/security/apparmor/procattr.c 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.16.29/security/apparmor/procattr.c 2006-10-12 21:11:15.000000000 -0700 @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2005 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 /proc/pid/attr handling + */ + +/* for isspace */ +#include + +#include "apparmor.h" +#include "inline.h" + +size_t sd_getprocattr(struct subdomain *sd, char *str, size_t size) +{ + int error = -EACCES; /* default to a perm denied */ + size_t len; + + if (__sd_is_confined(sd)) { + size_t lena, lenm, lenp = 0; + const char *enforce_str = " (enforce)"; + const char *complain_str = " (complain)"; + const char *mode_str = + SUBDOMAIN_COMPLAIN(sd) ? complain_str : enforce_str; + + lenm = strlen(mode_str); + + lena = strlen(sd->active->name); + + len = lena; + if (sd->active != sd->profile) { + lenp = strlen(sd->profile->name); + len += (lenp + 1); /* +1 for ^ */ + } + /* DONT null terminate strings we output via proc */ + len += (lenm + 1); /* for \n */ + + if (len <= size) { + if (lenp) { + memcpy(str, sd->profile->name, lenp); + str += lenp; + *str++ = '^'; + } + + memcpy(str, sd->active->name, lena); + str += lena; + memcpy(str, mode_str, lenm); + str += lenm; + *str++ = '\n'; + error = len; + } else { + error = -ERANGE; + } + } else { + const char *unconstrained_str = SD_UNCONSTRAINED "\n"; + len = strlen(unconstrained_str); + + /* DONT null terminate strings we output via proc */ + if (len <= size) { + memcpy(str, unconstrained_str, len); + error = len; + } else { + error = -ERANGE; + } + } + + return error; + +} +int sd_setprocattr_changehat(char *hatinfo, size_t infosize) +{ + int error = -EINVAL; + char *token = NULL, *hat, *smagic, *tmp; + __u32 magic; + int rc, len, consumed; + unsigned long flags; + + SD_DEBUG("%s: %p %zd\n", __FUNCTION__, hatinfo, infosize); + + /* strip leading white space */ + while (infosize && isspace(*hatinfo)) { + hatinfo++; + infosize--; + } + + if (infosize == 0) + goto out; + + /* + * Copy string to a new buffer so we can play with it + * It may be zero terminated but we add a trailing 0 + * for 100% safety + */ + token = kmalloc(infosize + 1, GFP_KERNEL); + + if (!token) { + error = -ENOMEM; + goto out; + } + + memcpy(token, hatinfo, infosize); + token[infosize] = 0; + + /* error is INVAL until we have at least parsed something */ + error = -EINVAL; + + tmp = token; + while (*tmp && *tmp != '^') { + tmp++; + } + + if (!*tmp || tmp == token) { + SD_WARN("%s: Invalid input '%s'\n", __FUNCTION__, token); + goto out; + } + + /* split magic and hat into two strings */ + *tmp = 0; + smagic = token; + + /* + * Initially set consumed=strlen(magic), as if sscanf + * consumes all input via the %x it will not process the %n + * directive. Otherwise, if sscanf does not consume all the + * input it will process the %n and update consumed. + */ + consumed = len = strlen(smagic); + + rc = sscanf(smagic, "%x%n", &magic, &consumed); + + if (rc != 1 || consumed != len) { + SD_WARN("%s: Invalid hex magic %s\n", + __FUNCTION__, + smagic); + goto out; + } + + hat = tmp + 1; + + if (!*hat) + hat = NULL; + + if (!hat && !magic) { + SD_WARN("%s: Invalid input, NULL hat and NULL magic\n", + __FUNCTION__); + goto out; + } + + SD_DEBUG("%s: Magic 0x%x Hat '%s'\n", + __FUNCTION__, magic, hat ? hat : NULL); + + write_lock_irqsave(&sd_lock, flags); + error = sd_change_hat(hat, magic); + write_unlock_irqrestore(&sd_lock, flags); + +out: + if (token) { + memset(token, 0, infosize); + kfree(token); + } + + return error; +} + +int sd_setprocattr_setprofile(struct task_struct *p, char *profilename, + size_t profilesize) +{ + int error = -EINVAL; + struct sdprofile *profile; + struct subdomain *sd; + char *name = NULL; + unsigned long flags; + + SD_DEBUG("%s: current %s(%d)\n", + __FUNCTION__, current->comm, current->pid); + + /* strip leading white space */ + while (profilesize && isspace(*profilename)) { + profilename++; + profilesize--; + } + + if (profilesize == 0) + goto out; + + /* + * Copy string to a new buffer so we guarantee it is zero + * terminated + */ + name = kmalloc(profilesize + 1, GFP_KERNEL); + + if (!name) { + error = -ENOMEM; + goto out; + } + + strncpy(name, profilename, profilesize); + name[profilesize] = 0; + + repeat: + if (strcmp(name, SD_UNCONSTRAINED) == 0) + profile = null_profile; + else + profile = sd_profilelist_find(name); + + if (!profile) { + SD_WARN("%s: Unable to switch task %s(%d) to profile '%s'. " + "No such profile.\n", + __FUNCTION__, + p->comm, p->pid, + name); + + error = -EINVAL; + goto out; + } + + + write_lock_irqsave(&sd_lock, flags); + + sd = SD_SUBDOMAIN(p->security); + + /* switch to unconstrained */ + if (profile == null_profile) { + if (__sd_is_confined(sd)) { + SD_WARN("%s: Unconstraining task %s(%d) " + "profile %s active %s\n", + __FUNCTION__, + p->comm, p->pid, + sd->profile->name, + sd->active->name); + + sd_switch_unconfined(sd); + } else { + SD_WARN("%s: task %s(%d) " + "is already unconstrained\n", + __FUNCTION__, p->comm, p->pid); + } + } else { + if (!sd) { + /* this task was created before module was + * loaded, allocate a subdomain + */ + SD_WARN("%s: task %s(%d) has no subdomain\n", + __FUNCTION__, p->comm, p->pid); + + /* unlock so we can safely GFP_KERNEL */ + write_unlock_irqrestore(&sd_lock, flags); + + sd = alloc_subdomain(p); + if (!sd) { + SD_WARN("%s: Unable to allocate subdomain for " + "task %s(%d). Cannot confine task to " + "profile %s\n", + __FUNCTION__, + p->comm, p->pid, + name); + + error = -ENOMEM; + put_sdprofile(profile); + + goto out; + } + + write_lock_irqsave(&sd_lock, flags); + if (!p->security) { + p->security = sd; + } else { /* race */ + free_subdomain(sd); + sd = SD_SUBDOMAIN(p->security); + } + } + + /* ensure the profile hasn't been replaced */ + + if (unlikely(profile->isstale)) { + WARN_ON(profile == null_complain_profile); + + /* drop refcnt obtained from earlier get_sdprofile */ + put_sdprofile(profile); + profile = sd_profilelist_find(name); + + if (!profile) { + /* Race, profile was removed. */ + write_unlock_irqrestore(&sd_lock, flags); + goto repeat; + } + } + + /* we do not do a normal task replace since we are not + * replacing with the same profile. + * If existing process is in a hat, it will be moved + * into the new parent profile, even if this new + * profile has a identical named hat. + */ + + SD_WARN("%s: Switching task %s(%d) " + "profile %s active %s to new profile %s\n", + __FUNCTION__, + p->comm, p->pid, + sd->profile ? sd->profile->name : SD_UNCONSTRAINED, + sd->active ? sd->profile->name : SD_UNCONSTRAINED, + name); + + sd_switch(sd, profile, profile); + + put_sdprofile(profile); /* drop ref we obtained above + * from sd_profilelist_find + */ + + /* Reset magic in case we were in a subhat before + * This is the only case where we zero the magic after + * calling sd_switch + */ + sd->sd_hat_magic = 0; + } + + write_unlock_irqrestore(&sd_lock, flags); + + error = 0; + +out: + kfree(name); + + return error; +} --- linux-2.6.16.29.orig/security/apparmor/shared.h 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.16.29/security/apparmor/shared.h 2006-10-12 21:11:15.000000000 -0700 @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2000, 2001, 2004, 2005 Novell/SUSE + * + * Immunix AppArmor LSM + * + * 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 _SHARED_H +#define _SHARED_H + +/* start of system offsets */ +#define POS_SD_FILE_MIN 0 +#define POS_SD_MAY_EXEC POS_SD_FILE_MIN +#define POS_SD_MAY_WRITE (POS_SD_MAY_EXEC + 1) +#define POS_SD_MAY_READ (POS_SD_MAY_WRITE + 1) +/* not used by Subdomain */ +#define POS_SD_MAY_APPEND (POS_SD_MAY_READ + 1) +/* end of system offsets */ + +#define POS_SD_MAY_LINK (POS_SD_MAY_APPEND + 1) +#define POS_SD_EXEC_INHERIT (POS_SD_MAY_LINK + 1) +#define POS_SD_EXEC_UNCONSTRAINED (POS_SD_EXEC_INHERIT + 1) +#define POS_SD_EXEC_PROFILE (POS_SD_EXEC_UNCONSTRAINED + 1) +#define POS_SD_EXEC_MMAP (POS_SD_EXEC_PROFILE + 1) +#define POS_SD_EXEC_UNSAFE (POS_SD_EXEC_MMAP + 1) +#define POS_SD_FILE_MAX POS_SD_EXEC_UNSAFE + +/* Modeled after MAY_READ, MAY_WRITE, MAY_EXEC def'ns */ +#define SD_MAY_EXEC (0x01 << POS_SD_MAY_EXEC) +#define SD_MAY_WRITE (0x01 << POS_SD_MAY_WRITE) +#define SD_MAY_READ (0x01 << POS_SD_MAY_READ) +#define SD_MAY_LINK (0x01 << POS_SD_MAY_LINK) +#define SD_EXEC_INHERIT (0x01 << POS_SD_EXEC_INHERIT) +#define SD_EXEC_UNCONSTRAINED (0x01 << POS_SD_EXEC_UNCONSTRAINED) +#define SD_EXEC_PROFILE (0x01 << POS_SD_EXEC_PROFILE) +#define SD_EXEC_MMAP (0x01 << POS_SD_EXEC_MMAP) +#define SD_EXEC_UNSAFE (0x01 << POS_SD_EXEC_UNSAFE) + +#define SD_EXEC_MODIFIERS (SD_EXEC_INHERIT | \ + SD_EXEC_UNCONSTRAINED | \ + SD_EXEC_PROFILE) + +#endif /* _SHARED_H */ --- linux-2.6.16.29.orig/security/Kconfig 2006-10-12 21:11:17.000000000 -0700 +++ linux-2.6.16.29/security/Kconfig 2006-10-12 21:11:14.000000000 -0700 @@ -100,6 +100,7 @@ config SECURITY_SECLVL If you are unsure how to answer this question, answer N. source security/selinux/Kconfig +source security/apparmor/Kconfig endmenu --- linux-2.6.16.29.orig/security/Makefile 2006-10-12 21:11:17.000000000 -0700 +++ linux-2.6.16.29/security/Makefile 2006-10-12 21:11:14.000000000 -0700 @@ -4,6 +4,7 @@ obj-$(CONFIG_KEYS) += keys/ subdir-$(CONFIG_SECURITY_SELINUX) += selinux +obj-$(CONFIG_SECURITY_APPARMOR) += commoncap.o apparmor/ # if we don't select a security model, use the default capabilities ifneq ($(CONFIG_SECURITY),y)