fs/namespace.c | 3 include/linux/audit.h | 5 include/linux/mnt_namespace.h | 3 kernel/audit.c | 6 security/Kconfig | 1 security/Makefile | 1 security/apparmor/Kbuild | 10 security/apparmor/Kconfig | 9 security/apparmor/Makefile | 28 security/apparmor/apparmor.h | 356 +++++ security/apparmor/apparmorfs.c | 432 +++++++ security/apparmor/capabilities.c | 71 + security/apparmor/inline.h | 393 ++++++ security/apparmor/list.c | 268 ++++ security/apparmor/lsm.c | 894 ++++++++++++++ security/apparmor/main.c | 1702 ++++++++++++++++++++++++++++ security/apparmor/match/Kbuild | 6 security/apparmor/match/Makefile | 5 security/apparmor/match/match.h | 132 ++ security/apparmor/match/match_default.c | 57 security/apparmor/match/match_pcre.c | 169 ++ security/apparmor/match/pcre_exec.c | 1945 ++++++++++++++++++++++++++++++++ security/apparmor/match/pcre_exec.h | 308 +++++ security/apparmor/match/pcre_tables.h | 184 +++ security/apparmor/module_interface.c | 845 +++++++++++++ security/apparmor/module_interface.h | 37 security/apparmor/procattr.c | 332 +++++ security/apparmor/shared.h | 46 28 files changed, 8245 insertions(+), 3 deletions(-) Index: b/include/linux/audit.h =================================================================== --- a/include/linux/audit.h +++ b/include/linux/audit.h @@ -110,6 +110,8 @@ #define AUDIT_LAST_KERN_ANOM_MSG 1799 #define AUDIT_ANOM_PROMISCUOUS 1700 /* Device changed promiscuous mode */ +#define AUDIT_SD 1500 /* AppArmor (SubDomain) audit */ + #define AUDIT_KERNEL 2000 /* Asynchronous audit record. NOT A REQUEST. */ /* Rule flags */ @@ -478,6 +480,9 @@ extern void audit_log(struct audit_ __attribute__((format(printf,4,5))); extern struct audit_buffer *audit_log_start(struct audit_context *ctx, gfp_t gfp_mask, int type); +extern void audit_log_vformat(struct audit_buffer *ab, + const char *fmt, va_list args) + __attribute__((format(printf,2,0))); extern void audit_log_format(struct audit_buffer *ab, const char *fmt, ...) __attribute__((format(printf,2,3))); Index: b/kernel/audit.c =================================================================== --- a/kernel/audit.c +++ b/kernel/audit.c @@ -956,8 +956,7 @@ static inline int audit_expand(struct au * will be called a second time. Currently, we assume that a printk * can't format message larger than 1024 bytes, so we don't either. */ -static void audit_log_vformat(struct audit_buffer *ab, const char *fmt, - va_list args) +void audit_log_vformat(struct audit_buffer *ab, const char *fmt, va_list args) { int len, avail; struct sk_buff *skb; @@ -1213,3 +1212,6 @@ EXPORT_SYMBOL(audit_log_start); EXPORT_SYMBOL(audit_log_end); EXPORT_SYMBOL(audit_log_format); EXPORT_SYMBOL(audit_log); +EXPORT_SYMBOL_GPL(audit_log_vformat); +EXPORT_SYMBOL_GPL(audit_log_untrustedstring); +EXPORT_SYMBOL_GPL(audit_log_d_path); Index: b/fs/namespace.c =================================================================== --- a/fs/namespace.c +++ b/fs/namespace.c @@ -37,7 +37,8 @@ static int event; static struct list_head *mount_hashtable __read_mostly; static int hash_mask __read_mostly, hash_bits __read_mostly; static struct kmem_cache *mnt_cache __read_mostly; -static struct rw_semaphore namespace_sem; +struct rw_semaphore namespace_sem; +EXPORT_SYMBOL_GPL(namespace_sem); /* /sys/fs */ decl_subsys(fs, NULL, NULL); Index: b/include/linux/mnt_namespace.h =================================================================== --- a/include/linux/mnt_namespace.h +++ b/include/linux/mnt_namespace.h @@ -6,6 +6,9 @@ #include #include +/* exported for AppArmor (SubDomain) */ +extern struct rw_semaphore namespace_sem; + struct mnt_namespace { atomic_t count; struct vfsmount * root; Index: b/security/Makefile =================================================================== --- a/security/Makefile +++ b/security/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_KEYS) += keys/ subdir-$(CONFIG_SECURITY_SELINUX) += selinux +obj-$(CONFIG_SECURITY_APPARMOR) += commoncap.o apparmor/ # if we don't select a security model, use the default capabilities ifneq ($(CONFIG_SECURITY),y) Index: b/security/Kconfig =================================================================== --- a/security/Kconfig +++ b/security/Kconfig @@ -94,6 +94,7 @@ config SECURITY_ROOTPLUG If you are unsure how to answer this question, answer N. source security/selinux/Kconfig +source security/apparmor/Kconfig endmenu Index: b/security/apparmor/Kbuild =================================================================== --- /dev/null +++ b/security/apparmor/Kbuild @@ -0,0 +1,10 @@ +# Makefile for AppArmor Linux Security Module +# +EXTRA_CFLAGS += -DAPPARMOR_VERSION=\"${APPARMOR_VER}\" + +obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o + +apparmor-y := main.o list.o procattr.o lsm.o apparmorfs.o capabilities.o \ + module_interface.o + +obj-$(CONFIG_SECURITY_APPARMOR) += match/ Index: b/security/apparmor/Kconfig =================================================================== --- /dev/null +++ b/security/apparmor/Kconfig @@ -0,0 +1,9 @@ +config SECURITY_APPARMOR + tristate "AppArmor support" + depends on SECURITY!=n + help + This enables the AppArmor security module. + Required userspace tools (if they are not included in your + distribution) and further information may be found at + + If you are unsure how to answer this question, answer N. Index: b/security/apparmor/Makefile =================================================================== --- /dev/null +++ b/security/apparmor/Makefile @@ -0,0 +1,28 @@ +# Makefile for AppArmor Linux Security Module +# +# kernel build Makefile is the Kbuild file + +REPO_VERSION := $(shell if [ -x /usr/bin/svn ] ; then \ + /usr/bin/svn info . 2> /dev/null | grep "^Last Changed Rev:" | sed "s/^Last Changed Rev: //" ; \ + fi) + +ifeq ("${REPO_VERSION}", "") +REPO_VERSION := "unknown" +endif + +KERNELVER := $(shell uname -r) + +KERNELDIR := /lib/modules/${KERNELVER}/build + +all: + $(MAKE) -C $(KERNELDIR) M=`pwd` modules CONFIG_SECURITY_APPARMOR=m \ + APPARMOR_VER=${REPO_VERSION} + mv apparmor.ko apparmor-${KERNELVER}.ko + mv match/aamatch_pcre.ko aamatch_pcre-${KERNELVER}.ko + +clean: + rm -f *~ *.o *.ko *.mod.c .*.cmd Module{s,}.symvers \ + match/*~ match/*.o match/*.ko match/.*.cmd match/*.mod.c + rm -rf .tmp_versions + + Index: b/security/apparmor/apparmor.h =================================================================== --- /dev/null +++ b/security/apparmor/apparmor.h @@ -0,0 +1,356 @@ +/* + * Copyright (C) 1998-2005 Novell/SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * AppArmor internal prototypes + */ + +#ifndef __APPARMOR_H +#define __APPARMOR_H + +#include /* Include for defn of iattr */ +#include /* defn of linux_binprm */ +#include + +#include "shared.h" + +/* Control parameters (0 or 1), settable thru module/boot flags or + * via /sys/kernel/security/apparmor/control */ +extern int apparmor_complain; +extern int apparmor_debug; +extern int apparmor_audit; +extern int apparmor_logsyscall; + +/* PIPEFS_MAGIC */ +#include +/* from net/socket.c */ +#define SOCKFS_MAGIC 0x534F434B +/* from inotify.c */ +#define INOTIFYFS_MAGIC 0xBAD1DEA + +#define VALID_FSTYPE(inode) ((inode)->i_sb->s_magic != PIPEFS_MAGIC && \ + (inode)->i_sb->s_magic != SOCKFS_MAGIC && \ + (inode)->i_sb->s_magic != INOTIFYFS_MAGIC) + +#define PROFILE_COMPLAIN(_profile) \ + (apparmor_complain == 1 || ((_profile) && (_profile)->flags.complain)) + +#define SUBDOMAIN_COMPLAIN(_sd) \ + (apparmor_complain == 1 || \ + ((_sd) && (_sd)->active && (_sd)->active->flags.complain)) + +#define PROFILE_AUDIT(_profile) \ + (apparmor_audit == 1 || ((_profile) && (_profile)->flags.audit)) + +#define SUBDOMAIN_AUDIT(_sd) \ + (apparmor_audit == 1 || \ + ((_sd) && (_sd)->active && (_sd)->active->flags.audit)) + +/* + * DEBUG remains global (no per profile flag) since it is mostly used in sysctl + * which is not related to profile accesses. + */ + +#define AA_DEBUG(fmt, args...) \ + do { \ + if (apparmor_debug) \ + printk(KERN_DEBUG "AppArmor: " fmt, ##args); \ + } while (0) +#define AA_INFO(fmt, args...) printk(KERN_INFO "AppArmor: " fmt, ##args) +#define AA_WARN(fmt, args...) printk(KERN_WARNING "AppArmor: " fmt, ##args) +#define AA_ERROR(fmt, args...) printk(KERN_ERR "AppArmor: " fmt, ##args) + + +/* apparmor logged syscall reject caching */ +enum aasyscall { + AA_SYSCALL_PTRACE, + AA_SYSCALL_SYSCTL_WRITE, + AA_SYSCALL_MOUNT, + AA_SYSCALL_UMOUNT +}; + +#define AA_SYSCALL_TO_MASK(X) (1 << (X)) + + +/* basic AppArmor data structures */ + +struct flagval { + int debug; + int complain; + int audit; +}; + +enum entry_match_type { + aa_entry_literal, + aa_entry_tailglob, + aa_entry_pattern, + aa_entry_invalid +}; + +/* struct aa_entry - file ACL * + * @filename: filename controlled by this ACL + * @mode: permissions granted by ACL + * @type: type of match to perform against @filename + * @extradata: any extra data needed by an extended matching type + * @list: list the ACL is on + * @listp: permission partitioned lists this ACL is on. + * + * Each entry describes a file and an allowed access mode. + */ +struct aa_entry { + char *filename; + int mode; /* mode is 'or' of READ, WRITE, EXECUTE, + * INHERIT, UNCONSTRAINED, and LIBRARY + * (meaning don't prefetch). */ + + enum entry_match_type type; + void *extradata; + + struct list_head list; + struct list_head listp[POS_AA_FILE_MAX + 1]; +}; + +#define AA_SECURE_EXEC_NEEDED 0x00000001 + +#define AA_EXEC_MODIFIER_MASK(mask) ((mask) & AA_EXEC_MODIFIERS) +#define AA_EXEC_MASK(mask) ((mask) & (AA_MAY_EXEC | AA_EXEC_MODIFIERS)) +#define AA_EXEC_UNSAFE_MASK(mask) ((mask) & (AA_MAY_EXEC | AA_EXEC_MODIFIERS |\ + AA_EXEC_UNSAFE)) + +/* struct aaprofile - basic confinement data + * @parent: non refcounted pointer to parent profile + * @name: the profiles name + * @file_entry: file ACL + * @file_entryp: vector of file ACL by permission granted + * @list: list this profile is on + * @sub: profiles list of subprofiles (HATS) + * @flags: flags controlling profile behavior + * @null_profile: if needed per profile learning and null confinement profile + * @isstale: flag to indicate the profile is stale + * @num_file_entries: number of file entries the profile contains + * @num_file_pentries: number of file entries for each partitioned list + * @capabilities: capabilities granted by the process + * @rcu: rcu head used when freeing the profile + * @count: reference count of the profile + * + * The AppArmor profile contains the basic confinement data. Each profile + * has a name and potentially a list of profile entries. The profiles are + * connected in a list + */ +struct aaprofile { + struct aaprofile *parent; + char *name; + + struct list_head file_entry; + struct list_head file_entryp[POS_AA_FILE_MAX + 1]; + struct list_head list; + struct list_head sub; + struct flagval flags; + struct aaprofile *null_profile; + int isstale; + + int num_file_entries; + int num_file_pentries[POS_AA_FILE_MAX + 1]; + + kernel_cap_t capabilities; + + struct rcu_head rcu; + + struct kref count; +}; + +enum aafile_type { + aa_file_default, + aa_file_shmem +}; + +/** + * aafile - file pointer confinement data + * + * Data structure assigned to each open file (by apparmor_file_alloc_security) + */ +struct aafile { + enum aafile_type type; + struct aaprofile *profile; +}; + +/** + * struct subdomain - primary label for confined tasks + * @active: the current active profile + * @hat_magic: the magic token controling the ability to leave a hat + * @list: list this subdomain is on + * @task: task that the subdomain confines + * @cached_caps: caps that have previously generated log entries + * @cached_syscalls: mediated syscalls that have previously been logged + * + * Contains the tasks current active profile (which could change due to + * change_hat). Plus the hat_magic needed during change_hat. + * + * N.B AppArmor's previous product name SubDomain was derived from the name + * of this structure/concept (changehat reducing a task into a sub-domain). + */ +struct subdomain { + struct aaprofile *active; /* The current active profile */ + u32 hat_magic; /* used with change_hat */ + struct list_head list; /* list of subdomains */ + struct task_struct *task; + + kernel_cap_t cached_caps; + unsigned int cached_syscalls; +}; + +typedef int (*aa_iter) (struct subdomain *, void *); + +/* aa_path_data + * temp (cookie) data used by aa_path_* functions, see inline.h + */ +struct aa_path_data { + struct dentry *root, *dentry; + struct mnt_namespace *mnt_namespace; + struct list_head *head, *pos; + int errno; +}; + +#define AA_SUBDOMAIN(sec) ((struct subdomain*)(sec)) +#define AA_PROFILE(sec) ((struct aaprofile*)(sec)) + +/* Lock protecting access to 'struct subdomain' accesses */ +extern spinlock_t sd_lock; + +extern struct aaprofile *null_complain_profile; + +/* aa_audit - AppArmor auditing structure + * Structure is populated by access control code and passed to aa_audit which + * provides for a single point of logging. + */ + +struct aa_audit { + unsigned short type, flags; + unsigned int result; + gfp_t gfp_mask; + int error_code; + + const char *name; + unsigned int ival; + union { + const void *pval; + va_list vaval; + }; +}; + +/* audit types */ +#define AA_AUDITTYPE_FILE 1 +#define AA_AUDITTYPE_DIR 2 +#define AA_AUDITTYPE_ATTR 3 +#define AA_AUDITTYPE_XATTR 4 +#define AA_AUDITTYPE_LINK 5 +#define AA_AUDITTYPE_CAP 6 +#define AA_AUDITTYPE_MSG 7 +#define AA_AUDITTYPE_SYSCALL 8 +#define AA_AUDITTYPE__END 9 + +/* audit flags */ +#define AA_AUDITFLAG_AUDITSS_SYSCALL 1 /* log syscall context */ +#define AA_AUDITFLAG_LOGERR 2 /* log operations that failed due to + non permission errors */ + +#define HINT_UNKNOWN_HAT "unknown_hat" +#define HINT_FORK "fork" +#define HINT_MANDPROF "missing_mandatory_profile" +#define HINT_CHGPROF "changing_profile" + +#define LOG_HINT(p, gfp, hint, fmt, args...) \ + do {\ + aa_audit_message(p, gfp, 0, \ + "LOGPROF-HINT " hint " " fmt, ##args);\ + } while(0) + +/* directory op type, for aa_perm_dir */ +enum aa_diroptype { + aa_dir_mkdir, + aa_dir_rmdir +}; + +/* xattr op type, for aa_xattr */ +enum aa_xattroptype { + aa_xattr_get, + aa_xattr_set, + aa_xattr_list, + aa_xattr_remove +}; + +#define BASE_PROFILE(p) ((p)->parent ? (p)->parent : (p)) +#define IN_SUBPROFILE(p) ((p)->parent) + +/* main.c */ +extern int alloc_null_complain_profile(void); +extern void free_null_complain_profile(void); +extern int attach_nullprofile(struct aaprofile *profile); +extern int aa_audit_message(struct aaprofile *active, gfp_t gfp, int, + const char *, ...); +extern int aa_audit_syscallreject(struct aaprofile *active, gfp_t gfp, + enum aasyscall call); +extern int aa_audit(struct aaprofile *active, const struct aa_audit *); +extern char *aa_get_name(struct dentry *dentry, struct vfsmount *mnt); + +extern int aa_attr(struct aaprofile *active, struct dentry *dentry, + struct iattr *iattr); +extern int aa_xattr(struct aaprofile *active, struct dentry *dentry, + const char *xattr, enum aa_xattroptype xattroptype); +extern int aa_capability(struct aaprofile *active, int cap); +extern int aa_perm(struct aaprofile *active, struct dentry *dentry, + struct vfsmount *mnt, int mask); +extern int aa_perm_nameidata(struct aaprofile *active, struct nameidata *nd, + int mask); +extern int aa_perm_dentry(struct aaprofile *active, struct dentry *dentry, + int mask); +extern int aa_perm_dir(struct aaprofile *active, struct dentry *dentry, + enum aa_diroptype diroptype); +extern int aa_link(struct aaprofile *active, + struct dentry *link, struct dentry *target); +extern int aa_fork(struct task_struct *p); +extern int aa_register(struct linux_binprm *bprm); +extern void aa_release(struct task_struct *p); +extern int aa_change_hat(const char *id, u32 hat_magic); +extern int aa_associate_filp(struct file *filp); + +/* list.c */ +extern struct aaprofile *aa_profilelist_find(const char *name); +extern int aa_profilelist_add(struct aaprofile *profile); +extern struct aaprofile *aa_profilelist_remove(const char *name); +extern void aa_profilelist_release(void); +extern struct aaprofile *aa_profilelist_replace(struct aaprofile *profile); +extern void aa_profile_dump(struct aaprofile *); +extern void aa_profilelist_dump(void); +extern void aa_subdomainlist_add(struct subdomain *); +extern void aa_subdomainlist_remove(struct subdomain *); +extern void aa_subdomainlist_iterate(aa_iter, void *); +extern void aa_subdomainlist_iterateremove(aa_iter, void *); +extern void aa_subdomainlist_release(void); + +/* module_interface.c */ +extern ssize_t aa_file_prof_add(void *, size_t); +extern ssize_t aa_file_prof_repl(void *, size_t); +extern ssize_t aa_file_prof_remove(const char *, size_t); +extern void free_aaprofile(struct aaprofile *profile); +extern void free_aaprofile_kref(struct kref *kref); + +/* procattr.c */ +extern size_t aa_getprocattr(struct aaprofile *active, char *str, size_t size); +extern int aa_setprocattr_changehat(char *hatinfo, size_t infosize); +extern int aa_setprocattr_setprofile(struct task_struct *p, char *profilename, + size_t profilesize); + +/* apparmorfs.c */ +extern int create_apparmorfs(void); +extern void destroy_apparmorfs(void); + +/* capabilities.c */ +extern const char *capability_to_name(unsigned int cap); +extern const char *syscall_to_name(enum aasyscall call); + +#endif /* __APPARMOR_H */ Index: b/security/apparmor/apparmorfs.c =================================================================== --- /dev/null +++ b/security/apparmor/apparmorfs.c @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2005 Novell/SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * AppArmor filesystem (part of securityfs) + */ + +#include +#include +#include +#include +#include + +#include "apparmor.h" +#include "inline.h" +#include "match/match.h" + +#define SECFS_AA "apparmor" +static struct dentry *aafs_dentry = NULL; + +/* profile */ +extern struct seq_operations apparmorfs_profiles_op; +static int aa_prof_open(struct inode *inode, struct file *file); +static int aa_prof_release(struct inode *inode, struct file *file); + +static struct file_operations apparmorfs_profiles_fops = { + .open = aa_prof_open, + .read = seq_read, + .llseek = seq_lseek, + .release = aa_prof_release, +}; + +/* matching */ +static ssize_t aa_matching_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos); + +static struct file_operations apparmorfs_matching_fops = { + .read = aa_matching_read, +}; + + +/* interface */ +static ssize_t aa_profile_load(struct file *f, const char __user *buf, + size_t size, loff_t *pos); +static ssize_t aa_profile_replace(struct file *f, const char __user *buf, + size_t size, loff_t *pos); +static ssize_t aa_profile_remove(struct file *f, const char __user *buf, + size_t size, loff_t *pos); + +static struct file_operations apparmorfs_profile_load = { + .write = aa_profile_load +}; + +static struct file_operations apparmorfs_profile_replace = { + .write = aa_profile_replace +}; + +static struct file_operations apparmorfs_profile_remove = { + .write = aa_profile_remove +}; + + +/* control */ +static u64 aa_control_get(void *data); +static void aa_control_set(void *data, u64 val); + +DEFINE_SIMPLE_ATTRIBUTE(apparmorfs_control_fops, aa_control_get, + aa_control_set, "%lld\n"); + + + +/* table of static entries */ + +static struct root_entry { + const char *name; + int mode; + int access; + struct file_operations *fops; + void *data; + + /* internal fields */ + struct dentry *dentry; + int parent_index; +} root_entries[] = { + /* our root, normally /sys/kernel/security/apparmor */ + {SECFS_AA, S_IFDIR, 0550}, /* DO NOT EDIT/MOVE */ + + /* interface for obtaining list of profiles currently loaded */ + {"profiles", S_IFREG, 0440, &apparmorfs_profiles_fops, + NULL}, + + /* interface for obtaining matching features supported */ + {"matching", S_IFREG, 0440, &apparmorfs_matching_fops, + NULL}, + + /* interface for loading/removing/replacing profiles */ + {".load", S_IFREG, 0640, &apparmorfs_profile_load, + NULL}, + {".replace", S_IFREG, 0640, &apparmorfs_profile_replace, + NULL}, + {".remove", S_IFREG, 0640, &apparmorfs_profile_remove, + NULL}, + + /* interface for setting binary config values */ + {"control", S_IFDIR, 0550}, + {"complain", S_IFREG, 0640, &apparmorfs_control_fops, + &apparmor_complain}, + {"audit", S_IFREG, 0640, &apparmorfs_control_fops, + &apparmor_audit}, + {"debug", S_IFREG, 0640, &apparmorfs_control_fops, + &apparmor_debug}, + {"logsyscall", S_IFREG, 0640, &apparmorfs_control_fops, + &apparmor_logsyscall}, + {NULL, S_IFDIR, 0}, + + /* root end */ + {NULL, S_IFDIR, 0} +}; + +#define AAFS_DENTRY root_entries[0].dentry + +static const unsigned int num_entries = + sizeof(root_entries) / sizeof(struct root_entry); + + + +static int aa_prof_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &apparmorfs_profiles_op); +} + + +static int aa_prof_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +static ssize_t aa_matching_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + const char *matching = aamatch_features(); + + return simple_read_from_buffer(buf, size, ppos, matching, + strlen(matching)); +} + +static char *aa_simple_write_to_buffer(const char __user *userbuf, + size_t alloc_size, size_t copy_size, + loff_t *pos, const char *msg) +{ + struct aaprofile *active; + char *data; + + if (*pos != 0) { + /* only writes from pos 0, that is complete writes */ + data = ERR_PTR(-ESPIPE); + goto out; + } + + /* Don't allow confined processes to load/replace/remove profiles. + * No sane person would add rules allowing this to a profile + * but we enforce the restriction anyways. + */ + rcu_read_lock(); + active = get_activeptr_rcu(); + if (active) { + AA_WARN("REJECTING access to profile %s (%s(%d) " + "profile %s active %s)\n", + msg, current->comm, current->pid, + BASE_PROFILE(active)->name, active->name); + + data = ERR_PTR(-EPERM); + goto out; + } + rcu_read_unlock(); + + data = vmalloc(alloc_size); + if (data == NULL) { + data = ERR_PTR(-ENOMEM); + goto out; + } + + if (copy_from_user(data, userbuf, copy_size)) { + vfree(data); + data = ERR_PTR(-EFAULT); + goto out; + } + +out: + return data; +} + +static ssize_t aa_profile_load(struct file *f, const char __user *buf, + size_t size, loff_t *pos) +{ + char *data; + ssize_t error; + + data = aa_simple_write_to_buffer(buf, size, size, pos, "load"); + + if (!IS_ERR(data)) { + error = aa_file_prof_add(data, size); + vfree(data); + } else { + error = PTR_ERR(data); + } + + return error; +} + +static ssize_t aa_profile_replace(struct file *f, const char __user *buf, + size_t size, loff_t *pos) +{ + char *data; + ssize_t error; + + data = aa_simple_write_to_buffer(buf, size, size, pos, "replacement"); + + if (!IS_ERR(data)) { + error = aa_file_prof_repl(data, size); + vfree(data); + } else { + error = PTR_ERR(data); + } + + return error; +} + +static ssize_t aa_profile_remove(struct file *f, const char __user *buf, + size_t size, loff_t *pos) +{ + char *data; + ssize_t error; + + /* aa_file_prof_remove needs a null terminated string so 1 extra + * byte is allocated and null the copied data is then null terminated + */ + data = aa_simple_write_to_buffer(buf, size+1, size, pos, "removal"); + + if (!IS_ERR(data)) { + data[size] = 0; + error = aa_file_prof_remove(data, size); + vfree(data); + } else { + error = PTR_ERR(data); + } + + return error; +} + +static u64 aa_control_get(void *data) +{ + return *(int *)data; +} + +static void aa_control_set(void *data, u64 val) +{ + if (val > 1) + val = 1; + + *(int*)data = (int)val; +} + +static void clear_apparmorfs(void) +{ + unsigned int i; + + for (i=0; i < num_entries;i++) { + unsigned int index; + + if (root_entries[i].mode == S_IFDIR) { + if (root_entries[i].name) + /* defer dir free till all sub-entries freed */ + continue; + else + /* cleanup parent */ + index = root_entries[i].parent_index; + } else { + index = i; + } + + if (root_entries[index].dentry) { + securityfs_remove(root_entries[index].dentry); + + AA_DEBUG("%s: deleted apparmorfs entry name=%s " + "dentry=%p\n", + __FUNCTION__, + root_entries[index].name, + root_entries[index].dentry); + + root_entries[index].dentry = NULL; + root_entries[index].parent_index = 0; + } + } +} + +static int populate_apparmorfs(struct dentry *root) +{ + unsigned int i, parent_index, depth; + + for (i = 0; i < num_entries; i++) { + root_entries[i].dentry = NULL; + root_entries[i].parent_index = 0; + } + + /* 1. Verify entry 0 is valid [sanity check] */ + if (num_entries == 0 || + !root_entries[0].name || + strcmp(root_entries[0].name, SECFS_AA) != 0 || + root_entries[0].mode != S_IFDIR) { + AA_ERROR("%s: root entry 0 is not SECFS_AA/dir\n", + __FUNCTION__); + goto error; + } + + /* 2. Build back pointers */ + parent_index = 0; + depth = 1; + + for (i = 1; i < num_entries; i++) { + root_entries[i].parent_index = parent_index; + + if (root_entries[i].name && + root_entries[i].mode == S_IFDIR) { + depth++; + parent_index = i; + } else if (!root_entries[i].name) { + if (root_entries[i].mode != S_IFDIR || depth == 0) { + AA_ERROR("%s: root_entry %d invalid (%u %d)", + __FUNCTION__, i, + root_entries[i].mode, + root_entries[i].parent_index); + goto error; + } + + depth--; + parent_index = root_entries[parent_index].parent_index; + } + } + + if (depth != 0) { + AA_ERROR("%s: root_entry table not correctly terminated\n", + __FUNCTION__); + goto error; + } + + /* 3. Create root (parent=NULL) */ + root_entries[0].dentry = securityfs_create_file( + root_entries[0].name, + root_entries[0].mode | + root_entries[0].access, + NULL, NULL, NULL); + + if (IS_ERR(root_entries[0].dentry)) + goto error; + else + AA_DEBUG("%s: created securityfs/apparmor [dentry=%p]\n", + __FUNCTION__, root_entries[0].dentry); + + + /* 4. create remaining nodes */ + for (i = 1; i < num_entries; i++) { + struct dentry *parent; + void *data = NULL; + struct file_operations *fops = NULL; + + /* end of directory ? */ + if (!root_entries[i].name) + continue; + + parent = root_entries[root_entries[i].parent_index].dentry; + + if (root_entries[i].mode != S_IFDIR) { + data = root_entries[i].data; + fops = root_entries[i].fops; + } + + root_entries[i].dentry = securityfs_create_file( + root_entries[i].name, + root_entries[i].mode | + root_entries[i].access, + parent, + data, + fops); + + if (IS_ERR(root_entries[i].dentry)) + goto cleanup_error; + + AA_DEBUG("%s: added apparmorfs entry " + "name=%s mode=%x dentry=%p [parent %p]\n", + __FUNCTION__, root_entries[i].name, + root_entries[i].mode|root_entries[i].access, + root_entries[i].dentry, parent); + } + + return 0; + +cleanup_error: + clear_apparmorfs(); + +error: + return -EINVAL; +} + +int create_apparmorfs(void) +{ + int error = 0; + + if (AAFS_DENTRY) { + error = -EEXIST; + AA_ERROR("%s: AppArmor securityfs already exists\n", + __FUNCTION__); + } else { + error = populate_apparmorfs(aafs_dentry); + if (error != 0) { + AA_ERROR("%s: Error populating AppArmor securityfs\n", + __FUNCTION__); + } + } + + return error; +} + +void destroy_apparmorfs(void) +{ + if (AAFS_DENTRY) + clear_apparmorfs(); +} Index: b/security/apparmor/capabilities.c =================================================================== --- /dev/null +++ b/security/apparmor/capabilities.c @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2005 Novell/SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * AppArmor capability definitions + */ + +#include "apparmor.h" + +static const char *cap_names[] = { + "chown", + "dac_override", + "dac_read_search", + "fowner", + "fsetid", + "kill", + "setgid", + "setuid", + "setpcap", + "linux_immutable", + "net_bind_service", + "net_broadcast", + "net_admin", + "net_raw", + "ipc_lock", + "ipc_owner", + "sys_module", + "sys_rawio", + "sys_chroot", + "sys_ptrace", + "sys_pacct", + "sys_admin", + "sys_boot", + "sys_nice", + "sys_resource", + "sys_time", + "sys_tty_config", + "mknod", + "lease", + "audit_write", + "audit_control" +}; + +const char *capability_to_name(unsigned int cap) +{ + const char *name; + + name = (cap < (sizeof(cap_names) / sizeof(char *)) + ? cap_names[cap] : "invalid-capability"); + + return name; +} + +static const char *syscall_names[] = { + "ptrace", + "sysctl (write)", + "mount", + "umount" +}; + +const char *syscall_to_name(enum aasyscall call) +{ + const char *name; + name = (call < (sizeof(syscall_names) / sizeof(char *)) + ? syscall_names[call] : "invalid-syscall"); + return name; +} Index: b/security/apparmor/inline.h =================================================================== --- /dev/null +++ b/security/apparmor/inline.h @@ -0,0 +1,393 @@ +/* + * Copyright (C) 2005 Novell/SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __INLINE_H +#define __INLINE_H + +#include + +static inline int __aa_is_confined(struct subdomain *sd) +{ + return (sd && sd->active); +} + +/** + * aa_is_confined + * Determine whether current task contains a valid profile (confined). + * Return %1 if confined, %0 otherwise. + */ +static inline int aa_is_confined(void) +{ + struct subdomain *sd = AA_SUBDOMAIN(current->security); + return __aa_is_confined(sd); +} + +static inline int __aa_sub_defined(struct subdomain *sd) +{ + return __aa_is_confined(sd) && !list_empty(&BASE_PROFILE(sd->active)->sub); +} + +/** + * aa_sub_defined - check to see if current task has any subprofiles + * Return 1 if true, 0 otherwise + */ +static inline int aa_sub_defined(void) +{ + struct subdomain *sd = AA_SUBDOMAIN(current->security); + return __aa_sub_defined(sd); +} + +/** + * get_aaprofile - increment refcount on profile @p + * @p: profile + */ +static inline struct aaprofile *get_aaprofile(struct aaprofile *p) +{ + if (p) + kref_get(&(BASE_PROFILE(p)->count)); + + return p; +} + +/** + * put_aaprofile - decrement refcount on profile @p + * @p: profile + */ +static inline void put_aaprofile(struct aaprofile *p) +{ + if (p) + kref_put(&BASE_PROFILE(p)->count, free_aaprofile_kref); +} + +/** + * get_task_activeptr_rcu - get pointer to @tsk's active profile. + * @tsk: task to get active profile from + * + * Requires rcu_read_lock is held + */ +static inline struct aaprofile *get_task_activeptr_rcu(struct task_struct *tsk) +{ + struct subdomain *sd = AA_SUBDOMAIN(tsk->security); + struct aaprofile *active = NULL; + + if (sd) + active = (struct aaprofile *) rcu_dereference(sd->active); + + return active; +} + +/** + * get_activeptr_rcu - get pointer to current task's active profile + * Requires rcu_read_lock is held + */ +static inline struct aaprofile *get_activeptr_rcu(void) +{ + return get_task_activeptr_rcu(current); +} + +/** + * get_task_active_aaprofile - get a reference to tsk's active profile. + * @tsk: the task to get the active profile reference for + */ +static inline struct aaprofile *get_task_active_aaprofile(struct task_struct *tsk) +{ + struct aaprofile *active; + + rcu_read_lock(); + active = get_aaprofile(get_task_activeptr_rcu(tsk)); + rcu_read_unlock(); + + return active; +} + +/** + * get_active_aaprofile - get a reference to the current tasks active profile + */ +static inline struct aaprofile *get_active_aaprofile(void) +{ + return get_task_active_aaprofile(current); +} + +/** + * cap_is_cached - check if @cap access has already been logged for current + * @cap: capability to test if cached + */ +static inline int cap_is_cached(int cap) +{ + struct subdomain *sd = AA_SUBDOMAIN(current->security); + return cap_raised(sd->cached_caps, cap); +} + +/** + * add_to_cached_caps - add a capability to the tasks logged capabilities cache + * @cap: the capability to add + */ +static inline void add_to_cached_caps(int cap) +{ + struct subdomain *sd = AA_SUBDOMAIN(current->security); + sd->cached_caps = cap_combine(sd->cached_caps, CAP_TO_MASK(cap)); +} + +/** + * clear_cached_caps - clear the tasks logged capabilities cache + */ +static inline void clear_cached_caps(struct subdomain *sd) +{ + sd->cached_caps = CAP_EMPTY_SET; +} + +/** + * syscall_is_cached - check if @call access has already been logged + * @call: syscall to test if cached + */ +static inline int syscall_is_cached(enum aasyscall call) +{ + struct subdomain *sd = AA_SUBDOMAIN(current->security); + return sd->cached_syscalls & AA_SYSCALL_TO_MASK(call); +} + +/** + * add_to_cached_syscalls - add a syscall to the tasks logged syscalls cache + * @call: the syscall to add + */ +static inline void add_to_cached_syscalls(enum aasyscall call) +{ + struct subdomain *sd = AA_SUBDOMAIN(current->security); + sd->cached_syscalls |= AA_SYSCALL_TO_MASK(call); +} + +/** + * clear_cached_syscalls - clear the tasks logged syscalls cache + */ +static inline void clear_cached_syscalls(struct subdomain *sd) +{ + sd->cached_syscalls = 0; +} + +/** + * aa_switch - change subdomain to use a new profile + * @sd: subdomain to switch the active profile on + * @newactive: new active profile + * + * aa_switch handles the changing of a subdomain's active profile. The + * sd_lock must be held to ensure consistency against other writers. + * Some write paths (ex. aa_register) require sd->active not to change + * over several operations, so the calling function is responsible + * for grabing the sd_lock to meet its consistency constraints before + * calling aa_switch + */ +static inline void aa_switch(struct subdomain *sd, struct aaprofile *newactive) +{ + struct aaprofile *oldactive = sd->active; + + /* noop if NULL */ + rcu_assign_pointer(sd->active, get_aaprofile(newactive)); + clear_cached_caps(sd); + clear_cached_syscalls(sd); + put_aaprofile(oldactive); +} + +/** + * aa_switch_unconfined - change subdomain to be unconfined (no profile) + * @sd: subdomain to switch + * + * aa_switch_unconfined handles the removal of a subdomain's active profile. + * The sd_lock must be held to ensure consistency against other writers. + * Like aa_switch the sd_lock is used to maintain consistency. + */ +static inline void aa_switch_unconfined(struct subdomain *sd) +{ + aa_switch(sd, NULL); + + /* reset magic in case we were in a subhat before */ + sd->hat_magic = 0; +} + +/** + * alloc_subdomain - allocate a new subdomain + * @tsk: task struct + * + * Allocate a new subdomain including a backpointer to it's referring task. + */ +static inline struct subdomain *alloc_subdomain(struct task_struct *tsk) +{ + struct subdomain *sd; + + sd = kzalloc(sizeof(struct subdomain), GFP_KERNEL); + if (!sd) + goto out; + + /* back pointer to task */ + sd->task = tsk; + + /* any readers of the list must make sure that they can handle + * case where sd->active is not yet set (null) + */ + aa_subdomainlist_add(sd); + +out: + return sd; +} + +/** + * free_subdomain - Free a subdomain previously allocated by alloc_subdomain + * @sd: subdomain + */ +static inline void free_subdomain(struct subdomain *sd) +{ + aa_subdomainlist_remove(sd); + kfree(sd); +} + +/** + * alloc_aaprofile - Allocate, initialize and return a new zeroed profile. + * Returns NULL on failure. + */ +static inline struct aaprofile *alloc_aaprofile(void) +{ + struct aaprofile *profile; + + profile = (struct aaprofile *)kzalloc(sizeof(struct aaprofile), + GFP_KERNEL); + AA_DEBUG("%s(%p)\n", __FUNCTION__, profile); + if (profile) { + int i; + + INIT_LIST_HEAD(&profile->list); + INIT_LIST_HEAD(&profile->sub); + INIT_LIST_HEAD(&profile->file_entry); + for (i = 0; i <= POS_AA_FILE_MAX; i++) { + INIT_LIST_HEAD(&profile->file_entryp[i]); + } + INIT_RCU_HEAD(&profile->rcu); + kref_init(&profile->count); + } + return profile; +} + +/** + * aa_put_name + * @name: name to release. + * + * Release space (free_page) allocated to hold pathname + * name may be NULL (checked for by free_page) + */ +static inline void aa_put_name(const char *name) +{ + free_page((unsigned long)name); +} + +/** __aa_find_profile + * @name: name of profile to find + * @head: list to search + * + * Return reference counted copy of profile. NULL if not found + * Caller must hold any necessary locks + */ +static inline struct aaprofile *__aa_find_profile(const char *name, + struct list_head *head) +{ + struct aaprofile *p; + + if (!name || !head) + return NULL; + + AA_DEBUG("%s: finding profile %s\n", __FUNCTION__, name); + list_for_each_entry(p, head, list) { + if (!strcmp(p->name, name)) { + /* return refcounted object */ + p = get_aaprofile(p); + return p; + } else { + AA_DEBUG("%s: skipping %s\n", __FUNCTION__, p->name); + } + } + return NULL; +} + +/** __aa_path_begin + * @rdentry: filesystem root dentry (searching for vfsmnts matching this) + * @dentry: dentry object to obtain pathname from (relative to matched vfsmnt) + * + * Setup data for iterating over vfsmounts (in current tasks namespace). + */ +static inline void __aa_path_begin(struct dentry *rdentry, + struct dentry *dentry, + struct aa_path_data *data) +{ + data->dentry = dentry; + data->root = dget(rdentry->d_sb->s_root); + data->mnt_namespace = current->nsproxy->mnt_ns; + data->head = &data->mnt_namespace->list; + data->pos = data->head->next; + prefetch(data->pos->next); + data->errno = 0; + + down_read(&namespace_sem); +} + +/** aa_path_begin + * @dentry: filesystem root dentry and object to obtain pathname from + * + * Utility function for calling _aa_path_begin for when the dentry we are + * looking for and the root are the same (this is the usual case). + */ +static inline void aa_path_begin(struct dentry *dentry, + struct aa_path_data *data) +{ + __aa_path_begin(dentry, dentry, data); +} + +/** aa_path_end + * @data: data object previously initialized by aa_path_begin + * + * End iterating over vfsmounts. + * If an error occured in begin or get, it is returned. Otherwise 0. + */ +static inline int aa_path_end(struct aa_path_data *data) +{ + up_read(&namespace_sem); + dput(data->root); + + return data->errno; +} + +/** aa_path_getname + * @data: data object previously initialized by aa_path_begin + * + * Return the next mountpoint which has the same root dentry as data->root. + * If no more mount points exist (or in case of error) NULL is returned + * (caller should call aa_path_end() and inspect return code to differentiate) + */ +static inline char *aa_path_getname(struct aa_path_data *data) +{ + char *name = NULL; + struct vfsmount *mnt; + + while (data->pos != data->head) { + mnt = list_entry(data->pos, struct vfsmount, mnt_list); + + /* advance to next -- so that it is done before we break */ + data->pos = data->pos->next; + prefetch(data->pos->next); + + if (mnt->mnt_root == data->root) { + name = aa_get_name(data->dentry, mnt); + if (IS_ERR(name)) { + data->errno = PTR_ERR(name); + name = NULL; + } + break; + } + } + + return name; +} + +#endif /* __INLINE_H__ */ Index: b/security/apparmor/list.c =================================================================== --- /dev/null +++ b/security/apparmor/list.c @@ -0,0 +1,268 @@ +/* + * Copyright (C) 1998-2005 Novell/SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * AppArmor Profile List Management + */ + +#include +#include "apparmor.h" +#include "inline.h" + +/* list of all profiles and lock */ +static LIST_HEAD(profile_list); +static rwlock_t profile_lock = RW_LOCK_UNLOCKED; + +/* list of all subdomains and lock */ +static LIST_HEAD(subdomain_list); +static rwlock_t subdomain_lock = RW_LOCK_UNLOCKED; + +/** + * aa_profilelist_find + * @name: profile name (program name) + * + * Search the profile list for profile @name. Return refcounted profile on + * success, NULL on failure. + */ +struct aaprofile *aa_profilelist_find(const char *name) +{ + struct aaprofile *p = NULL; + if (name) { + read_lock(&profile_lock); + p = __aa_find_profile(name, &profile_list); + read_unlock(&profile_lock); + } + return p; +} + +/** + * aa_profilelist_add - add new profile to list + * @profile: new profile to add to list + * + * NOTE: Caller must allocate necessary reference count that will be used + * by the profile_list. This is because profile allocation alloc_aaprofile() + * returns an unreferenced object with a initial count of %1. + * + * Return %1 on success, %0 on failure (already exists) + */ +int aa_profilelist_add(struct aaprofile *profile) +{ + struct aaprofile *old_profile; + int ret = 0; + + if (!profile) + goto out; + + write_lock(&profile_lock); + old_profile = __aa_find_profile(profile->name, &profile_list); + if (old_profile) { + put_aaprofile(old_profile); + goto out; + } + + list_add(&profile->list, &profile_list); + ret = 1; + out: + write_unlock(&profile_lock); + return ret; +} + +/** + * aa_profilelist_remove - remove a profile from the list by name + * @name: name of profile to be removed + * + * If the profile exists remove profile from list and return its reference. + * The reference count on profile is not decremented and should be decremented + * when the profile is no longer needed + */ +struct aaprofile *aa_profilelist_remove(const char *name) +{ + struct aaprofile *profile = NULL; + struct aaprofile *p, *tmp; + + if (!name) + goto out; + + write_lock(&profile_lock); + list_for_each_entry_safe(p, tmp, &profile_list, list) { + if (!strcmp(p->name, name)) { + list_del_init(&p->list); + /* mark old profile as stale */ + p->isstale = 1; + profile = p; + break; + } + } + write_unlock(&profile_lock); + +out: + return profile; +} + +/** + * aa_profilelist_replace - replace a profile on the list + * @profile: new profile + * + * Replace a profile on the profile list. Find the old profile by name in + * the list, and replace it with the new profile. NOTE: Caller must allocate + * necessary initial reference count for new profile as aa_profilelist_add(). + * + * This is an atomic list operation. Returns the old profile (which is still + * refcounted) if there was one, or NULL. + */ +struct aaprofile *aa_profilelist_replace(struct aaprofile *profile) +{ + struct aaprofile *oldprofile; + + write_lock(&profile_lock); + oldprofile = __aa_find_profile(profile->name, &profile_list); + if (oldprofile) { + list_del_init(&oldprofile->list); + /* mark old profile as stale */ + oldprofile->isstale = 1; + + /* __aa_find_profile incremented count, so adjust down */ + put_aaprofile(oldprofile); + } + + list_add(&profile->list, &profile_list); + write_unlock(&profile_lock); + + return oldprofile; +} + +/** + * aa_profilelist_release - Remove all profiles from profile_list + */ +void aa_profilelist_release(void) +{ + struct aaprofile *p, *tmp; + + write_lock(&profile_lock); + list_for_each_entry_safe(p, tmp, &profile_list, list) { + list_del_init(&p->list); + put_aaprofile(p); + } + write_unlock(&profile_lock); +} + +/** + * aa_subdomainlist_add - Add subdomain to subdomain_list + * @sd: new subdomain + */ +void aa_subdomainlist_add(struct subdomain *sd) +{ + unsigned long flags; + + if (!sd) { + AA_INFO("%s: bad subdomain\n", __FUNCTION__); + return; + } + + write_lock_irqsave(&subdomain_lock, flags); + /* new subdomains must be added to the end of the list due to a + * subtle interaction between fork and profile replacement. + */ + list_add_tail(&sd->list, &subdomain_list); + write_unlock_irqrestore(&subdomain_lock, flags); +} + +/** + * aa_subdomainlist_remove - Remove subdomain from subdomain_list + * @sd: subdomain to be removed + */ +void aa_subdomainlist_remove(struct subdomain *sd) +{ + unsigned long flags; + + if (sd) { + write_lock_irqsave(&subdomain_lock, flags); + list_del_init(&sd->list); + write_unlock_irqrestore(&subdomain_lock, flags); + } +} + +/** + * aa_subdomainlist_iterate - iterate over the subdomain list applying @func + * @func: method to be called for each element + * @cookie: user passed data + * + * Iterate over subdomain list applying @func, stop when @func returns + * non zero + */ +void aa_subdomainlist_iterate(aa_iter func, void *cookie) +{ + struct subdomain *node; + int ret = 0; + unsigned long flags; + + read_lock_irqsave(&subdomain_lock, flags); + list_for_each_entry(node, &subdomain_list, list) { + ret = (*func) (node, cookie); + if (ret != 0) + break; + } + read_unlock_irqrestore(&subdomain_lock, flags); +} + +/** + * aa_subdomainlist_release - Remove all subdomains from subdomain_list + */ +void aa_subdomainlist_release(void) +{ + struct subdomain *node, *tmp; + unsigned long flags; + + write_lock_irqsave(&subdomain_lock, flags); + list_for_each_entry_safe(node, tmp, &subdomain_list, list) { + list_del_init(&node->list); + } + write_unlock_irqrestore(&subdomain_lock, flags); +} + +/* seq_file helper routines + * Used by apparmorfs.c to iterate over profile_list + */ +static void *p_start(struct seq_file *f, loff_t *pos) +{ + struct aaprofile *node; + loff_t l = *pos; + + read_lock(&profile_lock); + list_for_each_entry(node, &profile_list, list) + if (!l--) + return node; + return NULL; +} + +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct list_head *lh = ((struct aaprofile *)p)->list.next; + (*pos)++; + return lh == &profile_list ? + NULL : list_entry(lh, struct aaprofile, list); +} + +static void p_stop(struct seq_file *f, void *v) +{ + read_unlock(&profile_lock); +} + +static int seq_show_profile(struct seq_file *f, void *v) +{ + struct aaprofile *profile = (struct aaprofile *)v; + seq_printf(f, "%s (%s)\n", profile->name, + PROFILE_COMPLAIN(profile) ? "complain" : "enforce"); + return 0; +} + +struct seq_operations apparmorfs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; Index: b/security/apparmor/lsm.c =================================================================== --- /dev/null +++ b/security/apparmor/lsm.c @@ -0,0 +1,894 @@ +/* + * Copyright (C) 2002-2005 Novell/SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * http://forge.novell.com/modules/xfmod/project/?apparmor + * + * Immunix AppArmor LSM interface + */ + +#include +#include +#include +#include + +#include "apparmor.h" +#include "inline.h" + +/* struct subdomain write update lock (read side is RCU). */ +spinlock_t sd_lock = SPIN_LOCK_UNLOCKED; + +/* Flag values, also controllable via apparmorfs/control. + * We explicitly do not allow these to be modifiable when exported via + * /sys/modules/parameters, as we want to do additional mediation and + * don't want to add special path code. */ + +/* Complain mode -- in complain mode access failures result in auditing only + * and task is allowed access. audit events are processed by userspace to + * generate policy. Default is 'enforce' (0). + * Value is also togglable per profile and referenced when global value is + * enforce. + */ +int apparmor_complain = 0; +module_param_named(complain, apparmor_complain, int, S_IRUSR); +MODULE_PARM_DESC(apparmor_complain, "Toggle AppArmor complain mode"); + +/* Debug mode */ +int apparmor_debug = 0; +module_param_named(debug, apparmor_debug, int, S_IRUSR); +MODULE_PARM_DESC(apparmor_debug, "Toggle AppArmor debug mode"); + +/* Audit mode */ +int apparmor_audit = 0; +module_param_named(audit, apparmor_audit, int, S_IRUSR); +MODULE_PARM_DESC(apparmor_audit, "Toggle AppArmor audit mode"); + +/* Syscall logging mode */ +int apparmor_logsyscall = 0; +module_param_named(logsyscall, apparmor_logsyscall, int, S_IRUSR); +MODULE_PARM_DESC(apparmor_logsyscall, "Toggle AppArmor logsyscall mode"); + +#ifndef MODULE +static int __init aa_getopt_complain(char *str) +{ + get_option(&str, &apparmor_complain); + return 1; +} +__setup("apparmor_complain=", aa_getopt_complain); + +static int __init aa_getopt_debug(char *str) +{ + get_option(&str, &apparmor_debug); + return 1; +} +__setup("apparmor_debug=", aa_getopt_debug); + +static int __init aa_getopt_audit(char *str) +{ + get_option(&str, &apparmor_audit); + return 1; +} +__setup("apparmor_audit=", aa_getopt_audit); + +static int __init aa_getopt_logsyscall(char *str) +{ + get_option(&str, &apparmor_logsyscall); + return 1; +} +__setup("apparmor_logsyscall=", aa_getopt_logsyscall); +#endif + +static int apparmor_ptrace(struct task_struct *parent, + struct task_struct *child) +{ + int error; + struct aaprofile *active; + + error = cap_ptrace(parent, child); + + active = get_task_active_aaprofile(parent); + + if (!error && active) + error = aa_audit_syscallreject(active, GFP_ATOMIC, + AA_SYSCALL_PTRACE); + + put_aaprofile(active); + + return error; +} + +static int apparmor_capget(struct task_struct *target, + kernel_cap_t *effective, + kernel_cap_t *inheritable, + kernel_cap_t *permitted) +{ + return cap_capget(target, effective, inheritable, permitted); +} + +static int apparmor_capset_check(struct task_struct *target, + kernel_cap_t *effective, + kernel_cap_t *inheritable, + kernel_cap_t *permitted) +{ + return cap_capset_check(target, effective, inheritable, permitted); +} + +static void apparmor_capset_set(struct task_struct *target, + kernel_cap_t *effective, + kernel_cap_t *inheritable, + kernel_cap_t *permitted) +{ + cap_capset_set(target, effective, inheritable, permitted); + return; +} + +static int apparmor_capable(struct task_struct *tsk, int cap) +{ + int error; + + /* cap_capable returns 0 on success, else -EPERM */ + error = cap_capable(tsk, cap); + + if (error == 0) { + struct aaprofile *active; + + active = get_task_active_aaprofile(tsk); + + if (active) + error = aa_capability(active, cap); + + put_aaprofile(active); + } + + return error; +} + +static int apparmor_sysctl(struct ctl_table *table, int op) +{ + int error = 0; + struct aaprofile *active; + + active = get_active_aaprofile(); + + if ((op & 002) && active && !capable(CAP_SYS_ADMIN)) + error = aa_audit_syscallreject(active, GFP_ATOMIC, + AA_SYSCALL_SYSCTL_WRITE); + + put_aaprofile(active); + + return error; +} + +static int apparmor_syslog(int type) +{ + return cap_syslog(type); +} + +static int apparmor_netlink_send(struct sock *sk, struct sk_buff *skb) +{ + return cap_netlink_send(sk, skb); +} + +static int apparmor_netlink_recv(struct sk_buff *skb, int cap) +{ + return cap_netlink_recv(skb, cap); +} + +static void apparmor_bprm_apply_creds(struct linux_binprm *bprm, int unsafe) +{ + cap_bprm_apply_creds(bprm, unsafe); + return; +} + +static int apparmor_bprm_set_security(struct linux_binprm *bprm) +{ + /* handle capability bits with setuid, etc */ + cap_bprm_set_security(bprm); + /* already set based on script name */ + if (bprm->sh_bang) + return 0; + return aa_register(bprm); +} + +static int apparmor_bprm_secureexec(struct linux_binprm *bprm) +{ + int ret = cap_bprm_secureexec(bprm); + + if (ret == 0 && + (unsigned long)bprm->security & AA_SECURE_EXEC_NEEDED) { + AA_DEBUG("%s: secureexec required for %s\n", + __FUNCTION__, bprm->filename); + ret = 1; + } + + return ret; +} + +static int apparmor_sb_mount(char *dev_name, struct nameidata *nd, char *type, + unsigned long flags, void *data) +{ + int error = 0; + struct aaprofile *active; + + active = get_active_aaprofile(); + + if (active) + error = aa_audit_syscallreject(active, GFP_ATOMIC, + AA_SYSCALL_MOUNT); + + put_aaprofile(active); + + return error; +} + +static int apparmor_umount(struct vfsmount *mnt, int flags) +{ + int error = 0; + struct aaprofile *active; + + active = get_active_aaprofile(); + + if (active) + error = aa_audit_syscallreject(active, GFP_ATOMIC, + AA_SYSCALL_UMOUNT); + + put_aaprofile(active); + + return error; +} + +static int apparmor_inode_mkdir(struct inode *inode, struct dentry *dentry, + int mask) +{ + struct aaprofile *active; + int error = 0; + + active = get_active_aaprofile(); + + if (active) + error = aa_perm_dir(active, dentry, aa_dir_mkdir); + + put_aaprofile(active); + + return error; +} + +static int apparmor_inode_rmdir(struct inode *inode, struct dentry *dentry) +{ + struct aaprofile *active; + int error = 0; + + active = get_active_aaprofile(); + + if (active) + error = aa_perm_dir(active, dentry, aa_dir_rmdir); + + put_aaprofile(active); + + return error; +} + +static int apparmor_inode_create(struct inode *inode, struct dentry *dentry, + int mask) +{ + struct aaprofile *active; + int error = 0; + + active = get_active_aaprofile(); + + /* At a minimum, need write perm to create */ + if (active) + error = aa_perm_dentry(active, dentry, MAY_WRITE); + + put_aaprofile(active); + + return error; +} + +static int apparmor_inode_link(struct dentry *old_dentry, struct inode *inode, + struct dentry *new_dentry) +{ + int error = 0; + struct aaprofile *active; + + active = get_active_aaprofile(); + + if (active) + error = aa_link(active, new_dentry, old_dentry); + + put_aaprofile(active); + + return error; +} + +static int apparmor_inode_unlink(struct inode *inode, struct dentry *dentry) +{ + struct aaprofile *active; + int error = 0; + + active = get_active_aaprofile(); + + if (active) + error = aa_perm_dentry(active, dentry, MAY_WRITE); + + put_aaprofile(active); + + return error; +} + +static int apparmor_inode_mknod(struct inode *inode, struct dentry *dentry, + int mode, dev_t dev) +{ + struct aaprofile *active; + int error = 0; + + active = get_active_aaprofile(); + + if (active) + error = aa_perm_dentry(active, dentry, MAY_WRITE); + + put_aaprofile(active); + + return error; +} + +static int apparmor_inode_rename(struct inode *old_inode, + struct dentry *old_dentry, + struct inode *new_inode, + struct dentry *new_dentry) +{ + struct aaprofile *active; + int error = 0; + + active = get_active_aaprofile(); + + if (active) { + error = aa_perm_dentry(active, old_dentry, MAY_READ | + MAY_WRITE); + + if (!error) + error = aa_perm_dentry(active, new_dentry, + MAY_WRITE); + } + + put_aaprofile(active); + + return error; +} + +static int apparmor_inode_permission(struct inode *inode, int mask, + struct nameidata *nd) +{ + int error = 0; + + /* Do not perform check on pipes or sockets + * Same as apparmor_file_permission + */ + if (VALID_FSTYPE(inode)) { + struct aaprofile *active; + + active = get_active_aaprofile(); + if (active) + error = aa_perm_nameidata(active, nd, mask); + put_aaprofile(active); + } + + return error; +} + +static int apparmor_inode_setattr(struct dentry *dentry, struct iattr *iattr) +{ + int error = 0; + + if (VALID_FSTYPE(dentry->d_inode)) { + struct aaprofile *active; + + active = get_active_aaprofile(); + /* + * Mediate any attempt to change attributes of a file + * (chmod, chown, chgrp, etc) + */ + if (active) + error = aa_attr(active, dentry, iattr); + + put_aaprofile(active); + } + + return error; +} + +static int apparmor_inode_setxattr(struct dentry *dentry, char *name, + void *value, size_t size, int flags) +{ + int error = 0; + + if (VALID_FSTYPE(dentry->d_inode)) { + struct aaprofile *active; + + active = get_active_aaprofile(); + if (active) + error = aa_xattr(active, dentry, name, aa_xattr_set); + put_aaprofile(active); + } + + return error; +} + +static int apparmor_inode_getxattr(struct dentry *dentry, char *name) +{ + int error = 0; + + if (VALID_FSTYPE(dentry->d_inode)) { + struct aaprofile *active; + + active = get_active_aaprofile(); + if (active) + error = aa_xattr(active, dentry, name, aa_xattr_get); + put_aaprofile(active); + } + + return error; +} +static int apparmor_inode_listxattr(struct dentry *dentry) +{ + int error = 0; + + if (VALID_FSTYPE(dentry->d_inode)) { + struct aaprofile *active; + + active = get_active_aaprofile(); + if (active) + error = aa_xattr(active, dentry, NULL, aa_xattr_list); + put_aaprofile(active); + } + + return error; +} + +static int apparmor_inode_removexattr(struct dentry *dentry, char *name) +{ + int error = 0; + + if (VALID_FSTYPE(dentry->d_inode)) { + struct aaprofile *active; + + active = get_active_aaprofile(); + if (active) + error = aa_xattr(active, dentry, name, + aa_xattr_remove); + put_aaprofile(active); + } + + return error; +} + +static int apparmor_file_permission(struct file *file, int mask) +{ + struct aaprofile *active; + struct aafile *aaf; + int error = 0; + + aaf = (struct aafile *)file->f_security; + /* bail out early if this isn't a mediated file */ + if (!aaf || !VALID_FSTYPE(file->f_dentry->d_inode)) + goto out; + + active = get_active_aaprofile(); + if (active && aaf->profile != active) + error = aa_perm(active, file->f_dentry, file->f_vfsmnt, + mask & (MAY_EXEC | MAY_WRITE | MAY_READ)); + put_aaprofile(active); + +out: + return error; +} + +static int apparmor_file_alloc_security(struct file *file) +{ + struct aaprofile *active; + int error = 0; + + active = get_active_aaprofile(); + if (active) { + struct aafile *aaf; + aaf = kmalloc(sizeof(struct aafile), GFP_KERNEL); + + if (aaf) { + aaf->type = aa_file_default; + aaf->profile = get_aaprofile(active); + } else { + error = -ENOMEM; + } + file->f_security = aaf; + } + put_aaprofile(active); + + return error; +} + +static void apparmor_file_free_security(struct file *file) +{ + struct aafile *aaf = (struct aafile *)file->f_security; + + if (aaf) { + put_aaprofile(aaf->profile); + kfree(aaf); + } +} + +static inline int aa_mmap(struct file *file, unsigned long prot, + unsigned long flags) +{ + int error = 0, mask = 0; + struct aaprofile *active; + struct aafile *aaf; + + active = get_active_aaprofile(); + if (!active || !file || + !(aaf = (struct aafile *)file->f_security) || + aaf->type == aa_file_shmem) + goto out; + + if (prot & PROT_READ) + mask |= MAY_READ; + + /* Private mappings don't require write perms since they don't + * write back to the files */ + if (prot & PROT_WRITE && !(flags & MAP_PRIVATE)) + mask |= MAY_WRITE; + if (prot & PROT_EXEC) + mask |= AA_EXEC_MMAP; + + AA_DEBUG("%s: 0x%x\n", __FUNCTION__, mask); + + if (mask) + error = aa_perm(active, file->f_dentry, file->f_vfsmnt, mask); + + put_aaprofile(active); + +out: + return error; +} + +static int apparmor_file_mmap(struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags) +{ + return aa_mmap(file, prot, flags); +} + +static int apparmor_file_mprotect(struct vm_area_struct *vma, + unsigned long reqprot, unsigned long prot) +{ + return aa_mmap(vma->vm_file, prot, + !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); +} + +static int apparmor_task_alloc_security(struct task_struct *p) +{ + return aa_fork(p); +} + +static void apparmor_task_free_security(struct task_struct *p) +{ + aa_release(p); +} + +static int apparmor_task_post_setuid(uid_t id0, uid_t id1, uid_t id2, + int flags) +{ + return cap_task_post_setuid(id0, id1, id2, flags); +} + +static void apparmor_task_reparent_to_init(struct task_struct *p) +{ + cap_task_reparent_to_init(p); + return; +} + +static int apparmor_shm_shmat(struct shmid_kernel *shp, char __user *shmaddr, + int shmflg) +{ + struct aafile *aaf = (struct aafile *)shp->shm_file->f_security; + + if (aaf) + aaf->type = aa_file_shmem; + + return 0; +} + +static int apparmor_getprocattr(struct task_struct *p, char *name, void *value, + size_t size) +{ + int error; + struct aaprofile *active; + char *str = value; + + /* AppArmor only supports the "current" process attribute */ + if (strcmp(name, "current") != 0) { + error = -EINVAL; + goto out; + } + + /* must be task querying itself or admin */ + if (current != p && !capable(CAP_SYS_ADMIN)) { + error = -EPERM; + goto out; + } + + active = get_task_active_aaprofile(p); + error = aa_getprocattr(active, str, size); + put_aaprofile(active); + +out: + return error; +} + +static int apparmor_setprocattr(struct task_struct *p, char *name, void *value, + size_t size) +{ + const char *cmd_changehat = "changehat ", + *cmd_setprofile = "setprofile "; + + int error = -EACCES; /* default to a perm denied */ + char *cmd = (char *)value; + + /* only support messages to current */ + if (strcmp(name, "current") != 0) { + error = -EINVAL; + goto out; + } + + if (!size) { + error = -ERANGE; + goto out; + } + + /* CHANGE HAT -- switch task into a subhat (subprofile) if defined */ + if (size > strlen(cmd_changehat) && + strncmp(cmd, cmd_changehat, strlen(cmd_changehat)) == 0) { + char *hatinfo = cmd + strlen(cmd_changehat); + size_t infosize = size - strlen(cmd_changehat); + + /* Only the current process may change it's hat */ + if (current != p) { + AA_WARN("%s: Attempt by foreign task %s(%d) " + "[user %d] to changehat of task %s(%d)\n", + __FUNCTION__, + current->comm, + current->pid, + current->uid, + p->comm, + p->pid); + + error = -EACCES; + goto out; + } + + error = aa_setprocattr_changehat(hatinfo, infosize); + if (error == 0) + /* success, set return to #bytes in orig request */ + error = size; + + /* SET NEW PROFILE */ + } else if (size > strlen(cmd_setprofile) && + strncmp(cmd, cmd_setprofile, strlen(cmd_setprofile)) == 0) { + struct aaprofile *active; + + /* only an unconfined process with admin capabilities + * may change the profile of another task + */ + + if (!capable(CAP_SYS_ADMIN)) { + AA_WARN("%s: Unprivileged attempt by task %s(%d) " + "[user %d] to assign profile to task %s(%d)\n", + __FUNCTION__, + current->comm, + current->pid, + current->uid, + p->comm, + p->pid); + error = -EACCES; + goto out; + } + + active = get_active_aaprofile(); + if (!active) { + char *profile = cmd + strlen(cmd_setprofile); + size_t profilesize = size - strlen(cmd_setprofile); + + error = aa_setprocattr_setprofile(p, profile, profilesize); + if (error == 0) + /* success, + * set return to #bytes in orig request + */ + error = size; + } else { + AA_WARN("%s: Attempt by confined task %s(%d) " + "[user %d] to assign profile to task %s(%d)\n", + __FUNCTION__, + current->comm, + current->pid, + current->uid, + p->comm, + p->pid); + + error = -EACCES; + } + put_aaprofile(active); + } else { + /* unknown operation */ + AA_WARN("%s: Unknown setprocattr command '%.*s' by task %s(%d) " + "[user %d] for task %s(%d)\n", + __FUNCTION__, + size < 16 ? (int)size : 16, + cmd, + current->comm, + current->pid, + current->uid, + p->comm, + p->pid); + + error = -EINVAL; + } + +out: + return error; +} + +struct security_operations apparmor_ops = { + .ptrace = apparmor_ptrace, + .capget = apparmor_capget, + .capset_check = apparmor_capset_check, + .capset_set = apparmor_capset_set, + .sysctl = apparmor_sysctl, + .capable = apparmor_capable, + .syslog = apparmor_syslog, + + .netlink_send = apparmor_netlink_send, + .netlink_recv = apparmor_netlink_recv, + + .bprm_apply_creds = apparmor_bprm_apply_creds, + .bprm_set_security = apparmor_bprm_set_security, + .bprm_secureexec = apparmor_bprm_secureexec, + + .sb_mount = apparmor_sb_mount, + .sb_umount = apparmor_umount, + + .inode_mkdir = apparmor_inode_mkdir, + .inode_rmdir = apparmor_inode_rmdir, + .inode_create = apparmor_inode_create, + .inode_link = apparmor_inode_link, + .inode_unlink = apparmor_inode_unlink, + .inode_mknod = apparmor_inode_mknod, + .inode_rename = apparmor_inode_rename, + .inode_permission = apparmor_inode_permission, + .inode_setattr = apparmor_inode_setattr, + .inode_setxattr = apparmor_inode_setxattr, + .inode_getxattr = apparmor_inode_getxattr, + .inode_listxattr = apparmor_inode_listxattr, + .inode_removexattr = apparmor_inode_removexattr, + .file_permission = apparmor_file_permission, + .file_alloc_security = apparmor_file_alloc_security, + .file_free_security = apparmor_file_free_security, + .file_mmap = apparmor_file_mmap, + .file_mprotect = apparmor_file_mprotect, + + .task_alloc_security = apparmor_task_alloc_security, + .task_free_security = apparmor_task_free_security, + .task_post_setuid = apparmor_task_post_setuid, + .task_reparent_to_init = apparmor_task_reparent_to_init, + + .shm_shmat = apparmor_shm_shmat, + + .getprocattr = apparmor_getprocattr, + .setprocattr = apparmor_setprocattr, +}; + +static int __init apparmor_init(void) +{ + int error; + const char *complainmsg = ": complainmode enabled"; + + if ((error = create_apparmorfs())) { + AA_ERROR("Unable to activate AppArmor filesystem\n"); + goto createfs_out; + } + + if ((error = alloc_null_complain_profile())){ + AA_ERROR("Unable to allocate null complain profile\n"); + goto alloc_out; + } + + if ((error = register_security(&apparmor_ops))) { + AA_ERROR("Unable to load AppArmor\n"); + goto register_security_out; + } + + AA_INFO("AppArmor initialized%s\n", + apparmor_complain ? complainmsg : ""); + aa_audit_message(NULL, GFP_KERNEL, 0, + "AppArmor initialized%s\n", + apparmor_complain ? complainmsg : ""); + + return error; + +register_security_out: + free_null_complain_profile(); + +alloc_out: + (void)destroy_apparmorfs(); + +createfs_out: + return error; + +} + +static int apparmor_exit_removeall_iter(struct subdomain *sd, void *cookie) +{ + /* spin_lock(&sd_lock) held here */ + + if (__aa_is_confined(sd)) { + AA_DEBUG("%s: Dropping profiles %s(%d) " + "profile %s(%p) active %s(%p)\n", + __FUNCTION__, + sd->task->comm, sd->task->pid, + BASE_PROFILE(sd->active)->name, + BASE_PROFILE(sd->active), + sd->active->name, sd->active); + aa_switch_unconfined(sd); + } + + return 0; +} + +static void __exit apparmor_exit(void) +{ + unsigned long flags; + + /* Remove profiles from the global profile list. + * This is just for tidyness as there is no way to reference this + * list once the AppArmor lsm hooks are detached (below) + */ + aa_profilelist_release(); + + /* Remove profiles from active tasks + * If this is not done, if module is reloaded after being removed, + * old profiles (still refcounted in memory) will become 'magically' + * reattached + */ + + spin_lock_irqsave(&sd_lock, flags); + aa_subdomainlist_iterate(apparmor_exit_removeall_iter, NULL); + spin_unlock_irqrestore(&sd_lock, flags); + + /* Free up list of active subdomain */ + aa_subdomainlist_release(); + + free_null_complain_profile(); + + destroy_apparmorfs(); + + if (unregister_security(&apparmor_ops)) + AA_WARN("Unable to properly unregister AppArmor\n"); + + /* delay for an rcu cycle to make ensure that profiles pending + * destruction in the rcu callback are freed. + */ + synchronize_rcu(); + + AA_INFO("AppArmor protection removed\n"); + aa_audit_message(NULL, GFP_KERNEL, 0, + "AppArmor protection removed\n"); +} + +module_init(apparmor_init); +module_exit(apparmor_exit); + +MODULE_VERSION(APPARMOR_VERSION); +MODULE_DESCRIPTION("AppArmor process confinement"); +MODULE_AUTHOR("Tony Jones "); +MODULE_LICENSE("GPL"); Index: b/security/apparmor/main.c =================================================================== --- /dev/null +++ b/security/apparmor/main.c @@ -0,0 +1,1702 @@ +/* + * Copyright (C) 2002-2005 Novell/SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * AppArmor Core + */ + +#include +#include +#include + +#include "apparmor.h" +#include "match/match.h" + +#include "inline.h" + +/* NULL complain profile + * + * Used when in complain mode, to emit Permitting messages for non-existant + * profiles and hats. This is necessary because of selective mode, in which + * case we need a complain null_profile and enforce null_profile + * + * The null_complain_profile cannot be statically allocated, because it + * can be associated to files which keep their reference even if apparmor is + * unloaded + */ +struct aaprofile *null_complain_profile; + +/*************************** + * Private utility functions + **************************/ + +/** + * dentry_xlate_error + * @dentry: pointer to dentry + * @error: error number + * @dtype: type of dentry + * + * Display error message when a dentry translation error occured + */ +static void dentry_xlate_error(struct dentry *dentry, int error, char *dtype) +{ + const unsigned int len = 16; + char buf[len]; + + if (dentry->d_inode) { + snprintf(buf, len, "%lu", dentry->d_inode->i_ino); + } else { + strncpy(buf, "", len); + buf[len-1]=0; + } + + AA_ERROR("An error occured while translating %s %p " + "inode# %s to a pathname. Error %d\n", + dtype, + dentry, + buf, + error); +} + +/** + * aa_taskattr_access + * @procrelname: name of file to check permission + * + * Determine if request is for write access to /proc/self/attr/current + * This file is the usermode iterface for changing it's hat. + */ +static inline int aa_taskattr_access(const char *procrelname) +{ + char buf[sizeof("/attr/current") + 10]; + const int maxbuflen = sizeof(buf); + /* assumption, 32bit pid (10 decimal digits incl \0) */ + + snprintf(buf, maxbuflen, "%d/attr/current", current->pid); + buf[maxbuflen - 1] = 0; + + return strcmp(buf, procrelname) == 0; +} + +/** + * aa_file_mode - get full mode for file entry from profile + * @profile: profile + * @name: filename + */ +static inline int aa_file_mode(struct aaprofile *profile, const char *name) +{ + struct aa_entry *entry; + int mode = 0; + + AA_DEBUG("%s: %s\n", __FUNCTION__, name); + if (!name) { + AA_DEBUG("%s: no name\n", __FUNCTION__); + goto out; + } + + if (!profile) { + AA_DEBUG("%s: no profile\n", __FUNCTION__); + goto out; + } + list_for_each_entry(entry, &profile->file_entry, list) { + if (aamatch_match(name, entry->filename, + entry->type, entry->extradata)) + mode |= entry->mode; + } +out: + return mode; +} + +/** + * aa_get_execmode - calculate what qualifier to apply to an exec + * @active: profile to search + * @name: name of file to exec + * @xmod: pointer to a execution mode bit for the rule that was matched + * if the rule has no execuition qualifier {pui} then + * %AA_MAY_EXEC is returned indicating a naked x + * if the has an exec qualifier then only the qualifier bit {pui} + * is returned (%AA_MAY_EXEC) is not set. + * @unsafe: true if secure_exec should be overriden + * Returns %0 (false): + * if unable to find profile or there are conflicting pattern matches. + * *xmod - is not modified + * *unsafe - is not modified + * + * Returns %1 (true): + * if exec rule matched + * if the rule has an execution mode qualifier {pui} then + * *xmod = the execution qualifier of the rule {pui} + * else + * *xmod = %AA_MAY_EXEC + * unsafe = presence of unsage flag + */ +static inline int aa_get_execmode(struct aaprofile *active, const char *name, + int *xmod, int *unsafe) +{ + struct aa_entry *entry; + struct aa_entry *match = NULL; + + int pattern_match_invalid = 0, rc = 0; + + /* search list of profiles with 'x' permission + * this will also include entries with 'p', 'u' and 'i' + * qualifiers. + * + * If we find a pattern match we will keep looking for an exact match + * If we find conflicting pattern matches we will flag (while still + * looking for an exact match). If all we have is a conflict, FALSE + * is returned. + */ + + list_for_each_entry(entry, &active->file_entryp[POS_AA_MAY_EXEC], + listp[POS_AA_MAY_EXEC]) { + if (!pattern_match_invalid && + entry->type == aa_entry_pattern && + aamatch_match(name, entry->filename, + entry->type, entry->extradata)) { + if (match && + AA_EXEC_UNSAFE_MASK(entry->mode) != + AA_EXEC_UNSAFE_MASK(match->mode)) + pattern_match_invalid = 1; + else + /* keep searching for an exact match */ + match = entry; + } else if ((entry->type == aa_entry_literal || + (!pattern_match_invalid && + entry->type == aa_entry_tailglob)) && + aamatch_match(name, entry->filename, + entry->type, + entry->extradata)) { + if (entry->type == aa_entry_literal) { + /* got an exact match -- there can be only + * one, asserted at profile load time + */ + match = entry; + pattern_match_invalid = 0; + break; + } else { + if (match && + AA_EXEC_UNSAFE_MASK(entry->mode) != + AA_EXEC_UNSAFE_MASK(match->mode)) + pattern_match_invalid = 1; + else + /* got a tailglob match, keep searching + * for an exact match + */ + match = entry; + } + } + + } + + rc = match && !pattern_match_invalid; + + if (rc) { + int mode = AA_EXEC_MASK(match->mode); + + /* check for qualifiers, if present + * we just return the qualifier + */ + if (mode & ~AA_MAY_EXEC) + mode = mode & ~AA_MAY_EXEC; + + *xmod = mode; + *unsafe = (match->mode & AA_EXEC_UNSAFE); + } else if (!match) { + AA_DEBUG("%s: Unable to find execute entry in profile " + "for image '%s'\n", + __FUNCTION__, + name); + } else if (pattern_match_invalid) { + AA_WARN("%s: Inconsistency in profile %s. " + "Two (or more) patterns specify conflicting exec " + "qualifiers ('u', 'i' or 'p') for image %s\n", + __FUNCTION__, + active->name, + name); + } + + return rc; +} + +/** + * aa_filter_mask + * @mask: requested mask + * @inode: potential directory inode + * + * This fn performs pre-verification of the requested mask + * We ignore append. Previously we required 'w' on a dir to add a file. + * No longer. Now we require 'w' on just the file itself. Traversal 'x' is + * also ignored for directories. + * + * Returned value of %0 indicates no need to perform a perm check. + */ +static inline int aa_filter_mask(int mask, struct inode *inode) +{ + if (mask) { + int elim = MAY_APPEND; + + if (inode && S_ISDIR(inode->i_mode)) + elim |= (MAY_EXEC | MAY_WRITE); + + mask &= ~elim; + } + + return mask; +} + +static inline void aa_permerror2result(int perm_result, struct aa_audit *sa) +{ + if (perm_result == 0) { /* success */ + sa->result = 1; + sa->error_code = 0; + } else { /* -ve internal error code or +ve mask of denied perms */ + sa->result = 0; + sa->error_code = perm_result; + } +} + +/************************* + * Main internal functions + ************************/ + +/** + * aa_file_perm - calculate access mode for file + * @active: profile to check against + * @name: name of file to calculate mode for + * @mask: permission mask requested for file + * + * Search the aa_entry list in @active. + * Search looking to verify all permissions passed in mask. + * Perform the search by looking at the partitioned list of entries, one + * partition per permission bit. + * + * Return %0 on success, else mask of non-allowed permissions + */ +static unsigned int aa_file_perm(struct aaprofile *active, const char *name, + int mask) +{ + int i, error = 0, mode; + +#define PROCPFX "/proc/" +#define PROCLEN sizeof(PROCPFX) - 1 + + AA_DEBUG("%s: %s 0x%x\n", __FUNCTION__, name, mask); + + /* should not enter with other than R/W/M/X/L */ + WARN_ON(mask & + ~(AA_MAY_READ | AA_MAY_WRITE | AA_MAY_EXEC | AA_EXEC_MMAP | + AA_MAY_LINK)); + + /* Special case access to /proc/self/attr/current + * Currently we only allow access if opened O_WRONLY + */ + if (mask == MAY_WRITE && strncmp(PROCPFX, name, PROCLEN) == 0 && + aa_taskattr_access(name + PROCLEN)) + goto done; + + mode = 0; + + /* iterate over partition, one permission bit at a time */ + for (i = 0; i <= POS_AA_FILE_MAX; i++) { + struct aa_entry *entry; + + /* do we have to accumulate this bit? + * or have we already accumulated it (shortcut below)? */ + if (!(mask & (1 << i)) || mode & (1 << i)) + continue; + + list_for_each_entry(entry, &active->file_entryp[i], + listp[i]) { + if (aamatch_match(name, entry->filename, + entry->type, entry->extradata)) { + /* Shortcut, accumulate all bits present */ + mode |= entry->mode; + + /* Mask bits are overloaded + * MAY_{EXEC,WRITE,READ,APPEND} are used by + * kernel, other values are used locally only. + */ + if ((mode & mask) == mask) { + AA_DEBUG("MATCH! %s=0x%x [total mode=0x%x]\n", + name, mask, mode); + + goto done; + } + } + } + } + + /* return permissions not satisfied */ + error = mask & ~mode; + +done: + return error; +} + +/** + * aa_link_perm - test permission to link to a file + * @active: profile to check against + * @link: name of link being created + * @target: name of target to be linked to + * + * Look up permission mode on both @link and @target. @link must have same + * permission mode as @target. At least @link must have the link bit enabled. + * Return %0 on success, error otherwise. + */ +static int aa_link_perm(struct aaprofile *active, + const char *link, const char *target) +{ + int l_mode, t_mode, ret; + + l_mode = aa_file_mode(active, link); + if (l_mode & AA_MAY_LINK) { + /* mask off link bit */ + l_mode &= ~AA_MAY_LINK; + + t_mode = aa_file_mode(active, target); + t_mode &= ~AA_MAY_LINK; + + ret = (l_mode == t_mode); + } else { + ret = 0; + } + + return ret; +} + +/** + * _aa_perm_dentry + * @active: profile to check against + * @dentry: requested dentry + * @mask: mask of requested operations + * @pname: pointer to hold matched pathname (if any) + * + * Helper function. Obtain pathname for specified dentry. Verify if profile + * authorizes mask operations on pathname (due to lack of vfsmnt it is sadly + * necessary to search mountpoints in namespace -- when nameidata is passed + * more fully, this code can go away). If more than one mountpoint matches + * but none satisfy the profile, only the first pathname (mountpoint) is + * returned for subsequent logging. + * + * Return %0 (success), +ve (mask of permissions not satisfied) or -ve (system + * error, most likely -%ENOMEM). + */ +static int _aa_perm_dentry(struct aaprofile *active, struct dentry *dentry, + int mask, const char **pname) +{ + char *name = NULL, *failed_name = NULL; + struct aa_path_data data; + int error = 0, failed_error = 0, path_error, + complain = PROFILE_COMPLAIN(active); + + /* search all paths to dentry */ + + aa_path_begin(dentry, &data); + do { + name = aa_path_getname(&data); + if (name) { + /* error here is 0 (success) or +ve (mask of perms) */ + error = aa_file_perm(active, name, mask); + + /* access via any path is enough */ + if (complain || error == 0) + break; /* Caller must free name */ + + /* Already have an path that failed? */ + if (failed_name) { + aa_put_name(name); + } else { + failed_name = name; + failed_error = error; + } + } + } while (name); + + if ((path_error = aa_path_end(&data)) != 0) { + dentry_xlate_error(dentry, path_error, "dentry"); + WARN_ON(name); /* name should not be set if error */ + error = path_error; + name = NULL; + } else if (name) { + if (failed_name) + aa_put_name(failed_name); + } else { + name = failed_name; + error = failed_error; + } + + *pname = name; + + return error; +} + +/************************** + * Global utility functions + *************************/ + +/** + * attach_nullprofile - allocate and attach a null_profile hat to profile + * @profile: profile to attach a null_profile hat to. + * + * Return %0 (success) or error (-%ENOMEM) + */ +int attach_nullprofile(struct aaprofile *profile) +{ + struct aaprofile *hat = NULL; + char *hatname = NULL; + + hat = alloc_aaprofile(); + if (!hat) + goto fail; + if (profile->flags.complain) + hatname = kstrdup("null-complain-profile", GFP_KERNEL); + else + hatname = kstrdup("null-profile", GFP_KERNEL); + if (!hatname) + goto fail; + + hat->flags.complain = profile->flags.complain; + hat->name = hatname; + hat->parent = profile; + + profile->null_profile = hat; + + return 0; + +fail: + kfree(hatname); + free_aaprofile(hat); + + return -ENOMEM; +} + + +/** + * alloc_null_complain_profile - Allocate the global null_complain_profile. + * + * Return %0 (success) or error (-%ENOMEM) + */ +int alloc_null_complain_profile(void) +{ + null_complain_profile = alloc_aaprofile(); + if (!null_complain_profile) + goto fail; + + null_complain_profile->name = + kstrdup("null-complain-profile", GFP_KERNEL); + + if (!null_complain_profile->name) + goto fail; + + null_complain_profile->flags.complain = 1; + if (attach_nullprofile(null_complain_profile)) + goto fail; + + return 0; + +fail: + /* free_aaprofile is safe for freeing partially constructed objects */ + free_aaprofile(null_complain_profile); + null_complain_profile = NULL; + + return -ENOMEM; +} + +/** + * free_null_complain_profile - Free null profiles + */ +void free_null_complain_profile(void) +{ + put_aaprofile(null_complain_profile); + null_complain_profile = NULL; +} + +/** + * aa_audit_message - Log a message to the audit subsystem + * @active: profile to check against + * @gfp: allocation flags + * @flags: audit flags + * @fmt: varargs fmt + */ +int aa_audit_message(struct aaprofile *active, gfp_t gfp, int flags, + const char *fmt, ...) +{ + int ret; + struct aa_audit sa; + + sa.type = AA_AUDITTYPE_MSG; + sa.name = fmt; + va_start(sa.vaval, fmt); + sa.flags = flags; + sa.gfp_mask = gfp; + sa.error_code = 0; + sa.result = 0; /* fake failure: force message to be logged */ + + ret = aa_audit(active, &sa); + + va_end(sa.vaval); + + return ret; +} + +/** + * aa_audit_syscallreject - Log a syscall rejection to the audit subsystem + * @active: profile to check against + * @gfp: memory allocation flags + * @call: aa syscall cache bit number + */ +int aa_audit_syscallreject(struct aaprofile *active, gfp_t gfp, + enum aasyscall call) +{ + struct aa_audit sa; + int error = -EPERM; + + if (!syscall_is_cached(call)) { + sa.type = AA_AUDITTYPE_SYSCALL; + sa.name = syscall_to_name(call); + sa.flags = 0; + sa.gfp_mask = gfp; + sa.error_code = 0; + sa.result = 0; /* failure */ + + error = aa_audit(active, &sa); + if (error == -EPERM) + add_to_cached_syscalls(call); + } + return error; +} + +/** + * aa_audit - Log an audit event to the audit subsystem + * @active: profile to check against + * @sa: audit event + */ +int aa_audit(struct aaprofile *active, const struct aa_audit *sa) +{ + struct audit_buffer *ab = NULL; + struct audit_context *ctx; + + const char *logcls; + unsigned int flags; + int audit = 0, + complain = 0, + error = -EINVAL, + opspec_error = -EACCES; + + const gfp_t gfp_mask = sa->gfp_mask; + + WARN_ON(sa->type >= AA_AUDITTYPE__END); + + /* + * sa->result: 1 success, 0 failure + * sa->error_code: success: 0 + * failure: +ve mask of failed permissions or -ve + * system error + */ + + if (likely(sa->result)) { + if (likely(!PROFILE_AUDIT(active))) { + /* nothing to log */ + error = 0; + goto out; + } else { + audit = 1; + logcls = "AUDITING"; + } + } else if (sa->error_code < 0) { + audit_log(current->audit_context, gfp_mask, AUDIT_SD, + "Internal error auditing event type %d (error %d)", + sa->type, sa->error_code); + AA_ERROR("Internal error auditing event type %d (error %d)\n", + sa->type, sa->error_code); + error = sa->error_code; + goto out; + } else if (sa->type == AA_AUDITTYPE_SYSCALL) { + /* Currently AA_AUDITTYPE_SYSCALL is for rejects only. + * Values set by aa_audit_syscallreject will get us here. + */ + logcls = "REJECTING"; + } else { + complain = PROFILE_COMPLAIN(active); + logcls = complain ? "PERMITTING" : "REJECTING"; + } + + /* test if event has already been logged and cached used to log + * only first time event occurs. + */ + if (sa->type == AA_AUDITTYPE_CAP) { + if (cap_is_cached(sa->ival)) { + opspec_error = -EPERM; + goto skip_logging; + } + } + + /* In future extend w/ per-profile flags + * (flags |= sa->active->flags) + */ + flags = sa->flags; + if (apparmor_logsyscall) + flags |= AA_AUDITFLAG_AUDITSS_SYSCALL; + + + /* Force full audit syscall logging regardless of global setting if + * we are rejecting a syscall + */ + if (sa->type == AA_AUDITTYPE_SYSCALL) { + ctx = current->audit_context; + } else { + ctx = (flags & AA_AUDITFLAG_AUDITSS_SYSCALL) ? + current->audit_context : NULL; + } + + ab = audit_log_start(ctx, gfp_mask, AUDIT_SD); + + if (!ab) { + AA_ERROR("Unable to log event (%d) to audit subsys\n", + sa->type); + if (complain) + error = 0; + goto out; + } + + /* messages get special handling */ + if (sa->type == AA_AUDITTYPE_MSG) { + audit_log_vformat(ab, sa->name, sa->vaval); + audit_log_end(ab); + error = 0; + goto out; + } + + /* log operation */ + + audit_log_format(ab, "%s ", logcls); /* REJECTING/ALLOWING/etc */ + + if (sa->type == AA_AUDITTYPE_FILE) { + int perm = audit ? sa->ival : sa->error_code; + + audit_log_format(ab, "%s%s%s%s%s access to %s ", + perm & AA_EXEC_MMAP ? "m" : "", + perm & AA_MAY_READ ? "r" : "", + perm & AA_MAY_WRITE ? "w" : "", + perm & AA_MAY_EXEC ? "x" : "", + perm & AA_MAY_LINK ? "l" : "", + sa->name); + + opspec_error = -EPERM; + + } else if (sa->type == AA_AUDITTYPE_DIR) { + audit_log_format(ab, "%s on %s ", + sa->ival == aa_dir_mkdir ? "mkdir" : "rmdir", + sa->name); + + } else if (sa->type == AA_AUDITTYPE_ATTR) { + struct iattr *iattr = (struct iattr*)sa->pval; + + audit_log_format(ab, + "attribute (%s%s%s%s%s%s%s) change to %s ", + iattr->ia_valid & ATTR_MODE ? "mode," : "", + iattr->ia_valid & ATTR_UID ? "uid," : "", + iattr->ia_valid & ATTR_GID ? "gid," : "", + iattr->ia_valid & ATTR_SIZE ? "size," : "", + ((iattr->ia_valid & ATTR_ATIME_SET) || + (iattr->ia_valid & ATTR_ATIME)) ? "atime," : "", + ((iattr->ia_valid & ATTR_MTIME_SET) || + (iattr->ia_valid & ATTR_MTIME)) ? "mtime," : "", + iattr->ia_valid & ATTR_CTIME ? "ctime," : "", + sa->name); + + } else if (sa->type == AA_AUDITTYPE_XATTR) { + const char *fmt; + switch (sa->ival) { + case aa_xattr_get: + fmt = "xattr get"; + break; + case aa_xattr_set: + fmt = "xattr set"; + break; + case aa_xattr_list: + fmt = "xattr list"; + break; + case aa_xattr_remove: + fmt = "xattr remove"; + break; + default: + fmt = "xattr "; + break; + } + + audit_log_format(ab, "%s on %s ", fmt, sa->name); + + } else if (sa->type == AA_AUDITTYPE_LINK) { + audit_log_format(ab, + "link access from %s to %s ", + sa->name, + (char*)sa->pval); + + } else if (sa->type == AA_AUDITTYPE_CAP) { + audit_log_format(ab, + "access to capability '%s' ", + capability_to_name(sa->ival)); + add_to_cached_caps(sa->ival); + opspec_error = -EPERM; + } else if (sa->type == AA_AUDITTYPE_SYSCALL) { + audit_log_format(ab, "access to syscall '%s' ", sa->name); + + opspec_error = -EPERM; + } else { + /* -EINVAL -- will WARN_ON above */ + goto out; + } + + audit_log_format(ab, "(%s(%d) profile %s active %s)", + current->comm, current->pid, + BASE_PROFILE(active)->name, active->name); + + audit_log_end(ab); + +skip_logging: + if (complain) + error = 0; + else + error = sa->result ? 0 : opspec_error; + +out: + return error; +} + +/** + * aa_get_name - retrieve fully qualified path name + * @dentry: relative path element + * @mnt: where in tree + * + * Returns fully qualified path name on sucess, NULL on failure. + * aa_put_name must be used to free allocated buffer. + */ +char *aa_get_name(struct dentry *dentry, struct vfsmount *mnt) +{ + char *page, *name; + + page = (char *)__get_free_page(GFP_KERNEL); + if (!page) { + name = ERR_PTR(-ENOMEM); + goto out; + } + + name = d_path(dentry, mnt, page, PAGE_SIZE); + /* check for (deleted) that d_path appends to pathnames if the dentry + * has been removed from the cache. + * The size > deleted_size and strcmp checks are redundant safe guards. + */ + if (IS_ERR(name)) { + free_page((unsigned long)page); + } else { + const char deleted_str[] = " (deleted)"; + const size_t deleted_size = sizeof(deleted_str) - 1; + size_t size; + size = strlen(name); + if (!IS_ROOT(dentry) && d_unhashed(dentry) && + size > deleted_size && + strcmp(name + size - deleted_size, deleted_str) == 0) + name[size - deleted_size] = '\0'; + AA_DEBUG("%s: full_path=%s\n", __FUNCTION__, name); + } + +out: + return name; +} + +/*********************************** + * Global permission check functions + ***********************************/ + +/** + * aa_attr - check whether attribute change allowed + * @active: profile to check against + * @dentry: file to check + * @iattr: attribute changes requested + */ +int aa_attr(struct aaprofile *active, struct dentry *dentry, + struct iattr *iattr) +{ + int error = 0, permerror; + struct aa_audit sa; + + sa.type = AA_AUDITTYPE_ATTR; + sa.pval = iattr; + sa.flags = 0; + sa.gfp_mask = GFP_KERNEL; + + permerror = _aa_perm_dentry(active, dentry, MAY_WRITE, &sa.name); + aa_permerror2result(permerror, &sa); + + error = aa_audit(active, &sa); + + aa_put_name(sa.name); + + return error; +} + +/** + * aa_xattr - check whether xattr attribute change allowed + * @active: profile to check against + * @dentry: file to check + * @xattr: xattr to check + * @xattroptype: type of xattr operation + */ +int aa_xattr(struct aaprofile *active, struct dentry *dentry, + const char *xattr, enum aa_xattroptype xattroptype) +{ + int error = 0, permerror, mask = 0; + struct aa_audit sa; + + /* if not confined or empty mask permission granted */ + if (!active) + goto out; + + if (xattroptype == aa_xattr_get || xattroptype == aa_xattr_list) + mask = MAY_READ; + else if (xattroptype == aa_xattr_set || xattroptype == aa_xattr_remove) + mask = MAY_WRITE; + + sa.type = AA_AUDITTYPE_XATTR; + sa.ival = xattroptype; + sa.pval = xattr; + sa.flags = 0; + sa.gfp_mask = GFP_KERNEL; + + permerror = _aa_perm_dentry(active, dentry, mask, &sa.name); + aa_permerror2result(permerror, &sa); + + error = aa_audit(active, &sa); + + aa_put_name(sa.name); + +out: + return error; +} + +/** + * aa_perm - basic apparmor permissions check + * @active: profile to check against + * @dentry: dentry + * @mnt: mountpoint + * @mask: access mode requested + * + * Determine if access (mask) for dentry is authorized by active + * profile. Result, %0 (success), -ve (error) + */ +int aa_perm(struct aaprofile *active, struct dentry *dentry, + struct vfsmount *mnt, int mask) +{ + int error = 0, permerror; + struct aa_audit sa; + + if (!active) + goto out; + + if ((mask = aa_filter_mask(mask, dentry->d_inode)) == 0) + goto out; + + sa.type = AA_AUDITTYPE_FILE; + sa.name = aa_get_name(dentry, mnt); + sa.ival = mask; + sa.flags = 0; + sa.gfp_mask = GFP_KERNEL; + + if (IS_ERR(sa.name)) { + permerror = PTR_ERR(sa.name); + sa.name = NULL; + } else { + permerror = aa_file_perm(active, sa.name, mask); + } + + aa_permerror2result(permerror, &sa); + + error = aa_audit(active, &sa); + + aa_put_name(sa.name); + +out: + return error; +} + +/** + * aa_perm_nameidata: interface to sd_perm accepting nameidata + * @active: profile to check against + * @nd: namespace data (for vfsmnt and dentry) + * @mask: access mode requested + */ +int aa_perm_nameidata(struct aaprofile *active, struct nameidata *nd, int mask) +{ + int error = 0; + + if (nd) + error = aa_perm(active, nd->dentry, nd->mnt, mask); + + return error; +} + +/** + * aa_perm_dentry - file permissions interface when no vfsmnt available + * @active: profile to check against + * @dentry: requested dentry + * @mask: access mode requested + * + * Determine if access (mask) for dentry is authorized by active profile. + * Result, %0 (success), -ve (error) + */ +int aa_perm_dentry(struct aaprofile *active, struct dentry *dentry, int mask) +{ + int error = 0, permerror; + struct aa_audit sa; + + if (!active) + goto out; + + if ((mask = aa_filter_mask(mask, dentry->d_inode)) == 0) + goto out; + + sa.type = AA_AUDITTYPE_FILE; + sa.ival = mask; + sa.flags = 0; + sa.gfp_mask = GFP_KERNEL; + + permerror = _aa_perm_dentry(active, dentry, mask, &sa.name); + aa_permerror2result(permerror, &sa); + + error = aa_audit(active, &sa); + + aa_put_name(sa.name); + +out: + return error; +} + +/** + * aa_perm_dir + * @active: profile to check against + * @dentry: requested dentry + * @diroptype: aa_dir_mkdir or aa_dir_rmdir + * + * Determine if directory operation (make/remove) for dentry is authorized + * by @active profile. + * Result, %0 (success), -ve (error) + */ +int aa_perm_dir(struct aaprofile *active, struct dentry *dentry, + enum aa_diroptype diroptype) +{ + int error = 0, permerror, mask; + struct aa_audit sa; + + WARN_ON(diroptype != aa_dir_mkdir && diroptype != aa_dir_rmdir); + + if (!active) + goto out; + + mask = MAY_WRITE; + + sa.type = AA_AUDITTYPE_DIR; + sa.ival = diroptype; + sa.flags = 0; + sa.gfp_mask = GFP_KERNEL; + + permerror = _aa_perm_dentry(active, dentry, mask, &sa.name); + aa_permerror2result(permerror, &sa); + + error = aa_audit(active, &sa); + + aa_put_name(sa.name); + +out: + return error; +} + +/** + * aa_capability - test permission to use capability + * @active: profile to check against + * @cap: capability to be tested + * + * Look up capability in active profile capability set. + * Return %0 (success), -%EPERM (error) + */ +int aa_capability(struct aaprofile *active, int cap) +{ + int error = 0; + + struct aa_audit sa; + + sa.type = AA_AUDITTYPE_CAP; + sa.name = NULL; + sa.ival = cap; + sa.flags = 0; + sa.error_code = 0; + sa.result = cap_raised(active->capabilities, cap); + sa.gfp_mask = GFP_ATOMIC; + + error = aa_audit(active, &sa); + + return error; +} + +/** + * aa_link - hard link check + * @active: profile to check against + * @link: dentry for link being created + * @target: dentry for link target + * + * Checks link permissions for all possible name combinations. This is + * particularly ugly. Returns %0 on sucess, error otherwise. + */ +int aa_link(struct aaprofile *active, struct dentry *link, + struct dentry *target) +{ + char *iname = NULL, *oname = NULL, + *failed_iname = NULL, *failed_oname = NULL; + unsigned int result = 0; + int error, path_error, error_code = 0, match = 0, + complain = PROFILE_COMPLAIN(active); + struct aa_path_data idata, odata; + struct aa_audit sa; + + if (!active) + return 0; + + /* Perform nested lookup for names. + * This is necessary in the case where /dev/block is mounted + * multiple times, i.e /dev/block->/a and /dev/block->/b + * This allows us to detect links where src/dest are on different + * mounts. N.B no support yet for links across bind mounts of + * the form mount -bind /mnt/subpath /mnt2 + * + * Getting direct access to vfsmounts (via nameidata) for link and + * target would allow all this uglyness to go away. + * + * If more than one mountpoint matches but none satisfy the profile, + * only the first pathname (mountpoint) is logged. + */ + + __aa_path_begin(target, link, &odata); + do { + oname = aa_path_getname(&odata); + if (oname) { + aa_path_begin(target, &idata); + do { + iname = aa_path_getname(&idata); + if (iname) { + result = aa_link_perm(active, oname, + iname); + + /* access via any path is enough */ + if (result || complain) { + match = 1; + break; + } + + /* Already have an path that failed? */ + if (failed_iname) { + aa_put_name(iname); + } else { + failed_iname = iname; + failed_oname = oname; + } + } + } while (iname && !match); + + /* should not be possible if we matched */ + if ((path_error = aa_path_end(&idata)) != 0) { + dentry_xlate_error(target, path_error, + "inner dentry [link]"); + + /* name should not be set if error */ + WARN_ON(iname); + + error_code = path_error; + } + + /* don't release if we're saving it */ + if (!match && failed_oname != oname) + aa_put_name(oname); + } + } while (oname && !match); + + if (error_code != 0) { + /* inner error */ + (void)aa_path_end(&odata); + } else if ((path_error = aa_path_end(&odata)) != 0) { + dentry_xlate_error(link, path_error, "outer dentry [link]"); + error_code = path_error; + } + + if (error_code != 0) { + /* inner or outer error */ + result = 0; + } else if (!match) { + /* failed to match */ + WARN_ON(iname); + WARN_ON(oname); + + result = 0; + iname = failed_iname; + oname = failed_oname; + } + + sa.type = AA_AUDITTYPE_LINK; + sa.name = oname; /* link */ + sa.pval = iname; /* target */ + sa.flags = 0; + sa.error_code = error_code; + sa.result = result; + sa.gfp_mask = GFP_KERNEL; + + error = aa_audit(active, &sa); + + if (failed_oname != oname) + aa_put_name(failed_oname); + if (failed_iname != iname) + aa_put_name(failed_iname); + + aa_put_name(oname); + aa_put_name(iname); + + return error; +} + +/******************************* + * Global task related functions + *******************************/ + +/** + * aa_fork - create a new subdomain + * @p: new process + * + * Create a new subdomain for newly created process @p if it's parent + * is already confined. Otherwise a subdomain will be lazily allocated + * will get one with NULL values. Return 0 on sucess. + * for the child if it subsequently execs (in aa_register). + * Return 0 on sucess. + * + * The sd_lock is used to maintain consistency against profile + * replacement/removal. + */ + +int aa_fork(struct task_struct *p) +{ + struct subdomain *sd = AA_SUBDOMAIN(current->security); + struct subdomain *newsd = NULL; + + AA_DEBUG("%s\n", __FUNCTION__); + + if (__aa_is_confined(sd)) { + unsigned long flags; + + newsd = alloc_subdomain(p); + + if (!newsd) + return -ENOMEM; + + /* Use locking here instead of getting the reference + * because we need both the old reference and the + * new reference to be consistent. + */ + spin_lock_irqsave(&sd_lock, flags); + aa_switch(newsd, sd->active); + newsd->hat_magic = sd->hat_magic; + spin_unlock_irqrestore(&sd_lock, flags); + + if (SUBDOMAIN_COMPLAIN(sd) && + sd->active == null_complain_profile) + LOG_HINT(sd->active, GFP_KERNEL, HINT_FORK, + "pid=%d child=%d\n", + current->pid, p->pid); + } + p->security = newsd; + return 0; +} + +/** + * aa_register - register a new program + * @bprm: binprm of program being registered + * + * Try to register a new program during execve(). This should give the + * new program a valid subdomain. + */ +int aa_register(struct linux_binprm *bprm) +{ + char *filename; + struct file *filp = bprm->file; + struct aaprofile *active; + struct aaprofile *newprofile = NULL, unconstrained_flag; + int error = -ENOMEM, + exec_mode = 0, + find_profile = 0, + find_profile_mandatory = 0, + unsafe_exec = 0, + complain = 0; + + AA_DEBUG("%s\n", __FUNCTION__); + + filename = aa_get_name(filp->f_dentry, filp->f_vfsmnt); + if (IS_ERR(filename)) { + AA_WARN("%s: Failed to get filename\n", __FUNCTION__); + goto out; + } + + error = 0; + + active = get_active_aaprofile(); + + if (!active) { + /* Unconfined task, load profile if it exists */ + find_profile = 1; + goto find_profile; + } + + complain = PROFILE_COMPLAIN(active); + + /* Confined task, determine what mode inherit, unconstrained or + * mandatory to load new profile + */ + if (aa_get_execmode(active, filename, &exec_mode, &unsafe_exec)) { + switch (exec_mode) { + case AA_EXEC_INHERIT: + /* do nothing - setting of profile + * already handed in aa_fork + */ + AA_DEBUG("%s: INHERIT %s\n", + __FUNCTION__, + filename); + break; + + case AA_EXEC_UNCONSTRAINED: + AA_DEBUG("%s: UNCONSTRAINED %s\n", + __FUNCTION__, + filename); + + /* unload profile */ + newprofile = &unconstrained_flag; + break; + + case AA_EXEC_PROFILE: + AA_DEBUG("%s: PROFILE %s\n", + __FUNCTION__, + filename); + + find_profile = 1; + find_profile_mandatory = 1; + break; + + case AA_MAY_EXEC: + /* this should not happen, entries + * with just EXEC only should be + * rejected at profile load time + */ + AA_ERROR("%s: Rejecting exec(2) of image '%s'. " + "AA_MAY_EXEC without exec qualifier invalid " + "(%s(%d) profile %s active %s\n", + __FUNCTION__, + filename, + current->comm, current->pid, + BASE_PROFILE(active)->name, active->name); + error = -EPERM; + break; + + default: + AA_ERROR("%s: Rejecting exec(2) of image '%s'. " + "Unknown exec qualifier %x " + "(%s (pid %d) profile %s active %s)\n", + __FUNCTION__, + filename, + exec_mode, + current->comm, current->pid, + BASE_PROFILE(active)->name, active->name); + error = -EPERM; + break; + } + + } else if (complain) { + /* There was no entry in calling profile + * describing mode to execute image in. + * Drop into null-profile (disabling secure exec). + */ + newprofile = get_aaprofile(null_complain_profile); + unsafe_exec = 1; + } else { + AA_WARN("%s: Rejecting exec(2) of image '%s'. " + "Unable to determine exec qualifier " + "(%s (pid %d) profile %s active %s)\n", + __FUNCTION__, + filename, + current->comm, current->pid, + BASE_PROFILE(active)->name, active->name); + error = -EPERM; + } + + +find_profile: + if (!find_profile) + goto apply_profile; + + /* Locate new profile */ + newprofile = aa_profilelist_find(filename); + if (newprofile) { + AA_DEBUG("%s: setting profile %s\n", + __FUNCTION__, newprofile->name); + } else if (find_profile_mandatory) { + /* Profile (mandatory) could not be found */ + + if (complain) { + LOG_HINT(active, GFP_KERNEL, HINT_MANDPROF, + "image=%s pid=%d profile=%s active=%s\n", + filename, + current->pid, + BASE_PROFILE(active)->name, active->name); + + newprofile = get_aaprofile(null_complain_profile); + } else { + AA_WARN("REJECTING exec(2) of image '%s'. " + "Profile mandatory and not found " + "(%s(%d) profile %s active %s)\n", + filename, + current->comm, current->pid, + BASE_PROFILE(active)->name, active->name); + error = -EPERM; + } + } else { + /* Profile (non-mandatory) could not be found */ + + /* Only way we can get into this code is if task + * is unconstrained. + */ + + WARN_ON(active); + + AA_DEBUG("%s: No profile found for exec image %s\n", + __FUNCTION__, + filename); + } /* newprofile */ + + +apply_profile: + /* Apply profile if necessary */ + if (newprofile) { + struct subdomain *sd, *lazy_sd = NULL; + unsigned long flags; + + if (newprofile == &unconstrained_flag) + newprofile = NULL; + + /* grab a lock - this is to guarentee consistency against + * other writers of subdomain (replacement/removal) + * + * Several things may have changed since the code above + * + * - Task may be presently unconfined (have no sd). In which + * case we have to lazily allocate one. Note we may be raced + * to this allocation by a setprofile. + * + * - If we are a confined process, active is a refcounted copy + * of the profile that was on the subdomain at entry. + * This allows us to not have to hold a lock around + * all this code. If profile replacement has taken place + * our active may not equal sd->active any more. + * This is okay since the operation is treated as if + * the transition occured before replacement. + * + * - If newprofile points to an actual profile (result of + * aa_profilelist_find above), this profile may have been + * replaced. We need to fix it up. Doing this to avoid + * having to hold a lock around all this code. + */ + + if (!active && !(sd = AA_SUBDOMAIN(current->security))) { + lazy_sd = alloc_subdomain(current); + if (!lazy_sd) { + AA_ERROR("%s: Failed to allocate subdomain\n", + __FUNCTION__); + error = -ENOMEM; + goto cleanup; + } + } + + spin_lock_irqsave(&sd_lock, flags); + + sd = AA_SUBDOMAIN(current->security); + if (lazy_sd) { + if (sd) { + /* raced by setprofile - created sd */ + free_subdomain(lazy_sd); + lazy_sd = NULL; + } else { + /* Not rcu used to get the write barrier + * correct */ + rcu_assign_pointer(current->security, lazy_sd); + sd = lazy_sd; + } + } + + /* Determine if profile we found earlier is stale. + * If so, reobtain it. N.B stale flag should never be + * set on null_complain profile. + */ + if (newprofile && unlikely(newprofile->isstale)) { + WARN_ON(newprofile == null_complain_profile); + + /* drop refcnt obtained from earlier get_aaprofile */ + put_aaprofile(newprofile); + + newprofile = aa_profilelist_find(filename); + + if (!newprofile) { + /* Race, profile was removed, not replaced. + * Redo with error checking + */ + spin_unlock_irqrestore(&sd_lock, flags); + goto find_profile; + } + } + + /* Handle confined exec. + * Can be at this point for the following reasons: + * 1. unconfined switching to confined + * 2. confined switching to different confinement + * 3. confined switching to unconfined + * + * Cases 2 and 3 are marked as requiring secure exec + * (unless policy specified "unsafe exec") + */ + if (__aa_is_confined(sd) && !unsafe_exec) { + unsigned long bprm_flags; + + bprm_flags = AA_SECURE_EXEC_NEEDED; + bprm->security = (void*) + ((unsigned long)bprm->security | bprm_flags); + } + + aa_switch(sd, newprofile); + put_aaprofile(newprofile); + + if (complain && newprofile == null_complain_profile) + LOG_HINT(newprofile, GFP_ATOMIC, HINT_CHGPROF, + "pid=%d\n", + current->pid); + + spin_unlock_irqrestore(&sd_lock, flags); + } + +cleanup: + aa_put_name(filename); + + put_aaprofile(active); + +out: + return error; +} + +/** + * aa_release - release the task's subdomain + * @p: task being released + * + * This is called after a task has exited and the parent has reaped it. + * @p->security blob is freed. + * + * This is the one case where we don't need to hold the sd_lock before + * removing a profile from a subdomain. Once the subdomain has been + * removed from the subdomain_list, we are no longer racing other writers. + * There may still be other readers so we must still use aa_switch + * to put the subdomain's reference safely. + */ +void aa_release(struct task_struct *p) +{ + struct subdomain *sd = AA_SUBDOMAIN(p->security); + if (sd) { + p->security = NULL; + + aa_subdomainlist_remove(sd); + aa_switch_unconfined(sd); + + kfree(sd); + } +} + +/***************************** + * global subprofile functions + ****************************/ + +/** + * do_change_hat - actually switch hats + * @hat_name: name of hat to swtich to + * @sd: current subdomain + * + * Switch to a new hat. Return %0 on success, error otherwise. + */ +static inline int do_change_hat(const char *hat_name, struct subdomain *sd) +{ + struct aaprofile *sub; + int error = 0; + + sub = __aa_find_profile(hat_name, &BASE_PROFILE(sd->active)->sub); + + if (sub) { + /* change hat */ + aa_switch(sd, sub); + put_aaprofile(sub); + } else { + /* There is no such subprofile change to a NULL profile. + * The NULL profile grants no file access. + * + * This feature is used by changehat_apache. + * + * N.B from the null-profile the task can still changehat back + * out to the parent profile (assuming magic != NULL) + */ + if (SUBDOMAIN_COMPLAIN(sd)) { + LOG_HINT(sd->active, GFP_ATOMIC, HINT_UNKNOWN_HAT, + "%s pid=%d " + "profile=%s active=%s\n", + hat_name, + current->pid, + BASE_PROFILE(sd->active)->name, + sd->active->name); + } else { + AA_DEBUG("%s: Unknown hatname '%s'. " + "Changing to NULL profile " + "(%s(%d) profile %s active %s)\n", + __FUNCTION__, + hat_name, + current->comm, current->pid, + BASE_PROFILE(sd->active)->name, + sd->active->name); + error = -EACCES; + } + aa_switch(sd, sd->active->null_profile); + } + + return error; +} + +/** + * aa_change_hat - change hat to/from subprofile + * @hat_name: specifies hat to change to + * @hat_magic: token to validate hat change + * + * Change to new @hat_name when current hat is top level profile, and store + * the @hat_magic in the current subdomain. If the new @hat_name is + * %NULL, and the @hat_magic matches that stored in the current subdomain + * return to original top level profile. Returns %0 on success, error + * otherwise. + */ +int aa_change_hat(const char *hat_name, u32 hat_magic) +{ + struct subdomain *sd = AA_SUBDOMAIN(current->security); + int error = 0; + + AA_DEBUG("%s: %p, 0x%x (pid %d)\n", + __FUNCTION__, + hat_name, hat_magic, + current->pid); + + /* Dump out above debugging in WARN mode if we are in AUDIT mode */ + if (SUBDOMAIN_AUDIT(sd)) { + AA_WARN("%s: %s, 0x%x (pid %d)\n", + __FUNCTION__, hat_name ? hat_name : "NULL", + hat_magic, current->pid); + } + + /* check to see if an unconfined process is doing a changehat. */ + if (!__aa_is_confined(sd)) { + error = -EPERM; + goto out; + } + + /* check to see if the confined process has any hats. */ + if (list_empty(&BASE_PROFILE(sd->active)->sub) && + !PROFILE_COMPLAIN(sd->active)) { + error = -ECHILD; + goto out; + } + + /* Check whether current domain is parent + * or one of the sibling children + */ + if (!IN_SUBPROFILE(sd->active)) { + /* + * parent + */ + if (hat_name) { + AA_DEBUG("%s: switching to %s, 0x%x\n", + __FUNCTION__, + hat_name, + hat_magic); + + /* + * N.B hat_magic == 0 has a special meaning + * this indicates that the task may never changehat + * back to it's parent, it will stay in this subhat + * (or null-profile, if the hat doesn't exist) until + * the task terminates + */ + sd->hat_magic = hat_magic; + error = do_change_hat(hat_name, sd); + } else { + /* Got here via changehat(NULL, magic) + * + * We used to simply update the magic cookie. + * That's an odd behaviour, so just do nothing. + */ + } + } else { + /* + * child -- check to make sure magic is same as what was + * passed when we switched into this profile, + * Handle special casing of NULL magic which confines task + * to subprofile and prohibits further changehats + */ + if (hat_magic == sd->hat_magic && sd->hat_magic) { + if (!hat_name) { + /* + * Got here via changehat(NULL, magic) + * Return from subprofile, back to parent + */ + aa_switch(sd, sd->active->parent); + + /* Reset hat_magic to zero. + * New value will be passed on next changehat + */ + sd->hat_magic = 0; + } else { + /* change to another (sibling) profile */ + error = do_change_hat(hat_name, sd); + } + } else if (sd->hat_magic) { + AA_ERROR("KILLING process %s(%d) " + "Invalid change_hat() magic# 0x%x " + "(hatname %s profile %s active %s)\n", + current->comm, current->pid, + hat_magic, + hat_name ? hat_name : "NULL", + BASE_PROFILE(sd->active)->name, + sd->active->name); + + /* terminate current process */ + (void)send_sig_info(SIGKILL, NULL, current); + } else { /* sd->hat_magic == NULL */ + AA_ERROR("KILLING process %s(%d) " + "Task was confined to current subprofile " + "(profile %s active %s)\n", + current->comm, current->pid, + BASE_PROFILE(sd->active)->name, + sd->active->name); + + /* terminate current process */ + (void)send_sig_info(SIGKILL, NULL, current); + } + + } + +out: + return error; +} Index: b/security/apparmor/match/Kbuild =================================================================== --- /dev/null +++ b/security/apparmor/match/Kbuild @@ -0,0 +1,6 @@ +# Makefile for AppArmor aamatch submodule +# + +obj-$(CONFIG_SECURITY_APPARMOR) += aamatch_pcre.o + +aamatch_pcre-y := match_pcre.o pcre_exec.o Index: b/security/apparmor/match/Makefile =================================================================== --- /dev/null +++ b/security/apparmor/match/Makefile @@ -0,0 +1,5 @@ +# Makefile for AppArmor aamatch submodule +# +obj-$(CONFIG_SECURITY_APPARMOR) += aamatch_pcre.o + +aamatch_pcre-y := match_pcre.o pcre_exec.o Index: b/security/apparmor/match/match.h =================================================================== --- /dev/null +++ b/security/apparmor/match/match.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2002-2005 Novell/SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * AppArmor submodule (match) prototypes + */ + +#ifndef __MATCH_H +#define __MATCH_H + +#include "../module_interface.h" +#include "../apparmor.h" + +/* The following functions implement an interface used by the primary + * AppArmor module to perform name matching (n.b. "AppArmor" was previously + * called "SubDomain"). + + * aamatch_alloc + * aamatch_free + * aamatch_features + * aamatch_serialize + * aamatch_match + * + * The intent is for the primary module to export (via virtual fs entries) + * the features provided by the submodule (aamatch_features) so that the + * parser may only load policy that can be supported. + * + * The primary module will call aamatch_serialize to allow the submodule + * to consume submodule specific data from parser data stream and will call + * aamatch_match to determine if a pathname matches an aa_entry. + */ + +typedef int (*aamatch_serializecb) + (struct aa_ext *, enum aa_code, void *, const char *); + +/** + * aamatch_alloc: allocate extradata (if necessary) + * @type: type of entry being allocated + * Return value: NULL indicates no data was allocated (ERR_PTR(x) on error) + */ +extern void* aamatch_alloc(enum entry_match_type type); + +/** + * aamatch_free: release data allocated by aamatch_alloc + * @entry_extradata: data previously allocated by aamatch_alloc + */ +extern void aamatch_free(void *entry_extradata); + +/** + * aamatch_features: return match types supported + * Return value: space seperated string (of types supported - use type=value + * to indicate variants of a type) + */ +extern const char* aamatch_features(void); + +/** + * aamatch_serialize: serialize extradata + * @entry_extradata: data previously allocated by aamatch_alloc + * @e: input stream + * @cb: callback fn (consume incoming data stream) + * Return value: 0 success, -ve error + */ +extern int aamatch_serialize(void *entry_extradata, struct aa_ext *e, + aamatch_serializecb cb); + +/** + * aamatch_match: determine if pathname matches entry + * @pathname: pathname to verify + * @entry_name: entry name + * @type: type of entry + * @entry_extradata: data previously allocated by aamatch_alloc + * Return value: 1 match, 0 othersise + */ +extern unsigned int aamatch_match(const char *pathname, const char *entry_name, + enum entry_match_type type, + void *entry_extradata); + + +/** + * sd_getmatch_type - return string representation of entry_match_type + * @type: entry match type + */ +static inline const char *sd_getmatch_type(enum entry_match_type type) +{ + const char *names[] = { + "aa_entry_literal", + "aa_entry_tailglob", + "aa_entry_pattern", + "aa_entry_invalid" + }; + + if (type >= aa_entry_invalid) { + type = aa_entry_invalid; + } + + return names[type]; +} + +/** + * aamatch_match_common - helper function to check if a pathname matches + * a literal/tailglob + * @path: path requested to search for + * @entry_name: name from aa_entry + * @type: type of entry + */ +static inline int aamatch_match_common(const char *path, + const char *entry_name, + enum entry_match_type type) +{ + int retval; + + /* literal, no pattern matching characters */ + if (type == aa_entry_literal) { + retval = (strcmp(entry_name, path) == 0); + /* trailing ** glob pattern */ + } else if (type == aa_entry_tailglob) { + retval = (strncmp(entry_name, path, + strlen(entry_name) - 2) == 0); + } else { + AA_WARN("%s: Invalid entry_match_type %d\n", + __FUNCTION__, type); + retval = 0; + } + + return retval; +} + +#endif /* __MATCH_H */ Index: b/security/apparmor/match/match_default.c =================================================================== --- /dev/null +++ b/security/apparmor/match/match_default.c @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2002-2005 Novell/SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * http://forge.novell.com/modules/xfmod/project/?apparmor + * + * AppArmor default match submodule (literal and tailglob) + */ + +#include +#include "match.h" + +static const char *features="literal tailglob"; + +void* aamatch_alloc(enum entry_match_type type) +{ + return NULL; +} + +void aamatch_free(void *ptr) +{ +} + +const char *aamatch_features(void) +{ + return features; +} + +int aamatch_serialize(void *entry_extradata, struct aa_ext *e, + aamatch_serializecb cb) +{ + return 0; +} + +unsigned int aamatch_match(const char *pathname, const char *entry_name, + enum entry_match_type type, void *entry_extradata) +{ + int ret; + + ret = aamatch_match_common(pathname, entry_name, type); + + return ret; +} + +EXPORT_SYMBOL_GPL(aamatch_alloc); +EXPORT_SYMBOL_GPL(aamatch_free); +EXPORT_SYMBOL_GPL(aamatch_features); +EXPORT_SYMBOL_GPL(aamatch_serialize); +EXPORT_SYMBOL_GPL(aamatch_match); + +MODULE_DESCRIPTION("AppArmor match module (aamatch) [default]"); +MODULE_AUTHOR("Tony Jones "); +MODULE_LICENSE("GPL"); Index: b/security/apparmor/match/match_pcre.c =================================================================== --- /dev/null +++ b/security/apparmor/match/match_pcre.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2002-2005 Novell/SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * http://forge.novell.com/modules/xfmod/project/?apparmor + * + * AppArmor aamatch submodule (w/ pattern expansion). + * + * This module makes use of a slightly modified version of the PCRE + * library developed by Philip Hazel . See the files + * pcre_* in this directory. + */ + +#include +#include "match.h" +#include "pcre_exec.h" +#include "pcre_tables.h" + +static const char *features="literal tailglob pattern=pcre"; + +struct aamatch_entry +{ + char *pattern; + pcre *compiled; +}; + +void* aamatch_alloc(enum entry_match_type entry_type) +{ +void *ptr=NULL; + + if (entry_type == aa_entry_pattern) { + ptr = kmalloc(sizeof(struct aamatch_entry), GFP_KERNEL); + if (ptr) + memset(ptr, 0, sizeof(struct aamatch_entry)); + else + ptr=ERR_PTR(-ENOMEM); + } else if (entry_type != aa_entry_literal && + entry_type != aa_entry_tailglob) { + ptr = ERR_PTR(-EINVAL); + } + + return ptr; +} + +void aamatch_free(void *ptr) +{ + if (ptr) { + struct aamatch_entry *ed = (struct aamatch_entry *) ptr; + kfree(ed->pattern); + kfree(ed->compiled); /* allocated by AA_READ_X */ + } + kfree(ptr); +} + +const char *aamatch_features(void) +{ + return features; +} + +int aamatch_serialize(void *entry_extradata, struct aa_ext *e, + aamatch_serializecb cb) +{ +#define AA_READ_X(E, C, D, N) \ + do { \ + if (!cb((E), (C), (D), (N))) { \ + error = -EINVAL; \ + goto done; \ + }\ + } while (0) + + int error = 0; + u32 size, magic, opts; + u8 t_char; + struct aamatch_entry *ed = (struct aamatch_entry *) entry_extradata; + + if (ed == NULL) + goto done; + + AA_READ_X(e, AA_DYN_STRING, &ed->pattern, NULL); + + /* size determines the real size of the pcre struct, + it is size_t - sizeof(pcre) on user side. + uschar must be the same in user and kernel space */ + /* check that we are processing the correct structure */ + AA_READ_X(e, AA_STRUCT, NULL, "pcre"); + AA_READ_X(e, AA_U32, &size, NULL); + AA_READ_X(e, AA_U32, &magic, NULL); + + /* the allocation of pcre is delayed because it depends on the size + * of the pattern */ + ed->compiled = (pcre *) kmalloc(size + sizeof(pcre), GFP_KERNEL); + if (!ed->compiled) { + error = -ENOMEM; + goto done; + } + + memset(ed->compiled, 0, size + sizeof(pcre)); + ed->compiled->magic_number = magic; + ed->compiled->size = size + sizeof(pcre); + + AA_READ_X(e, AA_U32, &opts, NULL); + ed->compiled->options = opts; + AA_READ_X(e, AA_U16, &ed->compiled->top_bracket, NULL); + AA_READ_X(e, AA_U16, &ed->compiled->top_backref, NULL); + AA_READ_X(e, AA_U8, &t_char, NULL); + ed->compiled->first_char = t_char; + AA_READ_X(e, AA_U8, &t_char, NULL); + ed->compiled->req_char = t_char; + AA_READ_X(e, AA_U8, &t_char, NULL); + ed->compiled->code[0] = t_char; + + AA_READ_X(e, AA_STATIC_BLOB, &ed->compiled->code[1], NULL); + + AA_READ_X(e, AA_STRUCTEND, NULL, NULL); + + /* stitch in pcre patterns, it was NULLed out by parser + * pcre_default_tables defined in pcre_tables.h */ + ed->compiled->tables = pcre_default_tables; + +done: + if (error != 0 && ed) { + kfree(ed->pattern); /* allocated by AA_READ_X */ + kfree(ed->compiled); + ed->pattern = NULL; + ed->compiled = NULL; + } + + return error; +} + +unsigned int aamatch_match(const char *pathname, const char *entry_name, + enum entry_match_type entry_type, void *entry_extradata) +{ + int ret; + + if (entry_type == aa_entry_pattern) { + int pcreret; + struct aamatch_entry *ed = + (struct aamatch_entry *) entry_extradata; + + pcreret = pcre_exec(ed->compiled, NULL, + pathname, strlen(pathname), + 0, 0, NULL, 0); + + ret = (pcreret >= 0); + + // XXX - this needs access to subdomain_debug, hmmm + //AA_DEBUG("%s(%d): %s %s %d\n", __FUNCTION__, + // ret, pathname, ed->pattern, pcreret); + } else { + ret = aamatch_match_common(pathname, entry_name, entry_type); + } + + return ret; +} + +EXPORT_SYMBOL_GPL(aamatch_alloc); +EXPORT_SYMBOL_GPL(aamatch_free); +EXPORT_SYMBOL_GPL(aamatch_features); +EXPORT_SYMBOL_GPL(aamatch_serialize); +EXPORT_SYMBOL_GPL(aamatch_match); + +MODULE_DESCRIPTION("AppArmor aa_match module [pcre]"); +MODULE_AUTHOR("Tony Jones "); +MODULE_LICENSE("GPL"); Index: b/security/apparmor/match/pcre_exec.c =================================================================== --- /dev/null +++ b/security/apparmor/match/pcre_exec.c @@ -0,0 +1,1945 @@ +/* + * This is a modified version of pcre.c containing only the code/data + * required to support pcre_exec() + */ + + +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* +This is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. See +the file Tech.Notes for some information on the internals. + +Written by: Philip Hazel + + Copyright (c) 1997-2001 University of Cambridge + +----------------------------------------------------------------------------- +Permission is granted to anyone to use this software for any purpose on any +computer system, and to redistribute it freely, subject to the following +restrictions: + +1. This software is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +2. The origin of this software must not be misrepresented, either by + explicit claim or by omission. + +3. Altered versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +4. If PCRE is embedded in any software that is released under the GNU + General Purpose Licence (GPL), then the terms of that licence shall + supersede any condition above with which it is incompatible. +----------------------------------------------------------------------------- +*/ + + +/* Define DEBUG to get debugging output on stdout. */ + +/* #define DEBUG */ + +/* Use a macro for debugging printing, 'cause that eliminates the use of #ifdef +inline, and there are *still* stupid compilers about that don't like indented +pre-processor statements. I suppose it's only been 10 years... */ + +#ifdef DEBUG +#define DPRINTF(p) PCRE_PRINTF p +#else +#define DPRINTF(p) /*nothing*/ +#endif + +/* Include the internals header, which itself includes Standard C headers plus +the external pcre header. */ + +#include "pcre_exec.h" + + +/* ---- CODE DELETED ---- */ + + +/* Min and max values for the common repeats; for the maxima, 0 => infinity */ + +static const char rep_min[] = { 0, 0, 1, 1, 0, 0 }; +static const char rep_max[] = { 0, 0, 0, 0, 1, 1 }; + + +/* ---- CODE DELETED ---- */ + + +/* Structure for building a chain of data that actually lives on the + * stack, for holding the values of the subject pointer at the start of each + * subpattern, so as to detect when an empty string has been matched by a + * subpattern - to break infinite loops. */ + +typedef struct eptrblock { + struct eptrblock *prev; + const uschar *saved_eptr; +} eptrblock; + +/* Flag bits for the match() function */ + +#define match_condassert 0x01 /* Called to check a condition assertion */ +#define match_isgroup 0x02 /* Set if start of bracketed group */ + + +/* ---- CODE DELETED ---- */ + + +/************************************************* + * * Global variables * + * *************************************************/ + +/* PCRE is thread-clean and doesn't use any global variables in the normal + * sense. However, it calls memory allocation and free functions via the two + * indirections below, which are can be changed by the caller, but are shared + * between all threads. */ + +#ifdef __KERNEL__ +static void *kern_malloc(size_t sz) +{ + return kmalloc(sz, GFP_KERNEL); +} +void *(*pcre_malloc)(size_t) = kern_malloc; +void (*pcre_free)(const void *) = kfree; +#else +void *(*pcre_malloc)(size_t) = malloc; +void (*pcre_free)(const void *) = free; +#endif + + +/************************************************* + * * Macros and tables for character handling * + * *************************************************/ + +/* When UTF-8 encoding is being used, a character is no longer just a single + * byte. The macros for character handling generate simple sequences when used in + * byte-mode, and more complicated ones for UTF-8 characters. */ + +#ifndef SUPPORT_UTF8 +#define GETCHARINC(c, eptr) c = *eptr++; +#define GETCHARLEN(c, eptr, len) c = *eptr; +#define BACKCHAR(eptr) +#endif + +/* ---- CODE DELETED ---- */ + +#ifdef DEBUG +/************************************************* +* Debugging function to print chars * +*************************************************/ + +/* Print a sequence of chars in printable format, stopping at the end of the +subject if the requested. + +Arguments: + p points to characters + length number to print + is_subject TRUE if printing from within md->start_subject + md pointer to matching data block, if is_subject is TRUE + +Returns: nothing +*/ + +static void +pchars(const uschar *p, int length, BOOL is_subject, match_data *md) +{ +int c; +if (is_subject && length > md->end_subject - p) length = md->end_subject - p; +while (length-- > 0) + if (isprint(c = *(p++))) PCRE_PRINTF("%c", c); else PCRE_PRINTF("\\x%02x", c); +} +#endif /* DEBUG */ + +/* ---- CODE DELETED ---- */ + + +/************************************************* +* Match a back-reference * +*************************************************/ + +/* If a back reference hasn't been set, the length that is passed is greater +than the number of characters left in the string, so the match fails. + +Arguments: + offset index into the offset vector + eptr points into the subject + length length to be matched + md points to match data block + ims the ims flags + +Returns: TRUE if matched +*/ + +static BOOL +match_ref(int offset, register const uschar *eptr, int length, match_data *md, + unsigned long int ims) +{ +const uschar *p = md->start_subject + md->offset_vector[offset]; + +#ifdef DEBUG +if (eptr >= md->end_subject) + PCRE_PRINTF("matching subject "); +else + { + PCRE_PRINTF("matching subject "); + pchars(eptr, length, TRUE, md); + } +PCRE_PRINTF(" against backref "); +pchars(p, length, FALSE, md); +PCRE_PRINTF("\n"); +#endif + +/* Always fail if not enough characters left */ + +if (length > md->end_subject - eptr) return FALSE; + +/* Separate the caselesss case for speed */ + +if ((ims & PCRE_CASELESS) != 0) + { + while (length-- > 0) + if (md->lcc[*p++] != md->lcc[*eptr++]) return FALSE; + } +else + { while (length-- > 0) if (*p++ != *eptr++) return FALSE; } + +return TRUE; +} + + +/************************************************* +* Match from current position * +*************************************************/ + +/* On entry ecode points to the first opcode, and eptr to the first character +in the subject string, while eptrb holds the value of eptr at the start of the +last bracketed group - used for breaking infinite loops matching zero-length +strings. + +Arguments: + eptr pointer in subject + ecode position in code + offset_top current top pointer + md pointer to "static" info for the match + ims current /i, /m, and /s options + eptrb pointer to chain of blocks containing eptr at start of + brackets - for testing for empty matches + flags can contain + match_condassert - this is an assertion condition + match_isgroup - this is the start of a bracketed group + +Returns: TRUE if matched +*/ + +static BOOL +match(register const uschar *eptr, register const uschar *ecode, + int offset_top, match_data *md, unsigned long int ims, eptrblock *eptrb, + int flags) +{ +unsigned long int original_ims = ims; /* Save for resetting on ')' */ +eptrblock newptrb; + +/* At the start of a bracketed group, add the current subject pointer to the +stack of such pointers, to be re-instated at the end of the group when we hit +the closing ket. When match() is called in other circumstances, we don't add to +the stack. */ + +if ((flags & match_isgroup) != 0) + { + newptrb.prev = eptrb; + newptrb.saved_eptr = eptr; + eptrb = &newptrb; + } + +/* Now start processing the operations. */ + +for (;;) + { + int op = (int)*ecode; + int min, max, ctype; + register int i; + register int c; + BOOL minimize = FALSE; + + /* Opening capturing bracket. If there is space in the offset vector, save + the current subject position in the working slot at the top of the vector. We + mustn't change the current values of the data slot, because they may be set + from a previous iteration of this group, and be referred to by a reference + inside the group. + + If the bracket fails to match, we need to restore this value and also the + values of the final offsets, in case they were set by a previous iteration of + the same bracket. + + If there isn't enough space in the offset vector, treat this as if it were a + non-capturing bracket. Don't worry about setting the flag for the error case + here; that is handled in the code for KET. */ + + if (op > OP_BRA) + { + int offset; + int number = op - OP_BRA; + + /* For extended extraction brackets (large number), we have to fish out the + number from a dummy opcode at the start. */ + + if (number > EXTRACT_BASIC_MAX) number = (ecode[4] << 8) | ecode[5]; + offset = number << 1; + +#ifdef DEBUG + PCRE_PRINTF("start bracket %d subject=", number); + pchars(eptr, 16, TRUE, md); + PCRE_PRINTF("\n"); +#endif + + if (offset < md->offset_max) + { + int save_offset1 = md->offset_vector[offset]; + int save_offset2 = md->offset_vector[offset+1]; + int save_offset3 = md->offset_vector[md->offset_end - number]; + + DPRINTF(("saving %d %d %d\n", save_offset1, save_offset2, save_offset3)); + md->offset_vector[md->offset_end - number] = eptr - md->start_subject; + + do + { + if (match(eptr, ecode+3, offset_top, md, ims, eptrb, match_isgroup)) + return TRUE; + ecode += (ecode[1] << 8) + ecode[2]; + } + while (*ecode == OP_ALT); + + DPRINTF(("bracket %d failed\n", number)); + + md->offset_vector[offset] = save_offset1; + md->offset_vector[offset+1] = save_offset2; + md->offset_vector[md->offset_end - number] = save_offset3; + + return FALSE; + } + + /* Insufficient room for saving captured contents */ + + else op = OP_BRA; + } + + /* Other types of node can be handled by a switch */ + + switch(op) + { + case OP_BRA: /* Non-capturing bracket: optimized */ + DPRINTF(("start bracket 0\n")); + do + { + if (match(eptr, ecode+3, offset_top, md, ims, eptrb, match_isgroup)) + return TRUE; + ecode += (ecode[1] << 8) + ecode[2]; + } + while (*ecode == OP_ALT); + DPRINTF(("bracket 0 failed\n")); + return FALSE; + + /* Conditional group: compilation checked that there are no more than + two branches. If the condition is false, skipping the first branch takes us + past the end if there is only one branch, but that's OK because that is + exactly what going to the ket would do. */ + + case OP_COND: + if (ecode[3] == OP_CREF) /* Condition is extraction test */ + { + int offset = (ecode[4] << 9) | (ecode[5] << 1); /* Doubled ref number */ + return match(eptr, + ecode + ((offset < offset_top && md->offset_vector[offset] >= 0)? + 6 : 3 + (ecode[1] << 8) + ecode[2]), + offset_top, md, ims, eptrb, match_isgroup); + } + + /* The condition is an assertion. Call match() to evaluate it - setting + the final argument TRUE causes it to stop at the end of an assertion. */ + + else + { + if (match(eptr, ecode+3, offset_top, md, ims, NULL, + match_condassert | match_isgroup)) + { + ecode += 3 + (ecode[4] << 8) + ecode[5]; + while (*ecode == OP_ALT) ecode += (ecode[1] << 8) + ecode[2]; + } + else ecode += (ecode[1] << 8) + ecode[2]; + return match(eptr, ecode+3, offset_top, md, ims, eptrb, match_isgroup); + } + /* Control never reaches here */ + + /* Skip over conditional reference or large extraction number data if + encountered. */ + + case OP_CREF: + case OP_BRANUMBER: + ecode += 3; + break; + + /* End of the pattern. If PCRE_NOTEMPTY is set, fail if we have matched + an empty string - recursion will then try other alternatives, if any. */ + + case OP_END: + if (md->notempty && eptr == md->start_match) return FALSE; + md->end_match_ptr = eptr; /* Record where we ended */ + md->end_offset_top = offset_top; /* and how many extracts were taken */ + return TRUE; + + /* Change option settings */ + + case OP_OPT: + ims = ecode[1]; + ecode += 2; + DPRINTF(("ims set to %02lx\n", ims)); + break; + + /* Assertion brackets. Check the alternative branches in turn - the + matching won't pass the KET for an assertion. If any one branch matches, + the assertion is true. Lookbehind assertions have an OP_REVERSE item at the + start of each branch to move the current point backwards, so the code at + this level is identical to the lookahead case. */ + + case OP_ASSERT: + case OP_ASSERTBACK: + do + { + if (match(eptr, ecode+3, offset_top, md, ims, NULL, match_isgroup)) break; + ecode += (ecode[1] << 8) + ecode[2]; + } + while (*ecode == OP_ALT); + if (*ecode == OP_KET) return FALSE; + + /* If checking an assertion for a condition, return TRUE. */ + + if ((flags & match_condassert) != 0) return TRUE; + + /* Continue from after the assertion, updating the offsets high water + mark, since extracts may have been taken during the assertion. */ + + do ecode += (ecode[1] << 8) + ecode[2]; while (*ecode == OP_ALT); + ecode += 3; + offset_top = md->end_offset_top; + continue; + + /* Negative assertion: all branches must fail to match */ + + case OP_ASSERT_NOT: + case OP_ASSERTBACK_NOT: + do + { + if (match(eptr, ecode+3, offset_top, md, ims, NULL, match_isgroup)) + return FALSE; + ecode += (ecode[1] << 8) + ecode[2]; + } + while (*ecode == OP_ALT); + + if ((flags & match_condassert) != 0) return TRUE; + + ecode += 3; + continue; + + /* Move the subject pointer back. This occurs only at the start of + each branch of a lookbehind assertion. If we are too close to the start to + move back, this match function fails. When working with UTF-8 we move + back a number of characters, not bytes. */ + + case OP_REVERSE: +#ifdef SUPPORT_UTF8 + c = (ecode[1] << 8) + ecode[2]; + for (i = 0; i < c; i++) + { + eptr--; + BACKCHAR(eptr) + } +#else + eptr -= (ecode[1] << 8) + ecode[2]; +#endif + + if (eptr < md->start_subject) return FALSE; + ecode += 3; + break; + + /* Recursion matches the current regex, nested. If there are any capturing + brackets started but not finished, we have to save their starting points + and reinstate them after the recursion. However, we don't know how many + such there are (offset_top records the completed total) so we just have + to save all the potential data. There may be up to 99 such values, which + is a bit large to put on the stack, but using malloc for small numbers + seems expensive. As a compromise, the stack is used when there are fewer + than 16 values to store; otherwise malloc is used. A problem is what to do + if the malloc fails ... there is no way of returning to the top level with + an error. Save the top 15 values on the stack, and accept that the rest + may be wrong. */ + + case OP_RECURSE: + { + BOOL rc; + int *save; + int stacksave[15]; + + c = md->offset_max; + + if (c < 16) save = stacksave; else + { + save = (int *)(pcre_malloc)((c+1) * sizeof(int)); + if (save == NULL) + { + save = stacksave; + c = 15; + } + } + + for (i = 1; i <= c; i++) + save[i] = md->offset_vector[md->offset_end - i]; + rc = match(eptr, md->start_pattern, offset_top, md, ims, eptrb, + match_isgroup); + for (i = 1; i <= c; i++) + md->offset_vector[md->offset_end - i] = save[i]; + if (save != stacksave) (pcre_free)(save); + if (!rc) return FALSE; + + /* In case the recursion has set more capturing values, save the final + number, then move along the subject till after the recursive match, + and advance one byte in the pattern code. */ + + offset_top = md->end_offset_top; + eptr = md->end_match_ptr; + ecode++; + } + break; + + /* "Once" brackets are like assertion brackets except that after a match, + the point in the subject string is not moved back. Thus there can never be + a move back into the brackets. Check the alternative branches in turn - the + matching won't pass the KET for this kind of subpattern. If any one branch + matches, we carry on as at the end of a normal bracket, leaving the subject + pointer. */ + + case OP_ONCE: + { + const uschar *prev = ecode; + const uschar *saved_eptr = eptr; + + do + { + if (match(eptr, ecode+3, offset_top, md, ims, eptrb, match_isgroup)) + break; + ecode += (ecode[1] << 8) + ecode[2]; + } + while (*ecode == OP_ALT); + + /* If hit the end of the group (which could be repeated), fail */ + + if (*ecode != OP_ONCE && *ecode != OP_ALT) return FALSE; + + /* Continue as from after the assertion, updating the offsets high water + mark, since extracts may have been taken. */ + + do ecode += (ecode[1] << 8) + ecode[2]; while (*ecode == OP_ALT); + + offset_top = md->end_offset_top; + eptr = md->end_match_ptr; + + /* For a non-repeating ket, just continue at this level. This also + happens for a repeating ket if no characters were matched in the group. + This is the forcible breaking of infinite loops as implemented in Perl + 5.005. If there is an options reset, it will get obeyed in the normal + course of events. */ + + if (*ecode == OP_KET || eptr == saved_eptr) + { + ecode += 3; + break; + } + + /* The repeating kets try the rest of the pattern or restart from the + preceding bracket, in the appropriate order. We need to reset any options + that changed within the bracket before re-running it, so check the next + opcode. */ + + if (ecode[3] == OP_OPT) + { + ims = (ims & ~PCRE_IMS) | ecode[4]; + DPRINTF(("ims set to %02lx at group repeat\n", ims)); + } + + if (*ecode == OP_KETRMIN) + { + if (match(eptr, ecode+3, offset_top, md, ims, eptrb, 0) || + match(eptr, prev, offset_top, md, ims, eptrb, match_isgroup)) + return TRUE; + } + else /* OP_KETRMAX */ + { + if (match(eptr, prev, offset_top, md, ims, eptrb, match_isgroup) || + match(eptr, ecode+3, offset_top, md, ims, eptrb, 0)) return TRUE; + } + } + return FALSE; + + /* An alternation is the end of a branch; scan along to find the end of the + bracketed group and go to there. */ + + case OP_ALT: + do ecode += (ecode[1] << 8) + ecode[2]; while (*ecode == OP_ALT); + break; + + /* BRAZERO and BRAMINZERO occur just before a bracket group, indicating + that it may occur zero times. It may repeat infinitely, or not at all - + i.e. it could be ()* or ()? in the pattern. Brackets with fixed upper + repeat limits are compiled as a number of copies, with the optional ones + preceded by BRAZERO or BRAMINZERO. */ + + case OP_BRAZERO: + { + const uschar *next = ecode+1; + if (match(eptr, next, offset_top, md, ims, eptrb, match_isgroup)) + return TRUE; + do next += (next[1] << 8) + next[2]; while (*next == OP_ALT); + ecode = next + 3; + } + break; + + case OP_BRAMINZERO: + { + const uschar *next = ecode+1; + do next += (next[1] << 8) + next[2]; while (*next == OP_ALT); + if (match(eptr, next+3, offset_top, md, ims, eptrb, match_isgroup)) + return TRUE; + ecode++; + } + break; + + /* End of a group, repeated or non-repeating. If we are at the end of + an assertion "group", stop matching and return TRUE, but record the + current high water mark for use by positive assertions. Do this also + for the "once" (not-backup up) groups. */ + + case OP_KET: + case OP_KETRMIN: + case OP_KETRMAX: + { + const uschar *prev = ecode - (ecode[1] << 8) - ecode[2]; + const uschar *saved_eptr = eptrb->saved_eptr; + + eptrb = eptrb->prev; /* Back up the stack of bracket start pointers */ + + if (*prev == OP_ASSERT || *prev == OP_ASSERT_NOT || + *prev == OP_ASSERTBACK || *prev == OP_ASSERTBACK_NOT || + *prev == OP_ONCE) + { + md->end_match_ptr = eptr; /* For ONCE */ + md->end_offset_top = offset_top; + return TRUE; + } + + /* In all other cases except a conditional group we have to check the + group number back at the start and if necessary complete handling an + extraction by setting the offsets and bumping the high water mark. */ + + if (*prev != OP_COND) + { + int offset; + int number = *prev - OP_BRA; + + /* For extended extraction brackets (large number), we have to fish out + the number from a dummy opcode at the start. */ + + if (number > EXTRACT_BASIC_MAX) number = (prev[4] << 8) | prev[5]; + offset = number << 1; + +#ifdef DEBUG + PCRE_PRINTF("end bracket %d", number); + PCRE_PRINTF("\n"); +#endif + + if (number > 0) + { + if (offset >= md->offset_max) md->offset_overflow = TRUE; else + { + md->offset_vector[offset] = + md->offset_vector[md->offset_end - number]; + md->offset_vector[offset+1] = eptr - md->start_subject; + if (offset_top <= offset) offset_top = offset + 2; + } + } + } + + /* Reset the value of the ims flags, in case they got changed during + the group. */ + + ims = original_ims; + DPRINTF(("ims reset to %02lx\n", ims)); + + /* For a non-repeating ket, just continue at this level. This also + happens for a repeating ket if no characters were matched in the group. + This is the forcible breaking of infinite loops as implemented in Perl + 5.005. If there is an options reset, it will get obeyed in the normal + course of events. */ + + if (*ecode == OP_KET || eptr == saved_eptr) + { + ecode += 3; + break; + } + + /* The repeating kets try the rest of the pattern or restart from the + preceding bracket, in the appropriate order. */ + + if (*ecode == OP_KETRMIN) + { + if (match(eptr, ecode+3, offset_top, md, ims, eptrb, 0) || + match(eptr, prev, offset_top, md, ims, eptrb, match_isgroup)) + return TRUE; + } + else /* OP_KETRMAX */ + { + if (match(eptr, prev, offset_top, md, ims, eptrb, match_isgroup) || + match(eptr, ecode+3, offset_top, md, ims, eptrb, 0)) return TRUE; + } + } + return FALSE; + + /* Start of subject unless notbol, or after internal newline if multiline */ + + case OP_CIRC: + if (md->notbol && eptr == md->start_subject) return FALSE; + if ((ims & PCRE_MULTILINE) != 0) + { + if (eptr != md->start_subject && eptr[-1] != NEWLINE) return FALSE; + ecode++; + break; + } + /* ... else fall through */ + + /* Start of subject assertion */ + + case OP_SOD: + if (eptr != md->start_subject) return FALSE; + ecode++; + break; + + /* Assert before internal newline if multiline, or before a terminating + newline unless endonly is set, else end of subject unless noteol is set. */ + + case OP_DOLL: + if ((ims & PCRE_MULTILINE) != 0) + { + if (eptr < md->end_subject) { if (*eptr != NEWLINE) return FALSE; } + else { if (md->noteol) return FALSE; } + ecode++; + break; + } + else + { + if (md->noteol) return FALSE; + if (!md->endonly) + { + if (eptr < md->end_subject - 1 || + (eptr == md->end_subject - 1 && *eptr != NEWLINE)) return FALSE; + + ecode++; + break; + } + } + /* ... else fall through */ + + /* End of subject assertion (\z) */ + + case OP_EOD: + if (eptr < md->end_subject) return FALSE; + ecode++; + break; + + /* End of subject or ending \n assertion (\Z) */ + + case OP_EODN: + if (eptr < md->end_subject - 1 || + (eptr == md->end_subject - 1 && *eptr != NEWLINE)) return FALSE; + ecode++; + break; + + /* Word boundary assertions */ + + case OP_NOT_WORD_BOUNDARY: + case OP_WORD_BOUNDARY: + { + BOOL prev_is_word = (eptr != md->start_subject) && + ((md->ctypes[eptr[-1]] & ctype_word) != 0); + BOOL cur_is_word = (eptr < md->end_subject) && + ((md->ctypes[*eptr] & ctype_word) != 0); + if ((*ecode++ == OP_WORD_BOUNDARY)? + cur_is_word == prev_is_word : cur_is_word != prev_is_word) + return FALSE; + } + break; + + /* Match a single character type; inline for speed */ + + case OP_ANY: + if ((ims & PCRE_DOTALL) == 0 && eptr < md->end_subject && *eptr == NEWLINE) + return FALSE; + if (eptr++ >= md->end_subject) return FALSE; +#ifdef SUPPORT_UTF8 + if (md->utf8) + while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++; +#endif + ecode++; + break; + + case OP_NOT_DIGIT: + if (eptr >= md->end_subject || + (md->ctypes[*eptr++] & ctype_digit) != 0) + return FALSE; + ecode++; + break; + + case OP_DIGIT: + if (eptr >= md->end_subject || + (md->ctypes[*eptr++] & ctype_digit) == 0) + return FALSE; + ecode++; + break; + + case OP_NOT_WHITESPACE: + if (eptr >= md->end_subject || + (md->ctypes[*eptr++] & ctype_space) != 0) + return FALSE; + ecode++; + break; + + case OP_WHITESPACE: + if (eptr >= md->end_subject || + (md->ctypes[*eptr++] & ctype_space) == 0) + return FALSE; + ecode++; + break; + + case OP_NOT_WORDCHAR: + if (eptr >= md->end_subject || + (md->ctypes[*eptr++] & ctype_word) != 0) + return FALSE; + ecode++; + break; + + case OP_WORDCHAR: + if (eptr >= md->end_subject || + (md->ctypes[*eptr++] & ctype_word) == 0) + return FALSE; + ecode++; + break; + + /* Match a back reference, possibly repeatedly. Look past the end of the + item to see if there is repeat information following. The code is similar + to that for character classes, but repeated for efficiency. Then obey + similar code to character type repeats - written out again for speed. + However, if the referenced string is the empty string, always treat + it as matched, any number of times (otherwise there could be infinite + loops). */ + + case OP_REF: + { + int length; + int offset = (ecode[1] << 9) | (ecode[2] << 1); /* Doubled ref number */ + ecode += 3; /* Advance past item */ + + /* If the reference is unset, set the length to be longer than the amount + of subject left; this ensures that every attempt at a match fails. We + can't just fail here, because of the possibility of quantifiers with zero + minima. */ + + length = (offset >= offset_top || md->offset_vector[offset] < 0)? + md->end_subject - eptr + 1 : + md->offset_vector[offset+1] - md->offset_vector[offset]; + + /* Set up for repetition, or handle the non-repeated case */ + + switch (*ecode) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRPLUS: + case OP_CRMINPLUS: + case OP_CRQUERY: + case OP_CRMINQUERY: + c = *ecode++ - OP_CRSTAR; + minimize = (c & 1) != 0; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + break; + + case OP_CRRANGE: + case OP_CRMINRANGE: + minimize = (*ecode == OP_CRMINRANGE); + min = (ecode[1] << 8) + ecode[2]; + max = (ecode[3] << 8) + ecode[4]; + if (max == 0) max = INT_MAX; + ecode += 5; + break; + + default: /* No repeat follows */ + if (!match_ref(offset, eptr, length, md, ims)) return FALSE; + eptr += length; + continue; /* With the main loop */ + } + + /* If the length of the reference is zero, just continue with the + main loop. */ + + if (length == 0) continue; + + /* First, ensure the minimum number of matches are present. We get back + the length of the reference string explicitly rather than passing the + address of eptr, so that eptr can be a register variable. */ + + for (i = 1; i <= min; i++) + { + if (!match_ref(offset, eptr, length, md, ims)) return FALSE; + eptr += length; + } + + /* If min = max, continue at the same level without recursion. + They are not both allowed to be zero. */ + + if (min == max) continue; + + /* If minimizing, keep trying and advancing the pointer */ + + if (minimize) + { + for (i = min;; i++) + { + if (match(eptr, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + if (i >= max || !match_ref(offset, eptr, length, md, ims)) + return FALSE; + eptr += length; + } + /* Control never gets here */ + } + + /* If maximizing, find the longest string and work backwards */ + + else + { + const uschar *pp = eptr; + for (i = min; i < max; i++) + { + if (!match_ref(offset, eptr, length, md, ims)) break; + eptr += length; + } + while (eptr >= pp) + { + if (match(eptr, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + eptr -= length; + } + return FALSE; + } + } + /* Control never gets here */ + + + + /* Match a character class, possibly repeatedly. Look past the end of the + item to see if there is repeat information following. Then obey similar + code to character type repeats - written out again for speed. */ + + case OP_CLASS: + { + const uschar *data = ecode + 1; /* Save for matching */ + ecode += 33; /* Advance past the item */ + + switch (*ecode) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRPLUS: + case OP_CRMINPLUS: + case OP_CRQUERY: + case OP_CRMINQUERY: + c = *ecode++ - OP_CRSTAR; + minimize = (c & 1) != 0; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + break; + + case OP_CRRANGE: + case OP_CRMINRANGE: + minimize = (*ecode == OP_CRMINRANGE); + min = (ecode[1] << 8) + ecode[2]; + max = (ecode[3] << 8) + ecode[4]; + if (max == 0) max = INT_MAX; + ecode += 5; + break; + + default: /* No repeat follows */ + min = max = 1; + break; + } + + /* First, ensure the minimum number of matches are present. */ + + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject) return FALSE; + GETCHARINC(c, eptr) /* Get character; increment eptr */ + +#ifdef SUPPORT_UTF8 + /* We do not yet support class members > 255 */ + if (c > 255) return FALSE; +#endif + + if ((data[c/8] & (1 << (c&7))) != 0) continue; + return FALSE; + } + + /* If max == min we can continue with the main loop without the + need to recurse. */ + + if (min == max) continue; + + /* If minimizing, keep testing the rest of the expression and advancing + the pointer while it matches the class. */ + + if (minimize) + { + for (i = min;; i++) + { + if (match(eptr, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + if (i >= max || eptr >= md->end_subject) return FALSE; + GETCHARINC(c, eptr) /* Get character; increment eptr */ + +#ifdef SUPPORT_UTF8 + /* We do not yet support class members > 255 */ + if (c > 255) return FALSE; +#endif + if ((data[c/8] & (1 << (c&7))) != 0) continue; + return FALSE; + } + /* Control never gets here */ + } + + /* If maximizing, find the longest possible run, then work backwards. */ + + else + { + const uschar *pp = eptr; + int len = 1; + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject) break; + GETCHARLEN(c, eptr, len) /* Get character, set length if UTF-8 */ + +#ifdef SUPPORT_UTF8 + /* We do not yet support class members > 255 */ + if (c > 255) break; +#endif + if ((data[c/8] & (1 << (c&7))) == 0) break; + eptr += len; + } + + while (eptr >= pp) + { + if (match(eptr--, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + +#ifdef SUPPORT_UTF8 + BACKCHAR(eptr) +#endif + } + return FALSE; + } + } + /* Control never gets here */ + + /* Match a run of characters */ + + case OP_CHARS: + { + register int length = ecode[1]; + ecode += 2; + +#ifdef DEBUG /* Sigh. Some compilers never learn. */ + if (eptr >= md->end_subject) + PCRE_PRINTF("matching subject against pattern "); + else + { + PCRE_PRINTF("matching subject "); + pchars(eptr, length, TRUE, md); + PCRE_PRINTF(" against pattern "); + } + pchars(ecode, length, FALSE, md); + PCRE_PRINTF("\n"); +#endif + + if (length > md->end_subject - eptr) return FALSE; + if ((ims & PCRE_CASELESS) != 0) + { + while (length-- > 0) + if (md->lcc[*ecode++] != md->lcc[*eptr++]) + return FALSE; + } + else + { + while (length-- > 0) if (*ecode++ != *eptr++) return FALSE; + } + } + break; + + /* Match a single character repeatedly; different opcodes share code. */ + + case OP_EXACT: + min = max = (ecode[1] << 8) + ecode[2]; + ecode += 3; + goto REPEATCHAR; + + case OP_UPTO: + case OP_MINUPTO: + min = 0; + max = (ecode[1] << 8) + ecode[2]; + minimize = *ecode == OP_MINUPTO; + ecode += 3; + goto REPEATCHAR; + + case OP_STAR: + case OP_MINSTAR: + case OP_PLUS: + case OP_MINPLUS: + case OP_QUERY: + case OP_MINQUERY: + c = *ecode++ - OP_STAR; + minimize = (c & 1) != 0; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + + /* Common code for all repeated single-character matches. We can give + up quickly if there are fewer than the minimum number of characters left in + the subject. */ + + REPEATCHAR: + if (min > md->end_subject - eptr) return FALSE; + c = *ecode++; + + /* The code is duplicated for the caseless and caseful cases, for speed, + since matching characters is likely to be quite common. First, ensure the + minimum number of matches are present. If min = max, continue at the same + level without recursing. Otherwise, if minimizing, keep trying the rest of + the expression and advancing one matching character if failing, up to the + maximum. Alternatively, if maximizing, find the maximum number of + characters and work backwards. */ + + DPRINTF(("matching %c{%d,%d} against subject %.*s\n", c, min, max, + max, eptr)); + + if ((ims & PCRE_CASELESS) != 0) + { + c = md->lcc[c]; + for (i = 1; i <= min; i++) + if (c != md->lcc[*eptr++]) return FALSE; + if (min == max) continue; + if (minimize) + { + for (i = min;; i++) + { + if (match(eptr, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + if (i >= max || eptr >= md->end_subject || + c != md->lcc[*eptr++]) + return FALSE; + } + /* Control never gets here */ + } + else + { + const uschar *pp = eptr; + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || c != md->lcc[*eptr]) break; + eptr++; + } + while (eptr >= pp) + if (match(eptr--, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + return FALSE; + } + /* Control never gets here */ + } + + /* Caseful comparisons */ + + else + { + for (i = 1; i <= min; i++) if (c != *eptr++) return FALSE; + if (min == max) continue; + if (minimize) + { + for (i = min;; i++) + { + if (match(eptr, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + if (i >= max || eptr >= md->end_subject || c != *eptr++) return FALSE; + } + /* Control never gets here */ + } + else + { + const uschar *pp = eptr; + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || c != *eptr) break; + eptr++; + } + while (eptr >= pp) + if (match(eptr--, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + return FALSE; + } + } + /* Control never gets here */ + + /* Match a negated single character */ + + case OP_NOT: + if (eptr >= md->end_subject) return FALSE; + ecode++; + if ((ims & PCRE_CASELESS) != 0) + { + if (md->lcc[*ecode++] == md->lcc[*eptr++]) return FALSE; + } + else + { + if (*ecode++ == *eptr++) return FALSE; + } + break; + + /* Match a negated single character repeatedly. This is almost a repeat of + the code for a repeated single character, but I haven't found a nice way of + commoning these up that doesn't require a test of the positive/negative + option for each character match. Maybe that wouldn't add very much to the + time taken, but character matching *is* what this is all about... */ + + case OP_NOTEXACT: + min = max = (ecode[1] << 8) + ecode[2]; + ecode += 3; + goto REPEATNOTCHAR; + + case OP_NOTUPTO: + case OP_NOTMINUPTO: + min = 0; + max = (ecode[1] << 8) + ecode[2]; + minimize = *ecode == OP_NOTMINUPTO; + ecode += 3; + goto REPEATNOTCHAR; + + case OP_NOTSTAR: + case OP_NOTMINSTAR: + case OP_NOTPLUS: + case OP_NOTMINPLUS: + case OP_NOTQUERY: + case OP_NOTMINQUERY: + c = *ecode++ - OP_NOTSTAR; + minimize = (c & 1) != 0; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + + /* Common code for all repeated single-character matches. We can give + up quickly if there are fewer than the minimum number of characters left in + the subject. */ + + REPEATNOTCHAR: + if (min > md->end_subject - eptr) return FALSE; + c = *ecode++; + + /* The code is duplicated for the caseless and caseful cases, for speed, + since matching characters is likely to be quite common. First, ensure the + minimum number of matches are present. If min = max, continue at the same + level without recursing. Otherwise, if minimizing, keep trying the rest of + the expression and advancing one matching character if failing, up to the + maximum. Alternatively, if maximizing, find the maximum number of + characters and work backwards. */ + + DPRINTF(("negative matching %c{%d,%d} against subject %.*s\n", c, min, max, + max, eptr)); + + if ((ims & PCRE_CASELESS) != 0) + { + c = md->lcc[c]; + for (i = 1; i <= min; i++) + if (c == md->lcc[*eptr++]) return FALSE; + if (min == max) continue; + if (minimize) + { + for (i = min;; i++) + { + if (match(eptr, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + if (i >= max || eptr >= md->end_subject || + c == md->lcc[*eptr++]) + return FALSE; + } + /* Control never gets here */ + } + else + { + const uschar *pp = eptr; + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || c == md->lcc[*eptr]) break; + eptr++; + } + while (eptr >= pp) + if (match(eptr--, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + return FALSE; + } + /* Control never gets here */ + } + + /* Caseful comparisons */ + + else + { + for (i = 1; i <= min; i++) if (c == *eptr++) return FALSE; + if (min == max) continue; + if (minimize) + { + for (i = min;; i++) + { + if (match(eptr, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + if (i >= max || eptr >= md->end_subject || c == *eptr++) return FALSE; + } + /* Control never gets here */ + } + else + { + const uschar *pp = eptr; + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || c == *eptr) break; + eptr++; + } + while (eptr >= pp) + if (match(eptr--, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; + return FALSE; + } + } + /* Control never gets here */ + + /* Match a single character type repeatedly; several different opcodes + share code. This is very similar to the code for single characters, but we + repeat it in the interests of efficiency. */ + + case OP_TYPEEXACT: + min = max = (ecode[1] << 8) + ecode[2]; + minimize = TRUE; + ecode += 3; + goto REPEATTYPE; + + case OP_TYPEUPTO: + case OP_TYPEMINUPTO: + min = 0; + max = (ecode[1] << 8) + ecode[2]; + minimize = *ecode == OP_TYPEMINUPTO; + ecode += 3; + goto REPEATTYPE; + + case OP_TYPESTAR: + case OP_TYPEMINSTAR: + case OP_TYPEPLUS: + case OP_TYPEMINPLUS: + case OP_TYPEQUERY: + case OP_TYPEMINQUERY: + c = *ecode++ - OP_TYPESTAR; + minimize = (c & 1) != 0; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + + /* Common code for all repeated single character type matches */ + + REPEATTYPE: + ctype = *ecode++; /* Code for the character type */ + + /* First, ensure the minimum number of matches are present. Use inline + code for maximizing the speed, and do the type test once at the start + (i.e. keep it out of the loop). Also we can test that there are at least + the minimum number of bytes before we start, except when doing '.' in + UTF8 mode. Leave the test in in all cases; in the special case we have + to test after each character. */ + + if (min > md->end_subject - eptr) return FALSE; + if (min > 0) switch(ctype) + { + case OP_ANY: +#ifdef SUPPORT_UTF8 + if (md->utf8) + { + for (i = 1; i <= min; i++) + { + if (eptr >= md->end_subject || + (*eptr++ == NEWLINE && (ims & PCRE_DOTALL) == 0)) + return FALSE; + while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++; + } + break; + } +#endif + /* Non-UTF8 can be faster */ + if ((ims & PCRE_DOTALL) == 0) + { for (i = 1; i <= min; i++) if (*eptr++ == NEWLINE) return FALSE; } + else eptr += min; + break; + + case OP_NOT_DIGIT: + for (i = 1; i <= min; i++) + if ((md->ctypes[*eptr++] & ctype_digit) != 0) return FALSE; + break; + + case OP_DIGIT: + for (i = 1; i <= min; i++) + if ((md->ctypes[*eptr++] & ctype_digit) == 0) return FALSE; + break; + + case OP_NOT_WHITESPACE: + for (i = 1; i <= min; i++) + if ((md->ctypes[*eptr++] & ctype_space) != 0) return FALSE; + break; + + case OP_WHITESPACE: + for (i = 1; i <= min; i++) + if ((md->ctypes[*eptr++] & ctype_space) == 0) return FALSE; + break; + + case OP_NOT_WORDCHAR: + for (i = 1; i <= min; i++) + if ((md->ctypes[*eptr++] & ctype_word) != 0) + return FALSE; + break; + + case OP_WORDCHAR: + for (i = 1; i <= min; i++) + if ((md->ctypes[*eptr++] & ctype_word) == 0) + return FALSE; + break; + } + + /* If min = max, continue at the same level without recursing */ + + if (min == max) continue; + + /* If minimizing, we have to test the rest of the pattern before each + subsequent match. */ + + if (minimize) + { + for (i = min;; i++) + { + if (match(eptr, ecode, offset_top, md, ims, eptrb, 0)) return TRUE; + if (i >= max || eptr >= md->end_subject) return FALSE; + + c = *eptr++; + switch(ctype) + { + case OP_ANY: + if ((ims & PCRE_DOTALL) == 0 && c == NEWLINE) return FALSE; +#ifdef SUPPORT_UTF8 + if (md->utf8) + while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++; +#endif + break; + + case OP_NOT_DIGIT: + if ((md->ctypes[c] & ctype_digit) != 0) return FALSE; + break; + + case OP_DIGIT: + if ((md->ctypes[c] & ctype_digit) == 0) return FALSE; + break; + + case OP_NOT_WHITESPACE: + if ((md->ctypes[c] & ctype_space) != 0) return FALSE; + break; + + case OP_WHITESPACE: + if ((md->ctypes[c] & ctype_space) == 0) return FALSE; + break; + + case OP_NOT_WORDCHAR: + if ((md->ctypes[c] & ctype_word) != 0) return FALSE; + break; + + case OP_WORDCHAR: + if ((md->ctypes[c] & ctype_word) == 0) return FALSE; + break; + } + } + /* Control never gets here */ + } + + /* If maximizing it is worth using inline code for speed, doing the type + test once at the start (i.e. keep it out of the loop). */ + + else + { + const uschar *pp = eptr; + switch(ctype) + { + case OP_ANY: + + /* Special code is required for UTF8, but when the maximum is unlimited + we don't need it. */ + +#ifdef SUPPORT_UTF8 + if (md->utf8 && max < INT_MAX) + { + if ((ims & PCRE_DOTALL) == 0) + { + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || *eptr++ == NEWLINE) break; + while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++; + } + } + else + { + for (i = min; i < max; i++) + { + eptr++; + while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++; + } + } + break; + } +#endif + /* Non-UTF8 can be faster */ + if ((ims & PCRE_DOTALL) == 0) + { + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || *eptr == NEWLINE) break; + eptr++; + } + } + else + { + c = max - min; + if (c > md->end_subject - eptr) c = md->end_subject - eptr; + eptr += c; + } + break; + + case OP_NOT_DIGIT: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_digit) != 0) + break; + eptr++; + } + break; + + case OP_DIGIT: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_digit) == 0) + break; + eptr++; + } + break; + + case OP_NOT_WHITESPACE: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_space) != 0) + break; + eptr++; + } + break; + + case OP_WHITESPACE: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_space) == 0) + break; + eptr++; + } + break; + + case OP_NOT_WORDCHAR: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_word) != 0) + break; + eptr++; + } + break; + + case OP_WORDCHAR: + for (i = min; i < max; i++) + { + if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_word) == 0) + break; + eptr++; + } + break; + } + + while (eptr >= pp) + { + if (match(eptr--, ecode, offset_top, md, ims, eptrb, 0)) + return TRUE; +#ifdef SUPPORT_UTF8 + if (md->utf8) + while (eptr > pp && (*eptr & 0xc0) == 0x80) eptr--; +#endif + } + return FALSE; + } + /* Control never gets here */ + + /* There's been some horrible disaster. */ + + default: + DPRINTF(("Unknown opcode %d\n", *ecode)); + md->errorcode = PCRE_ERROR_UNKNOWN_NODE; + return FALSE; + } + + /* Do not stick any code in here without much thought; it is assumed + that "continue" in the code above comes out to here to repeat the main + loop. */ + + } /* End of main loop */ +/* Control never reaches here */ +} + + +/************************************************* +* Execute a Regular Expression * +*************************************************/ + +/* This function applies a compiled re to a subject string and picks out +portions of the string if it matches. Two elements in the vector are set for +each substring: the offsets to the start and end of the substring. + +Arguments: + external_re points to the compiled expression + external_extra points to "hints" from pcre_study() or is NULL + subject points to the subject string + length length of subject string (may contain binary zeros) + start_offset where to start in the subject string + options option bits + offsets points to a vector of ints to be filled in with offsets + offsetcount the number of elements in the vector + +Returns: > 0 => success; value is the number of elements filled in + = 0 => success, but offsets is not big enough + -1 => failed to match + < -1 => some kind of unexpected problem +*/ + +int +pcre_exec(const pcre *external_re, const pcre_extra *external_extra, + const char *subject, int length, int start_offset, int options, int *offsets, + int offsetcount) +{ +int resetcount, ocount; +int first_char = -1; +int req_char = -1; +int req_char2 = -1; +unsigned long int ims = 0; +match_data match_block; +const uschar *start_bits = NULL; +const uschar *start_match = (const uschar *)subject + start_offset; +const uschar *end_subject; +const uschar *req_char_ptr = start_match - 1; +const real_pcre *re = (const real_pcre *)external_re; +const real_pcre_extra *extra = (const real_pcre_extra *)external_extra; +BOOL using_temporary_offsets = FALSE; +BOOL anchored; +BOOL startline; + +if ((options & ~PUBLIC_EXEC_OPTIONS) != 0) return PCRE_ERROR_BADOPTION; + +if (re == NULL || subject == NULL || + (offsets == NULL && offsetcount > 0)) return PCRE_ERROR_NULL; +if (re->magic_number != MAGIC_NUMBER) return PCRE_ERROR_BADMAGIC; + +anchored = ((re->options | options) & PCRE_ANCHORED) != 0; +startline = (re->options & PCRE_STARTLINE) != 0; + +match_block.start_pattern = re->code; +match_block.start_subject = (const uschar *)subject; +match_block.end_subject = match_block.start_subject + length; +end_subject = match_block.end_subject; + +match_block.endonly = (re->options & PCRE_DOLLAR_ENDONLY) != 0; +match_block.utf8 = (re->options & PCRE_UTF8) != 0; + +match_block.notbol = (options & PCRE_NOTBOL) != 0; +match_block.noteol = (options & PCRE_NOTEOL) != 0; +match_block.notempty = (options & PCRE_NOTEMPTY) != 0; + +match_block.errorcode = PCRE_ERROR_NOMATCH; /* Default error */ + +match_block.lcc = re->tables + lcc_offset; +match_block.ctypes = re->tables + ctypes_offset; + +/* The ims options can vary during the matching as a result of the presence +of (?ims) items in the pattern. They are kept in a local variable so that +restoring at the exit of a group is easy. */ + +ims = re->options & (PCRE_CASELESS|PCRE_MULTILINE|PCRE_DOTALL); + +/* If the expression has got more back references than the offsets supplied can +hold, we get a temporary bit of working store to use during the matching. +Otherwise, we can use the vector supplied, rounding down its size to a multiple +of 3. */ + +ocount = offsetcount - (offsetcount % 3); + +if (re->top_backref > 0 && re->top_backref >= ocount/3) + { + ocount = re->top_backref * 3 + 3; + match_block.offset_vector = (int *)(pcre_malloc)(ocount * sizeof(int)); + if (match_block.offset_vector == NULL) return PCRE_ERROR_NOMEMORY; + using_temporary_offsets = TRUE; + DPRINTF(("Got memory to hold back references\n")); + } +else match_block.offset_vector = offsets; + +match_block.offset_end = ocount; +match_block.offset_max = (2*ocount)/3; +match_block.offset_overflow = FALSE; + +/* Compute the minimum number of offsets that we need to reset each time. Doing +this makes a huge difference to execution time when there aren't many brackets +in the pattern. */ + +resetcount = 2 + re->top_bracket * 2; +if (resetcount > offsetcount) resetcount = ocount; + +/* Reset the working variable associated with each extraction. These should +never be used unless previously set, but they get saved and restored, and so we +initialize them to avoid reading uninitialized locations. */ + +if (match_block.offset_vector != NULL) + { + register int *iptr = match_block.offset_vector + ocount; + register int *iend = iptr - resetcount/2 + 1; + while (--iptr >= iend) *iptr = -1; + } + +/* Set up the first character to match, if available. The first_char value is +never set for an anchored regular expression, but the anchoring may be forced +at run time, so we have to test for anchoring. The first char may be unset for +an unanchored pattern, of course. If there's no first char and the pattern was +studied, there may be a bitmap of possible first characters. */ + +if (!anchored) + { + if ((re->options & PCRE_FIRSTSET) != 0) + { + first_char = re->first_char; + if ((ims & PCRE_CASELESS) != 0) first_char = match_block.lcc[first_char]; + } + else + if (!startline && extra != NULL && + (extra->options & PCRE_STUDY_MAPPED) != 0) + start_bits = extra->start_bits; + } + +/* For anchored or unanchored matches, there may be a "last known required +character" set. If the PCRE_CASELESS is set, implying that the match starts +caselessly, or if there are any changes of this flag within the regex, set up +both cases of the character. Otherwise set the two values the same, which will +avoid duplicate testing (which takes significant time). This covers the vast +majority of cases. It will be suboptimal when the case flag changes in a regex +and the required character in fact is caseful. */ + +if ((re->options & PCRE_REQCHSET) != 0) + { + req_char = re->req_char; + req_char2 = ((re->options & (PCRE_CASELESS | PCRE_ICHANGED)) != 0)? + (re->tables + fcc_offset)[req_char] : req_char; + } + +/* Loop for handling unanchored repeated matching attempts; for anchored regexs +the loop runs just once. */ + +do + { + int rc; + register int *iptr = match_block.offset_vector; + register int *iend = iptr + resetcount; + + /* Reset the maximum number of extractions we might see. */ + + while (iptr < iend) *iptr++ = -1; + + /* Advance to a unique first char if possible */ + + if (first_char >= 0) + { + if ((ims & PCRE_CASELESS) != 0) + while (start_match < end_subject && + match_block.lcc[*start_match] != first_char) + start_match++; + else + while (start_match < end_subject && *start_match != first_char) + start_match++; + } + + /* Or to just after \n for a multiline match if possible */ + + else if (startline) + { + if (start_match > match_block.start_subject + start_offset) + { + while (start_match < end_subject && start_match[-1] != NEWLINE) + start_match++; + } + } + + /* Or to a non-unique first char after study */ + + else if (start_bits != NULL) + { + while (start_match < end_subject) + { + register int c = *start_match; + if ((start_bits[c/8] & (1 << (c&7))) == 0) start_match++; else break; + } + } + +#ifdef DEBUG /* Sigh. Some compilers never learn. */ + PCRE_PRINTF(">>>> Match against: "); + pchars(start_match, end_subject - start_match, TRUE, &match_block); + PCRE_PRINTF("\n"); +#endif + + /* If req_char is set, we know that that character must appear in the subject + for the match to succeed. If the first character is set, req_char must be + later in the subject; otherwise the test starts at the match point. This + optimization can save a huge amount of backtracking in patterns with nested + unlimited repeats that aren't going to match. We don't know what the state of + case matching may be when this character is hit, so test for it in both its + cases if necessary. However, the different cased versions will not be set up + unless PCRE_CASELESS was given or the casing state changes within the regex. + Writing separate code makes it go faster, as does using an autoincrement and + backing off on a match. */ + + if (req_char >= 0) + { + register const uschar *p = start_match + ((first_char >= 0)? 1 : 0); + + /* We don't need to repeat the search if we haven't yet reached the + place we found it at last time. */ + + if (p > req_char_ptr) + { + /* Do a single test if no case difference is set up */ + + if (req_char == req_char2) + { + while (p < end_subject) + { + if (*p++ == req_char) { p--; break; } + } + } + + /* Otherwise test for either case */ + + else + { + while (p < end_subject) + { + register int pp = *p++; + if (pp == req_char || pp == req_char2) { p--; break; } + } + } + + /* If we can't find the required character, break the matching loop */ + + if (p >= end_subject) break; + + /* If we have found the required character, save the point where we + found it, so that we don't search again next time round the loop if + the start hasn't passed this character yet. */ + + req_char_ptr = p; + } + } + + /* When a match occurs, substrings will be set for all internal extractions; + we just need to set up the whole thing as substring 0 before returning. If + there were too many extractions, set the return code to zero. In the case + where we had to get some local store to hold offsets for backreferences, copy + those back references that we can. In this case there need not be overflow + if certain parts of the pattern were not used. */ + + match_block.start_match = start_match; + if (!match(start_match, re->code, 2, &match_block, ims, NULL, match_isgroup)) + continue; + + /* Copy the offset information from temporary store if necessary */ + + if (using_temporary_offsets) + { + if (offsetcount >= 4) + { + memcpy(offsets + 2, match_block.offset_vector + 2, + (offsetcount - 2) * sizeof(int)); + DPRINTF(("Copied offsets from temporary memory\n")); + } + if (match_block.end_offset_top > offsetcount) + match_block.offset_overflow = TRUE; + + DPRINTF(("Freeing temporary memory\n")); + (pcre_free)(match_block.offset_vector); + } + + rc = match_block.offset_overflow? 0 : match_block.end_offset_top/2; + + if (offsetcount < 2) rc = 0; else + { + offsets[0] = start_match - match_block.start_subject; + offsets[1] = match_block.end_match_ptr - match_block.start_subject; + } + + DPRINTF((">>>> returning %d\n", rc)); + return rc; + } + +/* This "while" is the end of the "do" above */ + +while (!anchored && + match_block.errorcode == PCRE_ERROR_NOMATCH && + start_match++ < end_subject); + +if (using_temporary_offsets) + { + DPRINTF(("Freeing temporary memory\n")); + (pcre_free)(match_block.offset_vector); + } + +DPRINTF((">>>> returning %d\n", match_block.errorcode)); + +return match_block.errorcode; +} + +/* End of pcre.c */ Index: b/security/apparmor/match/pcre_exec.h =================================================================== --- /dev/null +++ b/security/apparmor/match/pcre_exec.h @@ -0,0 +1,308 @@ +/* + * This is a modified header file containing the definitions from + * pcre.h and internal.h required to support pcre_exec() + */ + + +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* Copyright (c) 1997-2001 University of Cambridge */ + +#ifndef _PCRE_H +#define _PCRE_H + +/* ----- CODE ADDED ---- */ + +#ifdef __KERNEL__ +#include // for kmalloc/kfree +#endif + +#ifdef __KERNEL__ +#define PCRE_PRINTF printk +#define isprint(x) ((unsigned char)(x) >= 128 && (unsigned char)(x) <= 255) +#else +#define PCRE_PRINTF printf +#endif + +/* The value of NEWLINE determines the newline character. The default is to + * leave it up to the compiler, but some sites want to force a particular value. + * On Unix systems, "configure" can be used to override this default. */ + +#ifndef NEWLINE +#define NEWLINE '\n' +#endif + +/* ---- CODE DELETED ---- */ + +/* Options */ + +#define PCRE_CASELESS 0x0001 +#define PCRE_MULTILINE 0x0002 +#define PCRE_DOTALL 0x0004 +#define PCRE_EXTENDED 0x0008 +#define PCRE_ANCHORED 0x0010 +#define PCRE_DOLLAR_ENDONLY 0x0020 +#define PCRE_EXTRA 0x0040 +#define PCRE_NOTBOL 0x0080 +#define PCRE_NOTEOL 0x0100 +#define PCRE_UNGREEDY 0x0200 +#define PCRE_NOTEMPTY 0x0400 +#define PCRE_UTF8 0x0800 + +/* Exec-time and get-time error codes */ + +#define PCRE_ERROR_NOMATCH (-1) +#define PCRE_ERROR_NULL (-2) +#define PCRE_ERROR_BADOPTION (-3) +#define PCRE_ERROR_BADMAGIC (-4) +#define PCRE_ERROR_UNKNOWN_NODE (-5) +#define PCRE_ERROR_NOMEMORY (-6) +#define PCRE_ERROR_NOSUBSTRING (-7) + +/* ---- CODE DELETED ---- */ + +/* Types */ + +struct real_pcre; /* declaration; the definition is private */ +struct real_pcre_extra; /* declaration; the definition is private */ + +typedef struct real_pcre pcre; +typedef struct real_pcre_extra pcre_extra; + +/* ---- CODE DELETED ---- */ + +extern int pcre_exec(const pcre *, const pcre_extra *, + const char *, int, int, int, int *, + int); + +/* ---- CODE ADDED (from internal.h) ---- */ + +/* These are the public options that can change during matching. */ + +#define PCRE_IMS (PCRE_CASELESS|PCRE_MULTILINE|PCRE_DOTALL) + +/* Private options flags start at the most significant end of the four bytes, +but skip the top bit so we can use ints for convenience without getting tangled +with negative values. The public options defined in pcre.h start at the least +significant end. Make sure they don't overlap, though now that we have expanded +to four bytes there is plenty of space. */ + +#define PCRE_FIRSTSET 0x40000000 /* first_char is set */ +#define PCRE_REQCHSET 0x20000000 /* req_char is set */ +#define PCRE_STARTLINE 0x10000000 /* start after \n for multiline */ +#define PCRE_ICHANGED 0x04000000 /* i option changes within regex */ + +/* Options for the "extra" block produced by pcre_study(). */ + +#define PCRE_STUDY_MAPPED 0x01 /* a map of starting chars exists */ + +/* Masks for identifying the public options which are permitted at compile +time, run time or study time, respectively. */ + +#define PUBLIC_EXEC_OPTIONS \ + (PCRE_ANCHORED|PCRE_NOTBOL|PCRE_NOTEOL|PCRE_NOTEMPTY) + +/* Magic number to provide a small check against being handed junk. */ + +#define MAGIC_NUMBER 0x50435245UL /* 'PCRE' */ + +typedef int BOOL; + +#define FALSE 0 +#define TRUE 1 + +/* Opcode table: OP_BRA must be last, as all values >= it are used for brackets +that extract substrings. Starting from 1 (i.e. after OP_END), the values up to +OP_EOD must correspond in order to the list of escapes immediately above. */ + +enum { + OP_END, /* End of pattern */ + + /* Values corresponding to backslashed metacharacters */ + + OP_SOD, /* Start of data: \A */ + OP_NOT_WORD_BOUNDARY, /* \B */ + OP_WORD_BOUNDARY, /* \b */ + OP_NOT_DIGIT, /* \D */ + OP_DIGIT, /* \d */ + OP_NOT_WHITESPACE, /* \S */ + OP_WHITESPACE, /* \s */ + OP_NOT_WORDCHAR, /* \W */ + OP_WORDCHAR, /* \w */ + OP_EODN, /* End of data or \n at end of data: \Z. */ + OP_EOD, /* End of data: \z */ + + OP_OPT, /* Set runtime options */ + OP_CIRC, /* Start of line - varies with multiline switch */ + OP_DOLL, /* End of line - varies with multiline switch */ + OP_ANY, /* Match any character */ + OP_CHARS, /* Match string of characters */ + OP_NOT, /* Match anything but the following char */ + + OP_STAR, /* The maximizing and minimizing versions of */ + OP_MINSTAR, /* all these opcodes must come in pairs, with */ + OP_PLUS, /* the minimizing one second. */ + OP_MINPLUS, /* This first set applies to single characters */ + OP_QUERY, + OP_MINQUERY, + OP_UPTO, /* From 0 to n matches */ + OP_MINUPTO, + OP_EXACT, /* Exactly n matches */ + + OP_NOTSTAR, /* The maximizing and minimizing versions of */ + OP_NOTMINSTAR, /* all these opcodes must come in pairs, with */ + OP_NOTPLUS, /* the minimizing one second. */ + OP_NOTMINPLUS, /* This first set applies to "not" single characters */ + OP_NOTQUERY, + OP_NOTMINQUERY, + OP_NOTUPTO, /* From 0 to n matches */ + OP_NOTMINUPTO, + OP_NOTEXACT, /* Exactly n matches */ + + OP_TYPESTAR, /* The maximizing and minimizing versions of */ + OP_TYPEMINSTAR, /* all these opcodes must come in pairs, with */ + OP_TYPEPLUS, /* the minimizing one second. These codes must */ + OP_TYPEMINPLUS, /* be in exactly the same order as those above. */ + OP_TYPEQUERY, /* This set applies to character types such as \d */ + OP_TYPEMINQUERY, + OP_TYPEUPTO, /* From 0 to n matches */ + OP_TYPEMINUPTO, + OP_TYPEEXACT, /* Exactly n matches */ + + OP_CRSTAR, /* The maximizing and minimizing versions of */ + OP_CRMINSTAR, /* all these opcodes must come in pairs, with */ + OP_CRPLUS, /* the minimizing one second. These codes must */ + OP_CRMINPLUS, /* be in exactly the same order as those above. */ + OP_CRQUERY, /* These are for character classes and back refs */ + OP_CRMINQUERY, + OP_CRRANGE, /* These are different to the three seta above. */ + OP_CRMINRANGE, + + OP_CLASS, /* Match a character class */ + OP_REF, /* Match a back reference */ + OP_RECURSE, /* Match this pattern recursively */ + + OP_ALT, /* Start of alternation */ + OP_KET, /* End of group that doesn't have an unbounded repeat */ + OP_KETRMAX, /* These two must remain together and in this */ + OP_KETRMIN, /* order. They are for groups the repeat for ever. */ + + /* The assertions must come before ONCE and COND */ + + OP_ASSERT, /* Positive lookahead */ + OP_ASSERT_NOT, /* Negative lookahead */ + OP_ASSERTBACK, /* Positive lookbehind */ + OP_ASSERTBACK_NOT, /* Negative lookbehind */ + OP_REVERSE, /* Move pointer back - used in lookbehind assertions */ + + /* ONCE and COND must come after the assertions, with ONCE first, as there's + a test for >= ONCE for a subpattern that isn't an assertion. */ + + OP_ONCE, /* Once matched, don't back up into the subpattern */ + OP_COND, /* Conditional group */ + OP_CREF, /* Used to hold an extraction string number (cond ref) */ + + OP_BRAZERO, /* These two must remain together and in this */ + OP_BRAMINZERO, /* order. */ + + OP_BRANUMBER, /* Used for extracting brackets whose number is greater + than can fit into an opcode. */ + + OP_BRA /* This and greater values are used for brackets that + extract substrings up to a basic limit. After that, + use is made of OP_BRANUMBER. */ +}; + +/* The highest extraction number before we have to start using additional +bytes. (Originally PCRE didn't have support for extraction counts highter than +this number.) The value is limited by the number of opcodes left after OP_BRA, +i.e. 255 - OP_BRA. We actually set it a bit lower to leave room for additional +opcodes. */ + +#define EXTRACT_BASIC_MAX 150 + +/* All character handling must be done as unsigned characters. Otherwise there +are problems with top-bit-set characters and functions such as isspace(). +However, we leave the interface to the outside world as char *, because that +should make things easier for callers. We define a short type for unsigned char +to save lots of typing. I tried "uchar", but it causes problems on Digital +Unix, where it is defined in sys/types, so use "uschar" instead. */ + +typedef unsigned char uschar; + +/* The real format of the start of the pcre block; the actual code vector +runs on as long as necessary after the end. */ + +typedef struct real_pcre { + unsigned long int magic_number; + size_t size; + const unsigned char *tables; + unsigned long int options; + unsigned short int top_bracket; + unsigned short int top_backref; + uschar first_char; + uschar req_char; + uschar code[1]; +} real_pcre; + +/* The real format of the extra block returned by pcre_study(). */ + +typedef struct real_pcre_extra { + uschar options; + uschar start_bits[32]; +} real_pcre_extra; + +/* Structure for passing "static" information around between the functions +doing the matching, so that they are thread-safe. */ + +typedef struct match_data { + int errorcode; /* As it says */ + int *offset_vector; /* Offset vector */ + int offset_end; /* One past the end */ + int offset_max; /* The maximum usable for return data */ + const uschar *lcc; /* Points to lower casing table */ + const uschar *ctypes; /* Points to table of type maps */ + BOOL offset_overflow; /* Set if too many extractions */ + BOOL notbol; /* NOTBOL flag */ + BOOL noteol; /* NOTEOL flag */ + BOOL utf8; /* UTF8 flag */ + BOOL endonly; /* Dollar not before final \n */ + BOOL notempty; /* Empty string match not wanted */ + const uschar *start_pattern; /* For use when recursing */ + const uschar *start_subject; /* Start of the subject string */ + const uschar *end_subject; /* End of the subject string */ + const uschar *start_match; /* Start of this match attempt */ + const uschar *end_match_ptr; /* Subject position at end match */ + int end_offset_top; /* Highwater mark at end of match */ +} match_data; + +/* Bit definitions for entries in the pcre_ctypes table. */ + +#define ctype_space 0x01 +#define ctype_letter 0x02 +#define ctype_digit 0x04 +#define ctype_xdigit 0x08 +#define ctype_word 0x10 /* alphameric or '_' */ +#define ctype_meta 0x80 /* regexp meta char or zero (end pattern) */ + +/* Offsets for the bitmap tables in pcre_cbits. Each table contains a set +of bits for a class map. Some classes are built by combining these tables. */ + +#define cbit_length 320 /* Length of the cbits table */ + +/* Offsets of the various tables from the base tables pointer, and +total length. */ + +#define lcc_offset 0 +#define fcc_offset 256 + +#define fcc_offset 256 +#define cbits_offset 512 +#define ctypes_offset (cbits_offset + cbit_length) + +/* ----- CODE ADDED ---- */ + +#endif // _PCRE_H + /* End of pcre.h */ Index: b/security/apparmor/match/pcre_tables.h =================================================================== --- /dev/null +++ b/security/apparmor/match/pcre_tables.h @@ -0,0 +1,184 @@ + +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* This file is automatically written by the dftables auxiliary +program. If you edit it by hand, you might like to edit the Makefile to +prevent its ever being regenerated. + +This file is #included in the compilation of pcre.c to build the default +character tables which are used when no tables are passed to the compile +function. */ + +static unsigned char pcre_default_tables[] = { + +/* This table is a lower casing table. */ + + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 64, 97, 98, 99,100,101,102,103, + 104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119, + 120,121,122, 91, 92, 93, 94, 95, + 96, 97, 98, 99,100,101,102,103, + 104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119, + 120,121,122,123,124,125,126,127, + 128,129,130,131,132,133,134,135, + 136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151, + 152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167, + 168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183, + 184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199, + 200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215, + 216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231, + 232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247, + 248,249,250,251,252,253,254,255, + +/* This table is a case flipping table. */ + + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 64, 97, 98, 99,100,101,102,103, + 104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119, + 120,121,122, 91, 92, 93, 94, 95, + 96, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90,123,124,125,126,127, + 128,129,130,131,132,133,134,135, + 136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151, + 152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167, + 168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183, + 184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199, + 200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215, + 216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231, + 232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247, + 248,249,250,251,252,253,254,255, + +/* This table contains bit maps for various character classes. +Each map is 32 bytes long and the bits run from the least +significant end of each byte. The classes that have their own +maps are: space, xdigit, digit, upper, lower, word, graph +print, punct, and cntrl. Other classes are built from combinations. */ + + 0x00,0x3e,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03, + 0x7e,0x00,0x00,0x00,0x7e,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfe,0xff,0xff,0x07,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03, + 0xfe,0xff,0xff,0x87,0xfe,0xff,0xff,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0xfe,0xff,0x00,0xfc, + 0x01,0x00,0x00,0xf8,0x01,0x00,0x00,0x78, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + +/* This table identifies various classes of character by individual bits: + 0x01 white space character + 0x02 letter + 0x04 decimal digit + 0x08 hexadecimal digit + 0x10 alphanumeric or '_' + 0x80 regular expression metacharacter or binary zero +*/ + + 0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 0- 7 */ + 0x00,0x01,0x01,0x01,0x01,0x01,0x00,0x00, /* 8- 15 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 16- 23 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 24- 31 */ + 0x01,0x00,0x00,0x00,0x80,0x00,0x00,0x00, /* - ' */ + 0x80,0x80,0x80,0x80,0x00,0x00,0x80,0x00, /* ( - / */ + 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c, /* 0 - 7 */ + 0x1c,0x1c,0x00,0x00,0x00,0x00,0x00,0x80, /* 8 - ? */ + 0x00,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* @ - G */ + 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* H - O */ + 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* P - W */ + 0x12,0x12,0x12,0x80,0x00,0x00,0x80,0x10, /* X - _ */ + 0x00,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* ` - g */ + 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* h - o */ + 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* p - w */ + 0x12,0x12,0x12,0x80,0x80,0x00,0x00,0x00, /* x -127 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 128-135 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 136-143 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 144-151 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 152-159 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 160-167 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 168-175 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 176-183 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 184-191 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 192-199 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 200-207 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 208-215 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 216-223 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 224-231 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 232-239 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 240-247 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};/* 248-255 */ + +/* End of chartables.c */ Index: b/security/apparmor/module_interface.c =================================================================== --- /dev/null +++ b/security/apparmor/module_interface.c @@ -0,0 +1,845 @@ +/* + * Copyright (C) 1998-2005 Novell/SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * AppArmor userspace policy interface + */ + +#include + +#include "apparmor.h" +#include "inline.h" +#include "module_interface.h" +#include "match/match.h" + +/* aa_code defined in module_interface.h */ + +const int aacode_datasize[] = { 1, 2, 4, 8, 2, 2, 4, 0, 0, 0, 0, 0, 0 }; + +struct aa_taskreplace_data { + struct aaprofile *old_profile; + struct aaprofile *new_profile; +}; + +/* inlines must be forward of there use in newer version of gcc, + just forward declaring with a prototype won't work anymore */ + +static inline void free_aa_entry(struct aa_entry *entry) +{ + if (entry) { + kfree(entry->filename); + aamatch_free(entry->extradata); + kfree(entry); + } +} + +/** + * alloc_aa_entry - create new empty aa_entry + * This routine allocates, initializes, and returns a new aa_entry + * file entry structure. Structure is zeroed. Returns new structure on + * success, %NULL on failure. + */ +static inline struct aa_entry *alloc_aa_entry(void) +{ + struct aa_entry *entry; + + AA_DEBUG("%s\n", __FUNCTION__); + entry = kzalloc(sizeof(struct aa_entry), GFP_KERNEL); + if (entry) { + int i; + INIT_LIST_HEAD(&entry->list); + for (i = 0; i <= POS_AA_FILE_MAX; i++) { + INIT_LIST_HEAD(&entry->listp[i]); + } + } + return entry; +} + +/** + * free_aaprofile_rcu - rcu callback for free profiles + * @head: rcu_head struct of the profile whose reference is being put. + * + * the rcu callback routine, which delays the freeing of a profile when + * its last reference is put. + */ +static void free_aaprofile_rcu(struct rcu_head *head) +{ + struct aaprofile *p = container_of(head, struct aaprofile, rcu); + free_aaprofile(p); +} + +/** + * task_remove - remove profile from a task's subdomain + * @sd: task's subdomain + * + * remove the active profile from a task's subdomain, switching the task + * to an unconfined state. + */ +static inline void task_remove(struct subdomain *sd) +{ + /* spin_lock(&sd_lock) held here */ + AA_DEBUG("%s: removing profile from task %s(%d) profile %s active %s\n", + __FUNCTION__, + sd->task->comm, + sd->task->pid, + BASE_PROFILE(sd->active)->name, + sd->active->name); + + aa_switch_unconfined(sd); +} + +/** taskremove_iter - Iterator to unconfine subdomains which match cookie + * @sd: subdomain to consider for profile removal + * @cookie: pointer to the oldprofile which is being removed + * + * If the subdomain's active profile matches old_profile, then call + * task_remove() to remove the profile leaving the task (subdomain) unconfined. + */ +static int taskremove_iter(struct subdomain *sd, void *cookie) +{ + struct aaprofile *old_profile = (struct aaprofile *)cookie; + unsigned long flags; + + spin_lock_irqsave(&sd_lock, flags); + + if (__aa_is_confined(sd) && BASE_PROFILE(sd->active) == old_profile) { + task_remove(sd); + } + + spin_unlock_irqrestore(&sd_lock, flags); + + return 0; +} + +/** task_replace - replace subdomain's current profile with a new profile + * @sd: subdomain to replace the profile on + * @new: new profile + * + * Replace a task's (subdomain's) active profile with a new profile. If + * task was in a hat then the new profile will also be in the equivalent + * hat in the new profile if it exists. If it doesn't exist the + * task will be placed in the special null_profile state. + */ +static inline void task_replace(struct subdomain *sd, struct aaprofile *new) +{ + AA_DEBUG("%s: replacing profile for task %s(%d) " + "profile=%s (%p) active=%s (%p)\n", + __FUNCTION__, + sd->task->comm, sd->task->pid, + BASE_PROFILE(sd->active)->name, BASE_PROFILE(sd->active), + sd->active->name, sd->active); + + if (!sd->active) + goto out; + + if (IN_SUBPROFILE(sd->active)) { + struct aaprofile *nactive; + + /* The old profile was in a hat, check to see if the new + * profile has an equivalent hat */ + nactive = __aa_find_profile(sd->active->name, &new->sub); + + if (!nactive) + nactive = get_aaprofile(new->null_profile); + + aa_switch(sd, nactive); + put_aaprofile(nactive); + } else { + aa_switch(sd, new); + } + + out: + return; +} + +/** taskreplace_iter - Iterator to replace a subdomain's profile + * @sd: subdomain to consider for profile replacement + * @cookie: pointer to the old profile which is being replaced. + * + * If the subdomain's active profile matches old_profile call + * task_replace() to replace with the subdomain's active profile with + * the new profile. + */ +static int taskreplace_iter(struct subdomain *sd, void *cookie) +{ + struct aa_taskreplace_data *data = (struct aa_taskreplace_data *)cookie; + unsigned long flags; + + spin_lock_irqsave(&sd_lock, flags); + + if (__aa_is_confined(sd) && + BASE_PROFILE(sd->active) == data->old_profile) + task_replace(sd, data->new_profile); + + spin_unlock_irqrestore(&sd_lock, flags); + + return 0; +} + +static inline int aa_inbounds(struct aa_ext *e, size_t size) +{ + return (e->pos + size <= e->end); +} + +/** + * aaconvert - convert trailing values of serialized type codes + * @code: type code + * @dest: pointer to object to receive the converted value + * @src: pointer to value to convert + * + * for serialized type codes which have a trailing value, convert it + * and place it in @dest. If a code does not have a trailing value nop. + */ +static void aaconvert(enum aa_code code, void *dest, void *src) +{ + switch (code) { + case AA_U8: + *(u8 *)dest = *(u8 *) src; + break; + case AA_U16: + case AA_NAME: + case AA_DYN_STRING: + *(u16 *)dest = le16_to_cpu(get_unaligned((u16 *)src)); + break; + case AA_U32: + case AA_STATIC_BLOB: + *(u32 *)dest = le32_to_cpu(get_unaligned((u32 *)src)); + break; + case AA_U64: + *(u64 *)dest = le64_to_cpu(get_unaligned((u64 *)src)); + break; + default: + /* nop - all other type codes do not have a trailing value */ + ; + } +} + +/** + * aa_is_X - check if the next element is of type X + * @e: serialized data extent information + * @code: type code + * @data: object located at @e->pos (of type @code) is written into @data + * if @data is non-null. if data is null it means skip this + * entry + * check to see if the next element in the serialized data stream is of type + * X and check that it is with in bounds, if so put the associated value in + * @data. + * return the size of bytes associated with the returned data + * for complex object like blob and string a pointer to the allocated + * data is returned in data, but the size of the blob or string is + * returned. + */ +static u32 aa_is_X(struct aa_ext *e, enum aa_code code, void *data) +{ + void *pos = e->pos; + int ret = 0; + if (!aa_inbounds(e, AA_CODE_BYTE + aacode_datasize[code])) + goto fail; + if (code != *(u8 *)e->pos) + goto out; + e->pos += AA_CODE_BYTE; + if (code == AA_NAME) { + u16 size; + /* name codes are followed by X bytes */ + size = le16_to_cpu(get_unaligned((u16 *)e->pos)); + if (!aa_inbounds(e, (size_t) size)) + goto fail; + if (data) + *(u16 *)data = size; + e->pos += aacode_datasize[code]; + ret = 1 + aacode_datasize[code]; + } else if (code == AA_DYN_STRING) { + u16 size; + char *str; + /* strings codes are followed by X bytes */ + size = le16_to_cpu(get_unaligned((u16 *)e->pos)); + e->pos += aacode_datasize[code]; + if (!aa_inbounds(e, (size_t) size)) + goto fail; + if (data) { + * (char **)data = NULL; + str = kmalloc(size, GFP_KERNEL); + if (!str) + goto fail; + memcpy(str, e->pos, (size_t) size); + str[size-1] = '\0'; + * (char **)data = str; + } + e->pos += size; + ret = size; + } else if (code == AA_STATIC_BLOB) { + u32 size; + /* blobs are followed by X bytes, that can be 2^32 */ + size = le32_to_cpu(get_unaligned((u32 *)e->pos)); + e->pos += aacode_datasize[code]; + if (!aa_inbounds(e, (size_t) size)) + goto fail; + if (data) + memcpy(data, e->pos, (size_t) size); + e->pos += size; + ret = size; + } else { + if (data) + aaconvert(code, data, e->pos); + e->pos += aacode_datasize[code]; + ret = 1 + aacode_datasize[code]; + } +out: + return ret; +fail: + e->pos = pos; + return 0; +} + +/** + * aa_is_nameX - check is the next element is of type X with a name of @name + * @e: serialized data extent information + * @code: type code + * @data: location to store deserialized data if match isX criteria + * @name: name to match to the serialized element. + * + * check that the next serialized data element is of type X and has a tag + * name @name. If the code matches and name (if specified) matches then + * the packed data is unpacked into *data. (Note for strings this is the + * size, and the next data in the stream is the string data) + * returns %0 if either match failes + */ +static int aa_is_nameX(struct aa_ext *e, enum aa_code code, void *data, + const char *name) +{ + void *pos = e->pos; + u16 size; + u32 ret; + /* check for presence of a tagname, and if present name size + * AA_NAME tag value is a u16 */ + if (aa_is_X(e, AA_NAME, &size)) { + /* if a name is specified it must match. otherwise skip tag */ + if (name && ((strlen(name) != size-1) || + strncmp(name, (char *)e->pos, (size_t)size-1))) + goto fail; + e->pos += size; + } else if (name) { + goto fail; + } + + /* now check if data actually matches */ + ret = aa_is_X(e, code, data); + if (!ret) + goto fail; + return ret; + +fail: + e->pos = pos; + return 0; +} + +/* macro to wrap error case to make a block of reads look nicer */ +#define AA_READ_X(E, C, D, N) \ + do { \ + u32 __ret; \ + __ret = aa_is_nameX((E), (C), (D), (N)); \ + if (!__ret) \ + goto fail; \ + } while (0) + +/** + * aa_activate_net_entry - unpacked serialized net entries + * @e: serialized data extent information + * + * Ignore/skips net entries if they are present in the serialized data + * stream. Network confinement rules are currently unsupported but some + * user side tools can generate them so they are currently ignored. + */ +static inline int aa_activate_net_entry(struct aa_ext *e) +{ + AA_READ_X(e, AA_STRUCT, NULL, "ne"); + AA_READ_X(e, AA_U32, NULL, NULL); + AA_READ_X(e, AA_U32, NULL, NULL); + AA_READ_X(e, AA_U32, NULL, NULL); + AA_READ_X(e, AA_U16, NULL, NULL); + AA_READ_X(e, AA_U16, NULL, NULL); + AA_READ_X(e, AA_U32, NULL, NULL); + AA_READ_X(e, AA_U32, NULL, NULL); + AA_READ_X(e, AA_U16, NULL, NULL); + AA_READ_X(e, AA_U16, NULL, NULL); + /* interface name is optional so just ignore return code */ + aa_is_nameX(e, AA_DYN_STRING, NULL, NULL); + AA_READ_X(e, AA_STRUCTEND, NULL, NULL); + + return 1; +fail: + return 0; +} + +/** + * aa_activate_file_entry - unpack serialized file entry + * @e: serialized data extent information + * + * unpack the information used for a file ACL entry. + */ +static inline struct aa_entry *aa_activate_file_entry(struct aa_ext *e) +{ + struct aa_entry *entry = NULL; + + if (!(entry = alloc_aa_entry())) + goto fail; + + AA_READ_X(e, AA_STRUCT, NULL, "fe"); + AA_READ_X(e, AA_DYN_STRING, &entry->filename, NULL); + AA_READ_X(e, AA_U32, &entry->mode, NULL); + AA_READ_X(e, AA_U32, &entry->type, NULL); + + entry->extradata = aamatch_alloc(entry->type); + if (IS_ERR(entry->extradata)) { + entry->extradata = NULL; + goto fail; + } + + if (entry->extradata && + aamatch_serialize(entry->extradata, e, aa_is_nameX) != 0) { + goto fail; + } + AA_READ_X(e, AA_STRUCTEND, NULL, NULL); + + switch (entry->type) { + case aa_entry_literal: + AA_DEBUG("%s: %s [no pattern] mode=0x%x\n", + __FUNCTION__, + entry->filename, + entry->mode); + break; + case aa_entry_tailglob: + AA_DEBUG("%s: %s [tailglob] mode=0x%x\n", + __FUNCTION__, + entry->filename, + entry->mode); + break; + case aa_entry_pattern: + AA_DEBUG("%s: %s mode=0x%x\n", + __FUNCTION__, + entry->filename, + entry->mode); + break; + default: + AA_WARN("%s: INVALID entry_match_type %d\n", + __FUNCTION__, + (int)entry->type); + goto fail; + } + + return entry; + +fail: + free_aa_entry(entry); + return NULL; +} + +/** + * check_rule_and_add - check a file rule is valid and add to a profile + * @file_entry: file rule to add + * @profile: profile to add the rule to + * @message: error message returned if the addition failes. + * + * perform consistency check to ensure that a file rule entry is valid. + * If the rule is valid it is added to the profile. + */ +static inline int check_rule_and_add(struct aa_entry *file_entry, + struct aaprofile *profile, + const char **message) +{ + /* verify consistency of x, px, ix, ux for entry against + possible duplicates for this entry */ + int mode = AA_EXEC_MODIFIER_MASK(file_entry->mode); + int i; + + if (mode && !(AA_MAY_EXEC & file_entry->mode)) { + *message = "inconsistent rule, x modifiers without x"; + goto out; + } + + /* check that only 1 of the modifiers is set */ + if (mode && (mode & (mode - 1))) { + *message = "inconsistent rule, multiple x modifiers"; + goto out; + } + + /* ix -> m (required so that target exec binary may map itself) */ + if (mode & AA_EXEC_INHERIT) + file_entry->mode |= AA_EXEC_MMAP; + + list_add(&file_entry->list, &profile->file_entry); + profile->num_file_entries++; + + mode = file_entry->mode; + + /* Handle partitioned lists + * Chain entries onto sublists based on individual + * permission bits. This allows more rapid searching. + */ + for (i = 0; i <= POS_AA_FILE_MAX; i++) { + if (mode & (1 << i)) + /* profile->file_entryp[i] initially set to + * NULL in alloc_aaprofile() */ + list_add(&file_entry->listp[i], + &profile->file_entryp[i]); + } + + return 1; + +out: + free_aa_entry(file_entry); + return 0; +} + +#define AA_ENTRY_LIST(NAME) \ + do { \ + if (aa_is_nameX(e, AA_LIST, NULL, (NAME))) { \ + rulename = ""; \ + error_string = "Invalid file entry"; \ + while (!aa_is_nameX(e, AA_LISTEND, NULL, NULL)) { \ + struct aa_entry *file_entry; \ + file_entry = aa_activate_file_entry(e); \ + if (!file_entry) \ + goto fail; \ + if (!check_rule_and_add(file_entry, profile, \ + &error_string)) { \ + rulename = file_entry->filename; \ + goto fail; \ + } \ + } \ + } \ + } while (0) + +/** + * aa_activate_profile - unpack a serialized profile + * @e: serialized data extent information + * @error: error code returned if unpacking fails + */ +static struct aaprofile *aa_activate_profile(struct aa_ext *e, ssize_t *error) +{ + struct aaprofile *profile = NULL; + const char *rulename = ""; + const char *error_string = "Invalid Profile"; + + *error = -EPROTO; + + profile = alloc_aaprofile(); + if (!profile) { + error_string = "Could not allocate profile"; + *error = -ENOMEM; + goto fail; + } + + /* check that we have the right struct being passed */ + AA_READ_X(e, AA_STRUCT, NULL, "profile"); + AA_READ_X(e, AA_DYN_STRING, &profile->name, NULL); + + error_string = "Invalid flags"; + /* per profile debug flags (debug, complain, audit) */ + AA_READ_X(e, AA_STRUCT, NULL, "flags"); + AA_READ_X(e, AA_U32, &(profile->flags.debug), NULL); + AA_READ_X(e, AA_U32, &(profile->flags.complain), NULL); + AA_READ_X(e, AA_U32, &(profile->flags.audit), NULL); + AA_READ_X(e, AA_STRUCTEND, NULL, NULL); + + error_string = "Invalid capabilities"; + AA_READ_X(e, AA_U32, &(profile->capabilities), NULL); + + /* get the file entries. */ + AA_ENTRY_LIST("pgent"); /* pcre rules */ + AA_ENTRY_LIST("sgent"); /* simple globs */ + AA_ENTRY_LIST("fent"); /* regular file entries */ + + /* get the net entries */ + if (aa_is_nameX(e, AA_LIST, NULL, "net")) { + error_string = "Invalid net entry"; + while (!aa_is_nameX(e, AA_LISTEND, NULL, NULL)) { + if (!aa_activate_net_entry(e)) + goto fail; + } + } + rulename = ""; + + /* get subprofiles */ + if (aa_is_nameX(e, AA_LIST, NULL, "hats")) { + error_string = "Invalid profile hat"; + while (!aa_is_nameX(e, AA_LISTEND, NULL, NULL)) { + struct aaprofile *subprofile; + subprofile = aa_activate_profile(e, error); + if (!subprofile) + goto fail; + subprofile->parent = profile; + list_add(&subprofile->list, &profile->sub); + } + } + + error_string = "Invalid end of profile"; + AA_READ_X(e, AA_STRUCTEND, NULL, NULL); + + return profile; + +fail: + AA_WARN("%s: %s %s in profile %s\n", INTERFACE_ID, rulename, + error_string, profile && profile->name ? profile->name + : "unknown"); + + if (profile) { + free_aaprofile(profile); + profile = NULL; + } + + return NULL; +} + +/** + * aa_activate_top_profile - unpack a serialized base profile + * @e: serialized data extent information + * @error: error code returned if unpacking fails + * + * check interface version unpack a profile and all its hats and patch + * in any extra information that the profile needs. + */ +static void *aa_activate_top_profile(struct aa_ext *e, ssize_t *error) +{ + struct aaprofile *profile = NULL; + + /* get the interface version */ + if (!aa_is_nameX(e, AA_U32, &e->version, "version")) { + AA_WARN("%s: version missing\n", INTERFACE_ID); + *error = -EPROTONOSUPPORT; + goto fail; + } + + /* check that the interface version is currently supported */ + if (e->version != 2) { + AA_WARN("%s: unsupported interface version (%d)\n", + INTERFACE_ID, e->version); + *error = -EPROTONOSUPPORT; + goto fail; + } + + profile = aa_activate_profile(e, error); + if (!profile) + goto fail; + + if (!list_empty(&profile->sub) || profile->flags.complain) { + if (attach_nullprofile(profile)) + goto fail; + } + return profile; + +fail: + free_aaprofile(profile); + return NULL; +} + +/** + * aa_file_prof_add - add a new profile to the profile list + * @data: serialized data stream + * @size: size of the serialized data stream + * + * unpack and add a profile to the profile list. Return %0 or error + */ +ssize_t aa_file_prof_add(void *data, size_t size) +{ + struct aaprofile *profile = NULL; + + struct aa_ext e = { + .start = data, + .end = data + size, + .pos = data + }; + ssize_t error; + + profile = aa_activate_top_profile(&e, &error); + if (!profile) { + AA_DEBUG("couldn't activate profile\n"); + goto out; + } + + /* aa_activate_top_profile allocates profile with initial 1 count + * aa_profilelist_add transfers that ref to profile list without + * further incrementing + */ + if (aa_profilelist_add(profile)) { + error = size; + } else { + AA_WARN("trying to add profile (%s) that already exists.\n", + profile->name); + put_aaprofile(profile); + error = -EEXIST; + } + +out: + return error; +} + +/** + * aa_file_prof_repl - replace a profile on the profile list + * @udata: serialized data stream + * @size: size of the serialized data stream + * + * unpack and replace a profile on the profile list and uses of that profile + * by any subdomain. If the profile does not exist on the profile list + * it is added. Return %0 or error. + */ +ssize_t aa_file_prof_repl(void *udata, size_t size) +{ + struct aa_taskreplace_data data; + struct aa_ext e = { + .start = udata, + .end = udata + size, + .pos = udata + }; + + ssize_t error; + + data.new_profile = aa_activate_top_profile(&e, &error); + if (!data.new_profile) { + AA_DEBUG("couldn't activate profile\n"); + goto out; + } + + /* Refcount on data.new_profile is 1 (aa_activate_top_profile). + * + * This reference will be inherited by aa_profilelist_replace for it's + * profile list reference but this isn't sufficient. + * + * Another replace (*for-same-profile*) may race us here. + * Task A calls aa_profilelist_replace(new_profile) and is interrupted. + * Task B old_profile = aa_profilelist_replace() will return task A's + * new_profile with the count of 1. If task B proceeeds to put this + * profile it will dissapear from under task A. + * + * Grab extra reference on new_profile to prevent this + */ + + get_aaprofile(data.new_profile); + + data.old_profile = aa_profilelist_replace(data.new_profile); + + /* If there was an old profile, find all currently executing tasks + * using this profile and replace the old profile with the new. + */ + if (data.old_profile) { + AA_DEBUG("%s: try to replace profile (%p)%s\n", + __FUNCTION__, + data.old_profile, + data.old_profile->name); + + aa_subdomainlist_iterate(taskreplace_iter, (void *)&data); + + /* it's off global list, and we are done replacing */ + put_aaprofile(data.old_profile); + } + + /* release extra reference obtained above (race) */ + put_aaprofile(data.new_profile); + + error = size; + +out: + return error; +} + +/** + * aa_file_prof_remove - remove a profile from the system + * @name: name of the profile to remove + * @size: size of the name + * + * remove a profile from the profile list and all subdomain references + * to said profile. Return %0 on success, else error. + */ +ssize_t aa_file_prof_remove(const char *name, size_t size) +{ + struct aaprofile *old_profile; + + /* if the old profile exists it will be removed from the list and + * a reference is returned. + */ + old_profile = aa_profilelist_remove(name); + + if (old_profile) { + /* remove profile from any tasks using it */ + aa_subdomainlist_iterate(taskremove_iter, (void *)old_profile); + + /* drop reference obtained by aa_profilelist_remove */ + put_aaprofile(old_profile); + } else { + AA_WARN("%s: trying to remove profile (%s) that " + "doesn't exist - skipping.\n", __FUNCTION__, name); + return -ENOENT; + } + + return size; +} + +/** + * free_aaprofile_kref - free aaprofile by kref (called by put_aaprofile) + * @kr: kref callback for freeing of a profile + */ +void free_aaprofile_kref(struct kref *kr) +{ + struct aaprofile *p=container_of(kr, struct aaprofile, count); + + call_rcu(&p->rcu, free_aaprofile_rcu); +} + +/** + * free_aaprofile - free aaprofile structure + * @profile: the profile to free + * + * free a profile, its file entries hats and null_profile. All references + * to the profile, its hats and null_profile must have been put. + * If the profile was referenced by a subdomain free_aaprofile should be + * called from an rcu callback routine. + */ +void free_aaprofile(struct aaprofile *profile) +{ + struct aa_entry *ent, *tmp; + struct aaprofile *p, *ptmp; + + AA_DEBUG("%s(%p)\n", __FUNCTION__, profile); + + if (!profile) + return; + + /* profile is still on global profile list -- invalid */ + if (!list_empty(&profile->list)) { + AA_ERROR("%s: internal error, " + "profile '%s' still on global list\n", + __FUNCTION__, + profile->name); + BUG(); + } + + list_for_each_entry_safe(ent, tmp, &profile->file_entry, list) { + if (ent->filename) + AA_DEBUG("freeing aa_entry: %p %s\n", + ent->filename, ent->filename); + list_del_init(&ent->list); + free_aa_entry(ent); + } + + /* use free_aaprofile instead of put_aaprofile to destroy the + * null_profile, because the null_profile use the same reference + * counting as hats, ie. the count goes to the base profile. + */ + free_aaprofile(profile->null_profile); + list_for_each_entry_safe(p, ptmp, &profile->sub, list) { + list_del_init(&p->list); + p->parent = NULL; + put_aaprofile(p); + } + + if (profile->name) { + AA_DEBUG("%s: %s\n", __FUNCTION__, profile->name); + kfree(profile->name); + } + + kfree(profile); +} Index: b/security/apparmor/module_interface.h =================================================================== --- /dev/null +++ b/security/apparmor/module_interface.h @@ -0,0 +1,37 @@ +#ifndef __MODULEINTERFACE_H +#define __MODULEINTERFACE_H + +/* Codes of the types of basic structures that are understood */ +#define AA_CODE_BYTE (sizeof(u8)) +#define INTERFACE_ID "INTERFACE" + +#define APPARMOR_INTERFACE_VERSION 2 + +enum aa_code { + AA_U8, + AA_U16, + AA_U32, + AA_U64, + AA_NAME, /* same as string except it is items name */ + AA_DYN_STRING, + AA_STATIC_BLOB, + AA_STRUCT, + AA_STRUCTEND, + AA_LIST, + AA_LISTEND, + AA_OFFSET, + AA_BAD +}; + +/* aa_ext tracks the kernel buffer and read position in it. The interface + * data is copied into a kernel buffer in apparmorfs and then handed off to + * the activate routines. + */ +struct aa_ext { + void *start; + void *end; + void *pos; /* pointer to current position in the buffer */ + u32 version; +}; + +#endif /* __MODULEINTERFACE_H */ Index: b/security/apparmor/procattr.c =================================================================== --- /dev/null +++ b/security/apparmor/procattr.c @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2005 Novell/SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * AppArmor /proc/pid/attr handling + */ + +/* for isspace */ +#include + +#include "apparmor.h" +#include "inline.h" + +size_t aa_getprocattr(struct aaprofile *active, char *str, size_t size) +{ + int error = -EACCES; /* default to a perm denied */ + size_t len; + + if (active) { + size_t lena, lenm, lenp = 0; + const char *enforce_str = " (enforce)"; + const char *complain_str = " (complain)"; + const char *mode_str = + PROFILE_COMPLAIN(active) ? complain_str : enforce_str; + + lenm = strlen(mode_str); + + lena = strlen(active->name); + + len = lena; + if (IN_SUBPROFILE(active)) { + lenp = strlen(BASE_PROFILE(active)->name); + len += (lenp + 1); /* +1 for ^ */ + } + /* DONT null terminate strings we output via proc */ + len += (lenm + 1); /* for \n */ + + if (len <= size) { + if (lenp) { + memcpy(str, BASE_PROFILE(active)->name, + lenp); + str += lenp; + *str++ = '^'; + } + + memcpy(str, active->name, lena); + str += lena; + memcpy(str, mode_str, lenm); + str += lenm; + *str++ = '\n'; + error = len; + } else if (size == 0) { + error = len; + } else { + error = -ERANGE; + } + } else { + const char *unconstrained_str = "unconstrained\n"; + len = strlen(unconstrained_str); + + /* DONT null terminate strings we output via proc */ + if (len <= size) { + memcpy(str, unconstrained_str, len); + error = len; + } else if (size == 0) { + error = len; + } else { + error = -ERANGE; + } + } + + return error; + +} + +int aa_setprocattr_changehat(char *hatinfo, size_t infosize) +{ + int error = -EINVAL; + char *token = NULL, *hat, *smagic, *tmp; + u32 magic; + int rc, len, consumed; + unsigned long flags; + + AA_DEBUG("%s: %p %zd\n", __FUNCTION__, hatinfo, infosize); + + /* strip leading white space */ + while (infosize && isspace(*hatinfo)) { + hatinfo++; + infosize--; + } + + if (infosize == 0) + goto out; + + /* + * Copy string to a new buffer so we can play with it + * It may be zero terminated but we add a trailing 0 + * for 100% safety + */ + token = kmalloc(infosize + 1, GFP_KERNEL); + + if (!token) { + error = -ENOMEM; + goto out; + } + + memcpy(token, hatinfo, infosize); + token[infosize] = 0; + + /* error is INVAL until we have at least parsed something */ + error = -EINVAL; + + tmp = token; + while (*tmp && *tmp != '^') { + tmp++; + } + + if (!*tmp || tmp == token) { + AA_WARN("%s: Invalid input '%s'\n", __FUNCTION__, token); + goto out; + } + + /* split magic and hat into two strings */ + *tmp = 0; + smagic = token; + + /* + * Initially set consumed=strlen(magic), as if sscanf + * consumes all input via the %x it will not process the %n + * directive. Otherwise, if sscanf does not consume all the + * input it will process the %n and update consumed. + */ + consumed = len = strlen(smagic); + + rc = sscanf(smagic, "%x%n", &magic, &consumed); + + if (rc != 1 || consumed != len) { + AA_WARN("%s: Invalid hex magic %s\n", + __FUNCTION__, + smagic); + goto out; + } + + hat = tmp + 1; + + if (!*hat) + hat = NULL; + + if (!hat && !magic) { + AA_WARN("%s: Invalid input, NULL hat and NULL magic\n", + __FUNCTION__); + goto out; + } + + AA_DEBUG("%s: Magic 0x%x Hat '%s'\n", + __FUNCTION__, magic, hat ? hat : NULL); + + spin_lock_irqsave(&sd_lock, flags); + error = aa_change_hat(hat, magic); + spin_unlock_irqrestore(&sd_lock, flags); + +out: + if (token) { + memset(token, 0, infosize); + kfree(token); + } + + return error; +} + +int aa_setprocattr_setprofile(struct task_struct *p, char *profilename, + size_t profilesize) +{ + int error = -EINVAL; + struct aaprofile *profile = NULL; + struct subdomain *sd; + char *name = NULL; + unsigned long flags; + + AA_DEBUG("%s: current %s(%d)\n", + __FUNCTION__, current->comm, current->pid); + + /* strip leading white space */ + while (profilesize && isspace(*profilename)) { + profilename++; + profilesize--; + } + + if (profilesize == 0) + goto out; + + /* + * Copy string to a new buffer so we guarantee it is zero + * terminated + */ + name = kmalloc(profilesize + 1, GFP_KERNEL); + + if (!name) { + error = -ENOMEM; + goto out; + } + + strncpy(name, profilename, profilesize); + name[profilesize] = 0; + + repeat: + if (strcmp(name, "unconstrained") != 0) { + profile = aa_profilelist_find(name); + if (!profile) { + AA_WARN("%s: Unable to switch task %s(%d) to profile" + "'%s'. No such profile.\n", + __FUNCTION__, + p->comm, p->pid, + name); + + error = -EINVAL; + goto out; + } + } + + spin_lock_irqsave(&sd_lock, flags); + + sd = AA_SUBDOMAIN(p->security); + + /* switch to unconstrained */ + if (!profile) { + if (__aa_is_confined(sd)) { + AA_WARN("%s: Unconstraining task %s(%d) " + "profile %s active %s\n", + __FUNCTION__, + p->comm, p->pid, + BASE_PROFILE(sd->active)->name, + sd->active->name); + + aa_switch_unconfined(sd); + } else { + AA_WARN("%s: task %s(%d) " + "is already unconstrained\n", + __FUNCTION__, p->comm, p->pid); + } + } else { + if (!sd) { + /* this task was created before module was + * loaded, allocate a subdomain + */ + AA_WARN("%s: task %s(%d) has no subdomain\n", + __FUNCTION__, p->comm, p->pid); + + /* unlock so we can safely GFP_KERNEL */ + spin_unlock_irqrestore(&sd_lock, flags); + + sd = alloc_subdomain(p); + if (!sd) { + AA_WARN("%s: Unable to allocate subdomain for " + "task %s(%d). Cannot confine task to " + "profile %s\n", + __FUNCTION__, + p->comm, p->pid, + name); + + error = -ENOMEM; + put_aaprofile(profile); + + goto out; + } + + spin_lock_irqsave(&sd_lock, flags); + if (!AA_SUBDOMAIN(p->security)) { + p->security = sd; + } else { /* race */ + free_subdomain(sd); + sd = AA_SUBDOMAIN(p->security); + } + } + + /* ensure the profile hasn't been replaced */ + + if (unlikely(profile->isstale)) { + WARN_ON(profile == null_complain_profile); + + /* drop refcnt obtained from earlier get_aaprofile */ + put_aaprofile(profile); + profile = aa_profilelist_find(name); + + if (!profile) { + /* Race, profile was removed. */ + spin_unlock_irqrestore(&sd_lock, flags); + goto repeat; + } + } + + /* we do not do a normal task replace since we are not + * replacing with the same profile. + * If existing process is in a hat, it will be moved + * into the new parent profile, even if this new + * profile has a identical named hat. + */ + + AA_WARN("%s: Switching task %s(%d) " + "profile %s active %s to new profile %s\n", + __FUNCTION__, + p->comm, p->pid, + sd->active ? BASE_PROFILE(sd->active)->name : + "unconstrained", + sd->active ? sd->active->name : "unconstrained", + name); + + aa_switch(sd, profile); + + put_aaprofile(profile); /* drop ref we obtained above + * from aa_profilelist_find + */ + + /* Reset magic in case we were in a subhat before + * This is the only case where we zero the magic after + * calling aa_switch + */ + sd->hat_magic = 0; + } + + spin_unlock_irqrestore(&sd_lock, flags); + + error = 0; +out: + kfree(name); + + return error; +} Index: b/security/apparmor/shared.h =================================================================== --- /dev/null +++ b/security/apparmor/shared.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2000, 2001, 2004, 2005 Novell/SUSE + * + * Immunix AppArmor LSM + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef _SHARED_H +#define _SHARED_H + +/* start of system offsets */ +#define POS_AA_FILE_MIN 0 +#define POS_AA_MAY_EXEC POS_AA_FILE_MIN +#define POS_AA_MAY_WRITE (POS_AA_MAY_EXEC + 1) +#define POS_AA_MAY_READ (POS_AA_MAY_WRITE + 1) +#define POS_AA_MAY_APPEND (POS_AA_MAY_READ + 1) +/* end of system offsets */ + +#define POS_AA_MAY_LINK (POS_AA_MAY_APPEND + 1) +#define POS_AA_EXEC_INHERIT (POS_AA_MAY_LINK + 1) +#define POS_AA_EXEC_UNCONSTRAINED (POS_AA_EXEC_INHERIT + 1) +#define POS_AA_EXEC_PROFILE (POS_AA_EXEC_UNCONSTRAINED + 1) +#define POS_AA_EXEC_MMAP (POS_AA_EXEC_PROFILE + 1) +#define POS_AA_EXEC_UNSAFE (POS_AA_EXEC_MMAP + 1) +#define POS_AA_FILE_MAX POS_AA_EXEC_UNSAFE + +/* Modeled after MAY_READ, MAY_WRITE, MAY_EXEC def'ns */ +#define AA_MAY_EXEC (0x01 << POS_AA_MAY_EXEC) +#define AA_MAY_WRITE (0x01 << POS_AA_MAY_WRITE) +#define AA_MAY_READ (0x01 << POS_AA_MAY_READ) +#define AA_MAY_LINK (0x01 << POS_AA_MAY_LINK) +#define AA_EXEC_INHERIT (0x01 << POS_AA_EXEC_INHERIT) +#define AA_EXEC_UNCONSTRAINED (0x01 << POS_AA_EXEC_UNCONSTRAINED) +#define AA_EXEC_PROFILE (0x01 << POS_AA_EXEC_PROFILE) +#define AA_EXEC_MMAP (0x01 << POS_AA_EXEC_MMAP) +#define AA_EXEC_UNSAFE (0x01 << POS_AA_EXEC_UNSAFE) + +#define AA_EXEC_MODIFIERS (AA_EXEC_INHERIT | \ + AA_EXEC_UNCONSTRAINED | \ + AA_EXEC_PROFILE) + +#endif /* _SHARED_H */