apparmor-2.10.95/0000775000175000017500000000000012673232452012557 5ustar stevesteveapparmor-2.10.95/parser/0000775000175000017500000000000012673232616014055 5ustar stevesteveapparmor-2.10.95/parser/subdomain.conf0000664000175000017500000000365211513672602016706 0ustar stevesteve# subdomain.conf is a shared AppArmor configuration file that is sh sourcable. ################## AppArmor init.d configuration ################ # Move this to /etc/sysconfig/apparmor eventually ## Path: System/AppArmor ## Description: Enable the OWLSM extension to AppArmor ## Type: yesno ## Default: no # # Enable OWLSM extension to AppArmor? # OWLSM is an extension to AppArmor that prevents processes from # following symlinks they don't own and creating hardlinks to files they # don't own, in an attempt to prevent /tmp race attacks. However, OWLSM # can break some applications, so is disabled by default. SUBDOMAIN_ENABLE_OWLSM="no" ## Path: System/AppArmor ## Description: Enable the AppArmor event daemon for reporting ## Type: yesno ## Default: no # # Enable the AppArmor event daemon for reporting? APPARMOR_ENABLE_AAEVENTD="no" #SUBDOMAIN_MODULE_PANIC=XXX #This option controls how subdomain behaves when the init script attempts #to load the AppArmor module and fails. There are 4 options #warn - log a failure message. (default behavior) #build - attempt to build the AppArmor module is the module can't be loaded. # If successful # the module will be built for the running kernel and loaded. # If the build fails # a failure message is logged #panic - If the AppArmor module fails to load # a failure message will be logged # and the machine will drop to runlevel 1 (single user) #build-panic - If the AppArmor module fails to load # attempt to build the module # If building the module fails # panic (drop to runlevel 1) #SUBDOMAIN_MODULE_PANIC=warn ################## subdomain_parser configuration ################ #SUBDOMAIN_PATH=XXXX #This option specifies the include path that the subdomain_parser will #use by default. If no entry is specified /etc/subdomain.d is used by #default. #SUBDOMAIN_PATH=/etc/subdomain.d apparmor-2.10.95/parser/parser.h0000664000175000017500000003112512673100443015514 0ustar stevesteve/* * Copyright (c) 1999, 2000, 2001, 2002, 2004, 2005, 2006, 2007 * NOVELL (All rights reserved) * * Copyright (c) 2010 - 2012 * Canonical Ltd. (All rights reserved) * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License published by the Free Software Foundation. * * This program 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. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, contact Novell, Inc. or Canonical * Ltd. */ #ifndef __AA_PARSER_H #define __AA_PARSER_H #include #include #include #include #define _(s) gettext(s) #include #include "immunix.h" #include "libapparmor_re/apparmor_re.h" #include "libapparmor_re/aare_rules.h" #include using namespace std; #include class Profile; class rule_t; #define MODULE_NAME "apparmor" /* Global variable to pass token to lexer. Will be replaced by parameter * when lexer and parser are made reentrant */ extern int parser_token; #define WARN_RULE_NOT_ENFORCED 1 #define WARN_RULE_DOWNGRADED 2 extern dfaflags_t warnflags; typedef enum pattern_t pattern_t; struct prefixes { int audit; int deny; int owner; }; struct cod_pattern { char *regex; // posix regex }; struct value_list { char *value; struct value_list *next; }; struct cond_entry { char *name; int eq; /* where equals was used in specifying list */ struct value_list *vals; struct cond_entry *next; }; struct cond_entry_list { char *name; struct cond_entry *list; }; struct cod_entry { char *name; union { char *link_name; char *onexec; }; char *nt_name; Profile *prof; /* Special profile defined * just for this executable */ int mode; /* mode is 'or' of AA_* bits */ int audit; /* audit flags for mode */ int deny; /* TRUE or FALSE */ int alias_ignore; /* ignore for alias processing */ int subset; pattern_t pattern_type; struct cod_pattern pat; struct cod_entry *next; }; struct aa_rlimits { unsigned int specified; /* limits that are set */ rlim_t limits[RLIMIT_NLIMITS]; }; struct alt_name { char *name; struct alt_name *next; }; struct sd_hat { char *hat_name; unsigned int hat_magic; }; struct var_string { char *prefix; char *var; char *suffix; }; #define COD_READ_CHAR 'r' #define COD_WRITE_CHAR 'w' #define COD_APPEND_CHAR 'a' #define COD_EXEC_CHAR 'x' #define COD_LINK_CHAR 'l' #define COD_LOCK_CHAR 'k' #define COD_MMAP_CHAR 'm' #define COD_INHERIT_CHAR 'i' #define COD_UNCONFINED_CHAR 'U' #define COD_UNSAFE_UNCONFINED_CHAR 'u' #define COD_PROFILE_CHAR 'P' #define COD_UNSAFE_PROFILE_CHAR 'p' #define COD_LOCAL_CHAR 'C' #define COD_UNSAFE_LOCAL_CHAR 'c' #define OPTION_ADD 1 #define OPTION_REMOVE 2 #define OPTION_REPLACE 3 #define OPTION_STDOUT 4 #define OPTION_OFILE 5 #define BOOL int extern int preprocess_only; #define PATH_CHROOT_REL 0x1 #define PATH_NS_REL 0x2 #define PATH_CHROOT_NSATTACH 0x4 #define PATH_CHROOT_NO_ATTACH 0x8 #define PATH_MEDIATE_DELETED 0x10 #define PATH_DELEGATE_DELETED 0x20 #define PATH_ATTACH 0x40 #define PATH_NO_ATTACH 0x80 #ifdef DEBUG #define PDEBUG(fmt, args...) fprintf(stderr, "parser: " fmt, ## args) #else #define PDEBUG(fmt, args...) /* Do nothing */ #endif #define NPDEBUG(fmt, args...) /* Do nothing */ #define PERROR(fmt, args...) fprintf(stderr, fmt, ## args) #ifndef TRUE #define TRUE (1) #endif #ifndef FALSE #define FALSE (0) #endif #define MIN_PORT 0 #define MAX_PORT 65535 #ifndef unused #define unused __attribute__ ((unused)) #endif #define list_for_each(LIST, ENTRY) \ for ((ENTRY) = (LIST); (ENTRY); (ENTRY) = (ENTRY)->next) #define list_for_each_safe(LIST, ENTRY, TMP) \ for ((ENTRY) = (LIST), (TMP) = (LIST) ? (LIST)->next : NULL; (ENTRY); (ENTRY) = (TMP), (TMP) = (TMP) ? (TMP)->next : NULL) #define list_last_entry(LIST, ENTRY) \ for ((ENTRY) = (LIST); (ENTRY) && (ENTRY)->next; (ENTRY) = (ENTRY)->next) #define list_append(LISTA, LISTB) \ do { \ typeof(LISTA) ___tmp; \ list_last_entry((LISTA), ___tmp);\ ___tmp->next = (LISTB); \ } while (0) #define list_len(LIST) \ ({ \ int len = 0; \ typeof(LIST) tmp; \ list_for_each((LIST), tmp) \ len++; \ len; \ }) #define list_find_prev(LIST, ENTRY) \ ({ \ typeof(ENTRY) tmp, prev = NULL; \ list_for_each((LIST), tmp) { \ if (tmp == (ENTRY)) \ break; \ prev = tmp; \ } \ prev; \ }) #define list_remove_at(LIST, PREV, ENTRY) \ if (PREV) \ (PREV)->next = (ENTRY)->next; \ if ((ENTRY) == (LIST)) \ (LIST) = (ENTRY)->next; \ (ENTRY)->next = NULL; \ #define list_remove(LIST, ENTRY) \ do { \ typeof(ENTRY) prev = list_find_prev((LIST), (ENTRY)); \ list_remove_at((LIST), prev, (ENTRY)); \ } while (0) #define DUP_STRING(orig, new, field, fail_target) \ do { \ (new)->field = ((orig)->field) ? strdup((orig)->field) : NULL; \ if (((orig)->field) && !((new)->field)) \ goto fail_target; \ } while (0) #define u8 unsigned char #define u16 uint16_t #define u32 uint32_t #define u64 uint64_t #define cpu_to_le16(x) ((u16)(htole16 ((u16) x))) #define cpu_to_le32(x) ((u32)(htole32 ((u32) x))) #define cpu_to_le64(x) ((u64)(htole64 ((u64) x))) /* The encoding for kernal abi > 5 is * 28-31: reserved * 20-27: policy version * 12-19: policy abi version * 11: force complain flag * 10: reserved * 0-9: kernel abi version */ #define ENCODE_VERSION(C, P, PABI, KABI) \ ({ \ u32 version = (KABI) & 0x3ff; \ if ((KABI) > 5) { \ version |= (C) ? 1 << 11 : 0; \ version |= ((PABI) & 0xff) << 12; \ version |= ((P) & 0xff) << 20; \ } \ version; \ }) /* The parser fills this variable in automatically */ #define PROFILE_NAME_VARIABLE "profile_name" /* from parser_common.c */ extern uint32_t policy_version; extern uint32_t parser_abi_version; extern uint32_t kernel_abi_version; extern int force_complain; extern int perms_create; extern int net_af_max_override; extern int kernel_load; extern int kernel_supports_setload; extern int kernel_supports_network; extern int kernel_supports_policydb; extern int kernel_supports_diff_encode; extern int kernel_supports_mount; extern int kernel_supports_dbus; extern int kernel_supports_signal; extern int kernel_supports_ptrace; extern int kernel_supports_unix; extern int kernel_supports_stacking; extern int conf_verbose; extern int conf_quiet; extern int names_only; extern int option; extern int current_lineno; extern dfaflags_t dfaflags; extern const char *progname; extern char *profilename; extern char *profile_ns; extern char *current_filename; extern FILE *ofile; extern int read_implies_exec; extern void pwarn(const char *fmt, ...) __attribute__((__format__(__printf__, 1, 2))); /* from parser_main (cannot be used in tst builds) */ extern int force_complain; extern void display_version(void); extern int show_cache; extern int skip_cache; extern int skip_read_cache; extern int write_cache; extern int cond_clear_cache; extern int force_clear_cache; extern int create_cache_dir; extern int preprocess_only; extern int skip_mode_force; extern int abort_on_error; extern int skip_bad_cache_rebuild; extern int mru_skip_cache; extern int debug_cache; /* provided by parser_lex.l (cannot be used in tst builds) */ extern FILE *yyin; extern void yyrestart(FILE *fp); extern int yyparse(void); extern void yyerror(const char *msg, ...); extern int yylex(void); /* parser_include.c */ extern const char *basedir; /* parser_regex.c */ #define default_match_pattern "[^\\000]*" #define anyone_match_pattern "[^\\000]+" #define glob_default 0 #define glob_null 1 extern pattern_t convert_aaregex_to_pcre(const char *aare, int anchor, int glob, std::string& pcre, int *first_re_pos); extern int build_list_val_expr(std::string& buffer, struct value_list *list); extern int convert_entry(std::string& buffer, char *entry); extern int clear_and_convert_entry(std::string& buffer, char *entry); extern int process_regex(Profile *prof); extern int post_process_entry(struct cod_entry *entry); extern int process_policydb(Profile *prof); extern int process_policy_ents(Profile *prof); /* parser_variable.c */ int expand_entry_variables(char **name); extern int process_variables(Profile *prof); extern struct var_string *split_out_var(const char *string); extern void free_var_string(struct var_string *var); /* parser_misc.c */ extern void warn_uppercase(void); extern int is_blacklisted(const char *name, const char *path); extern struct value_list *new_value_list(char *value); extern struct value_list *dup_value_list(struct value_list *list); extern void free_value_list(struct value_list *list); extern void print_value_list(struct value_list *list); extern struct cond_entry *new_cond_entry(char *name, int eq, struct value_list *list); extern void move_conditional_value(const char *rulename, char **dst_ptr, struct cond_entry *cond_ent); extern void free_cond_entry(struct cond_entry *ent); extern void free_cond_list(struct cond_entry *ents); extern void print_cond_entry(struct cond_entry *ent); extern char *processid(const char *string, int len); extern char *processquoted(const char *string, int len); extern char *processunquoted(const char *string, int len); extern int get_keyword_token(const char *keyword); extern int name_to_capability(const char *keyword); extern int get_rlimit(const char *name); extern char *process_var(const char *var); extern int parse_mode(const char *mode); extern int parse_X_mode(const char *X, int valid, const char *str_mode, int *mode, int fail); bool label_contains_ns(const char *label); bool parse_label(bool *_stack, char **_ns, char **_name, const char *label, bool yyerr); extern struct cod_entry *new_entry(char *id, int mode, char *link_id); /* returns -1 if value != true or false, otherwise 0 == false, 1 == true */ extern int str_to_boolean(const char* str); extern struct cod_entry *copy_cod_entry(struct cod_entry *cod); extern void free_cod_entries(struct cod_entry *list); extern void __debug_capabilities(uint64_t capset, const char *name); void debug_cod_entries(struct cod_entry *list); #define SECONDS_P_MS (1000LL * 1000LL) long long convert_time_units(long long value, long long base, const char *units); /* parser_symtab.c */ struct set_value { char *val; struct set_value *next; }; extern int add_boolean_var(const char *var, int boolean); extern int get_boolean_var(const char *var); extern int new_set_var(const char *var, const char *value); extern int add_set_value(const char *var, const char *value); extern struct set_value *get_set_var(const char *var); extern char *get_next_set_value(struct set_value **context); extern int delete_set_var(const char *var_name); extern void dump_symtab(void); extern void dump_expanded_symtab(void); void free_symtabs(void); /* parser_alias.c */ extern int new_alias(const char *from, const char *to); extern int replace_profile_aliases(Profile *prof); extern void free_aliases(void); /* parser_merge.c */ extern int profile_merge_rules(Profile *prof); /* parser_interface.c */ extern int load_profile(int option, aa_kernel_interface *kernel_interface, Profile *prof, int cache_fd); extern void sd_serialize_profile(std::ostringstream &buf, Profile *prof, int flatten); extern int sd_load_buffer(int option, char *buffer, int size); extern int cache_fd; /* parser_policy.c */ extern void add_to_list(Profile *profile); extern void add_hat_to_policy(Profile *policy, Profile *hat); extern int add_entry_to_x_table(Profile *prof, char *name); extern void add_entry_to_policy(Profile *policy, struct cod_entry *entry); extern void post_process_file_entries(Profile *prof); extern void post_process_rule_entries(Profile *prof); extern int post_process_policy(int debug_only); extern int process_profile_regex(Profile *prof); extern int process_profile_variables(Profile *prof); extern int process_profile_policydb(Profile *prof); extern int post_merge_rules(void); extern int merge_hat_rules(Profile *prof); extern Profile *merge_policy(Profile *a, Profile *b); extern int load_policy(int option, aa_kernel_interface *kernel_interface, int cache_fd); extern int load_hats(std::ostringstream &buf, Profile *prof); extern int load_flattened_hats(Profile *prof, int option, aa_kernel_interface *kernel_interface, int cache_fd); extern void dump_policy_hats(Profile *prof); extern void dump_policy_names(void); void dump_policy(void); void free_policies(void); #endif /** __AA_PARSER_H */ apparmor-2.10.95/parser/network.c0000664000175000017500000002343612504631026015711 0ustar stevesteve/* * Copyright (c) 2014 * Canonical, Ltd. (All rights reserved) * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License published by the Free Software Foundation. * * This program 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. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, contact Novell, Inc. or Canonical * Ltd. */ #include #include #include #include #include #include #include #include "lib.h" #include "parser.h" #include "profile.h" #include "parser_yacc.h" #include "network.h" int parse_net_mode(const char *str_mode, int *mode, int fail) { return parse_X_mode("net", AA_VALID_NET_PERMS, str_mode, mode, fail); } /* Bleah C++ doesn't have non-trivial designated initializers so we just * have to make sure these are in order. This means we are more brittle * but there isn't much we can do. */ struct sock_type_map { const char *name; int value; }; struct sock_type_map sock_types[] = { { "none", 0 }, { "stream", SOCK_STREAM }, { "dgram", SOCK_DGRAM }, { "raw", SOCK_RAW }, { "rdm", SOCK_RDM }, { "seqpacket", SOCK_SEQPACKET }, { "dccp", SOCK_DCCP }, { "invalid", -1 }, { "invalid", -1 }, { "invalid", -1 }, { "packet", SOCK_PACKET }, { NULL, -1 }, /* * See comment above */ }; int net_find_type_val(const char *type) { int i; for (i = 0; sock_types[i].name; i++) { if (strcmp(sock_types[i].name, type) == 0) return sock_types[i].value; } return -1; } const char *net_find_type_name(int type) { int i; for (i = 0; sock_types[i].name; i++) { if (sock_types[i].value == type) return sock_types[i].name; } return NULL; } /* FIXME: currently just treating as a bit mask this will have to change * set up a table of mappings, there can be several mappings for a * given match. * currently the mapping does not set the protocol for stream/dgram to * anything other than 0. * network inet tcp -> network inet stream 0 instead of * network inet raw tcp. * some entries are just provided for completeness at this time */ /* values stolen from /etc/protocols - needs to change */ #define RAW_TCP 6 #define RAW_UDP 17 #define RAW_ICMP 1 #define RAW_ICMPv6 58 /* used by af_name.h to auto generate table entries for "name", AF_NAME * pair */ #define AA_GEN_NET_ENT(name, AF) \ {name, AF, "stream", SOCK_STREAM, "", 0xffffff}, \ {name, AF, "dgram", SOCK_DGRAM, "", 0xffffff}, \ {name, AF, "seqpacket", SOCK_SEQPACKET, "", 0xffffff}, \ {name, AF, "rdm", SOCK_RDM, "", 0xffffff}, \ {name, AF, "raw", SOCK_RAW, "", 0xffffff}, \ {name, AF, "packet", SOCK_PACKET, "", 0xffffff}, /*FIXME: missing {name, AF, "dccp", SOCK_DCCP, "", 0xfffffff}, */ static struct network_tuple network_mappings[] = { /* basic types */ #include "af_names.h" /* FIXME: af_names.h is missing AF_LLC, AF_TIPC */ /* mapped types */ {"inet", AF_INET, "raw", SOCK_RAW, "tcp", 1 << RAW_TCP}, {"inet", AF_INET, "raw", SOCK_RAW, "udp", 1 << RAW_UDP}, {"inet", AF_INET, "raw", SOCK_RAW, "icmp", 1 << RAW_ICMP}, {"inet", AF_INET, "tcp", SOCK_STREAM, "", 0xffffffff}, /* should we give raw tcp too? */ {"inet", AF_INET, "udp", SOCK_DGRAM, "", 0xffffffff}, /* should these be open masks? */ {"inet", AF_INET, "icmp", SOCK_RAW, "", 1 << RAW_ICMP}, {"inet6", AF_INET6, "tcp", SOCK_STREAM, "", 0xffffffff}, {"inet6", AF_INET6, "udp", SOCK_DGRAM, "", 0xffffffff}, /* what do we do with icmp on inet6? {"inet6", AF_INET, "icmp", SOCK_RAW, 0}, {"inet6", AF_INET, "icmpv6", SOCK_RAW, 0}, */ /* terminate */ {NULL, 0, NULL, 0, NULL, 0} }; /* The apparmor kernel patches up until 2.6.38 didn't handle networking * tables with sizes > AF_MAX correctly. This could happen when the * parser was built against newer kernel headers and then used to load * policy on an older kernel. This could happen during upgrades or * in multi-kernel boot systems. * * Try to detect the running kernel version and use that to determine * AF_MAX */ #define PROC_VERSION "/proc/sys/kernel/osrelease" static size_t kernel_af_max(void) { char buffer[32]; int major; autoclose int fd = -1; int res; if (!net_af_max_override) { return 0; } /* the override parameter is specifying the max value */ if (net_af_max_override > 0) return net_af_max_override; fd = open(PROC_VERSION, O_RDONLY); if (fd == -1) /* fall back to default provided during build */ return 0; res = read(fd, &buffer, sizeof(buffer) - 1); if (res <= 0) return 0; buffer[res] = '\0'; res = sscanf(buffer, "2.6.%d", &major); if (res != 1) return 0; switch(major) { case 24: case 25: case 26: return 34; case 27: return 35; case 28: case 29: case 30: return 36; case 31: case 32: case 33: case 34: case 35: return 37; case 36: case 37: return 38; /* kernels .38 and later should handle this correctly so no * static mapping needed */ default: return 0; } } /* Yuck. We grab AF_* values to define above from linux/socket.h because * they are more accurate than sys/socket.h for what the kernel actually * supports. However, we can't just include linux/socket.h directly, * because the AF_* definitions are protected with an ifdef KERNEL * wrapper, but we don't want to define that because that can cause * other redefinitions from glibc. However, because the kernel may have * more definitions than glibc, we need make sure AF_MAX reflects this, * hence the wrapping function. */ size_t get_af_max() { size_t af_max; /* HACK: declare that version without "create" had a static AF_MAX */ if (!perms_create && !net_af_max_override) net_af_max_override = -1; #if AA_AF_MAX > AF_MAX af_max = AA_AF_MAX; #else af_max = AF_MAX; #endif /* HACK: some kernels didn't handle network tables from parsers * compiled against newer kernel headers as they are larger than * the running kernel expected. If net_override is defined check * to see if there is a static max specified for that kernel */ if (net_af_max_override) { size_t max = kernel_af_max(); if (max && max < af_max) return max; } return af_max; } struct aa_network_entry *new_network_ent(unsigned int family, unsigned int type, unsigned int protocol) { struct aa_network_entry *new_entry; new_entry = (struct aa_network_entry *) calloc(1, sizeof(struct aa_network_entry)); if (new_entry) { new_entry->family = family; new_entry->type = type; new_entry->protocol = protocol; new_entry->next = NULL; } return new_entry; } const struct network_tuple *net_find_mapping(const struct network_tuple *map, const char *family, const char *type, const char *protocol) { if (!map) map = network_mappings; else /* assumes it points to last entry returned */ map++; for (; map->family_name; map++) { if (family) { PDEBUG("Checking family %s\n", map->family_name); if (strcmp(family, map->family_name) != 0) continue; PDEBUG("Found family %s\n", family); } if (type) { PDEBUG("Checking type %s\n", map->type_name); if (strcmp(type, map->type_name) != 0) continue; PDEBUG("Found type %s\n", type); } if (protocol) { /* allows the proto to be the "type", ie. tcp implies * stream */ if (!type) { PDEBUG("Checking protocol type %s\n", map->type_name); if (strcmp(protocol, map->type_name) == 0) goto match; } PDEBUG("Checking type %s protocol %s\n", map->type_name, map->protocol_name); if (strcmp(protocol, map->protocol_name) != 0) continue; /* fixme should we allow specifying protocol by # * without needing the protocol mapping? */ } /* if we get this far we have a match */ match: return map; } return NULL; } struct aa_network_entry *network_entry(const char *family, const char *type, const char *protocol) { struct aa_network_entry *new_entry, *entry = NULL; const struct network_tuple *mapping = NULL; while ((mapping = net_find_mapping(mapping, family, type, protocol))) { new_entry = new_network_ent(mapping->family, mapping->type, mapping->protocol); if (!new_entry) yyerror(_("Memory allocation error.")); new_entry->next = entry; entry = new_entry; } return entry; }; #define ALL_TYPES 0x43e const char *net_find_af_name(unsigned int af) { size_t i; if (af < 0 || af > get_af_max()) return NULL; for (i = 0; i < sizeof(network_mappings) / sizeof(*network_mappings); i++) { if (network_mappings[i].family == af) return network_mappings[i].family_name; } return NULL; } void __debug_network(unsigned int *array, const char *name) { unsigned int count = sizeof(sock_types)/sizeof(sock_types[0]); unsigned int mask = ~((1 << count) -1); unsigned int i, j; int none = 1; size_t af_max = get_af_max(); for (i = AF_UNSPEC; i < af_max; i++) if (array[i]) { none = 0; break; } if (none) return; printf("%s: ", name); /* This can only be set by an unqualified network rule */ if (array[AF_UNSPEC]) { printf("\n"); return; } for (i = 0; i < af_max; i++) { if (array[i]) { const char *fam = net_find_af_name(i); if (fam) printf("%s ", fam); else printf("#%u ", i); /* All types/protocols */ if (array[i] == 0xffffffff || array[i] == ALL_TYPES) continue; printf("{ "); for (j = 0; j < count; j++) { const char *type; if (array[i] & (1 << j)) { type = sock_types[j].name; if (type) printf("%s ", type); else printf("#%u ", j); } } if (array[i] & mask) printf("#%x ", array[i] & mask); printf("} "); } } printf("\n"); } apparmor-2.10.95/parser/parser_alias.c0000664000175000017500000001156512413327176016674 0ustar stevesteve/* * Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 * NOVELL (All rights reserved) * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License published by the Free Software Foundation. * * This program 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. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, contact Novell, Inc. */ #include #include #include #include #include #include "immunix.h" #include "parser.h" #include "profile.h" struct alias_rule { char *from; char *to; }; static void *alias_table; static int compare_alias(const void *a, const void *b) { char *a_name = ((struct alias_rule *) a)->from; char *b_name = ((struct alias_rule *) b)->from; int res = strcmp(a_name, b_name); if (res == 0) { a_name = ((struct alias_rule *) a)->to; b_name = ((struct alias_rule *) b)->to; res = strcmp(a_name, b_name); } return res; } int new_alias(const char *from, const char *to) { struct alias_rule *alias, **result; alias = (struct alias_rule *) calloc(1, sizeof(struct alias_rule)); if (!alias) { PERROR("Failed to allocate memory: %s\n", strerror(errno)); goto fail; } alias->from = strdup(from); if (!alias->from) { PERROR("Failed to allocate memory: %s\n", strerror(errno)); goto fail; } alias->to = strdup(to); if (!alias->to) { PERROR("Failed to allocate memory: %s\n", strerror(errno)); goto fail; } result = (struct alias_rule **) tsearch(alias, &alias_table, (comparison_fn_t) &compare_alias); if (!result) { PERROR("Failed to allocate memory: %s\n", strerror(errno)); goto fail; } if (*result != alias) { /* already existing alias */ PERROR("'%s' is already defined\n", from); goto fail; } return 1; fail: if (alias) { if (alias->from) free(alias->from); if (alias->to) free(alias->to); free(alias); } /* just drop duplicate aliases don't actually fail */ return 1; } static char *do_alias(struct alias_rule *alias, const char *target) { int len = strlen(target) - strlen(alias->from) + strlen(alias->to); char *n = (char *) malloc(len + 1); if (!n) { PERROR("Failed to allocate memory: %s\n", strerror(errno)); return NULL; } sprintf(n, "%s%s", alias->to, target + strlen(alias->from)); /*fprintf(stderr, "replaced alias: from: %s, to: %s, name: %s\n %s\n", alias->from, alias->to, target, new);*/ return n; } static Profile *target_prof; static struct cod_entry *target_list; static void process_entries(const void *nodep, VISIT value, int level unused) { struct alias_rule **t = (struct alias_rule **) nodep; struct cod_entry *entry, *dup = NULL; int len; if (value == preorder || value == endorder) return; len = strlen((*t)->from); list_for_each(target_list, entry) { if ((entry->mode & AA_SHARED_PERMS) || entry->alias_ignore) continue; if (entry->name && strncmp((*t)->from, entry->name, len) == 0) { char *n = do_alias(*t, entry->name); if (!n) return; dup = copy_cod_entry(entry); free(dup->name); dup->name = n; } if (entry->link_name && strncmp((*t)->from, entry->link_name, len) == 0) { char *n = do_alias(*t, entry->link_name); if (!n) return; if (!dup) dup = copy_cod_entry(entry); free(dup->link_name); dup->link_name = n; } if (dup) { dup->alias_ignore = 1; /* adds to the front of the list, list iteratition * will skip it */ entry->next = dup; dup = NULL; } } } static void process_name(const void *nodep, VISIT value, int level unused) { struct alias_rule **t = (struct alias_rule **) nodep; Profile *prof = target_prof; char *name; int len; if (value == preorder || value == endorder) return; len = strlen((*t)->from); if (prof->attachment) name = prof->attachment; else name = prof->name; if (name && strncmp((*t)->from, name, len) == 0) { struct alt_name *alt; char *n = do_alias(*t, name); if (!n) return; /* aliases create alternate names */ alt = (struct alt_name *) calloc(1, sizeof(struct alt_name)); if (!alt) { free(n); return; } alt->name = n; alt->next = prof->altnames; prof->altnames = alt; } } int replace_profile_aliases(Profile *prof) { target_prof = prof; twalk(alias_table, process_name); if (prof->entries) { target_list = prof->entries; target_prof = prof; twalk(alias_table, process_entries); } return 0; } static void free_alias(void *nodep) { struct alias_rule *t = (struct alias_rule *)nodep; free(t->from); free(t->to); free(t); } void free_aliases(void) { if (alias_table) tdestroy(alias_table, &free_alias); alias_table = NULL; } apparmor-2.10.95/parser/parser_variable.c0000664000175000017500000003756312651500036017366 0ustar stevesteve/* * Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 * NOVELL (All rights reserved) * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License published by the Free Software Foundation. * * This program 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. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, contact Novell, Inc. */ #include #include #include #include #include #include #include #include /* #define DEBUG */ #include "parser.h" #include "profile.h" #include "mount.h" #include "dbus.h" static inline const char *get_var_end(const char *var) { const char *eptr = var; while (*eptr) { if (*eptr == '}') return eptr; /* first character must be alpha */ if (eptr == var) { if (!isalpha(*eptr)) return NULL; /* invalid char */ } else { if (!(*eptr == '_' || isalnum(*eptr))) return NULL; /* invalid char */ } eptr++; } return NULL; /* no terminating '}' */ } static struct var_string *split_string(const char *string, const char *var_begin, const char *var_end) { struct var_string *n = (struct var_string *) calloc(1, sizeof(struct var_string)); unsigned int offset = strlen("@{"); if (!n) { PERROR("Memory allocation error\n"); return NULL; } if (var_begin != string) { n->prefix = strndup(string, var_begin - string); } n->var = strndup(var_begin + offset, var_end - (var_begin + offset)); if (strlen(var_end + 1) != 0) { n->suffix = strdup(var_end + 1); } return n; } struct var_string *split_out_var(const char *string) { struct var_string *n = NULL; const char *sptr; BOOL bEscape = 0; /* flag to indicate escape */ if (!string) /* shouldn't happen */ return NULL; sptr = string; while (!n && *sptr) { switch (*sptr) { case '\\': if (bEscape) { bEscape = FALSE; } else { bEscape = TRUE; } break; case '@': if (bEscape) { bEscape = FALSE; } else if (*(sptr + 1) == '{') { const char *eptr = get_var_end(sptr + 2); if (!eptr) break; /* no variable end found */ if (eptr == sptr + 2) { /* XXX - better diagnostics here, please */ PERROR("Empty variable name found!\n"); exit(1); } n = split_string(string, sptr, eptr); } break; default: if (bEscape) bEscape = FALSE; } sptr++; } return n; } void free_var_string(struct var_string *var) { if (!var) return; if (var->prefix) free(var->prefix); if (var->var) free(var->var); if (var->suffix) free(var->suffix); free(var); } static void trim_trailing_slash(std::string& str) { std::size_t found = str.find_last_not_of('/'); if (found != std::string::npos) str.erase(found + 1); else str.clear(); // str is all '/' } static void write_replacement(const char separator, const char* value, std::string& replacement, bool filter_leading_slash, bool filter_trailing_slash) { const char *p = value; replacement.append(1, separator); if (filter_leading_slash) while (*p == '/') p++; replacement.append(p); if (filter_trailing_slash) trim_trailing_slash(replacement); } static int expand_by_alternations(struct set_value **valuelist, struct var_string *split_var, char **name) { char *value, *first_value; std::string replacement; bool filter_leading_slash = false; bool filter_trailing_slash = false; first_value = get_next_set_value(valuelist); if (!first_value) { PERROR("ASSERT: set variable (%s) should always have at least one value assigned to it\n", split_var->var); exit(1); } free(*name); value = get_next_set_value(valuelist); if (!value) { /* only one entry for the variable, so just sub it in */ if (asprintf(name, "%s%s%s", split_var->prefix ? split_var->prefix : "", first_value, split_var->suffix ? split_var->suffix : "") == -1) return -1; return 0; } if (split_var->prefix && split_var->prefix[strlen(split_var->prefix) - 1] == '/') filter_leading_slash = true; if (split_var->suffix && *split_var->suffix == '/') filter_trailing_slash = true; write_replacement('{', first_value, replacement, filter_leading_slash, filter_trailing_slash); write_replacement(',', value, replacement, filter_leading_slash, filter_trailing_slash); while ((value = get_next_set_value(valuelist))) { write_replacement(',', value, replacement, filter_leading_slash, filter_trailing_slash); } if (asprintf(name, "%s%s}%s", split_var->prefix ? split_var->prefix : "", replacement.c_str(), split_var->suffix ? split_var->suffix : "") == -1) { return -1; } return 0; } /* doesn't handle variables in options atm */ int expand_entry_variables(char **name) { struct set_value *valuelist; struct var_string *split_var; int ret; assert(name); if (!*name) /* can happen when entry is optional */ return 0; while ((split_var = split_out_var(*name))) { valuelist = get_set_var(split_var->var); if (!valuelist) { int boolean = get_boolean_var(split_var->var); if (boolean == -1) PERROR("Found reference to variable %s, but is never declared\n", split_var->var); else PERROR("Found reference to set variable %s, but declared boolean\n", split_var->var); exit(1); } ret = expand_by_alternations(&valuelist, split_var, name); free_var_string(split_var); if (ret != 0) return -1; } return 0; } static int process_variables_in_entries(struct cod_entry *entry_list) { int error = 0; struct cod_entry *entry; list_for_each(entry_list, entry) { error = expand_entry_variables(&entry->name); if (error) return error; if (entry->link_name) { error = expand_entry_variables(&entry->link_name); if (error) return error; } } return 0; } static int process_variables_in_rules(Profile &prof) { for (RuleList::iterator i = prof.rule_ents.begin(); i != prof.rule_ents.end(); i++) { int error = (*i)->expand_variables(); if (error) return error; } return 0; } static int process_variables_in_name(Profile &prof) { /* this needs to be done before alias expansion, ie. altnames are * setup */ int error = expand_entry_variables(&prof.name); if (!error && prof.attachment) error = expand_entry_variables(&prof.attachment); return error; } static std::string escape_re(std::string str) { for (size_t i = 0; i < str.length(); i++) { if (str[i] == '\\') { /* skip \ and follow char. Skipping \ and first * char is enough for multichar escape sequence */ i++; continue; } if (strchr("{}[]*?", str[i]) != NULL) { str.insert(i++, "\\"); } } return str; } int process_profile_variables(Profile *prof) { int error = 0, rc; /* needs to be before PROFILE_NAME_VARIABLE so that variable will * have the correct name */ error = process_variables_in_name(*prof); if (!error) { /* escape profile name elements that could be interpreted * as regular expressions. */ error = new_set_var(PROFILE_NAME_VARIABLE, escape_re(prof->get_name(false)).c_str()); } if (!error) error = process_variables_in_entries(prof->entries); if (!error) error = process_variables_in_rules(*prof); rc = delete_set_var(PROFILE_NAME_VARIABLE); if (!error) error = rc; return error; } #ifdef UNIT_TEST #include "unit_test.h" int test_get_var_end(void) { int rc = 0; const char *retchar; const char *testchar; testchar = "TRUE}"; retchar = get_var_end(testchar); MY_TEST(retchar - testchar == strlen("TRUE"), "get var end for TRUE}"); testchar = "some_var}some other text"; retchar = get_var_end(testchar); MY_TEST(retchar - testchar == strlen("some_var"), "get var end for some_var}"); testchar = "some_var}some other} text"; retchar = get_var_end(testchar); MY_TEST(retchar - testchar == strlen("some_var"), "get var end for some_var} 2"); testchar = "FALSE"; retchar = get_var_end(testchar); MY_TEST(retchar == NULL, "get var end for FALSE"); testchar = "pah,pah}pah "; retchar = get_var_end(testchar); MY_TEST(retchar == NULL, "get var end for pah,pah}"); return rc; } int test_split_string(void) { int rc = 0; char *tst_string, *var_start, *var_end; struct var_string *ret_struct; const char *prefix = "abcdefg"; const char *var = "boogie"; const char *suffix = "suffixication"; asprintf(&tst_string, "%s@{%s}%s", prefix, var, suffix); var_start = tst_string + strlen(prefix); var_end = var_start + strlen(var) + strlen("@\{"); ret_struct = split_string(tst_string, var_start, var_end); MY_TEST(strcmp(ret_struct->prefix, prefix) == 0, "split string 1 prefix"); MY_TEST(strcmp(ret_struct->var, var) == 0, "split string 1 var"); MY_TEST(strcmp(ret_struct->suffix, suffix) == 0, "split string 1 suffix"); free_var_string(ret_struct); free(tst_string); asprintf(&tst_string, "@{%s}%s", var, suffix); var_start = tst_string; var_end = var_start + strlen(var) + strlen("@\{"); ret_struct = split_string(tst_string, var_start, var_end); MY_TEST(ret_struct->prefix == NULL, "split string 2 prefix"); MY_TEST(strcmp(ret_struct->var, var) == 0, "split string 2 var"); MY_TEST(strcmp(ret_struct->suffix, suffix) == 0, "split string 2 suffix"); free_var_string(ret_struct); free(tst_string); asprintf(&tst_string, "%s@{%s}", prefix, var); var_start = tst_string + strlen(prefix); var_end = var_start + strlen(var) + strlen("@\{"); ret_struct = split_string(tst_string, var_start, var_end); MY_TEST(strcmp(ret_struct->prefix, prefix) == 0, "split string 3 prefix"); MY_TEST(strcmp(ret_struct->var, var) == 0, "split string 3 var"); MY_TEST(ret_struct->suffix == NULL, "split string 3 suffix"); free_var_string(ret_struct); free(tst_string); asprintf(&tst_string, "@{%s}", var); var_start = tst_string; var_end = var_start + strlen(var) + strlen("@\{"); ret_struct = split_string(tst_string, var_start, var_end); MY_TEST(ret_struct->prefix == NULL, "split string 4 prefix"); MY_TEST(strcmp(ret_struct->var, var) == 0, "split string 4 var"); MY_TEST(ret_struct->suffix == NULL, "split string 4 suffix"); free_var_string(ret_struct); free(tst_string); return rc; } int test_split_out_var(void) { int rc = 0; char *tst_string, *tmp; struct var_string *ret_struct; const char *prefix = "abcdefg"; const char *var = "boogie"; const char *var2 = "V4rW1thNum5"; const char *var3 = "boogie_board"; const char *suffix = "suffixication"; /* simple case */ asprintf(&tst_string, "%s@{%s}%s", prefix, var, suffix); ret_struct = split_out_var(tst_string); MY_TEST(strcmp(ret_struct->prefix, prefix) == 0, "split out var 1 prefix"); MY_TEST(strcmp(ret_struct->var, var) == 0, "split out var 1 var"); MY_TEST(strcmp(ret_struct->suffix, suffix) == 0, "split out var 1 suffix"); free_var_string(ret_struct); free(tst_string); /* no prefix */ asprintf(&tst_string, "@{%s}%s", var, suffix); ret_struct = split_out_var(tst_string); MY_TEST(ret_struct->prefix == NULL, "split out var 2 prefix"); MY_TEST(strcmp(ret_struct->var, var) == 0, "split out var 2 var"); MY_TEST(strcmp(ret_struct->suffix, suffix) == 0, "split out var 2 suffix"); free_var_string(ret_struct); free(tst_string); /* no suffix */ asprintf(&tst_string, "%s@{%s}", prefix, var); ret_struct = split_out_var(tst_string); MY_TEST(strcmp(ret_struct->prefix, prefix) == 0, "split out var 3 prefix"); MY_TEST(strcmp(ret_struct->var, var) == 0, "split out var 3 var"); MY_TEST(ret_struct->suffix == NULL, "split out var 3 suffix"); free_var_string(ret_struct); free(tst_string); /* var only */ asprintf(&tst_string, "@{%s}", var); ret_struct = split_out_var(tst_string); MY_TEST(ret_struct->prefix == NULL, "split out var 4 prefix"); MY_TEST(strcmp(ret_struct->var, var) == 0, "split out var 4 var"); MY_TEST(ret_struct->suffix == NULL, "split out var 4 suffix"); free_var_string(ret_struct); free(tst_string); /* quoted var, shouldn't split */ asprintf(&tst_string, "%s\\@{%s}%s", prefix, var, suffix); ret_struct = split_out_var(tst_string); MY_TEST(ret_struct == NULL, "split out var - quoted @"); free_var_string(ret_struct); free(tst_string); /* quoted \, split should succeed */ asprintf(&tst_string, "%s\\\\@{%s}%s", prefix, var, suffix); ret_struct = split_out_var(tst_string); tmp = strndup(tst_string, strlen(prefix) + 2); MY_TEST(strcmp(ret_struct->prefix, tmp) == 0, "split out var 5 prefix"); MY_TEST(strcmp(ret_struct->var, var) == 0, "split out var 5 var"); MY_TEST(strcmp(ret_struct->suffix, suffix) == 0, "split out var 5 suffix"); free_var_string(ret_struct); free(tst_string); free(tmp); /* un terminated var, should fail */ asprintf(&tst_string, "%s@{%s%s", prefix, var, suffix); ret_struct = split_out_var(tst_string); MY_TEST(ret_struct == NULL, "split out var - un-terminated var"); free_var_string(ret_struct); free(tst_string); /* invalid char in var, should fail */ asprintf(&tst_string, "%s@{%s^%s}%s", prefix, var, var, suffix); ret_struct = split_out_var(tst_string); MY_TEST(ret_struct == NULL, "split out var - invalid char in var"); free_var_string(ret_struct); free(tst_string); /* two vars, should only strip out first */ asprintf(&tmp, "@{%s}%s}", suffix, suffix); asprintf(&tst_string, "%s@{%s}%s", prefix, var, tmp); ret_struct = split_out_var(tst_string); MY_TEST(strcmp(ret_struct->prefix, prefix) == 0, "split out var 6 prefix"); MY_TEST(strcmp(ret_struct->var, var) == 0, "split out var 6 var"); MY_TEST(strcmp(ret_struct->suffix, tmp) == 0, "split out var 6 suffix"); free_var_string(ret_struct); free(tst_string); free(tmp); /* quoted @ followed by var, split should succeed */ asprintf(&tst_string, "%s\\@@{%s}%s", prefix, var, suffix); ret_struct = split_out_var(tst_string); tmp = strndup(tst_string, strlen(prefix) + 2); MY_TEST(strcmp(ret_struct->prefix, tmp) == 0, "split out var 7 prefix"); MY_TEST(strcmp(ret_struct->var, var) == 0, "split out var 7 var"); MY_TEST(strcmp(ret_struct->suffix, suffix) == 0, "split out var 7 suffix"); free_var_string(ret_struct); free(tst_string); free(tmp); /* numeric char in var, should succeed */ asprintf(&tst_string, "%s@{%s}%s", prefix, var2, suffix); ret_struct = split_out_var(tst_string); MY_TEST(ret_struct && strcmp(ret_struct->prefix, prefix) == 0, "split out numeric var prefix"); MY_TEST(ret_struct && strcmp(ret_struct->var, var2) == 0, "split numeric var var"); MY_TEST(ret_struct && strcmp(ret_struct->suffix, suffix) == 0, "split out numeric var suffix"); free_var_string(ret_struct); free(tst_string); /* numeric first char in var, should fail */ asprintf(&tst_string, "%s@{6%s}%s", prefix, var2, suffix); ret_struct = split_out_var(tst_string); MY_TEST(ret_struct == NULL, "split out var - numeric first char in var"); free_var_string(ret_struct); free(tst_string); /* underscore char in var, should succeed */ asprintf(&tst_string, "%s@{%s}%s", prefix, var3, suffix); ret_struct = split_out_var(tst_string); MY_TEST(ret_struct && strcmp(ret_struct->prefix, prefix) == 0, "split out underscore var prefix"); MY_TEST(ret_struct && strcmp(ret_struct->var, var3) == 0, "split out underscore var"); MY_TEST(ret_struct && strcmp(ret_struct->suffix, suffix) == 0, "split out underscore var suffix"); free_var_string(ret_struct); free(tst_string); /* underscore first char in var, should fail */ asprintf(&tst_string, "%s@{_%s%s}%s", prefix, var2, var3, suffix); ret_struct = split_out_var(tst_string); MY_TEST(ret_struct == NULL, "split out var - underscore first char in var"); free_var_string(ret_struct); free(tst_string); return rc; } int main(void) { int rc = 0; int retval; retval = test_get_var_end(); if (retval != 0) rc = retval; retval = test_split_string(); if (retval != 0) rc = retval; retval = test_split_out_var(); if (retval != 0) rc = retval; return rc; } #endif /* UNIT_TEST */ apparmor-2.10.95/parser/techdoc.tex0000664000175000017500000014301711752320402016202 0ustar stevesteve\documentclass[a4paper]{article} %\usepackage{graphicx} %\usepackage{subfigure} \usepackage[utf8]{inputenc} \usepackage{url} %\usepackage{times} \usepackage[pdftex, pdfauthor={Andreas Gruenbacher and Seth Arnold}, pdftitle={AppArmor Technical Documentation},% \ifx\fixedpdfdate\@empty\else pdfcreationdate={\fixedpdfdate}, pdfmoddate={\fixedpdfdate}, \fi pdfsubject={AppArmor}, pdfkeywords={AppArmor} ]{hyperref} \hyphenation{App-Armor} \hyphenation{name-space} \renewcommand{\H}{\hspace{0pt}} \title{AppArmor Technical Documentation} \author{Andreas Gruenbacher and Seth Arnold \\ \url{{agruen,seth.arnold}@suse.de} \\ SUSE Labs / Novell} % don't include the (build!) date \date{} \begin{document} \maketitle \tableofcontents \newpage %\begin{abstract} %\end{abstract} \section{Introduction} In this paper we describe AppArmor from a technical point of view, introduce its concepts, and explain the design decisions taken. This text is intended for people interested in understanding why AppArmor works the way it does. You may be looking for less detailed, low-level, or kernel centric documentation; in that case, please refer to the AppArmor documentation web site~\cite{apparmor}. Sections~\ref{sec:overview} and ~\ref{sec:model} discuss the AppArmor security model, while Section~\ref{sec:walk-through} shows how to use it from a low-level point of view. Please be aware that lots of details are discussed here which the higher-level tools hide from the average user. \section{Overview} \label{sec:overview} AppArmor protects systems from insecure or untrusted processes by running them in confinement, still allowing them to share files with other parts of the system, exercising privilege, and communicating with other processes, but with some restrictions. These restrictions are mandatory; they are not bound to identity, group membership, or object ownership. In particular, the restrictions also apply to processes running with superuser privileges. AppArmor achieves this by plugging into the Linux Security Module (LSM) framework. The protections provided are in addition to the kernel's regular access control mechanisms. The AppArmor kernel module and accompanying user-space tools are available under the GPL license. (The exception is the libapparmor library, available under the LGPL license, which allows change\_hat(2) to be used by non-GPL binaries.) At the moment, AppArmor knows about two types of resources: files, and POSIX.1e (draft) capabilities. By controlling access to these resources, AppArmor can effectively prevent confined processes from accessing files in unwanted ways, from executing binaries which they are not meant to execute, and from exercising privileges such as acting on behalf of another user (which are traditionally restricted to the superuser). One use case for this kind of protection is a network daemon: even if the daemon is broken into, the additional restrictions imposed by AppArmor will prevent the attacker from attaining additional privileges beyond what the daemon is normally allowed to do. Because AppArmor controls which files a process can access in which ways down to the individual file level, the potential damage is much limited. There is work going on for teaching AppArmor about additional resources like ulimits, and interprocess and network communication, but at this time, these resource types are not covered. This is less severe than it might initially seem: in order to attack another process from a broken-into process like a network daemon, that other process has to actively listen. The set of actively listening processes is relatively small, and this sort of interprocess communication is a natural security boundary, so listening processes should be validating all their input already. For protection against bugs in the input validation of those processes, they should also be confined by AppArmor though, thus further limiting the potential damage. AppArmor protection is selective: it only confines processes for which policies (referred to as profiles) have been defined. All other processes will continue to run unrestricted by AppArmor. To confine a process, all it takes is to write a profile for it, take an existing profile, or automatically generate a profile: for the latter, the process can be run in \textit{learning} or \textit{complain} mode in which AppArmor allows all accesses, and logs all accesses that are not allowed by the current profile already. This log can then be used to automatically generate a suitable new profile, or refine an existing one. The application does not need to be modified. An example profile together with a complete low-level walk-through of AppArmor can be found in Section~\ref{sec:walk-through}. The apparmor.d(5) manual page contains further details. AppArmor is not based on labeling or label-based access and transition rules, so it does not stick a label on each each file in the file system (or more generally, on each object). It identifies files by name rather than by label, so if a process is granted read access to /etc/shadow and the system administrator renames /etc/shadow to /etc/shadow.old and replaces it with a copy (that may have an additional user in it, for example), the process will have access to the new /etc/shadow, and not to /etc/shadow.old. \section{The AppArmor Security Model} \label{sec:model} When a file is accessed by name with open(2), mkdir(2), etc., the kernel looks up the location of the object associated with the specified pathname in the file system hierarchy. The lookup is relative to the root directory for pathnames starting with a slash, and to the current working directory otherwise. Different processes can have have different working directories as well as different root directories. See path\_resolution(2) for a detailed discussion of how pathname resolution works. Either way, the result of the lookup is a pair of (dentry, vfsmount) kernel-internal objects that uniquely identify the location of the file in the file system hierarchy. The dentry points to the object if the object already exists, and is a placeholder for the object to be created otherwise. AppArmor uses the (dentry, vfsmount) pair to compute the pathname of the file within a process's filesystem namespace. The resulting pathname contains no relative pathname components (``.'' or ``..''), or symlinks. AppArmor checks if the current profile contains rules that match this pathname, and if those rules allow the requested access. Accesses that are not explicitly allowed are denied. \subsection{Symbolic Links} When looking up the (dentry, vfsmount) pair of a file, the kernel resolves symlinks where appropriate (and fails the lookup where symlink resolution is inappropriate). The pathname that AppArmor computes from a (dentry, vfsmount) pair never contains symlinks. This also means that if symlinks are used instead of directories for paths like /tmp, profiles need to be adjusted accordingly. A future version of AppArmor may have built-in support for this kind of pathname rewriting. \subsection{Namespaces} Linux allows different processes to live in separate namespaces, each of which forms an independent file system hierarchy. A recent paper by Al Viro and Ram Pai~\cite{ols06-pai} discusses all the intricate things possible with namespaces in recent 2.6 kernels. From the point of view of a process, an absolute path is a path that goes all the way up to the root directory of that process. This is ambiguous if processes have different root directories. Therefore, instead of paths relative to process root directories, AppArmor uses paths relative to the namespace root. Pathnames are meaningful only within a namespace. Each namespace has a root where all the files, directories, and mount points are hanging off from. The privilege of creating new namespaces is bound to the CAP\_{\H}SYS\_{\H}ADMIN capability, which grants a multitude of other things that would allow a process to break out of AppArmor confinement, so confined processes are not supposed to have this privilege, and processes with this capability need to be considered trusted. In this setup, privileged processes can still create separate namespaces and start processes in those namespaces; processes confinement will be relative to whatever namespace a process ends up in. It is unclear at this point how AppArmor should support separate namespaces --- either by computing all pathnames relative to one particular namespace considered global (assuming that such a globally meaningful namespace will exist in all setups in which AppArmor is relevant), or by allowing different sets of profiles to be associated with different namespaces. \subsection{Disconnected Files and Pseudo File Systems} In some situations, a process can end up with a file descriptor or working directory that was looked up by name at some point, but is not connected to the process's namespace anymore (and hasn't been deleted, either). This can happen when file descriptors are passed between processes that do not share the same namespace, or when a file system has been lazily unmounted (see the MNT\_DETACH flag of umount2(2)). Such files may still be visible to other processes, and they may become reconnected. AppArmor cannot compute the pathnames of such files. Granting unrestricted access would be insecure, and so AppArmor denies access to disconnected files. As a special case, the kernel supports a number of file systems that users can have file descriptors open for, but that can never be mounted. Those files are by definition disconnected. Anonymous pipes, futexes, inotify, and epoll are all examples of that. Accesses to those files is always allowed. Future versions of AppArmor will have better control over disconnected files by controlling file descriptor passing between processes. \subsection{Mount} Mounting can change a process's namespace in almost arbitrary ways. This is a problem because AppArmor's file access control is pathname based, and granting a process the right to arbitrarily change its namespace would subvert this protection mechanism. AppArmor therefore denies confined processes access to the mount(2), umount(2), and umount2(2) system calls. Future versions of AppArmor may offer fine-grained control over mount, and may grant confined processes specific mount operations. \subsection{The Kernel NFS Daemon} The security model of the various versions of NFS is that files are looked up by name as usual, but after that lookup, each file is only identified by a file handle in successive acesses. The file handle at a minimum includes some sort of filesystem identifier and the file's inode number. In Linux, the file handles used by most filesystems also include the inode number of the parent directory; this may change in the future. File handles are persistent across server restarts. This means that when the NFS daemon is presented with a file handle, clients must get access without having specified a pathname. A pathname can be computed from a (parent, child) inode pair that identifies the file down to the directory level if the dentry is properly connected to the dcache, but multiple hardlinks to the same file within the same directory cannot be distinguished, and properly connecting dentries comes at a cost in the NFS daemon. Because of this overhead and the questionable benefit, most setups do not guarantee that dentries will be connected, and so pathnames cannot always be computed. (See the no\_subtree\_check option in exports(5).) In addition, the NFS daemon is implemented in the kernel rather than as a user space process. There is no memory separation or other protection between the daemon and the rest of the kernel. This means that at best, the NFS daemon could cooperate with an additional access control mechanism like AppArmor --- but there would be no enforcement. Because of all of this, it makes little sense to put the kernel NFS daemon under AppArmor control. Administrators are advised to not assign profiles to the kernel nfsd daemons. \subsection{Why are the computed pathnames meaningful?} Whenever a process performs a name-based file access, the pathname or pathname component always refers to a specific path to that file: the path is either relative to the chroot if an absolute path is used, or else relative to the current working directory. The chroot or current working directory always has a unique pathname up to the namespace root (even if the process itself has no direct access above the chroot). This means that each name-based file access maps to a unique, canonical, absolute pathname. There may be additional paths pointing to the same file, but a particular name-based access still always refers to only one of them. These are the pathnames that AppArmor uses for permission checks. If directories along the path get renamed after a process changes into them (either with chroot(2) or with chdir(2)), the resulting pathname will differ from the pathnames that the process used. Consider the following sequence of operations for example: \begin{tabbing} \begin{tabular}{ll} \textbf{Process 1} & \textbf{Process 2} \\ chdir("/var/tmp/foo"); & \\ & rename("/var/tmp/foo", "/var/tmp/bar"); \\ creat("baz", 0666); & \\ \end{tabular} \end{tabbing} The creat operation will check against the path /var/tmp/\textit{bar}/baz, even though Process~1 never used \textit{bar.} This is the expected behavior; we are interested in the names of the objects along the path at the time of the access, not in their previous names. As already mentioned, a path lookup results in a pair of (dentry, vfsmount) kernel-internal objects. The pathname that AppArmor checks against is computed from these two objects after these objects have been looked up. The lookup and the pathname computation are not atomic, which means that pathname components could even be renamed after the lookup but before the pathname has been computed. It matters that the AppArmor access check is performed between the lookup and the actual access, but atomicity between the lookup and that access check is not necessary: there is no difference between a rename before the lookup and a rename after the lookup from AppArmor's point of view; all we care about is the current pathname at some point between the lookup and the access. A special case occurs when the lookup succeeds, but the file is deleted before the AppArmor access check. In this case the access is denied and errno is set to ENOENT, the same behavior as if the lookup had failed. \subsection{Path Permission Checking} On UNIX systems, when files are looked up by name, the lookup starts either at the root or the current working directory of a process. From there, each directory reached is checked for search permission (x). The permissions on the directories leading to the current working directory are not checked. When a file is being created or deleted, the parent directory of that file is checked for write and search access (wx). When a file is being accessed, the permissions of that file are checked for r, w, or x access, or a combination thereof. Each check can result in a failure with errno set to EACCES (Permission denied). In contrast, AppArmor first computes the pathname to a file. If a file is being created, the name being looked up is the name of the new file and not the name of the parent directory. If the file being looked up is a directory, AppArmor appends a slash to the pathname so that directory pathnames always end in a slash; otherwise the pathname will not end in a slash. It then checks for file access rules in the process's profile that match that pathname, and decides based on that. With some exceptions for execute modes as described in Section~\ref{sec:merging}, the permissions granted are the union of permissions of all matching rules. \subsection{Profile Permissions} \label{sec:permissions} AppArmor differentiates between slightly more permissions than UNIX does, as shown in Table~\ref{tab:permissions}: file access rules in AppArmor support the read (r), write (w), execute (x), memory map as executable (m), and link (l) permissions. The execute permission requires a modifier that further specifies which kind of execution is being granted: inherit the current profile (ix), use the profile defined for that executable (px), or execute unconfined without a profile (ux). In addition, the px and ux permissions have Px and Ux forms that will trigger Secure Execution (see Section~\ref{sec:secure-exec} below). The different permissions are used as follows: \begin{table}[tb] \center \begin{tabular}{|l|l|} \hline r & Read. \\ w & Write. \\ ix & Execute and inherit the current profile. \\ px & Execute under a specific profile. \\ Px & Execute secure and under a specific profile. \\ ux & Execute unconfined. \\ Ux & Execute secure and unconfined. \\ m & Memory map as executable. \\ l & Link. \\ \hline \end{tabular} \caption{File Access Permissions in Profiles} \label{tab:permissions} \end{table} \begin{description} \item[Read.] The profile read permission is required by all system calls that require the UNIX read permission. This includes open with O\_RDONLY, getdents (i.e., readdir), listxattr, getxattr, and mmap with PROT\_READ. \item[Write.] The profile write permission is required by all system calls that require the UNIX write permission, except for operations that create or remove files: while UNIX requires write access to the parent directory, AppArmor requires write access on the new file in this case (which does not exist at the time of the permission check for file creates). Operations that create files include open with O\_CREAT, creat, mkdir, symlink, and mknod. Operations that remove files include rename, unlink and rmdir. Operations that require write access in UNIX as well as AppArmor include open with O\_WRONLY (O\_RDWR requires read and write), setxattr, removexattr, and mmap with PROT\_WRITE. Other system calls such as chmod, chown, utime, and utimes are bound to file ownership or the respective capabilities in UNIX. AppArmor also requires profile write access for those operations. \item[Execute.] As mentioned above, AppArmor distinguishes a few different ways how files may be executed as described above. For directories, the UNIX execute permission maps to search access. AppArmor does not control directory search access. Traversing directories is always granted. \item[Memory map as executable.] The Linux kernel only requires read access to files in order to memory map them for execution with the PROT\_EXEC flag. AppArmor makes a distinction here, and requires the m profile permission in order for files to be mapped as executable. That way, it is more obvious in profiles what applications are allowed to do even if from a security point of view, the m permission provides a similar level of protection as the ix permission --- execute under the current profile. \item[Link.] Creating a hardlink requires the profile link permission (l) on the new path. In addition, the new path must have a subset of the r, w, x, and m permissions of the old path, and if the new path has the x permission, the execute flags (i, u, U, p, and P) of the old and the new path must be equal. \item[Rename.] A rename requires profile read and write access for the source file, and profile write access for the target file. \item[Stat.] Retrieving information about files is always allowed. We believe that providing policy for file information retrieval is more troublesome than the benefit it would provide. \end{description} \subsection{System Calls Taking File Handles, At System Calls} A number of system calls take file descriptors instead of pathnames as their parameters (ftruncate, fchmod, etc.), or take directory file descriptors, and resolve pathnames relative to those directories (openat, mkdirat, etc.). These system calls are treated like their non-f and non-at equivalents, and the same access checks are performed. At the point where AppArmor is asked to validate those file accesses, it is passed a (dentry, vfsmount) pair no matter which system call variant is used. \subsection{File Descriptor Passing and Revalidation} After a file descriptor has been obtained, the permitted accesses (read and/or write) are encoded in the file descriptor, and reads and writes are not revalidated against the profile for each access. This is consistent with how access checks are done in UNIX; such access checks would have a severe performance impact. The picture changes when a file descriptor is passed between processes and the other process is running under a different profile, or when a process switches profiles: in that case, read and write accesses are revalidated under the new profile. If the new profile does not allow them, the access is denied and errno is set to EACCES (Permission denied). File descriptors opened by unconfined processes are exempt from this rule. This is so that processes will still have access to their stdin, stdout, and stderr without having to list all possible sources of input and output in all profiles. \subsection{Deleted Files} Revalidation is problematic for deleted files for which a process still has an open file descriptor --- after all, the idea of the pathname of a deleted file is somewhat peculiar: the file is no longer reachable by any pathname, and it also cannot become re-attached to the filesystem namespace again. The traditional UNIX behavior is to determine access upon file access, and to never check again. Applications depend on this, particularly for temporary files. In addition to temporary files, deleted files can be used as an interprocess communication mechanism if the file descriptor is shared among multiple processes. AppArmor grants access to deleted files, just like it grants access to files opened by unconfined processes. It may control interprocess communication, including file descriptor passing, in a future version. \subsection{The access System Call} This system call determines whether a process has a given mode of access to a file in terms of the read, write, and execute permissions. This is not a sufficient replacement for performing the access check at the time of access even under traditional UNIX, because the access system call and the subsequent access are not atomic, and the permissions might change between the two operations. Applications are not supposed to rely on access(2). AppArmor introduces additional restrictions, some of which cannot be modeled in terms of read, write, and execute: for example, an AppArmor profile may allow a process to create files /tmp/foo-*, but not any other files in /tmp. There is no way to express this with access(2); in traditional UNIX, all that is required for creating files is write access to the parent directory. Access(2) will indicate that some accesses are allowed even when AppArmor will eventually deny them. \subsection{The ptrace System Call} The ability to ptrace allows a process to look up information about another process, read and write the memory of that process, and attach to (or trace) that process in order to debug it, or analyze its behavior. This gives total control over the process being traced, and so the kernel employs some restrictions over which processes may ptrace with other processes. In addition to these restrictions, AppArmor requires that if the tracing task is confined, it must either have the CAP\_{\H}SYS\_{\H}PTRACE capability, or be confined by the same profile and sub-profile as the process being traced. Attempts to switch to another profile or sub-profile by a process being traced is denied. \subsection{Secure Execution} \label{sec:secure-exec} In this mode, the kernel passes a flag to user space. When glibc finds this flag set, it unsets environment variables that are considered dangerous, and it prevents the dynamic loader from loading libraries controlled by the environment. With non-secure exec, the LD\_LIBRARY\_PATH environment variable can be used to switch to a different set of libraries, for example. The secure exec mechanism is not specific to AppArmor: set-user-id and set-group-id executables also use it, as well as SELinux, which introduced this glibc feature. \subsection{Exec Mode Merging in Profiles, Exact Matches} \label{sec:merging} When more than one rule in a profile matches a given path, all the permissions accumulate except for ix, px, Px, ux, and Ux: those permissions would conflict with each other; it would be unclear how to execute the new binary if more than one of these flags was set. To deal with this situation, AppArmor differentiates between rules that define exact matches and wildcard rules (see Table~\ref{tab:globbing} on page~\pageref{tab:globbing}). Execute flags in exact matches override execute flags in wildcard matches. If the execute flags of multiple rules still disagree, the profile is rejected at profile load time. \subsection{Capabilities} AppArmor uses the standard Linux capability mechanism. When the kernel checks if a certain capability can be exercised, AppArmor additionally checks if the current profile allows the requested capability, and rejects the use of the capability otherwise. \subsection{The sysctl System Call and /proc/sys} The sysctl system call and files below /proc/sys can be used to read and modify various kernel parameters. Root processes can easily bring the system down by setting kernel parameters to invalid values. To prevent against that, AppArmor denies confined processes that do not have the CAP\_{\H}SYS\_{\H}ADMIN capability write access to kernel parameters. \subsection{Subprofiles aka. Hats} Profiles can contain subprofiles that processes may switch to from the main profile. Switching from a subprofile into a sibling subprofile or back to the parent profile is allowed depending on how the subprofile was entered, and provided that the child knows a magic cookie.\footnote{ \textbf{A word of warning about change\_hat(2):} When used with a non-zero magic cookie for changing into a subprofile, that magic cookie can be used to change back out of the subprofile; in this mode, change\_hat(2) is not a strong confinement mechanism. If the code running in the subprofile can guess the magic cookie, it can break out of the subprofile. Likewise, if that code can manipulate the processes' behavior beyond the point where the process returns from the subprofile, it can influence what is done under the parent profile. Therefore, change\_hat(2) with a non-zero magic cookie is only safe in combination with restricted code environments, such as when the subprofile is used for executing Safe Perl (see Safe(3pm)), etc. } See the change\_hat(2) manual page for details. Each process may consist of multiple tasks. Each task may only change its own subprofile. The superuser cannot put a task into a different hat, but he can replace the entire profile and its subprofiles, or he can put a process in a different top-level profile (see Section~\ref{sec:association}). Internally, change\_hat(2) is implemented by writing to a special kernel-provided file. This is equivalent to a command like: \begin{small} \begin{verbatim} $ echo "changehat 123^hat_name" > /proc/$PID/attr/current \end{verbatim} \end{small} Here, the number is the magic cookie value, and hat\_name obviously is the name of the hat; either may be replaced by the empty string (but not both). \subsection{Association of Profiles with Processes} \label{sec:association} Profiles are associated with kernel tasks, which roughly correspond to threads in user space (see clone(2) for details). Currently there are two ways how a profile can be associated with a task: when an executable is started and a profile is defined for that executable, or when the administrator assigns a profile to a task explicitly. In addition to that, once a task is confined by a profile, that profile determines which other executables may be executed, and under which profile they may run (under the profile defined for that executable, the same profile as the current task, or unconfined; see Section~\ref{sec:permissions}). A process will consist of a single task after an exec, so in the exec case, the entire process will be confined. New tasks (threads as well as processes) inherit the same profile and subprofile as their parent task. Unconfined processes with the CAP\_{\H}SYS\_{\H}ADMIN privilege may assign a profile to a task with a command like this: \begin{small} \begin{verbatim} $ echo "setprofile /name/of/profile" > \ /proc/$PID/attr/current \end{verbatim} \end{small} After that, the task will be in the new top-level profile, even if the process was in a subprofile before. Processes with the CAP\_{\H}SYS\_{\H}ADMIN privilege as well as the process itself can query the profile a process is in by reading from that file: \begin{small} \begin{verbatim} $ cat /proc/$PID/attr/current unconfined $ cat /proc/$PID/attr/current /name/of/profile (complain) $ cat /proc/$PID/attr/current /name/of/profile^hat_name (enforce) \end{verbatim} \end{small} The output includes the name of the profile and subprofile as well as the mode the active profile is in. (When a task is in a subprofile, the subprofile is the active profile.) \subsection{Profile Loading, Replacement, and Removal} Before the kernel can use any profiles, they must be loaded. The profile sources consist of plain text. This text representation is converted into in a binary representation that the kernel can more easily deal with by the user-space profile loader. Profiles contain potentially long lists of file access rules that may include wildcards. In order to make the lookup efficient, the AppArmor kernel module does not actually go through all the file access rules when checking for access. Instead, the profile loader takes those rules and compiles them into transition tables. Pathnames are then looked up in those tables with a simple and efficient algorithm, the theory behind which is explained in the Lexical Analysis section of the Dragon Book~\cite{dragon86}. An init script loads all the known profiles into the kernel at an early boot stage. This happens automatically and the system administrator tools will take care of loading, reloading, or removing profiles after they manipulate them, so end users will not usually notice this step. Profiles can be replaced at any time during runtime, and all processes running under old profiles will transparently be switched to the updated versions. Profiles can also be removed. All processes running under a profile that is removed will become unconfined. Profiles are always replaced together with all their subprofiles. It may be that an updated profile no longer contains a specific subprofile. If that happens while processes are using that subprofile, those processes will be put in a profile that denies all accesses. Such processes may still change to sibling subprofiles or back to the parent profile subject to the change\_hat(2) semantics. \section{AppArmor Walk-Through} \label{sec:walk-through} AppArmor consists of a set of kernel patches and accompanying user-space tools, both of which are available at \url{http://developer.novell.com/wiki/index.php/Apparmor}. \subsection{Kernel Patches and Configuration} The AppArmor kernel patches are provided in a format convenient for use with quilt,\footnote{ \url{http://savannah.nongnu.org/projects/quilt} } however, other tools for applying the patches can be used, too. The patches are supposed to apply against recent kernel.org git kernels. A copy of the current git tree can be obtained from \url{git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git} with \textit{git clone} (see the \hbox{git-clone(1)} manual page). In case the the differences between the latest git tree and the tree the AppArmor patches are based on is too big, the patches won't apply cleanly. In this case, trying an older git tree may work better. After obtaining the AppArmor patches tarball and the git tree which will end up in the linux-2.6 directory by default, the AppArmor patches can be applied to the git tree as follows: \begin{small} \begin{verbatim} $ tar zxvf apparmor.tar.gz $ cd linux-2.6/ $ ln -s ../apparmor patches $ quilt push -a \end{verbatim} \end{small} When configuring the kernel, make sure that AppArmor is built in or as a module (CONFIG\_{\H}SECURITY\_{\H}APPARMOR must be 'y' or 'm'). AppArmor cannot be used together with other Linux Security Modules, so if CONFIG\_{\H}SECURITY\_{\H}CAPABILITIES or CONFIG\_{\H}SECURITY\_{\H}SELINUX is set to 'y', they must be disabled by adding \texttt{selinux=0} and/or \texttt{capability.disable=1} to the kernel command line (grub, lilo, yaboot, etc.). It is not sufficient to put SELinux into permissive mode --- at this time, AppArmor cannot be combined with other LSMs. \subsection{The securityfs file system} AppArmor uses securityfs for configuration and to report information. The usual mountpoint for securityfs is /sys/{\H}kernel/{\H}security. Unless your distribution automatically does so, you can mount securityfs with: \begin{small} \begin{verbatim} $ mount securityfs -t securityfs /sys/kernel/security \end{verbatim} \end{small} Once securityfs has been mounted and the apparmor module loaded, /sys/{\H}kernel/{\H}security/{\H}apparmor/{\H}profiles will show the profiles loaded into the kernel, as well as mark if the profiles are in enforcement mode or in learning mode: \begin{small} \begin{verbatim} $ cat /sys/kernel/security/apparmor/profiles /usr/bin/opera (complain) /usr/lib/firefox/firefox.sh (complain) /sbin/lspci (enforce) ... \end{verbatim} \end{small} Profile loading, replacement, and unloading, as well as configuration of AppArmor is also done via securityfs. %/sys/kernel/security/apparmor/control/ control seldom-used features of AppArmor: % audit if '1', audit all actions by confined processes % complain if '1', allow all actions by confined processes, report % accesses not granted by policy % debug if '1', emit copius debugging % logsyscall if '1', use audit framework's syscall debugging, if audit % has been instructed to create per-task contexts.[2] \subsection{Profile Loading} Profile loading, replacement, and removal is performed by the apparmor\_parser utility from the apparmor-parser package. The package can easily be built by running make in the package's top-level directory. Once that is done and the AppArmor module loaded, you may use the parser to load profiles with: \begin{small} \begin{verbatim} $ echo "/tmp/ls { /tmp/ls rm, }" | apparmor_parser \end{verbatim} \end{small} Once a profile for a program has been loaded into the kernel, you must use the --replace option for replacing the existing profile with a new one (this option may be used even if no profile by that name exists): \begin{small} \begin{verbatim} $ echo "/tmp/ls { /tmp/ls rm, }" | apparmor_parser --replace \end{verbatim} \end{small} \subsection{Anatomy of a Profile} AppArmor profiles use a simple declaritive language, fully described in the apparmor.d(5) manual page. By convention, profiles are stored in /etc/{\H}apparmor.d/. The AppArmor parser supports a simple cpp-style include mechanism to allow sharing pieces of policy. A simple profile looks like this: \begin{small} \begin{verbatim} /bin/ls flags=(complain) { /bin/ls rm, /lib/ld-2.5.so rmix, /etc/ld.so.cache rm, /lib/lib*.so* rm, /dev/pts/* w, /proc/meminfo r, /var/run/nscd/socket w, /var/run/nscd/passwd r, /var/run/nscd/group r, /tmp/ r, } \end{verbatim} \end{small} Here, the first /bin/ls is the name of the profile. This profile will be automatically used whenever an unconfined process executes /bin/ls. The flags instruct AppArmor to put the profile in complain (aka. learning) mode: in this mode, all operations are allowed, and any events that would have been denied are logged. This helps users to incrementally deploy AppArmor in production environments. The default if no flags are specified is enforcement mode, in which all operations not allowed by the profile are logged and denied. Complain mode can be enabled individually for profiles as shown above (followed by reloading the profile), or by globally putting all profiles in complain mode with: \begin{small} \begin{verbatim} $ echo 1 > /sys/kernel/security/apparmor/control/complain \end{verbatim} \end{small} The user-space tools also include two small utilities, enforce and complain, which will put profiles into enforce or complain mode: \begin{small} \begin{verbatim} $ enforce firefox Setting /usr/lib/firefox/firefox.sh to enforce mode. \end{verbatim} \end{small} Inside the body of the profile are any number of rules consisting of a pathname expression that may include globbing, and a set of permissions. Table~\ref{tab:globbing} shows the supported shell-inspired globbing constructs; Section~\ref{sec:permissions} on page~\pageref{sec:permissions} describes the permissions. \begin{table}[tb] \center \begin{tabular}{|l|l|} \hline {?} & Any single character except ``/''. \\ {*} & Any number of characters except ``/''. \\ {**} & Any number of characters including ``/''. \\ {[ab]} & One of ``a'' or ``b''. \\ {[a-c]} & One of ``a'', ``b'', or ``c''. \\ \{ab,cd\} & Alternation: either ``ab'' or ``cd''. \\ \hline \end{tabular} \caption{Globbing in File Access Rules. Alternation counts as an exact match in file access rules; all others count as wildcards (see Section~\ref{sec:merging}).} \label{tab:globbing} \end{table} When AppArmor looks up a directory the pathname being looked up will end with a slash (e.g., /var/tmp/), otherwise it will not. Only rules that match that trailing slash will match directories. Some examples, none matching the /tmp directory itself, are: \begin{tabbing} \begin{tabular}{ll} {/tmp/*} & Files directly in /tmp. \\ {/tmp/*/} & Directories directly in /tmp. \\ {/tmp/**} & Files and directories anywhere underneath /tmp. \\ {/tmp/**/} & Directories anywhere underneath /tmp. \\ \end{tabular} \end{tabbing} As explained in Section~\ref{sec:model}, AppArmor does not require execute access to allow directory traversal, or write access on a directory to create or rename files inside the directory. Instead, write access is required on the specific files that a confined process attempts to create, remove, rename, etc. Read access is required for reading the contents of a directory. AppArmor also mediates the use of POSIX 1003.1e draft capabilities; capabilities that a process is allowed to use are listed in the profile by their name in lower-case (with ``CAP\_'' stripped off), e.g., \begin{small} \begin{verbatim} #include /sbin/lspci { #include #include capability sys_admin, /sbin/lspci mr, /sys/bus/pci/ r, /sys/bus/pci/devices/ r, /sys/devices/** r, /usr/share/pci.ids r, } \end{verbatim} \end{small} This profile uses predefined include files which are part of the apparmor-profiles package. \subsection{Logging} AppArmor uses the kernel standard audit facility for reporting. When a profile is in complain mode, the log messages look like this: \begin{small} \begin{verbatim} type=APPARMOR msg=audit(1174506429.573:1789): PERMITTING r access to /home/sarnold/ (ls(16504) profile /tmp/ls active /tmp/ls) \end{verbatim} \end{small} When a profile is in enforcement mode, the log messages look like this: \begin{small} \begin{verbatim} type=APPARMOR msg=audit(1174508205.298:1791): REJECTING r access to /bin/ (ls(16552) profile /tmp/ls active /tmp/ls) \end{verbatim} \end{small} These log messages are sent to the kernel auditing facility; if auditd is not running, the kernel will forward these messages to printk for collection by klogd. Auditd must be configured with --with-apparmor to enable the \#defines to handle AppArmor's message type correctly. AppArmor also logs some important events in the process lifecycle, such as when processes in learning mode fork and change domain via exec. These other events, while not strictly related to permissions requested by the process, help the genprof profile generation tool reconstruct when specific accesses are required by processes --- this allows the tool to make more relevant and meaningful policy suggestions. \subsection{Generating Profiles By Hand} While the majority of our users are expected to generate profiles with the help of our profile tools, it is possible to write policy by hand. This final section gives a very quick walkthrough generating a simple profile for firefox. Since the kernel resolves symlinks to their ``final destinations'' before presenting AppArmor with policy questions, we first must see if /usr/{\H}bin/{\H}firefox is a symlink or the shell script that starts firefox; on our system, it is a symlink: \begin{small} \begin{verbatim} $ ls -l /usr/bin/firefox lrwxrwxrwx 1 root root 25 Mar 21 13:36 /usr/bin/firefox -> ../lib/firefox/firefox.sh \end{verbatim} \end{small} So we will start a profile for /usr/{\H}lib/{\H}firefox/{\H}firefox.sh. This shell script will execute firefox-bin, as we will see later; when it does so, we will tell AppArmor to inherit this profile. Thus, firefox-bin will be executing under the profile for /usr/{\H}lib/{\H}firefox/{\H}firefox.sh. To get started, we can make some assumptions about the privileges that firefox will need (both as a shell script and as a fairly complex GUI application): \begin{small} \begin{verbatim} $ cat /etc/apparmor.d/usr.lib.firefox.firefox.sh /usr/lib/firefox/firefox.sh flags=(complain) { /usr/lib/firefox/firefox.sh r, /bin/bash rmix, /lib/ld-2.5.so rmix, /etc/ld.so.cache rm, /lib/lib*.so* rm, /usr/lib/lib*.so* rm, } $ apparmor_parser --reload < \ /etc/apparmor.d/usr.lib.firefox.firefox.sh Replacement succeeded for "/usr/lib/firefox/firefox.sh". \end{verbatim} \end{small} The easiest way to see what accesses AppArmor allows, start a tail -F /var/{\H}log/{\H}audit/{\H}audit.log (or /var/{\H}log/{\H}messages, or wherever your audit messages are being sent). In another terminal, start firefox. tail will show a few hundred PERMITTING audit events like these: \begin{small} \begin{verbatim} type=APPARMOR msg=audit(1174512269.026:1804): PERMITTING rw access to /dev/tty (firefox(16950) profile /usr/lib/firefox/firefox.sh active /usr/lib/firefox/firefox.sh) type=APPARMOR msg=audit(1174512269.026:1805): PERMITTING r access to /usr/share/locale/locale.alias (firefox(16950) profile /usr/lib/firefox/firefox.sh active /usr/lib/firefox/firefox.sh) type=APPARMOR msg=audit(1174512269.026:1806): PERMITTING r access to /usr/lib/locale/en_US.utf8/LC_IDENTIFICATION (firefox(16950) profile /usr/lib/firefox/firefox.sh active /usr/lib/firefox/firefox.sh) \end{verbatim} \end{small} Because we want this profile to be fairly simple we'll be fairly permissive, add a few more rules to the profile and reload: \begin{small} \begin{verbatim} /dev/tty rw, /usr/share/locale/** r, /usr/lib/locale/** r, \end{verbatim} \end{small} Now re-run firefox. There is no need to handle all log entries at once. In complain mode, AppArmor will only report accesses that are not in the profile. This makes it fairly easy to add a few rules and re-run the application to determine what privileges are still necessary. We get a few more messages: \begin{small} \begin{verbatim} type=APPARMOR msg=audit(1174512791.236:5356): PERMITTING r access to /usr/lib/gconv/gconv-modules.cache (firefox(17031) profile /usr/lib/firefox/firefox.sh active /usr/lib/firefox/firefox.sh) type=APPARMOR msg=audit(1174512791.236:5357): PERMITTING r access to /proc/meminfo (firefox(17031) profile /usr/lib/firefox/firefox.sh active /usr/lib/firefox/firefox.sh) type=APPARMOR msg=audit(1174512791.240:5358): PERMITTING x access to /bin/basename (firefox(17032) profile /usr/lib/firefox/firefox.sh active /usr/lib/firefox/firefox.sh) type=APPARMOR msg=audit(1174512791.240:5359): LOGPROF-HINT changing_profile pid=17032 type=APPARMOR msg=audit(1174512791.240:5360): PERMITTING r access to /bin/basename (firefox(17032) profile null-complain-profile active null-complain-profile) ... type=APPARMOR msg=audit(1174512791.240:5364): PERMITTING mr access to /bin/basename (basename(17032) profile null-complain-profile active null-complain-profile) \end{verbatim} \end{small} So now, we add a few more rules: \begin{small} \begin{verbatim} /usr/lib/gconv/** r, /proc/meminfo r, /bin/basename rmix, \end{verbatim} \end{small} We selected ``rmix'' for /bin/basename --- most small shell utilities should not have a profile for themselves. There's nothing wrong with giving basename a profile, but the value of such a profile would be very limited. Giving other shell utilities their own profiles would be worse: the profile would need read access to the whole filesystem for shell scripts to function reliably. In our case, basename simply inherits privileges from another profile, then it has no more and no fewer privileges than the calling program --- which is often a fine tradeoff. The loader will need r and m access to execute basename, and we use ix to execute basename in the same profile. The kernel logs only reported r, m and x access; we have to choose the execute mode ourselves. Again, the standard user tools would prompt users for this decision and give consequences of decisions. We continue in this fashion, iteratively adding and changing rules as needed by the logs. Some of the logs report attribute modifications, such as: \begin{small} \begin{verbatim} type=APPARMOR msg=audit(1174519157.851:10357): PERMITTING attribute (mode,ctime,) change to /home/sarnold/.gnome2_private/ (firefox-bin(17338) profile /usr/lib/firefox/firefox.sh active /usr/lib/firefox/firefox.sh) \end{verbatim} \end{small} These need to be represented in the profile with simple w access. \begin{small} \begin{verbatim} /home/*/.gnome2_private/ w, \end{verbatim} \end{small} After nine iterations, the profile looks like this --- we have inserted blank lines between each iteration: \begin{small} \begin{verbatim} /usr/lib/firefox/firefox.sh flags=(complain) { /usr/lib/firefox/firefox.sh r, /bin/bash rmix, /lib/ld-2.5.so rmix, /etc/ld.so.cache rm, /lib/lib*.so* rm, /usr/lib/lib*.so* rm, /dev/tty rw, /usr/share/locale/** r, /usr/lib/locale/** r, /usr/lib/gconv/** r, /proc/meminfo r, /bin/basename rmix, /usr/bin/file rmix, /etc/magic r, /usr/share/misc/magic.mgc r, /bin/gawk rmix, /usr/lib/firefox/firefox-bin rmix, /usr/lib/firefox/lib*so rm, /opt/gnome/lib/lib*so* rm, /usr/share/X11/locale/* r, /var/run/nscd/socket w, /var/run/nscd/passwd r, /usr/share/X11/locale/** r, /home/*/.Xauthority r, /usr/lib/gconv/*so m, /home/*/.mozilla/** rw, /etc/resolv.conf r, /usr/lib/firefox/**.so rm, /usr/lib/firefox/** r, /etc/opt/gnome/** r, /var/run/dbus/system_bus_socket w, /etc/localtime r, /opt/gnome/lib/**.so rm, /var/cache/libx11/compose/* r, /tmp/orbit-*/ w, /dev/urandom r, /tmp/ r, /dev/null rw, /opt/gnome/lib/GConf/2/gconfd-2 rmix, /dev/log w, /tmp/orbit-*/* w, /tmp/gconfd-*/ r, /tmp/gconfd-*/** rwl, /home/*/.gconf/ r, /home/*/.gconf/* rw, /etc/fonts/** r, /var/cache/fontconfig/* r, /home/*/.fontconfig/** r, /usr/share/ghostscript/fonts/** r, /etc/passwd r, /var/tmp/ r, /bin/netstat rmix, /home/*/.gnome2_private/ w, /home/*/.gconfd/* rw, /proc/net/ r, /proc/net/* r, /usr/share/fonts/** r, /usr/lib/browser-plugins/ r, /usr/lib/browser-plugins/** rm, } \end{verbatim} \end{small} Sorting the entries in the profile can help show areas that can be collapsed with even more generic rules. After doing that and making a few rules slightly more generic, we end up with: \begin{small} \begin{verbatim} /usr/lib/firefox/firefox.sh { /bin/basename rmix, /bin/bash rmix, /bin/gawk rmix, /bin/netstat rmix, /dev/log w, /dev/null rw, /dev/tty rw, /dev/urandom r, /etc/fonts/** r, /etc/ld.so.cache rm, /etc/localtime r, /etc/magic r, /etc/opt/gnome/** r, /etc/passwd r, /etc/resolv.conf r, /home/*/.fontconfig/** r, /home/*/.gconfd/* rw, /home/*/.gconf/ r, /home/*/.gconf/* rw, /home/*/.gnome2_private/ w, /home/*/.mozilla/** rw, /home/*/.Xauthority r, /lib/ld-2.5.so rmix, /lib/lib*.so* rm, /opt/gnome/lib/GConf/2/gconfd-2 rmix, /opt/gnome/lib/**.so* rm, /proc/meminfo r, /proc/net/ r, /proc/net/* r, /tmp/gconfd-*/ r, /tmp/gconfd-*/** rwl, /tmp/orbit-*/ w, /tmp/orbit-*/* w, /tmp/ r, /usr/bin/file rmix, /usr/lib/browser-plugins/ r, /usr/lib/browser-plugins/** rm, /usr/lib/firefox/firefox-bin rmix, /usr/lib/firefox/firefox.sh r, /usr/lib/firefox/** r, /usr/lib/firefox/**.so rm, /usr/lib/gconv/** r, /usr/lib/gconv/*so m, /usr/lib/lib*.so* rm, /usr/lib/locale/** r, /usr/share/** r, /var/cache/fontconfig/* r, /var/cache/libx11/compose/* r, /var/run/dbus/system_bus_socket w, /var/run/nscd/passwd r, /var/run/nscd/socket w, /var/tmp/ r, } \end{verbatim} \end{small} \begin{thebibliography}{XX} \bibitem{apparmor} AppArmor documentation, \url{http://www.novell.com/documentation/apparmor/} \bibitem{ols06-pai} Al Viro and Ram Pai: {\em Shared-Subtree Concept, Implementation and Applications in Linux,} Ottawa Linux Symposium, July 19-22, 2006, \url{http://www.linuxsymposium.org/2006/} \bibitem{dragon86} Alfred V. Aho, Ravi Sethi, Jeffrey D. Ullman: {\em Compilers: Principles, Techniques, and Tools} (The ``Dragon Book''), Addison-Wesley, 1986, ISBN 0-201-10088-6. A second edition of this classic is available since August 2006 as ISBN 0-321-48681-1. \end{thebibliography} \end{document} % vim:textwidth=72 apparmor-2.10.95/parser/Makefile0000664000175000017500000003110612542635254015516 0ustar stevesteve# ---------------------------------------------------------------------- # Copyright (c) 1999, 2000, 2001, 2002, 2004, 2005, 2006, 2007 # NOVELL (All rights reserved) # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, contact Novell, Inc. # ---------------------------------------------------------------------- NAME=apparmor-parser all: COMMONDIR=../common/ include $(COMMONDIR)/Make.rules DESTDIR=/ APPARMOR_BIN_PREFIX=${DESTDIR}/lib/apparmor CONFDIR=/etc/apparmor INSTALL_CONFDIR=${DESTDIR}${CONFDIR} LOCALEDIR=/usr/share/locale MANPAGES=apparmor.d.5 apparmor.7 apparmor_parser.8 subdomain.conf.5 YACC := /usr/bin/bison YFLAGS := -d LEX := /usr/bin/flex LEXFLAGS = -B -v WARNINGS = -Wall EXTRA_WARNINGS = -Wsign-compare -Wmissing-field-initializers -Wformat-security -Wunused-parameter CXX_WARNINGS = ${WARNINGS} $(shell for warning in ${EXTRA_WARNINGS} ; do \ if ${CXX} $${warning} -S -o /dev/null -xc /dev/null >/dev/null 2>&1; then \ echo "$${warning}"; \ fi ; \ done) CPP_WARNINGS = ifndef CFLAGS CFLAGS = -g -O2 -pipe ifdef DEBUG CFLAGS += -pg -D DEBUG endif ifdef COVERAGE CFLAGS = -g -pg -fprofile-arcs -ftest-coverage endif endif #CFLAGS EXTRA_CXXFLAGS = ${CFLAGS} ${CPPFLAGS} ${CXX_WARNINGS} -std=gnu++0x -D_GNU_SOURCE EXTRA_CFLAGS = ${EXTRA_CXXFLAGS} ${CPP_WARNINGS} #LEXLIB := -lfl # override this on the make command to point to where the immunix.h file is # (yeah this is lame, but since we are tied to the kernel so tightly...) #INCLUDEDIR = /usr/src/linux/include INCLUDEDIR = ifdef INCLUDEDIR CFLAGS += -I$(INCLUDEDIR) endif # Internationalization support. Define a package and a LOCALEDIR EXTRA_CFLAGS+=-DPACKAGE=\"${NAME}\" -DLOCALEDIR=\"${LOCALEDIR}\" # Compile-time configuration of the location of the config file EXTRA_CFLAGS+=-DSUBDOMAIN_CONFDIR=\"${CONFDIR}\" SRCS = parser_common.c parser_include.c parser_interface.c parser_lex.c \ parser_main.c parser_misc.c parser_merge.c parser_symtab.c \ parser_yacc.c parser_regex.c parser_variable.c parser_policy.c \ parser_alias.c common_optarg.c lib.c network.c \ mount.cc dbus.cc profile.cc rule.cc signal.cc ptrace.cc \ af_rule.cc af_unix.cc policy_cache.c HDRS = parser.h parser_include.h immunix.h mount.h dbus.h lib.h profile.h \ rule.h common_optarg.h signal.h ptrace.h network.h af_rule.h af_unix.h \ policy_cache.h TOOLS = apparmor_parser OBJECTS = $(patsubst %.cc, %.o, $(SRCS:.c=.o)) AAREDIR= libapparmor_re AAREOBJECT = ${AAREDIR}/libapparmor_re.a AAREOBJECTS = $(AAREOBJECT) AARE_LDFLAGS = -static-libgcc -static-libstdc++ -L. AALIB = -Wl,-Bstatic -lapparmor -Wl,-Bdynamic -lpthread ifdef USE_SYSTEM # Using the system libapparmor so Makefile dependencies can't be used LIBAPPARMOR_A = INCLUDE_APPARMOR = APPARMOR_H = else LIBAPPARMOR_SRC = ../libraries/libapparmor/ LOCAL_LIBAPPARMOR_INCLUDE = $(LIBAPPARMOR_SRC)/include LOCAL_LIBAPPARMOR_LDPATH = $(LIBAPPARMOR_SRC)/src/.libs LIBAPPARMOR_A = $(LOCAL_LIBAPPARMOR_LDPATH)/libapparmor.a INCLUDE_APPARMOR = -I$(LOCAL_LIBAPPARMOR_INCLUDE) AARE_LDFLAGS += -L$(LOCAL_LIBAPPARMOR_LDPATH) APPARMOR_H = $(LOCAL_LIBAPPARMOR_INCLUDE)/sys/apparmor.h endif EXTRA_CFLAGS += $(INCLUDE_APPARMOR) LEX_C_FILES = parser_lex.c YACC_C_FILES = parser_yacc.c parser_yacc.h TESTS = tst_regex tst_misc tst_symtab tst_variable tst_lib TEST_CFLAGS = $(EXTRA_CFLAGS) -DUNIT_TEST -Wno-unused-result TEST_OBJECTS = $(filter-out \ parser_lex.o \ parser_yacc.o \ common_optarg.o \ parser_main.o \ policy_cache.o, ${OBJECTS}) \ $(AAREOBJECTS) TEST_LDFLAGS = $(AARE_LDFLAGS) TEST_LDLIBS = $(AALIB) ifdef V VERBOSE = 1 endif ifndef VERBOSE VERBOSE = 0 endif ifeq ($(VERBOSE),1) BUILD_OUTPUT = Q = else BUILD_OUTPUT = > /dev/null 2>&1 Q = @ endif export Q VERBOSE BUILD_OUTPUT po/${NAME}.pot: ${SRCS} ${HDRS} $(MAKE) -C po ${NAME}.pot NAME=${NAME} SOURCES="${SRCS} ${HDRS}" techdoc.pdf: techdoc.tex timestamp=$(shell date --utc "+%Y%m%d%H%M%S%z" -r $< );\ while pdflatex "\def\fixedpdfdate{$$timestamp}\input $<" ${BUILD_OUTPUT} || exit 1 ; \ grep -q "Label(s) may have changed" techdoc.log; \ do :; done techdoc/index.html: techdoc.pdf latex2html -show_section_numbers -split 0 -noinfo -nonavigation -noaddress techdoc.tex ${BUILD_OUTPUT} techdoc.txt: techdoc/index.html w3m -dump $< > $@ # targets arranged this way so that people who don't want full docs can # pick specific targets they want. arch: $(TOOLS) manpages: $(MANPAGES) htmlmanpages: $(HTMLMANPAGES) pdf: techdoc.pdf docs: manpages htmlmanpages pdf indep: docs $(Q)$(MAKE) -C po all all: arch indep .PHONY: coverage coverage: $(MAKE) clean apparmor_parser COVERAGE=1 ifndef USE_SYSTEM $(LIBAPPARMOR_A): @if [ ! -f $@ ]; then \ echo "error: $@ is missing. Pick one of these possible solutions:" 1>&2; \ echo " 1) Build against the in-tree libapparmor by building it first and then trying again. See the top-level README for help." 1>&2; \ echo " 2) Build against the system libapparmor by adding USE_SYSTEM=1 to your make command." 1>&2;\ return 1; \ fi endif apparmor_parser: $(OBJECTS) $(AAREOBJECTS) $(LIBAPPARMOR_A) $(CXX) $(LDFLAGS) $(EXTRA_CFLAGS) -o $@ $(OBJECTS) $(LIBS) \ ${LEXLIB} $(AAREOBJECTS) $(AARE_LDFLAGS) $(AALIB) parser_yacc.c parser_yacc.h: parser_yacc.y parser.h profile.h $(YACC) $(YFLAGS) -o parser_yacc.c parser_yacc.y parser_lex.c: parser_lex.l parser_yacc.h parser.h profile.h mount.h dbus.h policy_cache.h $(LEX) ${LEXFLAGS} -o$@ $< parser_lex.o: parser_lex.c parser.h parser_yacc.h $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< parser_misc.o: parser_misc.c parser.h parser_yacc.h profile.h cap_names.h $(APPARMOR_H) $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< parser_yacc.o: parser_yacc.c parser_yacc.h $(APPARMOR_H) $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< parser_main.o: parser_main.c parser.h parser_version.h policy_cache.h libapparmor_re/apparmor_re.h $(APPARMOR_H) $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< parser_interface.o: parser_interface.c parser.h profile.h libapparmor_re/apparmor_re.h $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< parser_include.o: parser_include.c parser.h parser_include.h $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< parser_merge.o: parser_merge.c parser.h profile.h $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< parser_regex.o: parser_regex.c parser.h profile.h libapparmor_re/apparmor_re.h libapparmor_re/aare_rules.h $(APPARMOR_H) $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< parser_symtab.o: parser_symtab.c parser.h $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< parser_variable.o: parser_variable.c parser.h profile.h $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< parser_policy.o: parser_policy.c parser.h parser_yacc.h profile.h $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< parser_alias.o: parser_alias.c parser.h profile.h $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< parser_common.o: parser_common.c parser.h $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< mount.o: mount.cc mount.h parser.h immunix.h rule.h $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< common_optarg.o: common_optarg.c common_optarg.h parser.h libapparmor_re/apparmor_re.h $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< policy_cache.o: policy_cache.c policy_cache.h parser.h lib.h $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< lib.o: lib.c lib.h parser.h $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< dbus.o: dbus.cc dbus.h parser.h immunix.h parser_yacc.h rule.h $(APPARMOR_H) $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< signal.o: signal.cc signal.h parser.h immunix.h parser_yacc.h rule.h $(APPARMOR_H) $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< ptrace.o: ptrace.cc ptrace.h parser.h immunix.h parser_yacc.h rule.h $(APPARMOR_H) $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< network.o: network.c network.h parser.h immunix.h parser_yacc.h rule.h af_names.h $(APPARMOR_H) $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< af_rule.o: af_rule.cc af_rule.h network.h parser.h profile.h immunix.h parser_yacc.h rule.h $(APPARMOR_H) $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< af_unix.o: af_unix.cc af_unix.h network.h af_rule.h parser.h profile.h immunix.h parser_yacc.h $(APPARMOR_H) $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< profile.o: profile.cc profile.h parser.h network.h $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< rule.o: rule.cc rule.h policydb.h $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< parser_version.h: Makefile @echo \#define PARSER_VERSION \"$(VERSION)\" > .ver @mv -f .ver $@ # af_names and capabilities generation has moved to common/Make.rules, # as well as the filtering that occurs for network protocols that # apparmor should not mediate. .PHONY: af_names.h af_names.h: echo "$(AF_NAMES)" | LC_ALL=C sed -n -e 's/[ \t]\?AF_MAX[ \t]\+[0-9]\+,//g' -e 's/[ \t]\+\?AF_\([A-Z0-9_]\+\)[ \t]\+\([0-9]\+\),/#ifndef AF_\1\n# define AF_\1 \2\n#endif\nAA_GEN_NET_ENT("\L\1", \UAF_\1)\n\n/pg' > $@ echo "$(AF_NAMES)" | LC_ALL=C sed -n -e 's/.*,[ \t]\+AF_MAX[ \t]\+\([0-9]\+\),\?.*/#define AA_AF_MAX \1\n/p' >> $@ # cat $@ cap_names.h: /usr/include/linux/capability.h echo "$(CAPABILITIES)" | LC_ALL=C sed -n -e "s/[ \\t]\\?CAP_\\([A-Z0-9_]\\+\\)/\{\"\\L\\1\", \\UCAP_\\1\},\\n/pg" > $@ tst_lib: lib.c parser.h $(filter-out lib.o, ${TEST_OBJECTS}) $(CXX) $(TEST_CFLAGS) -o $@ $< $(filter-out $(<:.c=.o), ${TEST_OBJECTS}) $(TEST_LDFLAGS) $(TEST_LDLIBS) tst_%: parser_%.c parser.h $(filter-out parser_%.o, ${TEST_OBJECTS}) $(CXX) $(TEST_CFLAGS) -o $@ $< $(filter-out $(<:.c=.o), ${TEST_OBJECTS}) $(TEST_LDFLAGS) $(TEST_LDLIBS) .SILENT: check .PHONY: check check: check_pod_files tests .SILENT: tests tests: apparmor_parser ${TESTS} sh -e -c 'for test in ${TESTS} ; do echo "*** running $${test}" && ./$${test}; done' $(Q)$(MAKE) -s -C tst tests # always need to rebuild. .SILENT: $(AAREOBJECT) .PHONY: $(AAREOBJECT) $(AAREOBJECT): $(MAKE) -C $(AAREDIR) CFLAGS="$(EXTRA_CXXFLAGS)" .PHONY: install-rhel4 install-rhel4: install-redhat .PHONY: install-redhat install-redhat: install -m 755 -d $(DESTDIR)/etc/init.d install -m 755 rc.apparmor.$(subst install-,,$@) $(DESTDIR)/etc/init.d/apparmor .PHONY: install-suse install-suse: install -m 755 -d $(DESTDIR)/etc/init.d install -m 755 rc.apparmor.$(subst install-,,$(@)) $(DESTDIR)/etc/init.d/boot.apparmor install -m 755 -d $(DESTDIR)/sbin ln -sf /etc/init.d/boot.apparmor $(DESTDIR)/sbin/rcapparmor ln -sf rcapparmor $(DESTDIR)/sbin/rcsubdomain .PHONY: install-slackware install-slackware: install -m 755 -d $(APPARMOR_BIN_PREFIX)/install install -m 755 frob_slack_rc $(APPARMOR_BIN_PREFIX)/install install -m 755 -d $(DESTDIR)/etc/rc.d install -m 755 rc.apparmor.$(subst install-,,$(@)) $(DESTDIR)/etc/rc.d/rc.apparmor .PHONY: install-debian install-debian: .PHONY: install-unknown install-unknown: INSTALLDEPS=arch ifndef DISTRO DISTRO=$(shell if [ -f /etc/slackware-version ] ; then \ echo slackware ; \ elif [ -f /etc/debian_version ] ; then \ echo debian ;\ elif which rpm > /dev/null ; then \ if [ "$(rpm --eval '0%{?suse_version}')" != "0" ] ; then \ echo suse ;\ elif [ "$(rpm --eval '%{_host_vendor}')" = redhat ] ; then \ echo rhel4 ;\ elif [ "$(rpm --eval '0%{?fedora}')" != "0" ] ; then \ echo rhel4 ;\ else \ echo unknown ;\ fi ;\ else \ echo unknown ;\ fi) endif ifdef DISTRO INSTALLDEPS+=install-$(DISTRO) endif .PHONY: install install: install-indep install-arch .PHONY: install-arch install-arch: $(INSTALLDEPS) install -m 755 -d $(DESTDIR)/sbin install -m 755 ${TOOLS} $(DESTDIR)/sbin .PHONY: install-indep install-indep: install -m 755 -d $(INSTALL_CONFDIR) install -m 644 subdomain.conf $(INSTALL_CONFDIR) install -m 644 parser.conf $(INSTALL_CONFDIR) install -m 755 -d ${DESTDIR}/var/lib/apparmor install -m 755 -d $(APPARMOR_BIN_PREFIX) install -m 755 rc.apparmor.functions $(APPARMOR_BIN_PREFIX) $(MAKE) -C po install NAME=${NAME} DESTDIR=${DESTDIR} $(MAKE) install_manpages DESTDIR=${DESTDIR} ifndef VERBOSE .SILENT: clean endif .PHONY: clean clean: pod_clean rm -f core core.* *.o *.s *.a *~ *.gcda *.gcno rm -f gmon.out rm -f $(TOOLS) $(TESTS) rm -f $(LEX_C_FILES) rm -f $(YACC_C_FILES) rm -f parser_version.h rm -f $(NAME)*.tar.gz $(NAME)*.tgz rm -f af_names.h rm -f cap_names.h rm -rf techdoc.aux techdoc.out techdoc.log techdoc.pdf techdoc.toc techdoc.txt techdoc/ $(MAKE) -s -C $(AAREDIR) clean $(MAKE) -s -C po clean $(MAKE) -s -C tst clean apparmor-2.10.95/parser/apparmor.pod0000664000175000017500000001347212252215151016375 0ustar stevesteve# ---------------------------------------------------------------------- # Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, # 2008, 2009 # NOVELL (All rights reserved) # # Copyright (c) 2010 # Canonical Ltd. (All rights reserved) # # Copyright (c) 2013 # Christian Boltz (All rights reserved) # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, contact Novell, Inc. # ---------------------------------------------------------------------- =pod =head1 NAME AppArmor - kernel enhancement to confine programs to a limited set of resources. =head1 DESCRIPTION AppArmor is a kernel enhancement to confine programs to a limited set of resources. AppArmor's unique security model is to bind access control attributes to programs rather than to users. AppArmor confinement is provided via I loaded into the kernel via apparmor_parser(8), typically through the F SysV initscript, which is used like this: # /etc/init.d/apparmor start # /etc/init.d/apparmor stop # /etc/init.d/apparmor restart AppArmor can operate in two modes: I, and I: =over 4 =item * I - Profiles loaded in enforcement mode will result in enforcement of the policy defined in the profile as well as reporting policy violation attempts to syslogd. =item * I - Profiles loaded in C mode will not enforce policy. Instead, it will report policy violation attempts. This mode is convenient for developing profiles. To manage complain mode for individual profiles the utilities aa-complain(8) and aa-enforce(8) can be used. These utilities take a program name as an argument. =back Profiles are traditionally stored in files in F under filenames with the convention of replacing the B in pathnames with B<.> (except for the root B) so profiles are easier to manage (e.g. the F profile would be named F). Profiles are applied to a process at exec(3) time (as seen through the execve(2) system call); an already running process cannot be confined. However, once a profile is loaded for a program, that program will be confined on the next exec(3). AppArmor supports the Linux kernel's securityfs filesystem, and makes available the list of the profiles currently loaded; to mount the filesystem: # mount -tsecurityfs securityfs /sys/kernel/security $ cat /sys/kernel/security/apparmor/profiles /usr/bin/mutt /usr/bin/gpg ... Normally, the initscript will mount securityfs if it has not already been done. AppArmor also restricts what privileged operations a confined process may execute, even if the process is running as root. A confined process cannot call the following system calls: create_module(2) delete_module(2) init_module(2) ioperm(2) iopl(2) ptrace(2) reboot(2) setdomainname(2) sethostname(2) swapoff(2) swapon(2) sysctl(2) =head1 ERRORS When a confined process tries to access a file it does not have permission to access, the kernel will report a message through audit, similar to: audit(1386511672.612:238): apparmor="DENIED" operation="exec" parent=7589 profile="/tmp/sh" name="/bin/uname" pid=7605 comm="sh" requested_mask="x" denied_mask="x" fsuid=0 ouid=0 audit(1386511672.613:239): apparmor="DENIED" operation="open" parent=7589 profile="/tmp/sh" name="/bin/uname" pid=7605 comm="sh" requested_mask="r" denied_mask="r" fsuid=0 ouid=0 audit(1386511772.804:246): apparmor="DENIED" operation="capable" parent=7246 profile="/tmp/sh" pid=7589 comm="sh" pid=7589 comm="sh" capability=2 capname="dac_override" The permissions requested by the process are described in the operation= and denied_mask= (for files - capabilities etc. use a slightly different log format). The "name" and process id of the running program are reported, as well as the profile name including any "hat" that may be active, separated by "//". ("Name" is in quotes, because the process name is limited to 15 bytes; it is the same as reported through the Berkeley process accounting.) For confined processes running under a profile that has been loaded in complain mode, enforcement will not take place and the log messages reported to audit will be of the form: audit(1386512577.017:275): apparmor="ALLOWED" operation="open" parent=8012 profile="/usr/bin/du" name="/etc/apparmor.d/tunables/" pid=8049 comm="du" requested_mask="r" denied_mask="r" fsuid=1000 ouid=0 audit(1386512577.017:276): apparmor="ALLOWED" operation="open" parent=8012 profile="/usr/bin/du" name="/etc/apparmor.d/tunables/" pid=8049 comm="du" requested_mask="r" denied_mask="r" fsuid=1000 ouid=0 If the userland auditd is not running, the kernel will send audit events to klogd; klogd will send the messages to syslog, which will log the messages with the KERN facility. Thus, REJECTING and PERMITTING messages may go to either F or F, depending upon local configuration. =head1 FILES =over 4 =item F =item F =item F =item F =item F =back =head1 SEE ALSO apparmor_parser(8), aa_change_hat(2), apparmor.d(5), subdomain.conf(5), aa-autodep(1), clean(1), auditd(8), aa-unconfined(8), aa-enforce(1), aa-complain(1), and L. =cut apparmor-2.10.95/parser/profile.h0000664000175000017500000001061612504631026015661 0ustar stevesteve/* * Copyright (c) 2012 * Canonical, Ltd. (All rights reserved) * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License published by the Free Software Foundation. * * This program 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. See the * GNU General Public License for more details. */ #ifndef __AA_PROFILE_H #define __AA_PROFILE_H #include #include #include #include "parser.h" #include "rule.h" #include "libapparmor_re/aare_rules.h" #include "network.h" class Profile; class block { public: }; struct deref_profileptr_lt { bool operator()(Profile * const &lhs, Profile * const &rhs) const; }; class ProfileList { public: set list; typedef set::iterator iterator; iterator begin() { return list.begin(); } iterator end() { return list.end(); } ProfileList() { }; virtual ~ProfileList() { clear(); } virtual bool empty(void) { return list.empty(); } virtual pair insert(Profile *); virtual void erase(ProfileList::iterator pos); void clear(void); void dump(void); void dump_profile_names(bool children); }; class flagvals { public: int hat; int complain; int audit; int path; void dump(void) { printf("Profile Mode:\t"); if (complain) printf("Complain"); else printf("Enforce"); if (audit) printf(", Audit"); if (hat) printf(", Hat"); printf("\n"); } }; struct capabilities { uint64_t allow; uint64_t audit; uint64_t deny; uint64_t quiet; capabilities(void) { allow = audit = deny = quiet = 0; } void dump() { if (allow != 0ull) __debug_capabilities(allow, "Capabilities"); if (audit != 0ull) __debug_capabilities(audit, "Audit Caps"); if (deny != 0ull) __debug_capabilities(deny, "Deny Caps"); if (quiet != 0ull) __debug_capabilities(quiet, "Quiet Caps"); }; }; struct dfa_stuff { aare_rules *rules; void *dfa; size_t size; dfa_stuff(void): rules(NULL), dfa(NULL), size(0) { } }; class Profile { public: char *ns; char *name; char *attachment; struct alt_name *altnames; void *xmatch; size_t xmatch_size; int xmatch_len; /* char *sub_name; */ /* subdomain name or NULL */ /* int default_deny; */ /* TRUE or FALSE */ int local; int local_mode; /* true if local, not hat */ int local_audit; Profile *parent; flagvals flags; struct capabilities caps; struct network net; struct aa_rlimits rlimits; char *exec_table[AA_EXEC_COUNT]; struct cod_entry *entries; RuleList rule_ents; ProfileList hat_table; struct dfa_stuff dfa; struct dfa_stuff policy; Profile(void) { ns = name = attachment = NULL; altnames = NULL; xmatch = NULL; xmatch_size = 0; xmatch_len = 0; local = local_mode = local_audit = 0; parent = NULL; flags = { 0, 0, 0, 0}; rlimits = {0, {}}; std::fill(exec_table, exec_table + AA_EXEC_COUNT, (char *)NULL); entries = NULL; }; virtual ~Profile(); bool operator<(Profile const &rhs)const { if (ns) { if (rhs.ns) { int res = strcmp(ns, rhs.ns); if (res != 0) return res < 0; } else return false; } else if (rhs.ns) return true; return strcmp(name, rhs.name) < 0; } void dump(void) { if (ns) printf("Ns:\t\t%s\n", ns); if (name) printf("Name:\t\t%s\n", name); else printf("Name:\t\t\n"); if (local) { if (parent) printf("Local To:\t%s\n", parent->name); else printf("Local To:\t\n"); } flags.dump(); caps.dump(); net.dump(); if (entries) debug_cod_entries(entries); for (RuleList::iterator i = rule_ents.begin(); i != rule_ents.end(); i++) { (*i)->dump(cout); } printf("\n"); hat_table.dump(); } bool alloc_net_table(); std::string hname(void) { if (!parent) return name; return parent->hname() + "//" + name; } /* assumes ns is set as part of profile creation */ std::string fqname(void) { if (parent) return parent->fqname() + "://" + name; else if (!ns) return hname(); return ":" + std::string(ns) + "://" + hname(); } std::string get_name(bool fqp) { if (fqp) return fqname(); return hname(); } void dump_name(bool fqp) { cout << get_name(fqp);; } }; #endif /* __AA_PROFILE_H */ apparmor-2.10.95/parser/immunix.h0000664000175000017500000001250312534527215015713 0ustar stevesteve/* * Copyright (c) 1999, 2000, 2001, 2002, 2004, 2005, 2006, 2007 * NOVELL (All rights reserved) * * 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. * * This program 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. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, contact Novell, Inc. */ #ifndef _IMMUNIX_H #define _IMMUNIX_H /* * Modeled after MAY_READ, MAY_WRITE, MAY_EXEC in the kernel. The value of * AA_MAY_EXEC must be identical to MAY_EXEC, etc. */ #define AA_MAY_EXEC (1 << 0) #define AA_MAY_WRITE (1 << 1) #define AA_MAY_READ (1 << 2) #define AA_MAY_APPEND (1 << 3) #define AA_OLD_MAY_LINK (1 << 4) #define AA_OLD_MAY_LOCK (1 << 5) #define AA_OLD_EXEC_MMAP (1 << 6) #define AA_EXEC_PUX (1 << 7) #define AA_EXEC_UNSAFE (1 << 8) #define AA_EXEC_INHERIT (1 << 9) #define AA_EXEC_MOD_0 (1 << 10) #define AA_EXEC_MOD_1 (1 << 11) #define AA_EXEC_MOD_2 (1 << 12) #define AA_EXEC_MOD_3 (1 << 13) #define AA_BASE_PERMS (AA_MAY_EXEC | AA_MAY_WRITE | \ AA_MAY_READ | AA_MAY_APPEND | \ AA_OLD_MAY_LINK | AA_OLD_MAY_LOCK | \ AA_EXEC_PUX | AA_OLD_EXEC_MMAP | \ AA_EXEC_UNSAFE | AA_EXEC_INHERIT | \ AA_EXEC_MOD_0 | AA_EXEC_MOD_1 | \ AA_EXEC_MOD_2 | AA_EXEC_MOD_3) #define AA_USER_SHIFT 0 #define AA_OTHER_SHIFT 14 #define AA_USER_PERMS (AA_BASE_PERMS << AA_USER_SHIFT) #define AA_OTHER_PERMS (AA_BASE_PERMS << AA_OTHER_SHIFT) #define AA_FILE_PERMS (AA_USER_PERMS | AA_OTHER_PERMS ) #define AA_CHANGE_HAT (1 << 30) #define AA_ONEXEC (1 << 30) #define AA_CHANGE_PROFILE (1 << 31) #define AA_SHARED_PERMS (AA_CHANGE_HAT | AA_CHANGE_PROFILE) #define AA_EXEC_MODIFIERS (AA_EXEC_MOD_0 | AA_EXEC_MOD_1 | \ AA_EXEC_MOD_2 | AA_EXEC_MOD_3) #define AA_EXEC_COUNT 16 #define AA_USER_EXEC_MODIFIERS (AA_EXEC_MODIFIERS << AA_USER_SHIFT) #define AA_OTHER_EXEC_MODIFIERS (AA_EXEC_MODIFIERS << AA_OTHER_SHIFT) #define AA_ALL_EXEC_MODIFIERS (AA_USER_EXEC_MODIFIERS | \ AA_OTHER_EXEC_MODIFIERS) #define AA_EXEC_TYPE (AA_EXEC_UNSAFE | AA_EXEC_INHERIT | \ AA_EXEC_PUX | AA_EXEC_MODIFIERS) #define AA_EXEC_UNCONFINED (AA_EXEC_MOD_0) #define AA_EXEC_PROFILE (AA_EXEC_MOD_1) #define AA_EXEC_LOCAL (AA_EXEC_MOD_0 | AA_EXEC_MOD_1) #define AA_VALID_PERMS (AA_FILE_PERMS | AA_OTHER_PERMS) #define AA_USER_EXEC (AA_MAY_EXEC << AA_USER_SHIFT) #define AA_OTHER_EXEC (AA_MAY_EXEC << AA_OTHER_SHIFT) #define AA_EXEC_BITS (AA_USER_EXEC | AA_OTHER_EXEC) #define ALL_AA_EXEC_UNSAFE ((AA_EXEC_UNSAFE << AA_USER_SHIFT) | \ (AA_EXEC_UNSAFE << AA_OTHER_SHIFT)) #define AA_USER_EXEC_TYPE (AA_EXEC_TYPE << AA_USER_SHIFT) #define AA_OTHER_EXEC_TYPE (AA_EXEC_TYPE << AA_OTHER_SHIFT) #define ALL_AA_EXEC_TYPE (AA_USER_EXEC_TYPE | AA_OTHER_EXEC_TYPE) #define ALL_USER_EXEC (AA_USER_EXEC | AA_USER_EXEC_TYPE) #define ALL_OTHER_EXEC (AA_OTHER_EXEC | AA_OTHER_EXEC_TYPE) #define AA_LINK_BITS ((AA_OLD_MAY_LINK << AA_USER_SHIFT) | \ (AA_OLD_MAY_LINK << AA_OTHER_SHIFT)) #define SHIFT_MODE(MODE, SHIFT) ((((MODE) & AA_BASE_PERMS) << (SHIFT))\ | ((MODE) & ~AA_FILE_PERMS)) #define SHIFT_TO_BASE(MODE, SHIFT) ((((MODE) & AA_FILE_PERMS) >> (SHIFT))\ | ((MODE) & ~AA_FILE_PERMS)) #define AA_LINK_SUBSET_TEST (AA_OLD_MAY_LINK << 1) #define LINK_SUBSET_BITS ((AA_LINK_SUBSET_TEST << AA_USER_SHIFT) | \ (AA_LINK_SUBSET_TEST << AA_OTHER_SHIFT)) #define LINK_TO_LINK_SUBSET(X) (((X) << 1) & AA_LINK_SUBSET_TEST) /* Pack the audit, and quiet masks into a single 28 bit field in the * format oq:oa:uq:ua */ #define PACK_AUDIT_CTL(audit, quiet) (((audit) & 0x1fc07f) | \ (((quiet) & 0x1fc07f) << 7)) #define AA_HAT_SIZE 975 /* Maximum size of a subdomain * ident (hat) */ #define AA_IP_TCP 0x0001 #define AA_IP_UDP 0x0002 #define AA_IP_RDP 0x0004 #define AA_IP_RAW 0x0008 #define AA_IPV6_TCP 0x0010 #define AA_IPV6_UDP 0x0020 #define AA_NETLINK 0x0040 enum pattern_t { ePatternBasic, ePatternTailGlob, ePatternRegex, ePatternInvalid, }; #define HAS_MAY_READ(mode) ((mode) & AA_MAY_READ) #define HAS_MAY_WRITE(mode) ((mode) & AA_MAY_WRITE) #define HAS_MAY_APPEND(mode) ((mode) & AA_MAY_APPEND) #define HAS_MAY_EXEC(mode) ((mode) & AA_MAY_EXEC) #define HAS_MAY_LINK(mode) ((mode) & AA_OLD_MAY_LINK) #define HAS_MAY_LOCK(mode) ((mode) & AA_OLD_MAY_LOCK) #define HAS_EXEC_MMAP(mode) ((mode) & AA_OLD_EXEC_MMAP) #define HAS_EXEC_UNSAFE(mode) ((mode) & AA_EXEC_UNSAFE) #define HAS_CHANGE_PROFILE(mode) ((mode) & AA_CHANGE_PROFILE) #include static inline int is_merged_x_consistent(int a, int b) { if ((a & AA_USER_EXEC) && (b & AA_USER_EXEC) && ((a & AA_USER_EXEC_TYPE) != (b & AA_USER_EXEC_TYPE))) { //fprintf(stderr, "failed user merge 0x%x 0x%x\n", a, b); return 0; } if ((a & AA_OTHER_EXEC) && (b & AA_OTHER_EXEC) && ((a & AA_OTHER_EXEC_TYPE) != (b & AA_OTHER_EXEC_TYPE))) { //fprintf(stderr, "failed other merge 0x%x 0x%x\n", a, b); return 0; } return 1; } #endif /* ! _IMMUNIX_H */ /* LocalWords: MMAP */ apparmor-2.10.95/parser/rc.apparmor.suse0000664000175000017500000000570212554262601017201 0ustar stevesteve#!/bin/sh # ---------------------------------------------------------------------- # Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 # NOVELL (All rights reserved) # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, contact Novell, Inc. # ---------------------------------------------------------------------- # rc.apparmor by Steve Beattie # # /etc/init.d/boot.apparmor # and its symbolic link # /sbin/rcapparmor # # chkconfig: 2345 01 99 # description: AppArmor rc file. This rc script inserts the apparmor \ # module and runs the parser on the /etc/apparmor.d/ \ # directory. # ### BEGIN INIT INFO # Provides: apparmor # Required-Start: boot.cleanup # Required-Stop: $null # Should-Start: $local_fs # Should-Stop: $null # Default-Start: B # Default-Stop: # Short-Description: AppArmor initialization # Description: AppArmor rc file. This rc script inserts the apparmor # module and runs the parser on the /etc/apparmor.d/ # directory. ### END INIT INFO APPARMOR_FUNCTIONS=/lib/apparmor/rc.apparmor.functions # source function library if [ -f /etc/init.d/functions ]; then . /etc/init.d/functions elif [ -f /etc/rc.d/init.d/functions ]; then . /etc/rc.d/init.d/functions elif [ -f /lib/lsb/init-functions ]; then . /lib/lsb/init-functions else exit 0 fi # Ugh, SUSE doesn't implement action aa_action() { STRING=$1 shift "$@" rc=$? if [ $rc -eq 0 ] ; then log_success_msg $"$STRING " else log_failure_msg $"$STRING " fi return $rc } aa_log_success_msg() { log_success_msg $* } aa_log_warning_msg() { log_warning_msg $* } aa_log_failure_msg() { log_failure_msg '\n'$* } aa_log_action_start() { echo -n } aa_log_action_end() { echo -n } aa_log_daemon_msg() { echo -en "$@ " } aa_log_skipped_msg() { echo -en "$@" echo -e "$rc_skipped" } _set_status() { return $1 } aa_log_end_msg() { _set_status $1 rc_status -v } usage() { echo "Usage: $0 {start|stop|restart|try-restart|reload|force-reload|status|kill}" } # source apparmor function library if [ -f "${APPARMOR_FUNCTIONS}" ]; then . ${APPARMOR_FUNCTIONS} else aa_log_failure_msg "Unable to find AppArmor initscript functions" exit 1 fi case "$1" in start) apparmor_start rc=$? ;; stop) apparmor_stop rc=$? ;; restart|reload|force-reload) apparmor_restart rc=$? ;; try-restart) apparmor_try_restart rc=$? ;; kill) apparmor_kill rc=$? ;; status) apparmor_status rc=$? ;; *) usage exit 1 ;; esac exit $rc apparmor-2.10.95/parser/parser_yacc.y0000664000175000017500000011645712673100443016550 0ustar stevesteve%{ /* * Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 * NOVELL (All rights reserved) * Copyright (c) 2010-2012 * Canonical Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License published by the Free Software Foundation. * * This program 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. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, contact Canonical, Ltd. */ #define YYERROR_VERBOSE 1 #include #include #include #include #include #include #include #include #include /* #define DEBUG */ #include "parser.h" #include "profile.h" #include "mount.h" #include "dbus.h" #include "af_unix.h" #include "parser_include.h" #include #include #include #include #ifndef CAP_AUDIT_WRITE #define CAP_AUDIT_WRITE 29 #endif #ifndef CAP_AUDIT_CONTROL #define CAP_AUDIT_CONTROL 30 #endif #ifndef CAP_SETFCAP #define CAP_SETFCAP 31 #endif #ifndef CAP_MAC_OVERRIDE #define CAP_MAC_OVERRIDE 32 #endif #define CIDR_32 htonl(0xffffffff) #define CIDR_24 htonl(0xffffff00) #define CIDR_16 htonl(0xffff0000) #define CIDR_8 htonl(0xff000000) /* undefine linux/capability.h CAP_TO_MASK */ #ifdef CAP_TO_MASK #undef CAP_TO_MASK #endif #define CAP_TO_MASK(x) (1ull << (x)) int parser_token = 0; struct cod_entry *do_file_rule(char *id, int mode, char *link_id, char *nt); mnt_rule *do_mnt_rule(struct cond_entry *src_conds, char *src, struct cond_entry *dst_conds, char *dst, int mode); mnt_rule *do_pivot_rule(struct cond_entry *old, char *root, char *transition); void add_local_entry(Profile *prof); %} %token TOK_ID %token TOK_CONDID %token TOK_CONDLISTID %token TOK_CARET %token TOK_OPEN %token TOK_CLOSE %token TOK_MODE %token TOK_END_OF_RULE %token TOK_EQUALS %token TOK_ARROW %token TOK_ADD_ASSIGN %token TOK_LE %token TOK_SET_VAR %token TOK_BOOL_VAR %token TOK_VALUE %token TOK_IF %token TOK_ELSE %token TOK_NOT %token TOK_DEFINED %token TOK_CHANGE_PROFILE %token TOK_NETWORK %token TOK_UNIX %token TOK_CREATE %token TOK_SHUTDOWN %token TOK_ACCEPT %token TOK_CONNECT %token TOK_LISTEN %token TOK_SETOPT %token TOK_GETOPT %token TOK_SETATTR %token TOK_GETATTR %token TOK_HAT %token TOK_UNSAFE %token TOK_SAFE %token TOK_COLON %token TOK_LINK %token TOK_OWNER %token TOK_OTHER %token TOK_SUBSET %token TOK_AUDIT %token TOK_DENY %token TOK_ALLOW %token TOK_PROFILE %token TOK_SET %token TOK_ALIAS %token TOK_PTRACE %token TOK_OPENPAREN %token TOK_CLOSEPAREN %token TOK_COMMA %token TOK_FILE %token TOK_MOUNT %token TOK_REMOUNT %token TOK_UMOUNT %token TOK_PIVOTROOT %token TOK_IN %token TOK_DBUS %token TOK_SIGNAL %token TOK_SEND %token TOK_RECEIVE %token TOK_BIND %token TOK_READ %token TOK_WRITE %token TOK_EAVESDROP %token TOK_PEER %token TOK_TRACE %token TOK_TRACEDBY %token TOK_READBY /* rlimits */ %token TOK_RLIMIT %token TOK_SOFT_RLIMIT %token TOK_RLIMIT_CPU %token TOK_RLIMIT_FSIZE %token TOK_RLIMIT_DATA %token TOK_RLIMIT_STACK %token TOK_RLIMIT_CORE %token TOK_RLIMIT_RSS %token TOK_RLIMIT_NOFILE %token TOK_RLIMIT_OFILE %token TOK_RLIMIT_AS %token TOK_RLIMIT_NPROC %token TOK_RLIMIT_MEMLOCK %token TOK_RLIMIT_LOCKS %token TOK_RLIMIT_SIGPENDING %token TOK_RLIMIT_MSGQUEUE %token TOK_RLIMIT_NICE %token TOK_RLIMIT_RTPRIO /* capabilities */ %token TOK_CAPABILITY /* debug flag values */ %token TOK_FLAGS %code requires { #include "parser.h" #include "profile.h" #include "mount.h" #include "dbus.h" #include "signal.h" #include "ptrace.h" #include "af_unix.h" } %union { char *id; char *flag_id; char *mode; struct aa_network_entry *network_entry; Profile *prof; struct cod_net_entry *net_entry; struct cod_entry *user_entry; mnt_rule *mnt_entry; dbus_rule *dbus_entry; signal_rule *signal_entry; ptrace_rule *ptrace_entry; unix_rule *unix_entry; flagvals flags; int fmode; uint64_t cap; unsigned int allowed_protocol; char *set_var; char *bool_var; char *var_val; struct value_list *val_list; struct cond_entry *cond_entry; struct cond_entry_list cond_entry_list; int boolean; struct prefixes prefix; } %type TOK_ID %type TOK_CONDID %type TOK_CONDLISTID %type TOK_MODE %type file_mode %type profile_base %type profile %type rules %type hat %type local_profile %type cond_rule %type network_rule %type rule %type file_rule %type file_rule_tail %type link_rule %type frule %type mnt_rule %type opt_conds %type cond %type cond_list %type opt_cond_list %type flags %type flagvals %type flagval %type caps %type capability %type change_profile_head %type change_profile %type TOK_SET_VAR %type TOK_BOOL_VAR %type TOK_VALUE %type valuelist %type expr %type id_or_var %type opt_id_or_var %type opt_subset_flag %type opt_audit_flag %type opt_owner_flag %type opt_profile_flag %type opt_flags %type opt_perm_mode %type opt_id %type opt_prefix %type dbus_perm %type dbus_perms %type opt_dbus_perm %type dbus_rule %type signal_perm %type signal_perms %type opt_signal_perm %type signal_rule %type ptrace_perm %type ptrace_perms %type opt_ptrace_perm %type ptrace_rule %type net_perm %type net_perms %type opt_net_perm %type unix_rule %type opt_target %type opt_named_transition %type opt_unsafe %type opt_file %% list: preamble profilelist { /* nothing */ }; profilelist: { /* nothing */ }; profilelist: profilelist profile { PDEBUG("Matched: list profile\n"); add_to_list($2); }; opt_profile_flag: { /* nothing */ $$ = 0; } | TOK_PROFILE { $$ = 1; } | hat_start { $$ = 2; } opt_id: { /* nothing */ $$ = NULL; } | TOK_ID { $$ = $1; } opt_id_or_var: { /* nothing */ $$ = NULL; } | id_or_var { $$ = $1; } profile_base: TOK_ID opt_id_or_var flags TOK_OPEN rules TOK_CLOSE { Profile *prof = $5; bool self_stack = false; if (!prof) { yyerror(_("Memory allocation error.")); } parse_label(&self_stack, &prof->ns, &prof->name, $1, true); free($1); if (self_stack) { yyerror(_("Profile names must begin with a '/' or a namespace")); } /* Honor the --namespace-string command line option */ if (profile_ns) { /** * Print warning if the profile specified a namespace * different than the one specified with the * --namespace-string command line option */ if (prof->ns && strcmp(prof->ns, profile_ns)) pwarn("%s: -n %s overriding policy specified namespace :%s:\n", progname, profile_ns, prof->ns); free(prof->ns); prof->ns = strdup(profile_ns); if (!prof->ns) yyerror(_("Memory allocation error.")); } prof->attachment = $2; if ($2 && !($2[0] == '/' || strncmp($2, "@{", 2) == 0)) yyerror(_("Profile attachment must begin with a '/' or variable.")); prof->flags = $3; if (force_complain && kernel_abi_version == 5) /* newer abis encode force complain as part of the * header */ prof->flags.complain = 1; post_process_file_entries(prof); post_process_rule_entries(prof); PDEBUG("%s: flags='%s%s'\n", $2, prof->flags.complain ? "complain, " : "", prof->flags.audit ? "audit" : ""); $$ = prof; }; profile: opt_profile_flag profile_base { Profile *prof = $2; if ($2->ns) PDEBUG("Matched: :%s://%s { ... }\n", $2->ns, $2->name); else PDEBUG("Matched: %s { ... }\n", $2->name); if ($2->name[0] != '/' && !($1 || $2->ns)) yyerror(_("Profile names must begin with a '/', namespace or keyword 'profile' or 'hat'.")); if ($1 == 2) prof->flags.hat = 1; $$ = prof; }; local_profile: TOK_PROFILE profile_base { Profile *prof = $2; if ($2) PDEBUG("Matched: local profile %s { ... }\n", prof->name); prof->local = 1; $$ = prof; }; hat: hat_start profile_base { Profile *prof = $2; if ($2) PDEBUG("Matched: hat %s { ... }\n", prof->name); prof->flags.hat = 1; $$ = prof; }; preamble: { /* nothing */ } | preamble alias { /* nothing */ }; | preamble varassign { /* nothing */ }; alias: TOK_ALIAS TOK_ID TOK_ARROW TOK_ID TOK_END_OF_RULE { if (!new_alias($2, $4)) yyerror(_("Failed to create alias %s -> %s\n"), $2, $4); free($2); free($4); }; varassign: TOK_SET_VAR TOK_EQUALS valuelist { struct value_list *list = $3; char *var_name = process_var($1); int err; if (!list || !list->value) yyerror("Assert: valuelist returned NULL"); PDEBUG("Matched: set assignment for (%s)\n", $1); err = new_set_var(var_name, list->value); if (err) { free(var_name); yyerror("variable %s was previously declared", $1); /* FIXME: it'd be handy to report the previous location */ } for (list = list->next; list; list = list->next) { err = add_set_value(var_name, list->value); if (err) { free(var_name); yyerror("Error adding %s to set var %s", list->value, $1); } } free_value_list($3); free(var_name); free($1); } varassign: TOK_SET_VAR TOK_ADD_ASSIGN valuelist { struct value_list *list = $3; char *var_name = process_var($1); int err; if (!list || !list->value) yyerror("Assert: valuelist returned NULL"); PDEBUG("Matched: additive assignment for (%s)\n", $1); /* do the first one outside the loop, subsequent * failures are indicative of symtab failures */ err = add_set_value(var_name, list->value); if (err) { free(var_name); yyerror("variable %s was not previously declared, but is being assigned additional values", $1); } for (list = list->next; list; list = list->next) { err = add_set_value(var_name, list->value); if (err) { free(var_name); yyerror("Error adding %s to set var %s", list->value, $1); } } free_value_list($3); free(var_name); free($1); } varassign: TOK_BOOL_VAR TOK_EQUALS TOK_VALUE { int boolean, err; char *var_name = process_var($1); PDEBUG("Matched: boolean assignment (%s) to %s\n", $1, $3); boolean = str_to_boolean($3); if (boolean == -1) { yyerror("Invalid boolean assignment for (%s): %s is not true or false", $1, $3); } err = add_boolean_var(var_name, boolean); free(var_name); if (err) { yyerror("variable %s was previously declared", $1); /* FIXME: it'd be handy to report the previous location */ } free($1); free($3); } valuelist: TOK_VALUE { struct value_list *val = new_value_list($1); if (!val) yyerror(_("Memory allocation error.")); PDEBUG("Matched: value (%s)\n", $1); $$ = val; } valuelist: valuelist TOK_VALUE { struct value_list *val = new_value_list($2); if (!val) yyerror(_("Memory allocation error.")); PDEBUG("Matched: value list\n"); list_append($1, val); $$ = $1; } flags: { /* nothing */ flagvals fv = { 0, 0, 0, 0 }; $$ = fv; }; opt_flags: { /* nothing */ $$ = 0; } | TOK_CONDID TOK_EQUALS { if (strcmp($1, "flags") != 0) yyerror("expected flags= got %s=", $1); free($1); $$ = 1; } flags: opt_flags TOK_OPENPAREN flagvals TOK_CLOSEPAREN { $$ = $3; }; flagvals: flagvals flagval { $1.complain = $1.complain || $2.complain; $1.audit = $1.audit || $2.audit; $1.path = $1.path | $2.path; if (($1.path & (PATH_CHROOT_REL | PATH_NS_REL)) == (PATH_CHROOT_REL | PATH_NS_REL)) yyerror(_("Profile flag chroot_relative conflicts with namespace_relative")); if (($1.path & (PATH_MEDIATE_DELETED | PATH_DELEGATE_DELETED)) == (PATH_MEDIATE_DELETED | PATH_DELEGATE_DELETED)) yyerror(_("Profile flag mediate_deleted conflicts with delegate_deleted")); if (($1.path & (PATH_ATTACH | PATH_NO_ATTACH)) == (PATH_ATTACH | PATH_NO_ATTACH)) yyerror(_("Profile flag attach_disconnected conflicts with no_attach_disconnected")); if (($1.path & (PATH_CHROOT_NSATTACH | PATH_CHROOT_NO_ATTACH)) == (PATH_CHROOT_NSATTACH | PATH_CHROOT_NO_ATTACH)) yyerror(_("Profile flag chroot_attach conflicts with chroot_no_attach")); $$ = $1; }; flagvals: flagval { $$ = $1; }; flagval: TOK_VALUE { flagvals fv = { 0, 0, 0, 0 }; if (strcmp($1, "debug") == 0) { yyerror(_("Profile flag 'debug' is no longer valid.")); } else if (strcmp($1, "complain") == 0) { fv.complain = 1; } else if (strcmp($1, "audit") == 0) { fv.audit = 1; } else if (strcmp($1, "chroot_relative") == 0) { fv.path |= PATH_CHROOT_REL; } else if (strcmp($1, "namespace_relative") == 0) { fv.path |= PATH_NS_REL; } else if (strcmp($1, "mediate_deleted") == 0) { fv.path |= PATH_MEDIATE_DELETED; } else if (strcmp($1, "delegate_deleted") == 0) { fv.path |= PATH_DELEGATE_DELETED; } else if (strcmp($1, "attach_disconnected") == 0) { fv.path |= PATH_ATTACH; } else if (strcmp($1, "no_attach_disconnected") == 0) { fv.path |= PATH_NO_ATTACH; } else if (strcmp($1, "chroot_attach") == 0) { fv.path |= PATH_CHROOT_NSATTACH; } else if (strcmp($1, "chroot_no_attach") == 0) { fv.path |= PATH_CHROOT_NO_ATTACH; } else { yyerror(_("Invalid profile flag: %s."), $1); } free($1); $$ = fv; }; opt_subset_flag: { /* nothing */ $$ = 0; } | TOK_SUBSET { $$ = 1; } | TOK_LE { $$ = 1; } opt_audit_flag: { /* nothing */ $$ = 0; } | TOK_AUDIT { $$ = 1; }; opt_owner_flag: { /* nothing */ $$ = 0; } | TOK_OWNER { $$ = 1; }; | TOK_OTHER { $$ = 2; }; opt_perm_mode: { /* nothing */ $$ = 0; } | TOK_ALLOW { $$ = 0; } | TOK_DENY { $$ = 1; } opt_prefix: opt_audit_flag opt_perm_mode opt_owner_flag { $$.audit = $1; $$.deny = $2; $$.owner = $3; } rules: { /* nothing */ Profile *prof = new Profile(); if (!prof) { yyerror(_("Memory allocation error.")); } $$ = prof; }; rules: rules opt_prefix rule { PDEBUG("matched: rules rule\n"); PDEBUG("rules rule: (%s)\n", $3->name); if (!$3) yyerror(_("Assert: `rule' returned NULL.")); $3->deny = $2.deny; if (($2.deny && ($3->mode & AA_EXEC_BITS) && ($3->mode & ALL_AA_EXEC_TYPE))) yyerror(_("Invalid mode, in deny rules 'x' must not be preceded by exec qualifier 'i', 'p', or 'u'")); else if (!$2.deny && ($3->mode & AA_EXEC_BITS) && !($3->mode & ALL_AA_EXEC_TYPE) && !($3->nt_name)) yyerror(_("Invalid mode, 'x' must be preceded by exec qualifier 'i', 'p', 'c', or 'u'")); if ($2.owner == 1) $3->mode &= (AA_USER_PERMS | AA_SHARED_PERMS); else if ($2.owner == 2) $3->mode &= (AA_OTHER_PERMS | AA_SHARED_PERMS); /* only set audit ctl quieting if the rule is not audited */ if (($2.deny && !$2.audit) || (!$2.deny && $2.audit)) $3->audit = $3->mode & ~ALL_AA_EXEC_TYPE; add_entry_to_policy($1, $3); $$ = $1; }; rules: rules opt_prefix TOK_OPEN rules TOK_CLOSE { struct cod_entry *entry, *tmp; if ($2.deny) yyerror(_("deny prefix not allowed")); PDEBUG("matched: %s%s%sblock\n", $2.audit ? "audit " : "", $2.deny ? "deny " : "", $2.owner ? "owner " : ""); list_for_each_safe($4->entries, entry, tmp) { entry->next = NULL; if (entry->mode & AA_EXEC_BITS) { if (entry->deny && (entry->mode & ALL_AA_EXEC_TYPE)) yyerror(_("Invalid mode, in deny rules 'x' must not be preceded by exec qualifier 'i', 'p', or 'u'")); else if (!entry->deny && !(entry->mode & ALL_AA_EXEC_TYPE)) yyerror(_("Invalid mode, 'x' must be preceded by exec qualifier 'i', 'p', or 'u'")); } if ($2.owner == 1) entry->mode &= (AA_USER_PERMS | AA_SHARED_PERMS); else if ($2.owner == 2) entry->mode &= (AA_OTHER_PERMS | AA_SHARED_PERMS); if ($2.audit && !entry->deny) entry->audit = entry->mode & ~ALL_AA_EXEC_TYPE; else if (!$2.audit && entry->deny) entry->audit = entry->mode & ~ALL_AA_EXEC_TYPE; add_entry_to_policy($1, entry); } $4->entries = NULL; // fix me transfer rules and free sub profile delete $4; $$ = $1; }; rules: rules opt_prefix network_rule { struct aa_network_entry *entry, *tmp; PDEBUG("Matched: network rule\n"); if ($2.owner) yyerror(_("owner prefix not allowed")); if (!$3) yyerror(_("Assert: `network_rule' return invalid protocol.")); if (!$1->alloc_net_table()) yyerror(_("Memory allocation error.")); list_for_each_safe($3, entry, tmp) { /* map to extended mediation if available */ if (entry->family == AF_UNIX && kernel_supports_unix) { unix_rule *rule = new unix_rule(entry->type, $2.audit, $2.deny); if (!rule) yyerror(_("Memory allocation error.")); $1->rule_ents.push_back(rule); } if (entry->type > SOCK_PACKET) { /* setting mask instead of a bit */ if ($2.deny) { $1->net.deny[entry->family] |= entry->type; if (!$2.audit) $1->net.quiet[entry->family] |= entry->type; } else { $1->net.allow[entry->family] |= entry->type; if ($2.audit) $1->net.audit[entry->family] |= entry->type; } } else { if ($2.deny) { $1->net.deny[entry->family] |= 1 << entry->type; if (!$2.audit) $1->net.quiet[entry->family] |= 1 << entry->type; } else { $1->net.allow[entry->family] |= 1 << entry->type; if ($2.audit) $1->net.audit[entry->family] |= 1 << entry->type; } } free(entry); } $$ = $1; } rules: rules opt_prefix mnt_rule { if ($2.owner) yyerror(_("owner prefix not allowed on mount rules")); if ($2.deny && $2.audit) { $3->deny = 1; } else if ($2.deny) { $3->deny = 1; $3->audit = $3->allow; } else if ($2.audit) { $3->audit = $3->allow; } $1->rule_ents.push_back($3); $$ = $1; } rules: rules opt_prefix dbus_rule { if ($2.owner) yyerror(_("owner prefix not allowed on dbus rules")); if ($2.deny && $2.audit) { $3->deny = 1; } else if ($2.deny) { $3->deny = 1; $3->audit = $3->mode; } else if ($2.audit) { $3->audit = $3->mode; } $1->rule_ents.push_back($3); $$ = $1; } rules: rules opt_prefix signal_rule { if ($2.owner) yyerror(_("owner prefix not allowed on signal rules")); if ($2.deny && $2.audit) { $3->deny = 1; } else if ($2.deny) { $3->deny = 1; $3->audit = $3->mode; } else if ($2.audit) { $3->audit = $3->mode; } $1->rule_ents.push_back($3); $$ = $1; } rules: rules opt_prefix ptrace_rule { if ($2.owner) yyerror(_("owner prefix not allowed on ptrace rules")); if ($2.deny && $2.audit) { $3->deny = 1; } else if ($2.deny) { $3->deny = 1; $3->audit = $3->mode; } else if ($2.audit) { $3->audit = $3->mode; } $1->rule_ents.push_back($3); $$ = $1; } rules: rules opt_prefix unix_rule { if ($2.owner) yyerror(_("owner prefix not allowed on unix rules")); if ($2.deny && $2.audit) { $3->deny = 1; } else if ($2.deny) { $3->deny = 1; $3->audit = $3->mode; } else if ($2.audit) { $3->audit = $3->mode; } $1->rule_ents.push_back($3); $$ = $1; } rules: rules opt_prefix change_profile { PDEBUG("matched: rules change_profile\n"); PDEBUG("rules change_profile: (%s)\n", $3->name); if (!$3) yyerror(_("Assert: `change_profile' returned NULL.")); if ($2.owner) yyerror(_("owner prefix not allowed on unix rules")); if ($2.deny && $2.audit) { $3->deny = 1; } else if ($2.deny) { $3->deny = 1; $3->audit = $3->mode; } else if ($2.audit) { $3->audit = $3->mode; } add_entry_to_policy($1, $3); $$ = $1; }; rules: rules opt_prefix capability { if ($2.owner) yyerror(_("owner prefix not allowed on capability rules")); if ($2.deny && $2.audit) { $1->caps.deny |= $3; } else if ($2.deny) { $1->caps.deny |= $3; $1->caps.quiet |= $3; } else { $1->caps.allow |= $3; if ($2.audit) $1->caps.audit |= $3; } $$ = $1; }; rules: rules hat { PDEBUG("Matched: hat rule\n"); if (!$2) yyerror(_("Assert: 'hat rule' returned NULL.")); add_hat_to_policy($1, $2); $$ = $1; }; rules: rules local_profile { PDEBUG("Matched: hat rule\n"); if (!$2) yyerror(_("Assert: 'local_profile rule' returned NULL.")); add_hat_to_policy($1, $2); add_local_entry($2); $$ = $1; }; rules: rules cond_rule { PDEBUG("Matched: conditional rules\n"); $$ = merge_policy($1, $2); } rules: rules TOK_SET TOK_RLIMIT TOK_ID TOK_LE TOK_VALUE opt_id TOK_END_OF_RULE { rlim_t value = RLIM_INFINITY; long long tmp; char *end; int limit = get_rlimit($4); if (limit == -1) yyerror("INVALID RLIMIT '%s'\n", $4); if (strcmp($6, "infinity") == 0) { value = RLIM_INFINITY; } else { const char *kb = "KB"; const char *mb = "MB"; const char *gb = "GB"; tmp = strtoll($6, &end, 0); switch (limit) { case RLIMIT_CPU: if (!end || $6 == end || tmp < 0) yyerror("RLIMIT '%s' invalid value %s\n", $4, $6); tmp = convert_time_units(tmp, SECONDS_P_MS, $7); if (tmp == -1LL) yyerror("RLIMIT '%s %s' < minimum value of 1s\n", $4, $6); else if (tmp < 0LL) yyerror("RLIMIT '%s' invalid value %s\n", $4, $6); if (!$7) pwarn(_("RLIMIT 'cpu' no units specified using default units of seconds\n")); value = tmp; break; case RLIMIT_RTTIME: /* RTTIME is measured in microseconds */ if (!end || $6 == end || tmp < 0) yyerror("RLIMIT '%s' invalid value %s %s\n", $4, $6, $7 ? $7 : ""); tmp = convert_time_units(tmp, 1LL, $7); if (tmp < 0LL) yyerror("RLIMIT '%s' invalid value %s %s\n", $4, $6, $7 ? $7 : ""); if (!$7) pwarn(_("RLIMIT 'rttime' no units specified using default units of microseconds\n")); value = tmp; break; case RLIMIT_NOFILE: case RLIMIT_NPROC: case RLIMIT_LOCKS: case RLIMIT_SIGPENDING: #ifdef RLIMIT_RTPRIO case RLIMIT_RTPRIO: if (!end || $6 == end || $7 || tmp < 0) yyerror("RLIMIT '%s' invalid value %s %s\n", $4, $6, $7 ? $7 : ""); value = tmp; break; #endif #ifdef RLIMIT_NICE case RLIMIT_NICE: if (!end || $6 == end || $7) yyerror("RLIMIT '%s' invalid value %s %s\n", $4, $6, $7 ? $7 : ""); if (tmp < -20 || tmp > 19) yyerror("RLIMIT '%s' out of range (-20 .. 19) %d\n", $4, tmp); value = tmp + 20; break; #endif case RLIMIT_FSIZE: case RLIMIT_DATA: case RLIMIT_STACK: case RLIMIT_CORE: case RLIMIT_RSS: case RLIMIT_AS: case RLIMIT_MEMLOCK: case RLIMIT_MSGQUEUE: if ($6 == end || tmp < 0) yyerror("RLIMIT '%s' invalid value %s %s\n", $4, $6, $7 ? $7 : ""); if (!$7) { ; /* use default of bytes */ } else if (strstr(kb, $7) == kb) { tmp *= 1024; } else if (strstr(mb, $7) == mb) { tmp *= 1024*1024; } else if (strstr(gb, $7) == gb) { tmp *= 1024*1024*1024; } else { yyerror("RLIMIT '%s' invalid value %s %s\n", $4, $6, $7); } value = tmp; break; default: yyerror("Unknown RLIMIT %d\n", $4); } } $1->rlimits.specified |= 1 << limit; $1->rlimits.limits[limit] = value; free($4); free($6); $$ = $1; }; cond_rule: TOK_IF expr TOK_OPEN rules TOK_CLOSE { Profile *ret = NULL; PDEBUG("Matched: found conditional rules\n"); if ($2) { ret = $4; } else { delete $4; } $$ = ret; } cond_rule: TOK_IF expr TOK_OPEN rules TOK_CLOSE TOK_ELSE TOK_OPEN rules TOK_CLOSE { Profile *ret = NULL; PDEBUG("Matched: found conditional else rules\n"); if ($2) { ret = $4; delete $8; } else { ret = $8; delete $4; } $$ = ret; } cond_rule: TOK_IF expr TOK_OPEN rules TOK_CLOSE TOK_ELSE cond_rule { Profile *ret = NULL; PDEBUG("Matched: found conditional else-if rules\n"); if ($2) { ret = $4; delete $7; } else { ret = $7; delete $4; } $$ = ret; } expr: TOK_NOT expr { $$ = !$2; } expr: TOK_BOOL_VAR { char *var_name = process_var($1); int boolean = get_boolean_var(var_name); PDEBUG("Matched: boolean expr %s value: %d\n", $1, boolean); if (boolean < 0) { /* FIXME check for set var */ yyerror(_("Unset boolean variable %s used in if-expression"), $1); } $$ = boolean; free(var_name); free($1); } expr: TOK_DEFINED TOK_SET_VAR { char *var_name = process_var($2); void *set_value = get_set_var(var_name); PDEBUG("Matched: defined set expr %s value %lx\n", $2, (long) set_value); $$ = !! (long) set_value; free(var_name); free($2); } expr: TOK_DEFINED TOK_BOOL_VAR { char *var_name = process_var($2); int boolean = get_boolean_var(var_name); PDEBUG("Matched: defined set expr %s value %d\n", $2, boolean); $$ = (boolean != -1); free(var_name); free($2); } id_or_var: TOK_ID { $$ = $1; } id_or_var: TOK_SET_VAR { $$ = $1; }; opt_target: /* nothing */ { $$ = NULL; } opt_target: TOK_ARROW id_or_var { $$ = $2; }; opt_named_transition: { /* nothing */ $$ = NULL; } | TOK_ARROW id_or_var { $$ = $2; }; rule: file_rule { $$ = $1; } | link_rule { $$ = $1; } opt_unsafe: { /* nothing */ $$ = 0; } | TOK_UNSAFE { $$ = 1; }; | TOK_SAFE { $$ = 2; }; opt_file: { /* nothing */ $$ = 0; } | TOK_FILE { $$ = 1; } frule: id_or_var file_mode opt_named_transition TOK_END_OF_RULE { $$ = do_file_rule($1, $2, NULL, $3); }; frule: file_mode opt_subset_flag id_or_var opt_named_transition TOK_END_OF_RULE { if ($2 && ($1 & ~AA_LINK_BITS)) yyerror(_("subset can only be used with link rules.")); if ($4 && ($1 & AA_LINK_BITS) && ($1 & AA_EXEC_BITS)) yyerror(_("link and exec perms conflict on a file rule using ->")); if ($4 && label_contains_ns($4) && ($1 & AA_LINK_BITS)) yyerror(_("link perms are not allowed on a named profile transition.\n")); if (($1 & AA_LINK_BITS)) { $$ = do_file_rule($3, $1, $4, NULL); $$->subset = $2; } else { $$ = do_file_rule($3, $1, NULL, $4); } }; file_rule: TOK_FILE TOK_END_OF_RULE { char *path = strdup("/{**,}"); int perms = ((AA_BASE_PERMS & ~AA_EXEC_TYPE) | (AA_EXEC_INHERIT | AA_MAY_EXEC)); /* duplicate to other permission set */ perms |= perms << AA_OTHER_SHIFT; if (!path) yyerror(_("Memory allocation error.")); $$ = do_file_rule(path, perms, NULL, NULL); } | opt_file file_rule_tail { $$ = $2; } file_rule_tail: opt_unsafe frule { if ($1) { if (!($2->mode & AA_EXEC_BITS)) yyerror(_("unsafe rule missing exec permissions")); if ($1 == 1) { $2->mode |= (($2->mode & AA_EXEC_BITS) << 8) & ALL_AA_EXEC_UNSAFE; } else if ($1 == 2) $2->mode &= ~ALL_AA_EXEC_UNSAFE; } $$ = $2; }; file_rule_tail: opt_unsafe id_or_var file_mode id_or_var { /* Oopsie, we appear to be missing an EOL marker. If we * were *smart*, we could work around it. Since we're * obviously not smart, we'll just punt with a more * sensible error. */ yyerror(_("missing an end of line character? (entry: %s)"), $2); }; link_rule: TOK_LINK opt_subset_flag id_or_var TOK_ARROW id_or_var TOK_END_OF_RULE { struct cod_entry *entry; PDEBUG("Matched: link tok_id (%s) -> (%s)\n", $3, $5); entry = new_entry($3, AA_LINK_BITS, $5); entry->subset = $2; PDEBUG("rule.entry: link (%s)\n", entry->name); $$ = entry; }; network_rule: TOK_NETWORK TOK_END_OF_RULE { size_t family; struct aa_network_entry *new_entry, *entry = NULL; for (family = AF_UNSPEC; family < get_af_max(); family++) { new_entry = new_network_ent(family, 0xffffffff, 0xffffffff); if (!new_entry) yyerror(_("Memory allocation error.")); new_entry->next = entry; entry = new_entry; } $$ = entry; } network_rule: TOK_NETWORK TOK_ID TOK_END_OF_RULE { struct aa_network_entry *entry; entry = network_entry($2, NULL, NULL); if (!entry) /* test for short circuiting of family */ entry = network_entry(NULL, $2, NULL); if (!entry) yyerror(_("Invalid network entry.")); free($2); $$ = entry; } network_rule: TOK_NETWORK TOK_ID TOK_ID TOK_END_OF_RULE { struct aa_network_entry *entry; entry = network_entry($2, $3, NULL); if (!entry) yyerror(_("Invalid network entry.")); free($2); free($3); $$ = entry; } cond: TOK_CONDID TOK_EQUALS TOK_VALUE { struct cond_entry *ent; struct value_list *value = new_value_list($3); if (!value) yyerror(_("Memory allocation error.")); ent = new_cond_entry($1, 1, value); if (!ent) { free_value_list(value); yyerror(_("Memory allocation error.")); } $$ = ent; } cond: TOK_CONDID TOK_EQUALS TOK_OPENPAREN valuelist TOK_CLOSEPAREN { struct cond_entry *ent = new_cond_entry($1, 1, $4); if (!ent) yyerror(_("Memory allocation error.")); $$ = ent; } cond: TOK_CONDID TOK_IN TOK_OPENPAREN valuelist TOK_CLOSEPAREN { struct cond_entry *ent = new_cond_entry($1, 0, $4); if (!ent) yyerror(_("Memory allocation error.")); $$ = ent; } opt_conds: { /* nothing */ $$ = NULL; } | opt_conds cond { $2->next = $1; $$ = $2; } cond_list: TOK_CONDLISTID TOK_EQUALS TOK_OPENPAREN opt_conds TOK_CLOSEPAREN { $$.name = $1; $$.list = $4; } opt_cond_list: { /* nothing */ $$ = { NULL, NULL }; } | cond_list { $$ = $1; } mnt_rule: TOK_MOUNT opt_conds opt_id TOK_END_OF_RULE { $$ = do_mnt_rule($2, $3, NULL, NULL, AA_MAY_MOUNT); } mnt_rule: TOK_MOUNT opt_conds opt_id TOK_ARROW opt_conds TOK_ID TOK_END_OF_RULE { $$ = do_mnt_rule($2, $3, $5, $6, AA_MAY_MOUNT); } mnt_rule: TOK_REMOUNT opt_conds opt_id TOK_END_OF_RULE { $$ = do_mnt_rule($2, NULL, NULL, $3, AA_DUMMY_REMOUNT); } mnt_rule: TOK_UMOUNT opt_conds opt_id TOK_END_OF_RULE { $$ = do_mnt_rule($2, NULL, NULL, $3, AA_MAY_UMOUNT); } mnt_rule: TOK_PIVOTROOT opt_conds opt_id opt_target TOK_END_OF_RULE { $$ = do_pivot_rule($2, $3, $4); } dbus_perm: TOK_VALUE { if (strcmp($1, "bind") == 0) $$ = AA_DBUS_BIND; else if (strcmp($1, "send") == 0 || strcmp($1, "write") == 0) $$ = AA_DBUS_SEND; else if (strcmp($1, "receive") == 0 || strcmp($1, "read") == 0) $$ = AA_DBUS_RECEIVE; else if (strcmp($1, "eavesdrop") == 0) $$ = AA_DBUS_EAVESDROP; else if ($1) { parse_dbus_mode($1, &$$, 1); } else $$ = 0; if ($1) free($1); } | TOK_BIND { $$ = AA_DBUS_BIND; } | TOK_SEND { $$ = AA_DBUS_SEND; } | TOK_RECEIVE { $$ = AA_DBUS_RECEIVE; } | TOK_READ { $$ = AA_DBUS_RECEIVE; } | TOK_WRITE { $$ = AA_DBUS_SEND; } | TOK_EAVESDROP { $$ = AA_DBUS_EAVESDROP; } | TOK_MODE { parse_dbus_mode($1, &$$, 1); free($1); } dbus_perms: { /* nothing */ $$ = 0; } | dbus_perms dbus_perm { $$ = $1 | $2; } | dbus_perms TOK_COMMA dbus_perm { $$ = $1 | $3; } opt_dbus_perm: { /* nothing */ $$ = 0; } | dbus_perm { $$ = $1; } | TOK_OPENPAREN dbus_perms TOK_CLOSEPAREN { $$ = $2; } dbus_rule: TOK_DBUS opt_dbus_perm opt_conds opt_cond_list TOK_END_OF_RULE { dbus_rule *ent; if ($4.name) { if (strcmp($4.name, "peer") != 0) yyerror(_("dbus rule: invalid conditional group %s=()"), $4.name); free($4.name); } ent = new dbus_rule($2, $3, $4.list); if (!ent) { yyerror(_("Memory allocation error.")); } $$ = ent; } net_perm: TOK_VALUE { if (strcmp($1, "create") == 0) $$ = AA_NET_CREATE; else if (strcmp($1, "bind") == 0) $$ = AA_NET_BIND; else if (strcmp($1, "listen") == 0) $$ = AA_NET_LISTEN; else if (strcmp($1, "accept") == 0) $$ = AA_NET_ACCEPT; else if (strcmp($1, "connect") == 0) $$ = AA_NET_CONNECT; else if (strcmp($1, "shutdown") == 0) $$ = AA_NET_SHUTDOWN; else if (strcmp($1, "getattr") == 0) $$ = AA_NET_GETATTR; else if (strcmp($1, "setattr") == 0) $$ = AA_NET_SETATTR; else if (strcmp($1, "getopt") == 0) $$ = AA_NET_GETOPT; else if (strcmp($1, "setopt") == 0) $$ = AA_NET_SETOPT; else if (strcmp($1, "send") == 0 || strcmp($1, "write") == 0) $$ = AA_NET_SEND; else if (strcmp($1, "receive") == 0 || strcmp($1, "read") == 0) $$ = AA_NET_RECEIVE; else if ($1) { parse_net_mode($1, &$$, 1); } else $$ = 0; if ($1) free($1); } | TOK_CREATE { $$ = AA_NET_CREATE; } | TOK_BIND { $$ = AA_NET_BIND; } | TOK_LISTEN { $$ = AA_NET_LISTEN; } | TOK_ACCEPT { $$ = AA_NET_ACCEPT; } | TOK_CONNECT { $$ = AA_NET_CONNECT; } | TOK_SHUTDOWN { $$ = AA_NET_SHUTDOWN; } | TOK_GETATTR { $$ = AA_NET_GETATTR; } | TOK_SETATTR { $$ = AA_NET_SETATTR; } | TOK_GETOPT { $$ = AA_NET_GETOPT; } | TOK_SETOPT { $$ = AA_NET_SETOPT; } | TOK_SEND { $$ = AA_NET_SEND; } | TOK_RECEIVE { $$ = AA_NET_RECEIVE; } | TOK_READ { $$ = AA_NET_RECEIVE; } | TOK_WRITE { $$ = AA_NET_SEND; } | TOK_MODE { parse_unix_mode($1, &$$, 1); free($1); } net_perms: { /* nothing */ $$ = 0; } | net_perms net_perm { $$ = $1 | $2; } | net_perms TOK_COMMA net_perm { $$ = $1 | $3; } opt_net_perm: { /* nothing */ $$ = 0; } | net_perm { $$ = $1; } | TOK_OPENPAREN net_perms TOK_CLOSEPAREN { $$ = $2; } unix_rule: TOK_UNIX opt_net_perm opt_conds opt_cond_list TOK_END_OF_RULE { unix_rule *ent; if ($4.name) { if (strcmp($4.name, "peer") != 0) yyerror(_("unix rule: invalid conditional group %s=()"), $4.name); free($4.name); } ent = new unix_rule($2, $3, $4.list); if (!ent) { yyerror(_("Memory allocation error.")); } $$ = ent; } signal_perm: TOK_VALUE { if (strcmp($1, "send") == 0 || strcmp($1, "write") == 0) $$ = AA_MAY_SEND; else if (strcmp($1, "receive") == 0 || strcmp($1, "read") == 0) $$ = AA_MAY_RECEIVE; else if ($1) { parse_signal_mode($1, &$$, 1); } else $$ = 0; if ($1) free($1); } | TOK_SEND { $$ = AA_MAY_SEND; } | TOK_RECEIVE { $$ = AA_MAY_RECEIVE; } | TOK_READ { $$ = AA_MAY_RECEIVE; } | TOK_WRITE { $$ = AA_MAY_SEND; } | TOK_MODE { parse_signal_mode($1, &$$, 1); free($1); } signal_perms: { /* nothing */ $$ = 0; } | signal_perms signal_perm { $$ = $1 | $2; } | signal_perms TOK_COMMA signal_perm { $$ = $1 | $3; } opt_signal_perm: { /* nothing */ $$ = 0; } | signal_perm { $$ = $1; } | TOK_OPENPAREN signal_perms TOK_CLOSEPAREN { $$ = $2; } signal_rule: TOK_SIGNAL opt_signal_perm opt_conds TOK_END_OF_RULE { signal_rule *ent = new signal_rule($2, $3); $$ = ent; } ptrace_perm: TOK_VALUE { if (strcmp($1, "trace") == 0 || strcmp($1, "write") == 0) $$ = AA_MAY_TRACE; else if (strcmp($1, "read") == 0) $$ = AA_MAY_READ; else if (strcmp($1, "tracedby") == 0) $$ = AA_MAY_TRACEDBY; else if (strcmp($1, "readby") == 0) $$ = AA_MAY_READBY; else if ($1) parse_ptrace_mode($1, &$$, 1); else $$ = 0; if ($1) free($1); } | TOK_TRACE { $$ = AA_MAY_TRACE; } | TOK_TRACEDBY { $$ = AA_MAY_TRACEDBY; } | TOK_READ { $$ = AA_MAY_READ; } | TOK_WRITE { $$ = AA_MAY_TRACE; } | TOK_READBY { $$ = AA_MAY_READBY; } | TOK_MODE { parse_ptrace_mode($1, &$$, 1); free($1); } ptrace_perms: { /* nothing */ $$ = 0; } | ptrace_perms ptrace_perm { $$ = $1 | $2; } | ptrace_perms TOK_COMMA ptrace_perm { $$ = $1 | $3; } opt_ptrace_perm: { /* nothing */ $$ = 0; } | ptrace_perm { $$ = $1; } | TOK_OPENPAREN ptrace_perms TOK_CLOSEPAREN { $$ = $2; } ptrace_rule: TOK_PTRACE opt_ptrace_perm opt_conds TOK_END_OF_RULE { ptrace_rule *ent = new ptrace_rule($2, $3); $$ = ent; } hat_start: TOK_CARET {} | TOK_HAT {} file_mode: TOK_MODE { /* A single TOK_MODE maps to the same permission in all * of user::other */ $$ = parse_mode($1); free($1); } change_profile_head: TOK_CHANGE_PROFILE opt_id { if ($2 && !($2[0] == '/' || strncmp($2, "@{", 2) == 0)) yyerror(_("Exec condition must begin with '/'.")); $$ = $2; } change_profile: change_profile_head opt_named_transition TOK_END_OF_RULE { struct cod_entry *entry; if ($2) { PDEBUG("Matched change_profile: tok_id (%s)\n", $2); entry = new_entry($2, AA_CHANGE_PROFILE, $1); } else { char *rule = strdup("**"); if (!rule) yyerror(_("Memory allocation error.")); PDEBUG("Matched change_profile,\n"); entry = new_entry(rule, AA_CHANGE_PROFILE, $1); } if (!entry) yyerror(_("Memory allocation error.")); PDEBUG("change_profile.entry: (%s)\n", entry->name); $$ = entry; }; capability: TOK_CAPABILITY caps TOK_END_OF_RULE { if ($2 == 0) { /* bare capability keyword - set all caps */ $$ = 0xffffffffffffffff; } else $$ = $2; }; caps: { /* nothing */ $$ = 0; } | caps TOK_ID { int cap = name_to_capability($2); if (cap == -1) yyerror(_("Invalid capability %s."), $2); free($2); $$ = $1 | CAP_TO_MASK(cap); } %% #define MAXBUFSIZE 4096 void vprintyyerror(const char *msg, va_list argptr) { char buf[MAXBUFSIZE]; vsnprintf(buf, sizeof(buf), msg, argptr); if (profilename) { PERROR(_("AppArmor parser error for %s%s%s at line %d: %s\n"), profilename, current_filename ? " in " : "", current_filename ? current_filename : "", current_lineno, buf); } else { PERROR(_("AppArmor parser error,%s%s line %d: %s\n"), current_filename ? " in " : "", current_filename ? current_filename : "", current_lineno, buf); } } void printyyerror(const char *msg, ...) { va_list arg; va_start(arg, msg); vprintyyerror(msg, arg); va_end(arg); } void yyerror(const char *msg, ...) { va_list arg; va_start(arg, msg); vprintyyerror(msg, arg); va_end(arg); exit(1); } struct cod_entry *do_file_rule(char *id, int mode, char *link_id, char *nt) { struct cod_entry *entry; PDEBUG("Matched: tok_id (%s) tok_mode (0x%x)\n", id, mode); entry = new_entry(id, mode, link_id); if (!entry) yyerror(_("Memory allocation error.")); entry->nt_name = nt; PDEBUG("rule.entry: (%s)\n", entry->name); return entry; } /* Note: NOT currently in use, used for * /foo x -> { /bah, } style transitions */ void add_local_entry(Profile *prof) { /* ugh this has to be called after the hat is attached to its parent */ if (prof->local_mode) { struct cod_entry *entry; char *trans = (char *) malloc(strlen(prof->parent->name) + strlen(prof->name) + 3); char *name = strdup(prof->name); if (!trans) yyerror(_("Memory allocation error.")); sprintf(name, "%s//%s", prof->parent->name, prof->name); entry = new_entry(name, prof->local_mode, NULL); entry->audit = prof->local_audit; entry->nt_name = trans; if (!entry) yyerror(_("Memory allocation error.")); add_entry_to_policy(prof, entry); } } static const char *mnt_cond_msg[] = {"", " not allowed as source conditional", " not allowed as target conditional", "", NULL}; int verify_mnt_conds(struct cond_entry *conds, int src) { struct cond_entry *entry; int error = 0; if (!conds) return 0; list_for_each(conds, entry) { int res = is_valid_mnt_cond(entry->name, src); if (res <= 0) { printyyerror(_("invalid mount conditional %s%s"), entry->name, res == -1 ? "" : mnt_cond_msg[src]); error++; } } return error; } mnt_rule *do_mnt_rule(struct cond_entry *src_conds, char *src, struct cond_entry *dst_conds, char *dst, int mode) { if (verify_mnt_conds(src_conds, MNT_SRC_OPT) != 0) yyerror(_("bad mount rule")); /* FIXME: atm conditions are not supported on dst if (verify_conds(dst_conds, DST_OPT) != 0) yyerror(_("bad mount rule")); */ if (dst_conds) yyerror(_("mount point conditions not currently supported")); mnt_rule *ent = new mnt_rule(src_conds, src, dst_conds, dst, mode); if (!ent) { yyerror(_("Memory allocation error.")); } return ent; } mnt_rule *do_pivot_rule(struct cond_entry *old, char *root, char *transition) { char *device = NULL; if (old) { if (strcmp(old->name, "oldroot") != 0) yyerror(_("invalid pivotroot conditional '%s'"), old->name); if (old->vals) { device = old->vals->value; old->vals->value = NULL; } free_cond_entry(old); } mnt_rule *ent = new mnt_rule(NULL, device, NULL, root, AA_MAY_PIVOTROOT); ent->trans = transition; return ent; } apparmor-2.10.95/parser/rc.apparmor.functions0000664000175000017500000003236411721700347020235 0ustar stevesteve#!/bin/sh # ---------------------------------------------------------------------- # Copyright (c) 1999-2008 NOVELL (All rights reserved) # Copyright (c) 2009-2012 Canonical Ltd. (All rights reserved) # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, contact Novell, Inc. # ---------------------------------------------------------------------- # rc.apparmor.functions by Steve Beattie # # NOTE: rc.apparmor initscripts that source this file need to implement # the following set of functions: # aa_action # aa_log_action_start # aa_log_action_end # aa_log_success_msg # aa_log_warning_msg # aa_log_failure_msg # aa_log_skipped_msg # aa_log_daemon_msg # aa_log_end_msg # Some nice defines that we use CONFIG_DIR=/etc/apparmor MODULE=apparmor OLD_MODULE=subdomain if [ -f "${CONFIG_DIR}/${MODULE}.conf" ] ; then APPARMOR_CONF="${CONFIG_DIR}/${MODULE}.conf" elif [ -f "${CONFIG_DIR}/${OLD_MODULE}.conf" ] ; then APPARMOR_CONF="${CONFIG_DIR}/${OLD_MODULE}.conf" elif [ -f "/etc/immunix/subdomain.conf" ] ; then aa_log_warning_msg "/etc/immunix/subdomain.conf is deprecated, use ${CONFIG_DIR}/subdomain.conf instead" APPARMOR_CONF="/etc/immunix/subdomain.conf" elif [ -f "/etc/subdomain.conf" ] ; then aa_log_warning_msg "/etc/subdomain.conf is deprecated, use ${CONFIG_DIR}/subdomain.conf instead" APPARMOR_CONF="/etc/subdomain.conf" else aa_log_warning_msg "Unable to find config file in ${CONFIG_DIR}, installation problem?" fi # Read configuration options from /etc/subdomain.conf, default is to # warn if subdomain won't load. SUBDOMAIN_MODULE_PANIC="warn" SUBDOMAIN_ENABLE_OWLSM="no" APPARMOR_ENABLE_AAEVENTD="no" if [ -f "${APPARMOR_CONF}" ] ; then #parse the conf file to see what we should do . "${APPARMOR_CONF}" fi PARSER=/sbin/apparmor_parser # SUBDOMAIN_DIR and APPARMOR_DIR might be defined in subdomain.conf|apparmor.conf if [ -d "${APPARMOR_DIR}" ] ; then PROFILE_DIR=${APPARMOR_DIR} elif [ -d "${SUBDOMAIN_DIR}" ] ; then PROFILE_DIR=${SUBDOMAIN_DIR} elif [ -d /etc/apparmor.d ] ; then PROFILE_DIR=/etc/apparmor.d elif [ -d /etc/subdomain.d ] ; then PROFILE_DIR=/etc/subdomain.d fi ABSTRACTIONS="-I${PROFILE_DIR}" AA_EV_BIN=/usr/sbin/aa-eventd AA_EV_PIDFILE=/var/run/aa-eventd.pid AA_STATUS=/usr/sbin/aa-status SD_EV_BIN=/usr/sbin/sd-event-dispatch.pl SD_EV_PIDFILE=/var/run/sd-event-dispatch.init.pid SD_STATUS=/usr/sbin/subdomain_status SECURITYFS=/sys/kernel/security SUBDOMAINFS_MOUNTPOINT=$(grep subdomainfs /etc/fstab | \ sed -e 's|^[[:space:]]*[^[:space:]]\+[[:space:]]\+\(/[^[:space:]]*\)[[:space:]]\+subdomainfs.*$|\1|' 2> /dev/null) # keep exit status from parser during profile load. 0 is good, 1 is bad STATUS=0 # Test if the apparmor "module" is present. is_apparmor_present() { local modules=$1 shift while [ $# -gt 0 ] ; do modules="$modules|$1" shift done # check for subdomainfs version of module grep -qE "^($modules)[[:space:]]" /proc/modules [ $? -ne 0 -a -d /sys/module/apparmor ] return $? } # This set of patterns to skip needs to be kept in sync with # AppArmor.pm::isSkippableFile() # returns 0 if profile should NOT be skipped # returns 1 on verbose skip # returns 2 on silent skip skip_profile() { local profile=$1 if [ "${profile%.rpmnew}" != "${profile}" -o \ "${profile%.rpmsave}" != "${profile}" -o \ -e "${PROFILE_DIR}/disable/`basename ${profile}`" -o \ "${profile%\~}" != "${profile}" ] ; then return 1 fi # Silently ignore the dpkg files if [ "${profile%.dpkg-new}" != "${profile}" -o \ "${profile%.dpkg-old}" != "${profile}" -o \ "${profile%.dpkg-dist}" != "${profile}" -o \ "${profile%.dpkg-bak}" != "${profile}" ] ; then return 2 fi return 0 } force_complain() { local profile=$1 # if profile not in complain mode if ! egrep -q "^/.*[ \t]+flags[ \t]*=[ \t]*\([ \t]*complain[ \t]*\)[ \t]+{" $profile ; then local link="${PROFILE_DIR}/force-complain/`basename ${profile}`" if [ -e "$link" ] ; then aa_log_warning_msg "found $link, forcing complain mode" return 0 fi fi return 1 } parse_profiles() { # get parser arg case "$1" in load) PARSER_ARGS="--add" PARSER_MSG="Loading AppArmor profiles " ;; reload) PARSER_ARGS="--replace" PARSER_MSG="Reloading AppArmor profiles " ;; *) aa_log_failure_msg "required 'load' or 'reload'" exit 1 ;; esac aa_log_action_start "$PARSER_MSG" # run the parser on all of the apparmor profiles if [ ! -f "$PARSER" ]; then aa_log_failure_msg "AppArmor parser not found" aa_log_action_end 1 exit 1 fi if [ ! -d "$PROFILE_DIR" ]; then aa_log_failure_msg "Profile directory not found" aa_log_action_end 1 exit 1 fi if [ -z "$(ls $PROFILE_DIR/)" ]; then aa_log_failure_msg "No profiles found" aa_log_action_end 1 return 1 fi for profile in $PROFILE_DIR/*; do skip_profile "${profile}" skip=$? # Ignore skip status == 2 (silent skip) if [ "$skip" -eq 1 ] ; then aa_log_skipped_msg "$profile" logger -t "AppArmor(init)" -p daemon.warn "Skipping profile $profile" STATUS=2 continue elif [ "$skip" -ne 0 ]; then continue fi if [ -f "${profile}" ] ; then COMPLAIN="" if force_complain "${profile}" ; then COMPLAIN="-C" fi $PARSER $ABSTRACTIONS $PARSER_ARGS $COMPLAIN "$profile" > /dev/null if [ $? -ne 0 ]; then aa_log_failure_msg "$profile failed to load" STATUS=1 fi fi done if [ $STATUS -eq 2 ]; then STATUS=0 fi aa_log_action_end "$STATUS" return $STATUS } profiles_names_list() { # run the parser on all of the apparmor profiles if [ ! -f "$PARSER" ]; then aa_log_failure_msg "- AppArmor parser not found" exit 1 fi if [ ! -d "$PROFILE_DIR" ]; then aa_log_failure_msg "- Profile directory not found" exit 1 fi for profile in $PROFILE_DIR/*; do if skip_profile "${profile}" && [ -f "${profile}" ] ; then LIST_ADD=$($PARSER $ABSTRACTIONS -N "$profile" ) if [ $? -eq 0 ]; then echo "$LIST_ADD" fi fi done } failstop_system() { level=$(runlevel | cut -d" " -f2) if [ $level -ne "1" ] ; then aa_log_failure_msg "- could not start AppArmor. Changing to runlevel 1" telinit 1; return -1; fi aa_log_failure_msg "- could not start AppArmor." return -1 } module_panic() { # the module failed to load, determine what action should be taken case "$SUBDOMAIN_MODULE_PANIC" in "warn"|"WARN") return 1 ;; "panic"|"PANIC") failstop_system rc=$? return $rc ;; *) aa_log_failure_msg "- invalid AppArmor module fail option" return -1 ;; esac } is_apparmor_loaded() { if ! is_securityfs_mounted ; then mount_securityfs fi mount_subdomainfs if [ -f "${SECURITYFS}/${MODULE}/profiles" ]; then SFS_MOUNTPOINT="${SECURITYFS}/${MODULE}" return 0 fi if [ -f "${SECURITYFS}/${OLD_MODULE}/profiles" ]; then SFS_MOUNTPOINT="${SECURITYFS}/${OLD_MODULE}" return 0 fi if [ -f "${SUBDOMAINFS_MOUNTPOINT}/profiles" ]; then SFS_MOUNTPOINT=${SUBDOMAINFS_MOUNTPOINT} return 0 fi # check for subdomainfs version of module is_apparmor_present apparmor subdomain return $? } is_securityfs_mounted() { test -d ${SECURITYFS} -a -d /sys/fs/cgroup/systemd || grep -q securityfs /proc/filesystems && grep -q securityfs /proc/mounts return $? } mount_securityfs() { if grep -q securityfs /proc/filesystems ; then aa_action "Mounting securityfs on ${SECURITYFS}" \ mount -t securityfs securityfs "${SECURITYFS}" return $? fi return 0 } mount_subdomainfs() { # for backwords compatibility if grep -q subdomainfs /proc/filesystems && \ ! grep -q subdomainfs /proc/mounts && \ [ -n "${SUBDOMAINFS_MOUNTPOINT}" ]; then aa_action "Mounting subdomainfs on ${SUBDOMAINFS_MOUNTPOINT}" \ mount "${SUBDOMAINFS_MOUNTPOINT}" return $? fi return 0 } unmount_subdomainfs() { SUBDOMAINFS=$(grep subdomainfs /proc/mounts | cut -d" " -f2 2> /dev/null) if [ -n "${SUBDOMAINFS}" ]; then aa_action "Unmounting subdomainfs" umount ${SUBDOMAINFS} fi } load_module() { local rc=0 if modinfo -F filename apparmor > /dev/null 2>&1 ; then MODULE=apparmor elif modinfo -F filename ${OLD_MODULE} > /dev/null 2>&1 ; then MODULE=${OLD_MODULE} fi if ! is_apparmor_present apparmor subdomain ; then aa_action "Loading AppArmor module" /sbin/modprobe -q $MODULE $1 rc=$? if [ $rc -ne 0 ] ; then module_panic rc=$? if [ $rc -ne 0 ] ; then exit $rc fi fi fi if ! is_apparmor_loaded ; then return 1 fi return $rc } apparmor_start() { aa_log_daemon_msg "Starting AppArmor" if ! is_apparmor_loaded ; then load_module rc=$? if [ $rc -ne 0 ] ; then aa_log_end_msg $rc return $rc fi fi if [ ! -w "$SFS_MOUNTPOINT/.load" ] ; then aa_log_failure_msg "Loading AppArmor profiles - failed, Do you have the correct privileges?" aa_log_end_msg 1 return 1 fi configure_owlsm # if there is anything in the profiles file don't load if ! read line < "$SFS_MOUNTPOINT/profiles"; then parse_profiles load else aa_log_skipped_msg ": already loaded with profiles." return 0 fi aa_log_end_msg 0 return 0 } remove_profiles() { # removing profiles as we directly read from subdomainfs # doesn't work, since we are removing entries which screws up # our position. Lets hope there are never enough profiles to # overflow the variable if ! is_apparmor_loaded ; then aa_log_failure_msg "AppArmor module is not loaded" return 1 fi if [ ! -w "$SFS_MOUNTPOINT/.remove" ] ; then aa_log_failure_msg "Root privileges not available" return 1 fi if [ ! -x "${PARSER}" ] ; then aa_log_failure_msg "Unable to execute AppArmor parser" return 1 fi retval=0 # We filter child profiles as removing the parent will remove # the children sed -e "s/ (\(enforce\|complain\))$//" "$SFS_MOUNTPOINT/profiles" | \ LC_COLLATE=C sort | grep -v // | while read profile ; do echo -n "$profile" > "$SFS_MOUNTPOINT/.remove" rc=$? if [ ${rc} -ne 0 ] ; then retval=${rc} fi done return ${retval} } apparmor_stop() { aa_log_daemon_msg "Unloading AppArmor profiles " remove_profiles rc=$? aa_log_end_msg $rc return $rc } apparmor_kill() { aa_log_daemon_msg "Unloading AppArmor modules " if ! is_apparmor_loaded ; then aa_log_failure_msg "AppArmor module is not loaded" return 1 fi unmount_subdomainfs if is_apparmor_present apparmor ; then MODULE=apparmor elif is_apparmor_present subdomain ; then MODULE=subdomain else aa_log_failure_msg "AppArmor is builtin" return 1 fi /sbin/modprobe -qr $MODULE rc=$? aa_log_end_msg $rc return $rc } __apparmor_restart() { if [ ! -w "$SFS_MOUNTPOINT/.load" ] ; then aa_log_failure_msg "Loading AppArmor profiles - failed, Do you have the correct privileges?" return 4 fi aa_log_daemon_msg "Restarting AppArmor" configure_owlsm parse_profiles reload # Clean out running profiles not associated with the current profile # set, excluding the libvirt dynamically generated profiles. # Note that we reverse sort the list of profiles to remove to # ensure that child profiles (e.g. hats) are removed before the # parent. We *do* need to remove the child profile and not rely # on removing the parent profile when the profile has had its # child profile names changed. profiles_names_list | awk ' BEGIN { while (getline < "'${SFS_MOUNTPOINT}'/profiles" ) { str = sub(/ \((enforce|complain)\)$/, "", $0); if (match($0, /^libvirt-[0-9a-f\-]+$/) == 0) arr[$str] = $str } } { if (length(arr[$0]) > 0) { delete arr[$0] } } END { for (key in arr) if (length(arr[key]) > 0) { printf("%s\n", arr[key]) } } ' | LC_COLLATE=C sort -r | while IFS= read profile ; do echo -n "$profile" > "$SFS_MOUNTPOINT/.remove" done # will not catch all errors, but still better than nothing rc=$? aa_log_end_msg $rc return $rc } apparmor_restart() { if ! is_apparmor_loaded ; then apparmor_start rc=$? return $rc fi __apparmor_restart return $? } apparmor_try_restart() { if ! is_apparmor_loaded ; then return 0 fi __apparmor_restart return $? } configure_owlsm () { if [ "${SUBDOMAIN_ENABLE_OWLSM}" = "yes" -a -f ${SFS_MOUNTPOINT}/control/owlsm ] ; then # Sigh, the "sh -c" is necessary for the SuSE aa_action # and it can't be abstracted out as a seperate function, as # that breaks under RedHat's action, which needs a # binary to invoke. aa_action "Enabling OWLSM extension" sh -c "echo -n \"1\" > \"${SFS_MOUNTPOINT}/control/owlsm\"" elif [ -f "${SFS_MOUNTPOINT}/control/owlsm" ] ; then aa_action "Disabling OWLSM extension" sh -c "echo -n \"0\" > \"${SFS_MOUNTPOINT}/control/owlsm\"" fi } apparmor_status () { if test -x ${AA_STATUS} ; then ${AA_STATUS} --verbose return $? fi if test -x ${SD_STATUS} ; then ${SD_STATUS} --verbose return $? fi if ! is_apparmor_loaded ; then echo "AppArmor is not loaded." rc=1 else echo "AppArmor is enabled." rc=0 fi echo "Install the apparmor-utils package to receive more detailed" echo "status information here (or examine ${SFS_MOUNTPOINT} directly)." return $rc } apparmor-2.10.95/parser/parser_symtab.c0000664000175000017500000004602612413327176017102 0ustar stevesteve/* * Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 * NOVELL (All rights reserved) * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License published by the Free Software Foundation. * * This program 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. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, contact Novell, Inc. */ #include #include #include #include #include #include #include "immunix.h" #include "parser.h" enum var_type { sd_boolean, sd_set, }; struct symtab { char *var_name; enum var_type type; int boolean; struct set_value *values; struct set_value *expanded; }; static void *my_symtab = NULL; static int __expand_variable(struct symtab *symbol); static struct symtab *new_symtab_entry(const char *name) { struct symtab *n = (struct symtab *) calloc(1, sizeof(*n)); if (!n) { PERROR("Failed to allocate memory: %s\n", strerror(errno)); return NULL; } n->var_name = strndup(name, PATH_MAX); if (!n->var_name) { PERROR("Failed to allocate memory: %s\n", strerror(errno)); free(n); return NULL; } return n; } static struct set_value *new_set_value(const char *val) { struct set_value *n = (struct set_value *) calloc(1, sizeof(*n)); if (!n) { PERROR("Failed to allocate memory: %s\n", strerror(errno)); return NULL; } n->val = strndup(val, PATH_MAX); if (!n->val) { PERROR("Failed to allocate memory: %s\n", strerror(errno)); free(n); return NULL; } return n; } static void free_values(struct set_value *val) { struct set_value *i = val, *tmp; while (i) { if (i->val) free(i->val); tmp = i; i = i->next; free(tmp); } } static void free_symtab(struct symtab *symtab) { if (!symtab) return; if (symtab->var_name) free(symtab->var_name); free_values(symtab->values); free_values(symtab->expanded); free(symtab); } /* abstract this out in case we switch data structures */ static void add_to_set(struct set_value **list, const char *val) { struct set_value *new_item = new_set_value(val); new_item->next = *list; *list = new_item; } static int compare_symtabs(const void *a, const void *b) { char *a_name = ((struct symtab *) a)->var_name; char *b_name = ((struct symtab *) b)->var_name; return strcmp(a_name, b_name); } static struct symtab *lookup_existing_symbol(const char *var) { struct symtab *tmp, **lookup; struct symtab *result = NULL; tmp = new_symtab_entry(var); if (!tmp) { goto out; } lookup = (struct symtab **) tfind(tmp, &my_symtab, (comparison_fn_t) &compare_symtabs); if (!lookup) { goto out; } result = (*lookup); out: free_symtab(tmp); return result; } /* add_boolean_var * creates copies of arguments, so caller can free them after use */ int add_boolean_var(const char *var, int value) { struct symtab *n, **result; int rc = 0; n = new_symtab_entry(var); if (!n) { rc = ENOMEM; goto err; } n->type = sd_boolean; n->boolean = value; result = (struct symtab **) tsearch(n, &my_symtab, (comparison_fn_t) &compare_symtabs); if (!result) { PERROR("Failed to allocate memory: %s\n", strerror(errno)); rc = errno; goto err; } if (*result != n) { /* already existing variable */ PERROR("'%s' is already defined\n", var); rc = 1; goto err; } return 0; err: free_symtab(n); return rc; }; int get_boolean_var(const char *var) { struct symtab *result; int rc = 0; result = lookup_existing_symbol(var); if (!result) { rc = -1; goto out; } if (result->type != sd_boolean) { PERROR("Variable %s is not a boolean variable\n", var); rc = -2; /* XXX - might change this to specific values */ goto out; } rc = result->boolean; out: return rc; } /* new_set_var * creates copies of arguments, so caller can free them after use */ int new_set_var(const char *var, const char *value) { struct symtab *n, **result; int rc = 0; n = new_symtab_entry(var); if (!n) { rc = ENOMEM; goto err; } n->type = sd_set; add_to_set(&(n->values), value); result = (struct symtab **) tsearch(n, &my_symtab, (comparison_fn_t) &compare_symtabs); if (!result) { PERROR("Failed to allocate memory: %s\n", strerror(errno)); rc = errno; goto err; } if (*result != n) { /* already existing variable */ PERROR("'%s' is already defined\n", var); rc = 1; goto err; } return 0; err: free_symtab(n); return rc; } /* add_set_value * creates copies of arguments, so caller can free them after use */ int add_set_value(const char *var, const char *value) { struct symtab *result; int rc = 0; result = lookup_existing_symbol(var); if (!result) { PERROR("Failed to find declaration for: %s\n", var); rc = 1; goto out; } if (result->type != sd_set) { PERROR("Variable %s is not a set variable\n", var); rc = 2; /* XXX - might change this to specific values */ goto out; } if (strcmp(result->var_name, var) != 0) { PERROR("ASSERT: tfind found %s when looking up variable %s\n", result->var_name, var); exit(1); } add_to_set(&(result->values), value); out: return rc; } /* returns a pointer to the value list, which should be used as the * argument to the get_next_set_value() function. */ struct set_value *get_set_var(const char *var) { struct symtab *result; struct set_value *valuelist = NULL; result = lookup_existing_symbol(var); if (!result) { goto out; } if (result->type != sd_set) { goto out; } if (strcmp(result->var_name, var) != 0) { PERROR("ASSERT: tfind found %s when looking up variable %s\n", result->var_name, var); exit(1); } if (!result->expanded) { int err = __expand_variable(result); if (err) { PERROR("failure expanding variable %s\n", var); exit(1); } } valuelist = result->expanded; out: return valuelist; } /* iterator to walk the list of set values */ char *get_next_set_value(struct set_value **list) { struct set_value *next; char *ret; if (!list || !(*list)) return NULL; ret = (*list)->val; next = (*list)->next; (*list) = next; return ret; } /* delete_symbol * removes an individual variable from the symbol table. We don't * support this in the language, but for special variables that change * between profiles, we need this. */ int delete_set_var(const char *var_name) { int rc = 0; struct symtab **result, *n, *var; n = new_symtab_entry(var_name); if (!n) { rc = ENOMEM; goto out; } result = (struct symtab **) tfind(n, &my_symtab, (comparison_fn_t) &compare_symtabs); if (!result) { /* XXX Warning? */ goto out; } var = (*result); result = (struct symtab **) tdelete(n, &my_symtab, (comparison_fn_t) &compare_symtabs); if (!result) { PERROR("ASSERT: delete_set_var: tfind found var %s but tdelete failed to delete it\n", var_name); exit(1); } if (var->type != sd_set) { PERROR("ASSERT: delete_set_var: deleting %s but is a boolean variable\n", var_name); exit(1); } free_symtab(var); out: free_symtab(n); return rc; } static void *seenlist = NULL; static int is_seen(const char *var) { char **lookup; lookup = (char **) tfind(var, &seenlist, (comparison_fn_t) &strcmp); return (lookup != NULL); } static void push_seen_var(const char *var) { char **lookup; lookup = (char **) tsearch(var, &seenlist, (comparison_fn_t) &strcmp); if (*lookup != var) { PERROR("ASSERT: '%s' is already in the seenlist\n", var); exit(1); } } static void pop_seen_var(const char *var) { char **lookup; lookup = (char **) tdelete(var, &seenlist, (comparison_fn_t) &strcmp); if (lookup == NULL) { PERROR("ASSERT: popped var '%s' not found on the seenlist\n", var); exit(1); } } static int __expand_variable(struct symtab *symbol) { struct set_value *list, *expanded = NULL; int retval = 0; struct var_string *split = NULL; if (symbol->type == sd_boolean) { PERROR("Referenced variable %s is a boolean used in set context\n", symbol->var_name); return 2; } /* already done */ if (symbol->expanded) return 0; push_seen_var(symbol->var_name); for (list = symbol->values; list; list = list->next) { struct set_value *work_list = new_set_value(list->val); while (work_list) { struct symtab *ref; struct set_value *ref_item; struct set_value *t_value = work_list; int rc; work_list = work_list->next; split = split_out_var(t_value->val); if (!split) { /* fully expanded */ add_to_set(&expanded, t_value->val); goto next; } if (is_seen(split->var)) { PERROR("Variable @{%s} is referenced recursively (by @{%s})\n", split->var, symbol->var_name); retval = 1; free_values(t_value); goto out; } ref = lookup_existing_symbol(split->var); if (!ref) { PERROR("Variable @{%s} references undefined variable @{%s}\n", symbol->var_name, split->var); retval = 3; free_values(t_value); goto out; } rc = __expand_variable(ref); if (rc != 0) { retval = rc; free_values(t_value); goto out; } if (!ref->expanded) { PERROR("ASSERT: Variable @{%s} should have been expanded but isn't\n", split->var); exit(1); } for (ref_item = ref->expanded; ref_item; ref_item = ref_item->next) { char *expanded_string; if (!asprintf(&expanded_string, "%s%s%s", split->prefix ? split->prefix : "", ref_item->val, split->suffix ? split->suffix : "")) { PERROR("Out of memory\n"); exit(1); } add_to_set(&work_list, expanded_string); free(expanded_string); } next: t_value->next = NULL; free_values(t_value); free_var_string(split); } } symbol->expanded = expanded; out: pop_seen_var(symbol->var_name); free_var_string(split); return retval; } static void expand_variable(const void *nodep, VISIT value, int level unused) { struct symtab **t = (struct symtab **) nodep; if (value == preorder || value == endorder) return; if ((*t)->type == sd_boolean) return; __expand_variable(*t); } void expand_variables(void) { twalk(my_symtab, &expand_variable); } static inline void dump_set_values(struct set_value *value) { struct set_value *t = value; while (t) { printf(" \"%s\"", t->val); t = t->next; } } static void __dump_symtab_entry(struct symtab *entry, int do_expanded) { switch (entry->type) { case sd_boolean: printf("$%s = %s\n", entry->var_name, entry->boolean ? "true" : "false"); break; case sd_set: printf("@%s =", entry->var_name); if (do_expanded) { if (!entry->expanded) { __expand_variable(entry); } dump_set_values(entry->expanded); } else { dump_set_values(entry->values); } printf("\n"); break; default: PERROR("ASSERT: unknown symbol table type for %s\n", entry->var_name); exit(1); } } static void dump_symtab_entry(const void *nodep, VISIT value, int level unused) { struct symtab **t = (struct symtab **) nodep; if (value == preorder || value == endorder) return; __dump_symtab_entry(*t, 0); } static void dump_expanded_symtab_entry(const void *nodep, VISIT value, int level unused) { struct symtab **t = (struct symtab **) nodep; if (value == preorder || value == endorder) return; __dump_symtab_entry(*t, 1); } void dump_symtab(void) { twalk(my_symtab, &dump_symtab_entry); } void dump_expanded_symtab(void) { twalk(my_symtab, &dump_expanded_symtab_entry); } void free_symtabs(void) { if (my_symtab) tdestroy(my_symtab, (__free_fn_t)&free_symtab); my_symtab = NULL; } #ifdef UNIT_TEST #include "unit_test.h" int test_compare_symtab(void) { int rc = 0; int retval; struct symtab *a, *b, *c; a = new_symtab_entry("blah"); b = new_symtab_entry("suck"); MY_TEST(a && b, "allocation test"); retval = compare_symtabs(a, b); MY_TEST(retval < 0, "comparison 1"); retval = compare_symtabs(b, a); MY_TEST(retval > 0, "comparison 2"); retval = compare_symtabs(b, a); MY_TEST(retval != 0, "comparison 3"); retval = compare_symtabs(b, b); MY_TEST(retval == 0, "comparison 4"); c = new_symtab_entry("blah"); retval = compare_symtabs(a, c); MY_TEST(retval == 0, "comparison 5"); free_symtab(a); free_symtab(b); free_symtab(c); return rc; } int test_seenlist(void) { int rc = 0; MY_TEST(!is_seen("oogabooga"), "lookup unseen variable"); push_seen_var("oogabooga"); MY_TEST(is_seen("oogabooga"), "lookup seen variable 1"); MY_TEST(!is_seen("not_seen"), "lookup unseen variable 2"); push_seen_var("heebiejeebie"); MY_TEST(is_seen("oogabooga"), "lookup seen variable 2"); MY_TEST(is_seen("heebiejeebie"), "lookup seen variable 3"); MY_TEST(!is_seen("not_seen"), "lookup unseen variable 3"); pop_seen_var("oogabooga"); MY_TEST(!is_seen("oogabooga"), "lookup unseen variable 4"); MY_TEST(is_seen("heebiejeebie"), "lookup seen variable 4"); MY_TEST(!is_seen("not_seen"), "lookup unseen variable 5"); pop_seen_var("heebiejeebie"); MY_TEST(!is_seen("heebiejeebie"), "lookup unseen variable 6"); //pop_seen_var("not_seen"); /* triggers assert */ return rc; } int test_add_set_to_boolean(void) { int rc = 0; int retval; /* test adding a set value to a boolean variable */ retval = add_boolean_var("not_a_set_variable", 1); MY_TEST(retval == 0, "new boolean variable 3"); retval = add_set_value("not_a_set_variable", "a set value"); MY_TEST(retval != 0, "add set value to boolean"); free_symtabs(); return rc; } int test_expand_bool_within_set(void) { int rc = 0; int retval; struct symtab *retsym; /* test expanding a boolean var within a set variable */ retval = add_boolean_var("not_a_set_variable", 1); MY_TEST(retval == 0, "new boolean variable 4"); retval = new_set_var("set_variable", "set_value@{not_a_set_variable}"); MY_TEST(retval == 0, "add set value with embedded boolean"); retsym = lookup_existing_symbol("set_variable"); MY_TEST(retsym != NULL, "get set variable w/boolean"); retval = __expand_variable(retsym); MY_TEST(retval != 0, "expand set variable with embedded boolean"); free_symtabs(); return rc; } int test_expand_recursive_set_vars(void) { int rc = 0; int retval; struct symtab *retsym; /* test expanding a recursive var within a set variable */ retval = new_set_var("recursive_1", "set_value@{recursive_2}"); MY_TEST(retval == 0, "new recursive set variable 1"); retval = new_set_var("recursive_2", "set_value@{recursive_3}"); MY_TEST(retval == 0, "new recursive set variable 2"); retval = new_set_var("recursive_3", "set_value@{recursive_1}"); MY_TEST(retval == 0, "new recursive set variable 3"); retsym = lookup_existing_symbol("recursive_1"); MY_TEST(retsym != NULL, "get recursive set variable"); retval = __expand_variable(retsym); MY_TEST(retval != 0, "expand recursive set variable"); free_symtabs(); return rc; } int test_expand_undefined_set_var(void) { int rc = 0; int retval; struct symtab *retsym; /* test expanding an undefined var within a set variable */ retval = new_set_var("defined_var", "set_value@{undefined_var}"); MY_TEST(retval == 0, "new undefined test set variable"); retsym = lookup_existing_symbol("defined_var"); MY_TEST(retsym != NULL, "get undefined test set variable"); retval = __expand_variable(retsym); MY_TEST(retval != 0, "expand undefined set variable"); free_symtabs(); return rc; } int test_expand_set_var_during_dump(void) { int rc = 0; int retval; struct symtab *retsym; /* test expanding an defined var within a set variable during var dump*/ retval = new_set_var("set_var_1", "set_value@{set_var_2}"); MY_TEST(retval == 0, "new dump expansion set variable 1"); retval = new_set_var("set_var_2", "some other set_value"); MY_TEST(retval == 0, "new dump expansion set variable 2"); retsym = lookup_existing_symbol("set_var_1"); MY_TEST(retsym != NULL, "get dump expansion set variable 1"); __dump_symtab_entry(retsym, 0); __dump_symtab_entry(retsym, 1); __dump_symtab_entry(retsym, 0); free_symtabs(); return rc; } int test_delete_set_var(void) { int rc = 0; int retval; retval = new_set_var("deleteme", "delete this variable"); MY_TEST(retval == 0, "new delete set variable"); retval = delete_set_var("deleteme"); MY_TEST(retval == 0, "delete set variable"); free_symtabs(); return rc; } int main(void) { int rc = 0; int retval; struct set_value *retptr; rc = test_compare_symtab(); retval = test_seenlist(); if (rc == 0) rc = retval; retval = test_add_set_to_boolean(); if (rc == 0) rc = retval; retval = test_expand_bool_within_set(); if (rc == 0) rc = retval; retval = test_expand_recursive_set_vars(); if (rc == 0) rc = retval; retval = test_expand_undefined_set_var(); if (rc == 0) rc = retval; retval = test_expand_set_var_during_dump(); if (rc == 0) rc = retval; retval = test_delete_set_var(); if (rc == 0) rc = retval; retval = new_set_var("test", "test value"); MY_TEST(retval == 0, "new set variable 1"); retval = new_set_var("test", "different value"); MY_TEST(retval != 0, "new set variable 2"); retval = new_set_var("testing", "testing"); MY_TEST(retval == 0, "new set variable 3"); retval = new_set_var("monopuff", "Mockingbird"); MY_TEST(retval == 0, "new set variable 4"); retval = new_set_var("stereopuff", "Unsupervised"); MY_TEST(retval == 0, "new set variable 5"); retval = add_set_value("stereopuff", "Fun to Steal"); MY_TEST(retval == 0, "add set value 1"); retval = add_set_value("stereopuff", "/in/direction"); MY_TEST(retval == 0, "add set value 2"); retval = add_set_value("no_such_variable", "stereopuff"); MY_TEST(retval != 0, "add to non-existent set var"); retval = add_boolean_var("abuse", 0); MY_TEST(retval == 0, "new boolean variable 1"); retval = add_boolean_var("abuse", 1); MY_TEST(retval != 0, "duplicate boolean variable 1"); retval = add_boolean_var("stereopuff", 1); MY_TEST(retval != 0, "duplicate boolean variable 2"); retval = add_boolean_var("shenanigan", 1); MY_TEST(retval == 0, "new boolean variable 2"); retval = get_boolean_var("shenanigan"); MY_TEST(retval == 1, "get boolean variable 1"); retval = get_boolean_var("abuse"); MY_TEST(retval == 0, "get boolean variable 2"); retval = get_boolean_var("non_existant"); MY_TEST(retval < 0, "get nonexistant boolean variable"); retval = get_boolean_var("stereopuff"); MY_TEST(retval < 0, "get boolean variable that's declared a set var"); retptr = get_set_var("daves_not_here_man"); MY_TEST(retptr == NULL, "get non-existent set variable"); retptr = get_set_var("abuse"); MY_TEST(retptr == NULL, "get set variable that's declared a boolean"); /* test walking set values */ retptr = get_set_var("monopuff"); MY_TEST(retptr != NULL, "get set variable 1"); retval = strcmp(get_next_set_value(&retptr), "Mockingbird"); MY_TEST(retval == 0, "get set value 1"); MY_TEST(get_next_set_value(&retptr) == NULL, "get no more set values 1"); retval = new_set_var("eek", "Mocking@{monopuff}bir@{stereopuff}d@{stereopuff}"); MY_TEST(retval == 0, "new set variable 4"); dump_symtab(); expand_variables(); dump_symtab(); dump_expanded_symtab(); free_symtabs(); return rc; } #endif /* UNIT_TEST */ apparmor-2.10.95/parser/mount.cc0000664000175000017500000005414412467167244015543 0ustar stevesteve/* * Copyright (c) 2010 * Canonical, Ltd. (All rights reserved) * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License published by the Free Software Foundation. * * This program 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. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, contact Novell, Inc. or Canonical * Ltd. */ /** * The mount command, its mix of options and flags, its permissions and * mapping are a mess. * mount [-lhV] * * mount -a [-fFnrsvw] [-t vfstype] [-O optlist] * * mount [-fnrsvw] [-o option[,option]...] device|dir * * mount [-fnrsvw] [-t vfstype] [-o options] device dir * *---------------------------------------------------------------------- * Mount flags of no interest for apparmor mediation * -a, --all * -F fork for simultaneous mount * -f fake, do everything except that actual system call * -h --help * -i, --internal-only * -n mount without writing in /etc/mtab * -O limits what is auto mounted * -p, --pass-fd num * -s Tolerate sloppy mount options * -U uuid * -V --version * --no-canonicalize * *---------------------------------------------------------------------- * what do we do with these * -l list? * -L