From 1d152eecb843ffa7f47d3753683ad95269adf8f3 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 30 Aug 2006 00:27:59 +0000 Subject: [PATCH] Import nextgen branch of AppArmor --- module-nextgen/Makefile | 25 - module-nextgen/aamatch/match.h | 137 -- module-nextgen/apparmor.h | 302 --- module-nextgen/apparmor/Kconfig | 9 + module-nextgen/{Kbuild => apparmor/Makefile} | 8 +- module-nextgen/apparmor/apparmor.h | 325 ++++ module-nextgen/apparmor/apparmorfs.c | 432 +++++ module-nextgen/{ => apparmor}/capabilities.c | 10 +- module-nextgen/apparmor/inline.h | 333 ++++ module-nextgen/{ => apparmor}/list.c | 107 +- module-nextgen/apparmor/lsm.c | 840 ++++++++ module-nextgen/apparmor/main.c | 1618 ++++++++++++++++ .../Kbuild => apparmor/match/Makefile} | 1 - module-nextgen/apparmor/match/match.h | 132 ++ module-nextgen/apparmor/match/match_default.c | 57 + .../{aamatch => apparmor/match}/match_pcre.c | 80 +- .../{aamatch => apparmor/match}/pcre_exec.c | 0 .../{aamatch => apparmor/match}/pcre_exec.h | 0 .../{aamatch => apparmor/match}/pcre_tables.h | 0 module-nextgen/apparmor/module_interface.c | 840 ++++++++ module-nextgen/apparmor/module_interface.h | 37 + module-nextgen/{ => apparmor}/procattr.c | 129 +- module-nextgen/apparmor/shared.h | 41 + module-nextgen/apparmor_version.c | 42 - module-nextgen/apparmorfs.c | 440 ----- module-nextgen/inline.h | 364 ---- module-nextgen/lsm.c | 959 ---------- module-nextgen/main.c | 1691 ----------------- module-nextgen/module_interface.c | 712 ------- module-nextgen/module_interface.h | 37 - module-nextgen/shared.h | 47 - 31 files changed, 4827 insertions(+), 4928 deletions(-) delete mode 100644 module-nextgen/Makefile delete mode 100644 module-nextgen/aamatch/match.h delete mode 100644 module-nextgen/apparmor.h create mode 100644 module-nextgen/apparmor/Kconfig rename module-nextgen/{Kbuild => apparmor/Makefile} (50%) create mode 100644 module-nextgen/apparmor/apparmor.h create mode 100644 module-nextgen/apparmor/apparmorfs.c rename module-nextgen/{ => apparmor}/capabilities.c (81%) create mode 100644 module-nextgen/apparmor/inline.h rename module-nextgen/{ => apparmor}/list.c (62%) create mode 100644 module-nextgen/apparmor/lsm.c create mode 100644 module-nextgen/apparmor/main.c rename module-nextgen/{aamatch/Kbuild => apparmor/match/Makefile} (99%) create mode 100644 module-nextgen/apparmor/match/match.h create mode 100644 module-nextgen/apparmor/match/match_default.c rename module-nextgen/{aamatch => apparmor/match}/match_pcre.c (57%) rename module-nextgen/{aamatch => apparmor/match}/pcre_exec.c (100%) rename module-nextgen/{aamatch => apparmor/match}/pcre_exec.h (100%) rename module-nextgen/{aamatch => apparmor/match}/pcre_tables.h (100%) create mode 100644 module-nextgen/apparmor/module_interface.c create mode 100644 module-nextgen/apparmor/module_interface.h rename module-nextgen/{ => apparmor}/procattr.c (66%) create mode 100644 module-nextgen/apparmor/shared.h delete mode 100644 module-nextgen/apparmor_version.c delete mode 100644 module-nextgen/apparmorfs.c delete mode 100644 module-nextgen/inline.h delete mode 100644 module-nextgen/lsm.c delete mode 100644 module-nextgen/main.c delete mode 100644 module-nextgen/module_interface.c delete mode 100644 module-nextgen/module_interface.h delete mode 100644 module-nextgen/shared.h diff --git a/module-nextgen/Makefile b/module-nextgen/Makefile deleted file mode 100644 index 1e05c3278..000000000 --- a/module-nextgen/Makefile +++ /dev/null @@ -1,25 +0,0 @@ -# Makefile for AppArmor Linux Security Module (previously called "SubDomain") -# -# kernel build Makefile is the Kbuild file - -REPO_VERSION := $(shell if [ -x /usr/bin/svn ] ; then \ - /usr/bin/svn info . 2> /dev/null | grep "^Last Changed Rev:" | sed "s/^Last Changed Rev: //" ; \ - fi) - -ifeq ("${REPO_VERSION}", "") -REPO_VERSION := "unknown" -endif - -KERNELVER := $(shell uname -r) - -KERNELDIR := /lib/modules/${KERNELVER}/build - -all: - $(MAKE) -C $(KERNELDIR) M=`pwd` APPARMOR_VER=${REPO_VERSION} $@ - mv apparmor.ko apparmor-${KERNELVER}.ko - mv aamatch/aamatch_pcre.ko aamatch_pcre-${KERNELVER}.ko - -clean: - rm -f *.o *.ko *.mod.c .*.cmd Modules.symvers \ - aamatch/*.o aamatch/*.ko aamatch/.*.cmd aamatch/*.mod.c - rm -rf .tmp_versions diff --git a/module-nextgen/aamatch/match.h b/module-nextgen/aamatch/match.h deleted file mode 100644 index f3ee3572f..000000000 --- a/module-nextgen/aamatch/match.h +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2002-2005 Novell/SUSE - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. - * - * AppArmor submodule (match) prototypes - */ - -#ifndef __MATCH_H -#define __MATCH_H - -#include "../module_interface.h" -#include "../apparmor.h" - -/* The following functions implement an interface used by the primary - * AppArmor module to perform name matching (n.b. "AppArmor" was previously - * called "SubDomain"). - - * sdmatch_alloc - * sdmatch_free - * sdmatch_features - * sdmatch_serialize - * sdmatch_match - * - * The intent is for the primary module to export (via virtual fs entries) - * the features provided by the submodule (sdmatch_features) so that the - * parser may only load policy that can be supported. - * - * The primary module will call sdmatch_serialize to allow the submodule - * to consume submodule specific data from parser data stream and will call - * sdmatch_match to determine if a pathname matches an sd_entry. - */ - -typedef int (*sdmatch_serializecb) - (struct sd_ext *, enum sd_code, void *, const char *); - -/** - * sdmatch_alloc: allocate extradata (if necessary) - * @entry_type: type of entry being allocated - * Return value: NULL indicates no data was allocated (ERR_PTR(x) on error) - */ -extern void* sdmatch_alloc(enum entry_t entry_type); - -/** - * sdmatch_free: release data allocated by sdmatch_alloc - * @entry_extradata: data previously allocated by sdmatch_alloc - */ -extern void sdmatch_free(void *entry_extradata); - -/** - * sdmatch_features: return match types supported - * Return value: space seperated string (of types supported - use type=value - * to indicate variants of a type) - */ -extern const char* sdmatch_features(void); - -/** - * sdmatch_serialize: serialize extradata - * @entry_extradata: data previously allocated by sdmatch_alloc - * @e: input stream - * @cb: callback fn (consume incoming data stream) - * Return value: 0 success, -ve error - */ -extern int sdmatch_serialize(void *entry_extradata, struct sd_ext *e, - sdmatch_serializecb cb); - -/** - * sdmatch_match: determine if pathname matches entry - * @pathname: pathname to verify - * @entry_name: entry name - * @entry_type: type of entry - * @entry_extradata: data previously allocated by sdmatch_alloc - * Return value: 1 match, 0 othersise - */ -extern unsigned int sdmatch_match(const char *pathname, const char *entry_name, - enum entry_t entry_type, - void *entry_extradata); - - -/** - * sd_getentry_type - return string representation of entry_t - * @etype: entry type - */ -static inline const char *sd_getentry_type(enum entry_t etype) -{ - const char *etype_names[] = { - "sd_entry_literal", - "sd_entry_tailglob", - "sd_entry_pattern", - "sd_entry_invalid" - }; - - if (etype >= sd_entry_invalid) { - etype = sd_entry_invalid; - } - - return etype_names[etype]; -} - -/** - * sdmatch_match_common - helper function to check if a pathname matches - * a literal/tailglob - * @path: path requested to search for - * @entry_name: name from sd_entry - * @etype: type of entry - */ -static inline int sdmatch_match_common(const char *path, - const char *entry_name, - enum entry_t etype) -{ - int retval; - - /* literal, no pattern matching characters */ - if (etype == sd_entry_literal) { - retval = (strcmp(entry_name, path) == 0); - /* trailing ** glob pattern */ - } else if (etype == sd_entry_tailglob) { - retval = (strncmp(entry_name, path, - strlen(entry_name) - 2) == 0); - } else { - SD_WARN("%s: Invalid entry_t %d\n", __FUNCTION__, etype); - retval = 0; - } - -#if 0 - SD_DEBUG("%s(%d): %s %s [%s]\n", - __FUNCTION__, retval, path, entry_name, - sd_getentry_type(etype)); -#endif - - return retval; -} - -#endif /* __MATCH_H */ diff --git a/module-nextgen/apparmor.h b/module-nextgen/apparmor.h deleted file mode 100644 index 2c7d716d4..000000000 --- a/module-nextgen/apparmor.h +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Copyright (C) 1998-2005 Novell/SUSE - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. - * - * AppArmor internal prototypes - */ - -#ifndef __SUBDOMAIN_H -#define __SUBDOMAIN_H - -/* defn of iattr */ -#include - -/* defn of linux_binprm */ -#include - -#include "shared.h" - -/* Control parameters (0 or 1), settable thru module/boot flags or - * via /sys/kernel/security/subdomain/control */ -extern int subdomain_complain; -extern int subdomain_debug; -extern int subdomain_audit; -extern int subdomain_logsyscall; - -#define SD_UNCONSTRAINED "unconstrained" - -/* $ echo -n subdomain.o | md5sum | cut -c -8 */ -#define SD_ID_MAGIC 0x8c235e38 - -#define PROFILE_COMPLAIN(_profile) \ - (subdomain_complain == 1 || ((_profile) && (_profile)->flags.complain)) - -#define SUBDOMAIN_COMPLAIN(_sd) \ - (subdomain_complain == 1 || \ - ((_sd) && (_sd)->active && (_sd)->active->flags.complain)) - -#define SUBDOMAIN_AUDIT(_sd) \ - (subdomain_audit == 1 || \ - ((_sd) && (_sd)->active && (_sd)->active->flags.audit)) - -/* - * DEBUG remains global (no per profile flag) since it is mostly used in sysctl - * which is not related to profile accesses. - */ - -#define SD_DEBUG(fmt, args...) \ - do { \ - if (subdomain_debug) \ - printk(KERN_DEBUG "AppArmor: " fmt, ##args); \ - } while (0) -#define SD_INFO(fmt, args...) printk(KERN_INFO "AppArmor: " fmt, ##args) -#define SD_WARN(fmt, args...) printk(KERN_WARNING "AppArmor: " fmt, ##args) -#define SD_ERROR(fmt, args...) printk(KERN_ERR "AppArmor: " fmt, ##args) - -/* basic AppArmor data structures */ - -struct flagval { - int debug; - int complain; - int audit; -}; - -enum entry_t { - sd_entry_literal, - sd_entry_tailglob, - sd_entry_pattern, - sd_entry_invalid -}; - -/** - * sd_entry - file ACL * - * Each entry describes a file and an allowed access mode. - */ -struct sd_entry { - char *filename; - int mode; /* mode is 'or' of READ, WRITE, EXECUTE, - * INHERIT, UNCONSTRAINED, and LIBRARY - * (meaning don't prefetch). */ - - enum entry_t entry_type; - void *extradata; - - struct list_head list; - struct list_head listp[POS_SD_FILE_MAX + 1]; -}; - -#define SD_SECURE_EXEC_NEEDED 0x00000001 - -#define SD_EXEC_MODIFIER_MASK(mask) ((mask) & SD_EXEC_MODIFIERS) - -#define SD_EXEC_MASK(mask) ((mask) & (SD_MAY_EXEC | SD_EXEC_MODIFIERS)) - -#define SD_EXEC_UNSAFE_MASK(mask) ((mask) & (SD_MAY_EXEC |\ - SD_EXEC_MODIFIERS |\ - SD_EXEC_UNSAFE)) - -/** - * sdprofile - basic confinement data - * - * The AppArmor profile contains the basic confinement data. Each profile - * has a name and potentially a list of subdomain entries. The profiles are - * connected in a list - */ -struct sdprofile { - char *name; /* profile name */ - - struct list_head file_entry; /* file ACL */ - struct list_head file_entryp[POS_SD_FILE_MAX + 1]; - struct list_head list; /* list of profiles */ - struct list_head sub; /* sub profiles, for change_hat */ - struct flagval flags; /* per profile debug flags */ - - int isstale; /* is profile stale */ - - int num_file_entries; - int num_file_pentries[POS_SD_FILE_MAX + 1]; - - kernel_cap_t capabilities; - - atomic_t count; /* reference count */ -}; - -enum sdfile_type { - sd_file_default, - sd_file_shmem -}; - -/** - * sdfile - file pointer confinement data - * - * Data structure assigned to each open file (by subdomain_file_alloc_security) - */ -struct sdfile { - enum sdfile_type type; - struct sdprofile *profile; -}; - -/** - * subdomain - a task's subdomain - * - * Contains the original profile obtained from execve() as well as the - * current active profile (which could change due to change_hat). Plus - * the hat_magic needed during change_hat. - */ -struct subdomain { - __u32 sd_magic; /* magic value to distinguish blobs */ - struct sdprofile *profile; /* The profile obtained from execve() */ - struct sdprofile *active; /* The current active profile */ - __u32 sd_hat_magic; /* used with change_hat */ - struct list_head list; /* list of subdomains */ - struct task_struct *task; -}; - -typedef int (*sd_iter) (struct subdomain *, void *); - -/* sd_path_data - * temp (cookie) data used by sd_path_* functions, see inline.h - */ -struct sd_path_data { - struct dentry *root, *dentry; - struct namespace *namespace; - struct list_head *head, *pos; - int errno; -}; - -#define SD_SUBDOMAIN(sec) ((struct subdomain*)(sec)) -#define SD_PROFILE(sec) ((struct sdprofile*)(sec)) - -/* Lock protecting access to 'struct subdomain' accesses */ -extern rwlock_t sd_lock; - -extern struct sdprofile *null_profile; -extern struct sdprofile *null_complain_profile; - -/** sd_audit - * - * Auditing structure - */ - -struct sd_audit { - unsigned short type, flags; - unsigned int result; - unsigned int gfp_mask; - int errorcode; - - const char *name; - unsigned int ival; - union{ - const void *pval; - va_list vaval; - }; -}; - -/* audit types */ -#define SD_AUDITTYPE_FILE 1 -#define SD_AUDITTYPE_DIR 2 -#define SD_AUDITTYPE_ATTR 3 -#define SD_AUDITTYPE_XATTR 4 -#define SD_AUDITTYPE_LINK 5 -#define SD_AUDITTYPE_CAP 6 -#define SD_AUDITTYPE_MSG 7 -#define SD_AUDITTYPE_SYSCALL 8 -#define SD_AUDITTYPE__END 9 - -/* audit flags */ -#define SD_AUDITFLAG_AUDITSS_SYSCALL 1 /* log syscall context */ -#define SD_AUDITFLAG_LOGERR 2 /* log operations that failed due to - non permission errors */ - -#define HINT_UNKNOWN_HAT "unknown_hat" -#define HINT_FORK "fork" -#define HINT_MANDPROF "missing_mandatory_profile" -#define HINT_CHGPROF "changing_profile" - -#define LOG_HINT(sd, gfp, hint, fmt, args...) \ - do {\ - sd_audit_message(sd, gfp, 0, \ - "LOGPROF-HINT " hint " " fmt, ##args);\ - } while(0) - -/* diroptype */ -#define SD_DIR_MKDIR 0 -#define SD_DIR_RMDIR 1 - -/* xattroptype */ -#define SD_XATTR_GET 0 -#define SD_XATTR_SET 1 -#define SD_XATTR_LIST 2 -#define SD_XATTR_REMOVE 3 - -/* main.c */ -extern int alloc_nullprofiles(void); -extern void free_nullprofiles(void); -extern int sd_audit_message(struct subdomain *, unsigned int gfp, int, - const char *, ...); -extern int sd_audit_syscallreject(struct subdomain *, unsigned int gfp, - const char *); -extern int sd_audit(struct subdomain *, const struct sd_audit *); -extern char *sd_get_name(struct dentry *dentry, struct vfsmount *mnt); - -extern int sd_attr(struct subdomain *sd, struct dentry *dentry, - struct iattr *iattr); -extern int sd_xattr(struct subdomain *sd, struct dentry *dentry, - const char *xattr, int xattroptype); -extern int sd_capability(struct subdomain *sd, int cap); -extern int sd_perm(struct subdomain *sd, struct dentry *dentry, - struct vfsmount *mnt, int mask); -extern int sd_perm_nameidata(struct subdomain *sd, struct nameidata *nd, - int mask); -extern int sd_perm_dentry(struct subdomain *sd, struct dentry *dentry, - int mask); -extern int sd_perm_dir(struct subdomain *sd, struct dentry *dentry, - int diroptype); -extern int sd_link(struct subdomain *sd, - struct dentry *link, struct dentry *target); -extern int sd_fork(struct task_struct *p); -extern int sd_register(struct linux_binprm *bprm); -extern void sd_release(struct task_struct *p); -extern int sd_change_hat(const char *id, __u32 hat_magic); -extern int sd_associate_filp(struct file *filp); - -/* list.c */ -extern struct sdprofile *sd_profilelist_find(const char *name); -extern int sd_profilelist_add(struct sdprofile *profile); -extern struct sdprofile *sd_profilelist_remove(const char *name); -extern void sd_profilelist_release(void); -extern struct sdprofile *sd_profilelist_replace(struct sdprofile *profile); -extern void sd_profile_dump(struct sdprofile *); -extern void sd_profilelist_dump(void); -extern void sd_subdomainlist_add(struct subdomain *); -extern void sd_subdomainlist_remove(struct subdomain *); -extern void sd_subdomainlist_iterate(sd_iter, void *); -extern void sd_subdomainlist_iterateremove(sd_iter, void *); -extern void sd_subdomainlist_release(void); - -/* subdomain_interface.c */ -extern void free_sdprofile(struct sdprofile *profile); -extern int sd_sys_security(unsigned int id, unsigned call, unsigned long *args); - -/* procattr.c */ -extern size_t sd_getprocattr(struct subdomain *sd, char *str, size_t size); -extern int sd_setprocattr_changehat(char *hatinfo, size_t infosize); -extern int sd_setprocattr_setprofile(struct task_struct *p, char *profilename, - size_t profilesize); - -/* apparmorfs.c */ -extern int create_subdomainfs(void); -extern int destroy_subdomainfs(void); - -/* capabilities.c */ -extern const char *capability_to_name(unsigned int cap); - -/* apparmor_version.c */ -extern const char *apparmor_version(void); -extern const char *apparmor_version_nl(void); - -#endif /* __SUBDOMAIN_H */ diff --git a/module-nextgen/apparmor/Kconfig b/module-nextgen/apparmor/Kconfig new file mode 100644 index 000000000..ca61a3c8a --- /dev/null +++ b/module-nextgen/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. diff --git a/module-nextgen/Kbuild b/module-nextgen/apparmor/Makefile similarity index 50% rename from module-nextgen/Kbuild rename to module-nextgen/apparmor/Makefile index 887ef0598..d0ae2df4d 100644 --- a/module-nextgen/Kbuild +++ b/module-nextgen/apparmor/Makefile @@ -1,10 +1,6 @@ # Makefile for AppArmor Linux Security Module # -EXTRA_CFLAGS += -DAPPARMOR_VERSION=\"${APPARMOR_VER}\" - +subdir-$(CONFIG_SECURITY_APPARMOR) += match obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o -apparmor-y := main.o list.o procattr.o lsm.o apparmorfs.o capabilities.o \ - module_interface.o apparmor_version.o - -obj-$(CONFIG_SECURITY_APPARMOR) += aamatch/ +apparmor-y := main.o list.o procattr.o lsm.o apparmorfs.o capabilities.o module_interface.o diff --git a/module-nextgen/apparmor/apparmor.h b/module-nextgen/apparmor/apparmor.h new file mode 100644 index 000000000..16681afe8 --- /dev/null +++ b/module-nextgen/apparmor/apparmor.h @@ -0,0 +1,325 @@ +/* + * Copyright (C) 1998-2005 Novell/SUSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * AppArmor internal prototypes + */ + +#ifndef __SUBDOMAIN_H +#define __SUBDOMAIN_H + +#include /* Include for defn of iattr */ +#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) + +/* 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_EXEC_MODIFIER_MASK(mask) ((mask) & (AA_EXEC_UNCONSTRAINED |\ + AA_EXEC_INHERIT |\ + AA_EXEC_PROFILE)) + +#define AA_EXEC_MASK(mask) ((mask) & (AA_MAY_EXEC |\ + AA_EXEC_UNCONSTRAINED |\ + AA_EXEC_INHERIT |\ + AA_EXEC_PROFILE)) + + +/* 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; +}; + +/** + * 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 + * + * 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; +}; + +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 namespace *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; + unsigned int 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, unsigned int gfp, int, + const char *, ...); +extern int aa_audit_syscallreject(struct aaprofile *active, unsigned int gfp, + const char *); +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 file *file); +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); + +#endif /* __SUBDOMAIN_H */ diff --git a/module-nextgen/apparmor/apparmorfs.c b/module-nextgen/apparmor/apparmorfs.c new file mode 100644 index 000000000..a84ccab3d --- /dev/null +++ b/module-nextgen/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: Subdomain securityfs already exists\n", + __FUNCTION__); + } else { + error = populate_apparmorfs(aafs_dentry); + if (error != 0) { + AA_ERROR("%s: Error populating Subdomain securityfs\n", + __FUNCTION__); + } + } + + return error; +} + +void destroy_apparmorfs(void) +{ + if (AAFS_DENTRY) + clear_apparmorfs(); +} diff --git a/module-nextgen/capabilities.c b/module-nextgen/apparmor/capabilities.c similarity index 81% rename from module-nextgen/capabilities.c rename to module-nextgen/apparmor/capabilities.c index 419b04dd2..9d538c9f7 100644 --- a/module-nextgen/capabilities.c +++ b/module-nextgen/apparmor/capabilities.c @@ -11,7 +11,7 @@ #include "apparmor.h" -static const char *capnames[] = { +static const char *cap_names[] = { "chown", "dac_override", "dac_read_search", @@ -45,10 +45,10 @@ static const char *capnames[] = { const char *capability_to_name(unsigned int cap) { - const char *capname; + const char *name; - capname = (cap < (sizeof(capnames) / sizeof(char *)) - ? capnames[cap] : "invalid-capability"); + name = (cap < (sizeof(cap_names) / sizeof(char *)) + ? cap_names[cap] : "invalid-capability"); - return capname; + return name; } diff --git a/module-nextgen/apparmor/inline.h b/module-nextgen/apparmor/inline.h new file mode 100644 index 000000000..dc52aadb1 --- /dev/null +++ b/module-nextgen/apparmor/inline.h @@ -0,0 +1,333 @@ +/* + * 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); +} + +/** + * 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)); + 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->namespace = current->namespace; + data->head = &data->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 (!name) + data->errno = -ENOMEM; + break; + } + } + + return name; +} + +#endif /* __INLINE_H__ */ diff --git a/module-nextgen/list.c b/module-nextgen/apparmor/list.c similarity index 62% rename from module-nextgen/list.c rename to module-nextgen/apparmor/list.c index ed1ee0293..2fec31365 100644 --- a/module-nextgen/list.c +++ b/module-nextgen/apparmor/list.c @@ -22,45 +22,47 @@ static LIST_HEAD(subdomain_list); static rwlock_t subdomain_lock = RW_LOCK_UNLOCKED; /** - * sd_profilelist_find + * aa_profilelist_find * @name: profile name (program name) * * Search the profile list for profile @name. Return refcounted profile on * success, NULL on failure. */ -struct sdprofile *sd_profilelist_find(const char *name) +struct aaprofile *aa_profilelist_find(const char *name) { - struct sdprofile *p = NULL; + struct aaprofile *p = NULL; if (name) { read_lock(&profile_lock); - p = __sd_find_profile(name, &profile_list); + p = __aa_find_profile(name, &profile_list); read_unlock(&profile_lock); } return p; } /** - * sd_profilelist_add + * aa_profilelist_add - add new profile to list * @profile: new profile to add to list * - * Add new profile to list. Reference count on profile is incremented. - * Return 1 on success, 0 on failure (bad profile or already exists) + * 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 sd_profilelist_add(struct sdprofile *profile) +int aa_profilelist_add(struct aaprofile *profile) { - struct sdprofile *old_profile; + struct aaprofile *old_profile; int ret = 0; if (!profile) goto out; write_lock(&profile_lock); - old_profile = __sd_find_profile(profile->name, &profile_list); + old_profile = __aa_find_profile(profile->name, &profile_list); if (old_profile) { - put_sdprofile(old_profile); + put_aaprofile(old_profile); goto out; } - profile = get_sdprofile(profile); list_add(&profile->list, &profile_list); ret = 1; @@ -70,17 +72,17 @@ int sd_profilelist_add(struct sdprofile *profile) } /** - * sd_profilelist_remove + * 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 sdprofile *sd_profilelist_remove(const char *name) +struct aaprofile *aa_profilelist_remove(const char *name) { - struct sdprofile *profile = NULL; - struct sdprofile *p, *tmp; + struct aaprofile *profile = NULL; + struct aaprofile *p, *tmp; if (!name) goto out; @@ -102,29 +104,31 @@ out: } /** - * sd_profilelist_replace - * @profile - new 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. This is an atomic - * list operation. Returns the old profile (which is still refcounted) if - * there was one, or NULL. + * 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 sdprofile *sd_profilelist_replace(struct sdprofile *profile) +struct aaprofile *aa_profilelist_replace(struct aaprofile *profile) { - struct sdprofile *oldprofile; + struct aaprofile *oldprofile; write_lock(&profile_lock); - oldprofile = __sd_find_profile(profile->name, &profile_list); + oldprofile = __aa_find_profile(profile->name, &profile_list); if (oldprofile) { list_del_init(&oldprofile->list); /* mark old profile as stale */ oldprofile->isstale = 1; - /* __sd_find_profile incremented count, so adjust down */ - put_sdprofile(oldprofile); + /* __aa_find_profile incremented count, so adjust down */ + put_aaprofile(oldprofile); } - profile = get_sdprofile(profile); + list_add(&profile->list, &profile_list); write_unlock(&profile_lock); @@ -132,34 +136,30 @@ struct sdprofile *sd_profilelist_replace(struct sdprofile *profile) } /** - * sd_profilelist_release - * - * Remove all profiles from profile_list + * aa_profilelist_release - Remove all profiles from profile_list */ -void sd_profilelist_release(void) +void aa_profilelist_release(void) { - struct sdprofile *p, *tmp; + struct aaprofile *p, *tmp; write_lock(&profile_lock); list_for_each_entry_safe(p, tmp, &profile_list, list) { list_del_init(&p->list); - put_sdprofile(p); + put_aaprofile(p); } write_unlock(&profile_lock); } /** - * sd_subdomainlist_add + * aa_subdomainlist_add - Add subdomain to subdomain_list * @sd: new subdomain - * - * Add subdomain to subdomain_list */ -void sd_subdomainlist_add(struct subdomain *sd) +void aa_subdomainlist_add(struct subdomain *sd) { unsigned long flags; if (!sd) { - SD_INFO("%s: bad subdomain\n", __FUNCTION__); + AA_INFO("%s: bad subdomain\n", __FUNCTION__); return; } @@ -172,12 +172,10 @@ void sd_subdomainlist_add(struct subdomain *sd) } /** - * sd_subdomainlist_remove + * aa_subdomainlist_remove - Remove subdomain from subdomain_list * @sd: subdomain to be removed - * - * Remove subdomain from subdomain_list */ -void sd_subdomainlist_remove(struct subdomain *sd) +void aa_subdomainlist_remove(struct subdomain *sd) { unsigned long flags; @@ -189,13 +187,14 @@ void sd_subdomainlist_remove(struct subdomain *sd) } /** - * sd_subdomainlist_iterate + * 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, stop when sd_iter func returns non zero + * Iterate over subdomain list applying @func, stop when @func returns + * non zero */ -void sd_subdomainlist_iterate(sd_iter func, void *cookie) +void aa_subdomainlist_iterate(aa_iter func, void *cookie) { struct subdomain *node; int ret = 0; @@ -211,11 +210,9 @@ void sd_subdomainlist_iterate(sd_iter func, void *cookie) } /** - * sd_subdomainlist_release - * - * Remove all subdomains from subdomain_list + * aa_subdomainlist_release - Remove all subdomains from subdomain_list */ -void sd_subdomainlist_release() +void aa_subdomainlist_release() { struct subdomain *node, *tmp; unsigned long flags; @@ -228,11 +225,11 @@ void sd_subdomainlist_release() } /* seq_file helper routines - * Used by subdomainfs.c to iterate over profile_list + * Used by apparmorfs.c to iterate over profile_list */ static void *p_start(struct seq_file *f, loff_t *pos) { - struct sdprofile *node; + struct aaprofile *node; loff_t l = *pos; read_lock(&profile_lock); @@ -244,10 +241,10 @@ static void *p_start(struct seq_file *f, loff_t *pos) static void *p_next(struct seq_file *f, void *p, loff_t *pos) { - struct list_head *lh = ((struct sdprofile *)p)->list.next; + struct list_head *lh = ((struct aaprofile *)p)->list.next; (*pos)++; return lh == &profile_list ? - NULL : list_entry(lh, struct sdprofile, list); + NULL : list_entry(lh, struct aaprofile, list); } static void p_stop(struct seq_file *f, void *v) @@ -257,13 +254,13 @@ static void p_stop(struct seq_file *f, void *v) static int seq_show_profile(struct seq_file *f, void *v) { - struct sdprofile *profile = (struct sdprofile *)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 subdomainfs_profiles_op = { +struct seq_operations apparmorfs_profiles_op = { .start = p_start, .next = p_next, .stop = p_stop, diff --git a/module-nextgen/apparmor/lsm.c b/module-nextgen/apparmor/lsm.c new file mode 100644 index 000000000..7990fb49b --- /dev/null +++ b/module-nextgen/apparmor/lsm.c @@ -0,0 +1,840 @@ +/* + * 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 "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_active_aaprofile(); + + if (!error && active) { + error = aa_audit_syscallreject(active, GFP_KERNEL, "ptrace"); + WARN_ON(error != -EPERM); + } + + 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_KERNEL, + "sysctl (write)"); + WARN_ON(error != -EPERM); + } + + 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) +{ + return cap_netlink_recv(skb); +} + +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->file); +} + +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_KERNEL, "mount"); + WARN_ON(error != -EPERM); + } + + 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_KERNEL, "umount"); + WARN_ON(error != -EPERM); + } + + 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 aaprofile *f_profile; + int error = 0; + + f_profile = AA_PROFILE(file->f_security); + /* bail out early if this isn't a mediated file */ + if (!(f_profile && VALID_FSTYPE(file->f_dentry->d_inode))) + goto out; + + active = get_active_aaprofile(); + if (active && f_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; + + active = get_active_aaprofile(); + file->f_security = get_aaprofile(active); + put_aaprofile(active); + + return 0; +} + +static void apparmor_file_free_security(struct file *file) +{ + struct aaprofile *p = AA_PROFILE(file->f_security); + put_aaprofile(p); +} + +static int apparmor_file_mmap(struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags) +{ + int error = 0, mask = 0; + struct aaprofile *active; + + if (!file) + goto out; + + active = get_active_aaprofile(); + + 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 |= MAY_EXEC; + + AA_DEBUG("%s: 0x%x\n", __FUNCTION__, mask); + + error = aa_perm(active, file->f_dentry, file->f_vfsmnt, mask); + + put_aaprofile(active); + +out: + return error; +} + +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_getprocattr(struct task_struct *p, char *name, void *value, + size_t size) +{ + int error; + struct aaprofile *active; + char *str = value; + + /* Subdomain only supports the "current" process attribute */ + if (strcmp(name, "current") != 0) { + error = -EINVAL; + goto out; + } + + if (!size) { + error = -ERANGE; + goto out; + } + + /* must be task querying itself or admin */ + if (current != p && !capable(CAP_SYS_ADMIN)) { + error = -EPERM; + goto out; + } + + 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, + + .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, + + .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, + + .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"); +} + +security_initcall(apparmor_init); +module_exit(apparmor_exit); + +MODULE_DESCRIPTION("AppArmor process confinement"); +MODULE_AUTHOR("Tony Jones "); +MODULE_LICENSE("GPL"); diff --git a/module-nextgen/apparmor/main.c b/module-nextgen/apparmor/main.c new file mode 100644 index 000000000..2362136e3 --- /dev/null +++ b/module-nextgen/apparmor/main.c @@ -0,0 +1,1618 @@ +/* + * 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 subdomain is + * unloaded + */ +struct aaprofile *null_complain_profile; + +/*************************** + * Private utility functions + **************************/ + +/** + * 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. + * + * Returns %0 (false): + * if unable to find profile or there are conflicting pattern matches. + * *xmod - is not modified + * + * Returns %1 (true): + * if not confined + * *xmod = %AA_MAY_EXEC + * 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 + */ +static inline int aa_get_execmode(struct aaprofile *active, const char *name, + int *xmod) +{ + 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_MASK(entry->mode) != + AA_EXEC_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_MASK(entry->mode) != + AA_EXEC_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; + } 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; + + *xmod = AA_MAY_EXEC; + return 1; +} + +/** + * 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/X/L */ + WARN_ON(mask & + ~(AA_MAY_READ | AA_MAY_WRITE | AA_MAY_EXEC | 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 && + (!list_empty(&BASE_PROFILE(active)->sub) || + PROFILE_COMPLAIN(active)) && 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) { + AA_ERROR("%s: An error occured while translating dentry %p " + "inode# %lu to a pathname. Error %d\n", + __FUNCTION__, + dentry, + dentry->d_inode->i_ino, + path_error); + + 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, unsigned int 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 + * @msg: string describing syscall being rejected + * @gfp: memory allocation flags + */ +int aa_audit_syscallreject(struct aaprofile *active, unsigned int gfp, + const char *msg) +{ + struct aa_audit sa; + + sa.type = AA_AUDITTYPE_SYSCALL; + sa.name = msg; + sa.flags = 0; + sa.gfp_mask = gfp; + sa.error_code = 0; + sa.result = 0; /* failure */ + + return aa_audit(active, &sa); +} + +/** + * 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 unsigned int 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_AA, + "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"; + } + + /* 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_AA); + + 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 access to %s ", + 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)); + + 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) ", current->comm, current->pid); + + if (0) + audit_log_format(ab, "[global deny])"); + else + audit_log_format(ab, "profile %s active %s)", + BASE_PROFILE(active)->name, + active->name); + + audit_log_end(ab); + + 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 = NULL; + + page = (char *)__get_free_page(GFP_KERNEL); + if (!page) + goto out; + + name = d_path_flags(dentry, mnt, page, PAGE_SIZE, + DPATH_SYSROOT|DPATH_NODELETED); + + 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 subdomain permissions check + * @active: profile to check against + * @dentry: dentry + * @mnt: mountpoint + * @mask: access mode requested + * + * Determine if access (mask) for dentry is authorized by subdomain 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; + + permerror = (sa.name ? aa_file_perm(active, sa.name, mask) : -ENOMEM); + + 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) { + AA_ERROR("%s: An error occured while " + "translating inner dentry %p " + "inode %lu to a pathname. Error %d\n", + __FUNCTION__, + target, + target->d_inode->i_ino, + path_error); + + /* 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) { + AA_ERROR("%s: An error occured while translating outer " + "dentry %p inode %lu to a pathname. Error %d\n", + __FUNCTION__, + link, + link->d_inode->i_ino, + path_error); + + error_code = path_error; + } + + if (error_code != 0) { + /* inner or outer error */ + result = 0; + } else if (match) { + result = 1; + } else { + /* failed to match */ + WARN_ON(iname); + WARN_ON(oname); + + result = 0; + iname = failed_iname; + oname = failed_oname; + } + + sa.type = 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 struct for the newly created process @p. + * Copy parent info to child. If parent has no subdomain, child + * will get one with %NULL values. 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 = alloc_subdomain(p); + + AA_DEBUG("%s\n", __FUNCTION__); + + if (!newsd) + return -ENOMEM; + + if (sd) { + unsigned long flags; + + /* 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 + * @filp: file 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 file *filp) +{ + char *filename; + struct subdomain *sd; + struct aaprofile *active; + struct aaprofile *newprofile = NULL, unconstrained_flag; + int error = -ENOMEM, + exec_mode = 0, + find_profile = 0, + find_profile_mandatory = 0, + complain = 0; + + AA_DEBUG("%s\n", __FUNCTION__); + + sd = AA_SUBDOMAIN(current->security); + + if (sd) { + complain = SUBDOMAIN_COMPLAIN(sd); + } else { + /* task has no subdomain. This can happen when a task is + * created when subdomain is not loaded. Allocate and + * attach a subdomain to the task + */ + sd = alloc_subdomain(current); + if (!sd) { + AA_WARN("%s: Failed to allocate subdomain\n", + __FUNCTION__); + goto out; + } + + current->security = sd; + } + + filename = aa_get_name(filp->f_dentry, filp->f_vfsmnt); + if (!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; + } + + /* Confined task, determine what mode inherit, unconstrained or + * mandatory to load new profile + */ + if (aa_get_execmode(active, filename, &exec_mode)) { + 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, sd->active->name); + error = -EPERM; + break; + } + + } else if (complain) { + /* There was no entry in calling profile + * describing mode to execute image in. + * Drop into null-profile + */ + newprofile = get_aaprofile(null_complain_profile); + } 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) { + 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 + * + * - 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 sd->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. + */ + + spin_lock_irqsave(&sd_lock, flags); + + /* 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; + } + } + + 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); + } + + 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 = -EACCES; + 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; +} diff --git a/module-nextgen/aamatch/Kbuild b/module-nextgen/apparmor/match/Makefile similarity index 99% rename from module-nextgen/aamatch/Kbuild rename to module-nextgen/apparmor/match/Makefile index 1e556f2b2..8e0b2dc1b 100644 --- a/module-nextgen/aamatch/Kbuild +++ b/module-nextgen/apparmor/match/Makefile @@ -1,6 +1,5 @@ # Makefile for AppArmor aamatch submodule # - obj-$(CONFIG_SECURITY_APPARMOR) += aamatch_pcre.o aamatch_pcre-y := match_pcre.o pcre_exec.o diff --git a/module-nextgen/apparmor/match/match.h b/module-nextgen/apparmor/match/match.h new file mode 100644 index 000000000..26b347253 --- /dev/null +++ b/module-nextgen/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 */ diff --git a/module-nextgen/apparmor/match/match_default.c b/module-nextgen/apparmor/match/match_default.c new file mode 100644 index 000000000..b5e085142 --- /dev/null +++ b/module-nextgen/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"); diff --git a/module-nextgen/aamatch/match_pcre.c b/module-nextgen/apparmor/match/match_pcre.c similarity index 57% rename from module-nextgen/aamatch/match_pcre.c rename to module-nextgen/apparmor/match/match_pcre.c index 30295b27c..0a9071b6b 100644 --- a/module-nextgen/aamatch/match_pcre.c +++ b/module-nextgen/apparmor/match/match_pcre.c @@ -22,49 +22,49 @@ static const char *features="literal tailglob pattern=pcre"; -struct sdmatch_entry +struct aamatch_entry { char *pattern; pcre *compiled; }; -void* sdmatch_alloc(enum entry_t entry_type) +void* aamatch_alloc(enum entry_match_type entry_type) { void *ptr=NULL; - if (entry_type == sd_entry_pattern) { - ptr = kmalloc(sizeof(struct sdmatch_entry), GFP_KERNEL); + if (entry_type == aa_entry_pattern) { + ptr = kmalloc(sizeof(struct aamatch_entry), GFP_KERNEL); if (ptr) - memset(ptr, 0, sizeof(struct sdmatch_entry)); + memset(ptr, 0, sizeof(struct aamatch_entry)); else ptr=ERR_PTR(-ENOMEM); - } else if (entry_type != sd_entry_literal && - entry_type != sd_entry_tailglob) { + } else if (entry_type != aa_entry_literal && + entry_type != aa_entry_tailglob) { ptr = ERR_PTR(-EINVAL); } return ptr; } -void sdmatch_free(void *ptr) +void aamatch_free(void *ptr) { if (ptr) { - struct sdmatch_entry *ed = (struct sdmatch_entry *) ptr; + struct aamatch_entry *ed = (struct aamatch_entry *) ptr; kfree(ed->pattern); - kfree(ed->compiled); /* allocated by SD_READ_X */ + kfree(ed->compiled); /* allocated by AA_READ_X */ } kfree(ptr); } -const char *sdmatch_features(void) +const char *aamatch_features(void) { return features; } -int sdmatch_serialize(void *entry_extradata, struct sd_ext *e, - sdmatch_serializecb cb) +int aamatch_serialize(void *entry_extradata, struct aa_ext *e, + aamatch_serializecb cb) { -#define SD_READ_X(E, C, D, N) \ +#define AA_READ_X(E, C, D, N) \ do { \ if (!cb((E), (C), (D), (N))) { \ error = -EINVAL; \ @@ -75,20 +75,20 @@ int sdmatch_serialize(void *entry_extradata, struct sd_ext *e, int error = 0; u32 size, magic, opts; u8 t_char; - struct sdmatch_entry *ed = (struct sdmatch_entry *) entry_extradata; + struct aamatch_entry *ed = (struct aamatch_entry *) entry_extradata; if (ed == NULL) goto done; - SD_READ_X(e, SD_DYN_STRING, &ed->pattern, NULL); + 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 */ - SD_READ_X(e, SD_STRUCT, NULL, "pcre"); - SD_READ_X(e, SD_U32, &size, "pattern.size"); - SD_READ_X(e, SD_U32, &magic, "pattern.magic"); + AA_READ_X(e, AA_STRUCT, NULL, "pcre"); + AA_READ_X(e, AA_U32, &size, "pattern.size"); + AA_READ_X(e, AA_U32, &magic, "pattern.magic"); /* the allocation of pcre is delayed because it depends on the size * of the pattern */ @@ -102,20 +102,20 @@ int sdmatch_serialize(void *entry_extradata, struct sd_ext *e, ed->compiled->magic_number = magic; ed->compiled->size = size + sizeof(pcre); - SD_READ_X(e, SD_U32, &opts, "pattern.options"); + AA_READ_X(e, AA_U32, &opts, "pattern.options"); ed->compiled->options = opts; - SD_READ_X(e, SD_U16, &ed->compiled->top_bracket, "pattern.top_bracket"); - SD_READ_X(e, SD_U16, &ed->compiled->top_backref, "pattern.top_backref"); - SD_READ_X(e, SD_U8, &t_char, "pattern.first_char"); + AA_READ_X(e, AA_U16, &ed->compiled->top_bracket, "pattern.top_bracket"); + AA_READ_X(e, AA_U16, &ed->compiled->top_backref, "pattern.top_backref"); + AA_READ_X(e, AA_U8, &t_char, "pattern.first_char"); ed->compiled->first_char = t_char; - SD_READ_X(e, SD_U8, &t_char, "pattern.req_char"); + AA_READ_X(e, AA_U8, &t_char, "pattern.req_char"); ed->compiled->req_char = t_char; - SD_READ_X(e, SD_U8, &t_char, "pattern.code[0]"); + AA_READ_X(e, AA_U8, &t_char, "pattern.code[0]"); ed->compiled->code[0] = t_char; - SD_READ_X(e, SD_STATIC_BLOB, &ed->compiled->code[1], NULL); + AA_READ_X(e, AA_STATIC_BLOB, &ed->compiled->code[1], NULL); - SD_READ_X(e, SD_STRUCTEND, NULL, 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 */ @@ -123,7 +123,7 @@ int sdmatch_serialize(void *entry_extradata, struct sd_ext *e, done: if (error != 0 && ed) { - kfree(ed->pattern); /* allocated by SD_READ_X */ + kfree(ed->pattern); /* allocated by AA_READ_X */ kfree(ed->compiled); ed->pattern = NULL; ed->compiled = NULL; @@ -132,15 +132,15 @@ done: return error; } -unsigned int sdmatch_match(const char *pathname, const char *entry_name, - enum entry_t entry_type, void *entry_extradata) +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 == sd_entry_pattern) { + if (entry_type == aa_entry_pattern) { int pcreret; - struct sdmatch_entry *ed = - (struct sdmatch_entry *) entry_extradata; + struct aamatch_entry *ed = + (struct aamatch_entry *) entry_extradata; pcreret = pcre_exec(ed->compiled, NULL, pathname, strlen(pathname), @@ -149,20 +149,20 @@ unsigned int sdmatch_match(const char *pathname, const char *entry_name, ret = (pcreret >= 0); // XXX - this needs access to subdomain_debug, hmmm - //SD_DEBUG("%s(%d): %s %s %d\n", __FUNCTION__, + //AA_DEBUG("%s(%d): %s %s %d\n", __FUNCTION__, // ret, pathname, ed->pattern, pcreret); } else { - ret = sdmatch_match_common(pathname, entry_name, entry_type); + ret = aamatch_match_common(pathname, entry_name, entry_type); } return ret; } -EXPORT_SYMBOL_GPL(sdmatch_alloc); -EXPORT_SYMBOL_GPL(sdmatch_free); -EXPORT_SYMBOL_GPL(sdmatch_features); -EXPORT_SYMBOL_GPL(sdmatch_serialize); -EXPORT_SYMBOL_GPL(sdmatch_match); +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 "); diff --git a/module-nextgen/aamatch/pcre_exec.c b/module-nextgen/apparmor/match/pcre_exec.c similarity index 100% rename from module-nextgen/aamatch/pcre_exec.c rename to module-nextgen/apparmor/match/pcre_exec.c diff --git a/module-nextgen/aamatch/pcre_exec.h b/module-nextgen/apparmor/match/pcre_exec.h similarity index 100% rename from module-nextgen/aamatch/pcre_exec.h rename to module-nextgen/apparmor/match/pcre_exec.h diff --git a/module-nextgen/aamatch/pcre_tables.h b/module-nextgen/apparmor/match/pcre_tables.h similarity index 100% rename from module-nextgen/aamatch/pcre_tables.h rename to module-nextgen/apparmor/match/pcre_tables.h diff --git a/module-nextgen/apparmor/module_interface.c b/module-nextgen/apparmor/module_interface.c new file mode 100644 index 000000000..45807b369 --- /dev/null +++ b/module-nextgen/apparmor/module_interface.c @@ -0,0 +1,840 @@ +/* + * 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; + } + /* 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, "file.mode"); + AA_READ_X(e, AA_U32, &entry->type, "file.pattern_type"); + + 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: + aamatch_free(entry->extradata); + 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; + } + + 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), "profile.flags.debug"); + AA_READ_X(e, AA_U32, &(profile->flags.complain), + "profile.flags.complain"); + AA_READ_X(e, AA_U32, &(profile->flags.audit), "profile.flags.audit"); + AA_READ_X(e, AA_STRUCTEND, NULL, NULL); + + error_string = "Invalid capabilities"; + AA_READ_X(e, AA_U32, &(profile->capabilities), "profile.capabilities"); + + /* 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); +} diff --git a/module-nextgen/apparmor/module_interface.h b/module-nextgen/apparmor/module_interface.h new file mode 100644 index 000000000..d50330cac --- /dev/null +++ b/module-nextgen/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 SUBDOMAIN_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 */ diff --git a/module-nextgen/procattr.c b/module-nextgen/apparmor/procattr.c similarity index 66% rename from module-nextgen/procattr.c rename to module-nextgen/apparmor/procattr.c index 952ae0e41..11708c60a 100644 --- a/module-nextgen/procattr.c +++ b/module-nextgen/apparmor/procattr.c @@ -15,25 +15,25 @@ #include "apparmor.h" #include "inline.h" -size_t sd_getprocattr(struct subdomain *sd, char *str, size_t size) +size_t aa_getprocattr(struct aaprofile *active, char *str, size_t size) { int error = -EACCES; /* default to a perm denied */ size_t len; - if (__sd_is_confined(sd)) { + if (active) { size_t lena, lenm, lenp = 0; const char *enforce_str = " (enforce)"; const char *complain_str = " (complain)"; const char *mode_str = - SUBDOMAIN_COMPLAIN(sd) ? complain_str : enforce_str; + PROFILE_COMPLAIN(active) ? complain_str : enforce_str; lenm = strlen(mode_str); - lena = strlen(sd->active->name); + lena = strlen(active->name); len = lena; - if (sd->active != sd->profile) { - lenp = strlen(sd->profile->name); + if (IN_SUBPROFILE(active)) { + lenp = strlen(BASE_PROFILE(active)->name); len += (lenp + 1); /* +1 for ^ */ } /* DONT null terminate strings we output via proc */ @@ -41,12 +41,13 @@ size_t sd_getprocattr(struct subdomain *sd, char *str, size_t size) if (len <= size) { if (lenp) { - memcpy(str, sd->profile->name, lenp); + memcpy(str, BASE_PROFILE(active)->name, + lenp); str += lenp; *str++ = '^'; } - memcpy(str, sd->active->name, lena); + memcpy(str, active->name, lena); str += lena; memcpy(str, mode_str, lenm); str += lenm; @@ -56,7 +57,7 @@ size_t sd_getprocattr(struct subdomain *sd, char *str, size_t size) error = -ERANGE; } } else { - const char *unconstrained_str = SD_UNCONSTRAINED "\n"; + const char *unconstrained_str = "unconstrained\n"; len = strlen(unconstrained_str); /* DONT null terminate strings we output via proc */ @@ -71,15 +72,16 @@ size_t sd_getprocattr(struct subdomain *sd, char *str, size_t size) return error; } -int sd_setprocattr_changehat(char *hatinfo, size_t infosize) + +int aa_setprocattr_changehat(char *hatinfo, size_t infosize) { int error = -EINVAL; char *token = NULL, *hat, *smagic, *tmp; - __u32 magic; + u32 magic; int rc, len, consumed; unsigned long flags; - SD_DEBUG("%s: %p %zd\n", __FUNCTION__, hatinfo, infosize); + AA_DEBUG("%s: %p %zd\n", __FUNCTION__, hatinfo, infosize); /* strip leading white space */ while (infosize && isspace(*hatinfo)) { @@ -114,7 +116,7 @@ int sd_setprocattr_changehat(char *hatinfo, size_t infosize) } if (!*tmp || tmp == token) { - SD_WARN("%s: Invalid input '%s'\n", __FUNCTION__, token); + AA_WARN("%s: Invalid input '%s'\n", __FUNCTION__, token); goto out; } @@ -133,7 +135,7 @@ int sd_setprocattr_changehat(char *hatinfo, size_t infosize) rc = sscanf(smagic, "%x%n", &magic, &consumed); if (rc != 1 || consumed != len) { - SD_WARN("%s: Invalid hex magic %s\n", + AA_WARN("%s: Invalid hex magic %s\n", __FUNCTION__, smagic); goto out; @@ -145,17 +147,17 @@ int sd_setprocattr_changehat(char *hatinfo, size_t infosize) hat = NULL; if (!hat && !magic) { - SD_WARN("%s: Invalid input, NULL hat and NULL magic\n", + AA_WARN("%s: Invalid input, NULL hat and NULL magic\n", __FUNCTION__); goto out; } - SD_DEBUG("%s: Magic 0x%x Hat '%s'\n", + AA_DEBUG("%s: Magic 0x%x Hat '%s'\n", __FUNCTION__, magic, hat ? hat : NULL); - write_lock_irqsave(&sd_lock, flags); - error = sd_change_hat(hat, magic); - write_unlock_irqrestore(&sd_lock, flags); + spin_lock_irqsave(&sd_lock, flags); + error = aa_change_hat(hat, magic); + spin_unlock_irqrestore(&sd_lock, flags); out: if (token) { @@ -166,16 +168,16 @@ out: return error; } -int sd_setprocattr_setprofile(struct task_struct *p, char *profilename, +int aa_setprocattr_setprofile(struct task_struct *p, char *profilename, size_t profilesize) { int error = -EINVAL; - struct sdprofile *profile; + struct aaprofile *profile = NULL; struct subdomain *sd; char *name = NULL; unsigned long flags; - SD_DEBUG("%s: current %s(%d)\n", + AA_DEBUG("%s: current %s(%d)\n", __FUNCTION__, current->comm, current->pid); /* strip leading white space */ @@ -202,40 +204,37 @@ int sd_setprocattr_setprofile(struct task_struct *p, char *profilename, name[profilesize] = 0; repeat: - if (strcmp(name, SD_UNCONSTRAINED) == 0) - profile = null_profile; - else - profile = sd_profilelist_find(name); + 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); - if (!profile) { - SD_WARN("%s: Unable to switch task %s(%d) to profile '%s'. " - "No such profile.\n", - __FUNCTION__, - p->comm, p->pid, - name); - - error = -EINVAL; - goto out; + error = -EINVAL; + goto out; + } } + spin_lock_irqsave(&sd_lock, flags); - write_lock_irqsave(&sd_lock, flags); - - sd = SD_SUBDOMAIN(p->security); + sd = AA_SUBDOMAIN(p->security); /* switch to unconstrained */ - if (profile == null_profile) { - if (__sd_is_confined(sd)) { - SD_WARN("%s: Unconstraining task %s(%d) " + if (!profile) { + if (__aa_is_confined(sd)) { + AA_WARN("%s: Unconstraining task %s(%d) " "profile %s active %s\n", __FUNCTION__, p->comm, p->pid, - sd->profile->name, + BASE_PROFILE(sd->active)->name, sd->active->name); - sd_switch_unconfined(sd); + aa_switch_unconfined(sd); } else { - SD_WARN("%s: task %s(%d) " + AA_WARN("%s: task %s(%d) " "is already unconstrained\n", __FUNCTION__, p->comm, p->pid); } @@ -244,15 +243,15 @@ int sd_setprocattr_setprofile(struct task_struct *p, char *profilename, /* this task was created before module was * loaded, allocate a subdomain */ - SD_WARN("%s: task %s(%d) has no subdomain\n", + AA_WARN("%s: task %s(%d) has no subdomain\n", __FUNCTION__, p->comm, p->pid); /* unlock so we can safely GFP_KERNEL */ - write_unlock_irqrestore(&sd_lock, flags); + spin_unlock_irqrestore(&sd_lock, flags); sd = alloc_subdomain(p); if (!sd) { - SD_WARN("%s: Unable to allocate subdomain for " + AA_WARN("%s: Unable to allocate subdomain for " "task %s(%d). Cannot confine task to " "profile %s\n", __FUNCTION__, @@ -260,17 +259,17 @@ int sd_setprocattr_setprofile(struct task_struct *p, char *profilename, name); error = -ENOMEM; - put_sdprofile(profile); + put_aaprofile(profile); goto out; } - write_lock_irqsave(&sd_lock, flags); - if (!p->security) { + spin_lock_irqsave(&sd_lock, flags); + if (!AA_SUBDOMAIN(p->security)) { p->security = sd; } else { /* race */ free_subdomain(sd); - sd = SD_SUBDOMAIN(p->security); + sd = AA_SUBDOMAIN(p->security); } } @@ -279,13 +278,13 @@ int sd_setprocattr_setprofile(struct task_struct *p, char *profilename, if (unlikely(profile->isstale)) { WARN_ON(profile == null_complain_profile); - /* drop refcnt obtained from earlier get_sdprofile */ - put_sdprofile(profile); - profile = sd_profilelist_find(name); + /* drop refcnt obtained from earlier get_aaprofile */ + put_aaprofile(profile); + profile = aa_profilelist_find(name); if (!profile) { /* Race, profile was removed. */ - write_unlock_irqrestore(&sd_lock, flags); + spin_unlock_irqrestore(&sd_lock, flags); goto repeat; } } @@ -297,31 +296,31 @@ int sd_setprocattr_setprofile(struct task_struct *p, char *profilename, * profile has a identical named hat. */ - SD_WARN("%s: Switching task %s(%d) " + AA_WARN("%s: Switching task %s(%d) " "profile %s active %s to new profile %s\n", __FUNCTION__, p->comm, p->pid, - sd->profile ? sd->profile->name : SD_UNCONSTRAINED, - sd->active ? sd->profile->name : SD_UNCONSTRAINED, + sd->active ? BASE_PROFILE(sd->active)->name : + "unconstrained", + sd->active ? sd->active->name : "unconstrained", name); - sd_switch(sd, profile, profile); + aa_switch(sd, profile); - put_sdprofile(profile); /* drop ref we obtained above - * from sd_profilelist_find + 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 sd_switch + * calling aa_switch */ - sd->sd_hat_magic = 0; + sd->hat_magic = 0; } - write_unlock_irqrestore(&sd_lock, flags); + spin_unlock_irqrestore(&sd_lock, flags); error = 0; - out: kfree(name); diff --git a/module-nextgen/apparmor/shared.h b/module-nextgen/apparmor/shared.h new file mode 100644 index 000000000..0fc733b34 --- /dev/null +++ b/module-nextgen/apparmor/shared.h @@ -0,0 +1,41 @@ +/* + * 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_FILE_MAX POS_AA_EXEC_PROFILE + +/* 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_MODIFIERS(X) (X & (AA_EXEC_INHERIT | \ + A_EXEC_UNCONSTRAINED | \ + AA_EXEC_PROFILE)) + +#endif /* _SHARED_H */ diff --git a/module-nextgen/apparmor_version.c b/module-nextgen/apparmor_version.c deleted file mode 100644 index 8712b6233..000000000 --- a/module-nextgen/apparmor_version.c +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2005 Novell/SUSE - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. - * - * AppArmor version definition - */ - -#ifndef APPARMOR_VERSION -#error "-DAPPARMOR_VERSION must be specified when compiling this file" -#endif - -#define APPARMOR_VERSION_STR_PFX "APPARMOR_VERSION=" - -#include -MODULE_VERSION(APPARMOR_VERSION); - -/* apparmor_version_str exists to allow a strings on module to - * see APPARMOR_VERSION= prefix - */ -static const char *apparmor_version_str = - APPARMOR_VERSION_STR_PFX APPARMOR_VERSION; - -/* apparmor_version_str_nl exists to allow an easy way to get a newline - * terminated string without having to do dynamic memory allocation - */ -static const char *apparmor_version_str_nl = APPARMOR_VERSION "\n"; - -const char *apparmor_version(void) -{ - const int len = sizeof(APPARMOR_VERSION_STR_PFX) - 1; - - return apparmor_version_str + len; -} - -const char *apparmor_version_nl(void) -{ - return apparmor_version_str_nl; -} diff --git a/module-nextgen/apparmorfs.c b/module-nextgen/apparmorfs.c deleted file mode 100644 index 4a5cb6de3..000000000 --- a/module-nextgen/apparmorfs.c +++ /dev/null @@ -1,440 +0,0 @@ -/* - * Copyright (C) 2005 Novell/SUSE - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. - * - * AppArmor filesystem (part of securityfs) - */ - -#include -#include -#include -#include -#include - -#include "apparmor.h" -#include "inline.h" -#include "aamatch/match.h" - -#define SECFS_SD "apparmor" -static struct dentry *sdfs_dentry = NULL; - -/* profile */ -extern struct seq_operations subdomainfs_profiles_op; -static int sd_prof_open(struct inode *inode, struct file *file); -static int sd_prof_release(struct inode *inode, struct file *file); - -static struct file_operations subdomainfs_profiles_fops = { - .open = sd_prof_open, - .read = seq_read, - .llseek = seq_lseek, - .release = sd_prof_release, -}; - -/* version */ -static ssize_t sd_version_read(struct file *file, char __user *buf, - size_t size, loff_t *ppos); - -static struct file_operations subdomainfs_version_fops = { - .read = sd_version_read, -}; - -/* matching */ -static ssize_t sd_matching_read(struct file *file, char __user *buf, - size_t size, loff_t *ppos); - -static struct file_operations subdomainfs_matching_fops = { - .read = sd_matching_read, -}; - - -/* interface */ -extern ssize_t sd_file_prof_add(void *, size_t); -extern ssize_t sd_file_prof_repl(void *, size_t); -extern ssize_t sd_file_prof_remove(const char *, int); - -static ssize_t sd_profile_load(struct file *f, const char __user *buf, - size_t size, loff_t *pos); -static ssize_t sd_profile_replace(struct file *f, const char __user *buf, - size_t size, loff_t *pos); -static ssize_t sd_profile_remove(struct file *f, const char __user *buf, - size_t size, loff_t *pos); - -static struct file_operations subdomainfs_profile_load = { - .write = sd_profile_load -}; - -static struct file_operations subdomainfs_profile_replace = { - .write = sd_profile_replace -}; - -static struct file_operations subdomainfs_profile_remove = { - .write = sd_profile_remove -}; - - -/* control */ -static u64 sd_control_get(void *data); -static void sd_control_set(void *data, u64 val); - -DEFINE_SIMPLE_ATTRIBUTE(subdomainfs_control_fops, sd_control_get, - sd_control_set, "%lld\n"); - - - -/* table of static entries */ - -static struct root_entry { - const char *name; - int mode; - int access; - struct file_operations *fops; - void *data; - - /* internal fields */ - struct dentry *dentry; - int parent_index; -} root_entries[] = { - /* our root, normally /sys/kernel/security/subdomain */ - {SECFS_SD, S_IFDIR, 0550}, /* DO NOT EDIT/MOVE */ - - /* interface for obtaining list of profiles currently loaded */ - {"profiles", S_IFREG, 0440, &subdomainfs_profiles_fops, - NULL}, - - /* interface for obtaining version# of subdomain */ - {"version", S_IFREG, 0440, &subdomainfs_version_fops, - NULL}, - - /* interface for obtaining matching features supported */ - {"matching", S_IFREG, 0440, &subdomainfs_matching_fops, - NULL}, - - /* interface for loading/removing/replacing profiles */ - {".load", S_IFREG, 0640, &subdomainfs_profile_load, - NULL}, - {".replace", S_IFREG, 0640, &subdomainfs_profile_replace, - NULL}, - {".remove", S_IFREG, 0640, &subdomainfs_profile_remove, - NULL}, - - /* interface for setting binary config values */ - {"control", S_IFDIR, 0550}, - {"complain", S_IFREG, 0640, &subdomainfs_control_fops, - &subdomain_complain}, - {"audit", S_IFREG, 0640, &subdomainfs_control_fops, - &subdomain_audit}, - {"debug", S_IFREG, 0640, &subdomainfs_control_fops, - &subdomain_debug}, - {"logsyscall", S_IFREG, 0640, &subdomainfs_control_fops, - &subdomain_logsyscall}, - {NULL, S_IFDIR, 0}, - - /* root end */ - {NULL, S_IFDIR, 0} -}; - -#define SDFS_DENTRY root_entries[0].dentry - -static const unsigned int num_entries = - sizeof(root_entries) / sizeof(struct root_entry); - - - -static int sd_prof_open(struct inode *inode, struct file *file) -{ - return seq_open(file, &subdomainfs_profiles_op); -} - - -static int sd_prof_release(struct inode *inode, struct file *file) -{ - return seq_release(inode, file); -} - -static ssize_t sd_version_read(struct file *file, char __user *buf, - size_t size, loff_t *ppos) -{ - const char *version = apparmor_version_nl(); - - return simple_read_from_buffer(buf, size, ppos, version, - strlen(version)); -} - -static ssize_t sd_matching_read(struct file *file, char __user *buf, - size_t size, loff_t *ppos) -{ - const char *matching = sdmatch_features(); - - return simple_read_from_buffer(buf, size, ppos, matching, - strlen(matching)); -} - -static char *sd_simple_write_to_buffer(const char __user *userbuf, - size_t alloc_size, size_t copy_size, - loff_t *pos, const char *msg) -{ - char *data; - - if (*pos != 0) { - /* only writes from pos 0, that is complete writes */ - data = ERR_PTR(-ESPIPE); - goto out; - } - - /* Don't allow confined processes to load/replace/remove profiles. - * No sane person would add rules allowing this to a profile - * but we enforce the restriction anyways. - */ - if (sd_is_confined()) { - struct subdomain *sd = SD_SUBDOMAIN(current->security); - - SD_WARN("REJECTING access to profile %s (%s(%d) " - "profile %s active %s)\n", - msg, current->comm, current->pid, - sd->profile->name, sd->active->name); - - data = ERR_PTR(-EPERM); - goto out; - } - - data = vmalloc(alloc_size); - if (data == NULL) { - data = ERR_PTR(-ENOMEM); - goto out; - } - - if (copy_from_user(data, userbuf, copy_size)) { - vfree(data); - data = ERR_PTR(-EFAULT); - goto out; - } - -out: - return data; -} - -static ssize_t sd_profile_load(struct file *f, const char __user *buf, - size_t size, loff_t *pos) -{ - char *data; - ssize_t error; - - data = sd_simple_write_to_buffer(buf, size, size, pos, "load"); - - if (!IS_ERR(data)) { - error = sd_file_prof_add(data, size); - vfree(data); - } else { - error = PTR_ERR(data); - } - - return error; -} - -static ssize_t sd_profile_replace(struct file *f, const char __user *buf, - size_t size, loff_t *pos) -{ - char *data; - ssize_t error; - - data = sd_simple_write_to_buffer(buf, size, size, pos, "replacement"); - - if (!IS_ERR(data)) { - error = sd_file_prof_repl(data, size); - vfree(data); - } else { - error = PTR_ERR(data); - } - - return error; -} - -static ssize_t sd_profile_remove(struct file *f, const char __user *buf, - size_t size, loff_t *pos) -{ - char *data; - ssize_t error; - - /* sd_file_prof_remove needs a null terminated string so 1 extra - * byte is allocated and null the copied data is then null terminated - */ - data = sd_simple_write_to_buffer(buf, size+1, size, pos, "removal"); - - if (!IS_ERR(data)) { - data[size] = 0; - error = sd_file_prof_remove(data, size); - vfree(data); - } else { - error = PTR_ERR(data); - } - - return error; -} - -static u64 sd_control_get(void *data) -{ - return *(int *)data; -} - -static void sd_control_set(void *data, u64 val) -{ - if (val > 1) - val = 1; - - *(int*)data = (int)val; -} - -static void clear_subdomainfs(void) -{ - unsigned int i; - - for (i=0; i < num_entries;i++) { - unsigned int index; - - if (root_entries[i].mode == S_IFDIR) { - if (root_entries[i].name) - /* defer dir free till all sub-entries freed */ - continue; - else - /* cleanup parent */ - index = root_entries[i].parent_index; - } else { - index = i; - } - - if (root_entries[index].dentry) { - securityfs_remove(root_entries[index].dentry); - - SD_DEBUG("%s: deleted subdomainfs entry name=%s " - "dentry=%p\n", - __FUNCTION__, - root_entries[index].name, - root_entries[index].dentry); - - root_entries[index].dentry = NULL; - root_entries[index].parent_index = 0; - } - } -} - -static int populate_subdomainfs(struct dentry *root) -{ - unsigned int i, parent_index, depth; - -#define ENT root_entries[i] - - for (i = 0; i < num_entries; i++) { - root_entries[i].dentry = NULL; - root_entries[i].parent_index = 0; - } - - /* 1. Verify entry 0 is valid [sanity check] */ - if (num_entries == 0 || - !root_entries[0].name || - strcmp(root_entries[0].name, SECFS_SD) != 0 || - root_entries[0].mode != S_IFDIR) { - SD_ERROR("%s: root entry 0 is not SECFS_SD/dir\n", - __FUNCTION__); - goto error; - } - - /* 2. Verify table structure */ - parent_index = 0; - depth = 1; - - for (i = 1; i < num_entries; i++) { - ENT.parent_index = parent_index; - - if (ENT.name && ENT.mode == S_IFDIR) { - depth++; - parent_index = i; - } else if (!ENT.name) { - if (ENT.mode != S_IFDIR || depth == 0) { - SD_ERROR("%s: root_entry %d invalid (%u %d)", - __FUNCTION__, i, - ENT.mode, ENT.parent_index); - goto error; - } - - depth--; - parent_index = root_entries[parent_index].parent_index; - } - } - - if (depth != 0) { - SD_ERROR("%s: root_entry table not correctly terminated\n", - __FUNCTION__); - goto error; - } - - /* 3. Create root (parent=NULL) */ - i=0; - - ENT.dentry = securityfs_create_file(ENT.name, - ENT.mode | ENT.access, - NULL, NULL, NULL); - - if (ENT.dentry) - SD_DEBUG("%s: created securityfs/subdomain [dentry=%p]\n", - __FUNCTION__, ENT.dentry); - else - goto error; - - - /* 4. create remaining nodes */ - for (i = 1; i < num_entries; i++) { - struct dentry *parent; - - /* end of directory ? */ - if (!ENT.name) - continue; - - parent = root_entries[ENT.parent_index].dentry; - - ENT.dentry = securityfs_create_file(ENT.name, - ENT.mode | ENT.access, - parent, - ENT.mode != S_IFDIR ? ENT.data : NULL, - ENT.mode != S_IFDIR ? ENT.fops : NULL); - - if (!ENT.dentry) - goto cleanup_error; - - SD_DEBUG("%s: added subdomainfs entry " - "name=%s mode=%x dentry=%p [parent %p]\n", - __FUNCTION__, ENT.name, ENT.mode|ENT.access, - ENT.dentry, parent); - } - - return 1; - -cleanup_error: - clear_subdomainfs(); - -error: - return 0; -} - -int create_subdomainfs(void) -{ - if (SDFS_DENTRY) - SD_ERROR("%s: Subdomain securityfs already exists\n", - __FUNCTION__); - else if (!populate_subdomainfs(sdfs_dentry)) - SD_ERROR("%s: Error populating Subdomain securityfs\n", - __FUNCTION__); - - return (SDFS_DENTRY != NULL); -} - -int destroy_subdomainfs(void) -{ - if (SDFS_DENTRY) - clear_subdomainfs(); - - return 1; -} diff --git a/module-nextgen/inline.h b/module-nextgen/inline.h deleted file mode 100644 index 61af6e308..000000000 --- a/module-nextgen/inline.h +++ /dev/null @@ -1,364 +0,0 @@ -/* - * Copyright (C) 2005 Novell/SUSE - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. - */ - -#ifndef __INLINE_H -#define __INLINE_H - -#include - -static inline int __sd_is_confined(struct subdomain *sd) -{ - int rc = 0; - - if (sd && sd->sd_magic == SD_ID_MAGIC && sd->profile) { - BUG_ON(!sd->active); - rc = 1; - } - - return rc; -} - -/** - * sd_is_confined - * @sd: subdomain - * - * Check if @sd is confined (contains a valid profile) - * Return 1 if confined, 0 otherwise. - */ -static inline int sd_is_confined(void) -{ - struct subdomain *sd = SD_SUBDOMAIN(current->security); - return __sd_is_confined(sd); -} - -static inline int __sd_sub_defined(struct subdomain *sd) -{ - return __sd_is_confined(sd) && !list_empty(&sd->profile->sub); -} - -/** - * sd_sub_defined - * @sd: subdomain - * - * Check if @sd has at least one subprofile - * Return 1 if true, 0 otherwise - */ -static inline int sd_sub_defined(void) -{ - struct subdomain *sd = SD_SUBDOMAIN(current->security); - return __sd_sub_defined(sd); -} - -/** - * get_sdprofile - * @p: profile - * - * Increment refcount on profile - */ -static inline struct sdprofile *get_sdprofile(struct sdprofile *p) -{ - if (p) - atomic_inc(&p->count); - return p; -} - -/** - * put_sdprofile - * @p: profile - * - * Decrement refcount on profile - */ -static inline void put_sdprofile(struct sdprofile *p) -{ - if (p) - if (atomic_dec_and_test(&p->count)) - free_sdprofile(p); -} - -/** - * sd_switch - * @sd: subdomain to switch - * @profile: new profile - * @active: new active - * - * Change subdomain to use new profiles. - */ -static inline void sd_switch(struct subdomain *sd, - struct sdprofile *profile, - struct sdprofile *active) -{ - /* noop if NULL */ - put_sdprofile(sd->profile); - put_sdprofile(sd->active); - - sd->profile = get_sdprofile(profile); - sd->active = get_sdprofile(active); -} - -/** - * sd_switch_unconfined - * @sd: subdomain to switch - * - * Change subdomain to unconfined - */ -static inline void sd_switch_unconfined(struct subdomain *sd) -{ - sd_switch(sd, NULL, NULL); - - /* reset magic in case we were in a subhat before */ - sd->sd_hat_magic = 0; -} - -/** - * alloc_subdomain - * @tsk: task struct - * - * Allocate a new subdomain including a backpointer to it's referring task. - */ -static inline struct subdomain *alloc_subdomain(struct task_struct *tsk) -{ - struct subdomain *sd; - - sd = kmalloc(sizeof(struct subdomain), GFP_KERNEL); - if (!sd) - goto out; - - /* zero it first */ - memset(sd, 0, sizeof(struct subdomain)); - sd->sd_magic = SD_ID_MAGIC; - - /* back pointer to task */ - sd->task = tsk; - - /* any readers of the list must make sure that they can handle - * case where sd->profile and sd->active are not yet set (null) - */ - sd_subdomainlist_add(sd); - -out: - return sd; -} - -/** - * free_subdomain - * @sd: subdomain - * - * Free a subdomain previously allocated by alloc_subdomain - */ -static inline void free_subdomain(struct subdomain *sd) -{ - sd_subdomainlist_remove(sd); - kfree(sd); -} - -/** - * alloc_sdprofile - * - * Allocate, initialize and return a new zeroed profile. - * Returns NULL on failure. - */ -static inline struct sdprofile *alloc_sdprofile(void) -{ - struct sdprofile *profile; - - profile = (struct sdprofile *)kmalloc(sizeof(struct sdprofile), - GFP_KERNEL); - SD_DEBUG("%s(%p)\n", __FUNCTION__, profile); - if (profile) { - int i; - memset(profile, 0, sizeof(struct sdprofile)); - INIT_LIST_HEAD(&profile->list); - INIT_LIST_HEAD(&profile->sub); - INIT_LIST_HEAD(&profile->file_entry); - for (i = 0; i <= POS_SD_FILE_MAX; i++) { - INIT_LIST_HEAD(&profile->file_entryp[i]); - } - } - return profile; -} - -/** - * sd_put_name - * @name: name to release. - * - * Release space (free_page) allocated to hold pathname - * name may be NULL (checked for by free_page) - */ -static inline void sd_put_name(const char *name) -{ - free_page((unsigned long)name); -} - -/** __sd_find_profile - * @name: name of profile to find - * @head: list to search - * - * Return reference counted copy of profile. NULL if not found - * Caller must hold any necessary locks - */ -static inline struct sdprofile *__sd_find_profile(const char *name, - struct list_head *head) -{ - struct sdprofile *p; - - if (!name || !head) - return NULL; - - SD_DEBUG("%s: finding profile %s\n", __FUNCTION__, name); - list_for_each_entry(p, head, list) { - if (!strcmp(p->name, name)) { - /* return refcounted object */ - p = get_sdprofile(p); - return p; - } else { - SD_DEBUG("%s: skipping %s\n", __FUNCTION__, p->name); - } - } - return NULL; -} - -static inline struct subdomain *__get_sdcopy(struct subdomain *new, - struct task_struct *tsk) -{ - struct subdomain *old, *temp = NULL; - - old = SD_SUBDOMAIN(tsk->security); - - if (old) { - new->sd_magic = old->sd_magic; - new->sd_hat_magic = old->sd_hat_magic; - - new->active = get_sdprofile(old->active); - - if (old->profile == old->active) - new->profile = new->active; - else - new->profile = get_sdprofile(old->profile); - - temp = new; - } - - return temp; -} - -/** get_sdcopy - * @new: subdomain to hold copy - * - * Make copy of current subdomain containing refcounted profile and active - * Used to protect readers against racing writers (changehat and profile - * replacement). - */ -static inline struct subdomain *get_sdcopy(struct subdomain *new) -{ - struct subdomain *temp; - unsigned long flags; - - read_lock_irqsave(&sd_lock, flags); - - temp = __get_sdcopy(new, current); - - read_unlock_irqrestore(&sd_lock, flags); - - return temp; -} - -/** get_sdcopy - * @temp: subdomain to drop refcounts on - * - * Drop refcounted profile/active in copy of subdomain made by get_sdcopy - */ -static inline void put_sdcopy(struct subdomain *temp) -{ - if (temp) { - put_sdprofile(temp->active); - if (temp->active != temp->profile) - (void)put_sdprofile(temp->profile); - } -} - -/** sd_path_begin2 - * @rdentry: filesystem root dentry (searching for vfsmnts matching this) - * @dentry: dentry object to obtain pathname from (relative to matched vfsmnt) - * - * Setup data for iterating over vfsmounts (in current tasks namespace). - */ -static inline void sd_path_begin2(struct dentry *rdentry, - struct dentry *dentry, - struct sd_path_data *data) -{ - data->dentry = dentry; - data->root = dget(rdentry->d_sb->s_root); - data->namespace = current->namespace; - data->head = &data->namespace->list; - data->pos = data->head->next; - prefetch(data->pos->next); - data->errno = 0; - - down_read(&namespace_sem); -} - -/** sd_path_begin - * @dentry filesystem root dentry and object to obtain pathname from - * - * Utility function for calling _sd_path_begin for when the dentry we are - * looking for and the root are the same (this is the usual case). - */ -static inline void sd_path_begin(struct dentry *dentry, - struct sd_path_data *data) -{ - sd_path_begin2(dentry, dentry, data); -} - -/** sd_path_end - * @data: data object previously initialized by sd_path_begin - * - * End iterating over vfsmounts. - * If an error occured in begin or get, it is returned. Otherwise 0. - */ -static inline int sd_path_end(struct sd_path_data *data) -{ - up_read(&namespace_sem); - dput(data->root); - - return data->errno; -} - -/** sd_path_getname - * @data: data object previously initialized by sd_path_begin - * - * Return the next mountpoint which has the same root dentry as data->root. - * If no more mount points exist (or in case of error) NULL is returned - * (caller should call sd_path_end() and inspect return code to differentiate) - */ -static inline char *sd_path_getname(struct sd_path_data *data) -{ - char *name = NULL; - struct vfsmount *mnt; - - while (data->pos != data->head) { - mnt = list_entry(data->pos, struct vfsmount, mnt_list); - - /* advance to next -- so that it is done before we break */ - data->pos = data->pos->next; - prefetch(data->pos->next); - - if (mnt->mnt_root == data->root) { - name = sd_get_name(data->dentry, mnt); - if (IS_ERR(name)) { - data->errno = PTR_ERR(name); - name = NULL; - } - break; - } - } - - return name; -} - -#endif /* __INLINE_H__ */ diff --git a/module-nextgen/lsm.c b/module-nextgen/lsm.c deleted file mode 100644 index 40d44bd78..000000000 --- a/module-nextgen/lsm.c +++ /dev/null @@ -1,959 +0,0 @@ -/* - * Copyright (C) 2002-2005 Novell/SUSE - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. - * - * http://forge.novell.com/modules/xfmod/project/?apparmor - * - * Immunix AppArmor LSM interface (previously called "SubDomain") - */ - -#include -#include -#include - -/* superblock types */ - -/* PIPEFS_MAGIC */ -#include -/* from net/socket.c */ -#define SOCKFS_MAGIC 0x534F434B -/* from inotify.c */ -#define INOTIFYFS_MAGIC 0xBAD1DEA - -#define VALID_FSTYPE(inode) ((inode)->i_sb->s_magic != PIPEFS_MAGIC && \ - (inode)->i_sb->s_magic != SOCKFS_MAGIC && \ - (inode)->i_sb->s_magic != INOTIFYFS_MAGIC) - -#include - -#include "apparmor.h" -#include "inline.h" - -/* main SD lock [see get_sdcopy and put_sdcopy] */ -rwlock_t sd_lock = RW_LOCK_UNLOCKED; - -/* Flag values, also controllable via subdomainfs/control. - * We explicitly do not allow these to be modifiable when exported via - * /sys/modules/parameters, as we want to do additional mediation and - * don't want to add special path code. */ - -/* Complain mode (used to be 'bitch' mode) */ -int subdomain_complain = 0; -module_param_named(complain, subdomain_complain, int, S_IRUSR); -MODULE_PARM_DESC(subdomain_complain, "Toggle AppArmor complain mode"); - -/* Debug mode */ -int subdomain_debug = 0; -module_param_named(debug, subdomain_debug, int, S_IRUSR); -MODULE_PARM_DESC(subdomain_debug, "Toggle AppArmor debug mode"); - -/* Audit mode */ -int subdomain_audit = 0; -module_param_named(audit, subdomain_audit, int, S_IRUSR); -MODULE_PARM_DESC(subdomain_audit, "Toggle AppArmor audit mode"); - -/* Syscall logging mode */ -int subdomain_logsyscall = 0; -module_param_named(logsyscall, subdomain_logsyscall, int, S_IRUSR); -MODULE_PARM_DESC(subdomain_logsyscall, "Toggle AppArmor logsyscall mode"); - -#ifndef MODULE -static int __init sd_getopt_complain(char *str) -{ - get_option(&str, &subdomain_complain); - return 1; -} -__setup("subdomain_complain=", sd_getopt_complain); - -static int __init sd_getopt_debug(char *str) -{ - get_option(&str, &subdomain_debug); - return 1; -} -__setup("subdomain_debug=", sd_getopt_debug); - -static int __init sd_getopt_audit(char *str) -{ - get_option(&str, &subdomain_audit); - return 1; -} -__setup("subdomain_audit=", sd_getopt_audit); - -static int __init sd_getopt_logsyscall(char *str) -{ - get_option(&str, &subdomain_logsyscall); - return 1; -} -__setup("subdomain_logsyscall=", sd_getopt_logsyscall); -#endif - -static int subdomain_ptrace(struct task_struct *parent, - struct task_struct *child) -{ - int error; - struct subdomain *sd; - unsigned long flags; - - error = cap_ptrace(parent, child); - - if (error == 0 && parent->security) { - read_lock_irqsave(&sd_lock, flags); - - sd = SD_SUBDOMAIN(parent->security); - - if (__sd_is_confined(sd)) { - error = sd_audit_syscallreject(sd, GFP_ATOMIC, - "ptrace"); - WARN_ON(error != -EPERM); - } - - read_unlock_irqrestore(&sd_lock, flags); - } - - return error; -} - -static int subdomain_capget(struct task_struct *target, - kernel_cap_t * effective, - kernel_cap_t * inheritable, - kernel_cap_t * permitted) -{ - return cap_capget(target, effective, inheritable, permitted); -} - -static int subdomain_capset_check(struct task_struct *target, - kernel_cap_t *effective, - kernel_cap_t *inheritable, - kernel_cap_t *permitted) -{ - return cap_capset_check(target, effective, inheritable, permitted); -} - -static void subdomain_capset_set(struct task_struct *target, - kernel_cap_t *effective, - kernel_cap_t *inheritable, - kernel_cap_t *permitted) -{ - cap_capset_set(target, effective, inheritable, permitted); - return; -} - -static int subdomain_capable(struct task_struct *tsk, int cap) -{ - int error; - - /* cap_capable returns 0 on success, else -EPERM */ - error = cap_capable(tsk, cap); - - if (error == 0 && current->security) { - struct subdomain *sd, sdcopy; - unsigned long flags; - - read_lock_irqsave(&sd_lock, flags); - sd = __get_sdcopy(&sdcopy, tsk); - read_unlock_irqrestore(&sd_lock, flags); - - error = sd_capability(sd, cap); - - put_sdcopy(sd); - } - - return error; -} - -static int subdomain_sysctl(struct ctl_table *table, int op) -{ - int error = 0; - struct subdomain *sd; - unsigned long flags; - - if (!current->security) - return 0; - - read_lock_irqsave(&sd_lock, flags); - - sd = SD_SUBDOMAIN(current->security); - - if ((op & 002) && __sd_is_confined(sd) && !capable(CAP_SYS_ADMIN)) { - error = sd_audit_syscallreject(sd, GFP_ATOMIC, - "sysctl (write)"); - WARN_ON(error != -EPERM); - } - - read_unlock_irqrestore(&sd_lock, flags); - - return error; -} - -static int subdomain_syslog(int type) -{ - return cap_syslog(type); -} - -static int subdomain_netlink_send(struct sock *sk, struct sk_buff *skb) -{ - return cap_netlink_send(sk, skb); -} - -static int subdomain_netlink_recv(struct sk_buff *skb) -{ - return cap_netlink_recv(skb); -} - -static void subdomain_bprm_apply_creds(struct linux_binprm *bprm, int unsafe) -{ - cap_bprm_apply_creds(bprm, unsafe); - return; -} - -static int subdomain_bprm_set_security(struct linux_binprm *bprm) -{ - /* handle capability bits with setuid, etc */ - cap_bprm_set_security(bprm); - /* already set based on script name */ - if (bprm->sh_bang) - return 0; - return sd_register(bprm); -} - -static int subdomain_bprm_secureexec(struct linux_binprm *bprm) -{ - int ret = cap_bprm_secureexec(bprm); - - if (ret == 0 && (unsigned long)bprm->security & SD_SECURE_EXEC_NEEDED) { - SD_DEBUG("%s: secureexec required for %s\n", - __FUNCTION__, bprm->filename); - ret = 1; - } - - return ret; -} - -static int subdomain_sb_mount(char *dev_name, struct nameidata *nd, char *type, - unsigned long flags, void *data) -{ - int error = 0; - struct subdomain *sd; - unsigned long lockflags; - - if (!current->security) - return 0; - - read_lock_irqsave(&sd_lock, lockflags); - - sd = SD_SUBDOMAIN(current->security); - - if (__sd_is_confined(sd)) { - error = sd_audit_syscallreject(sd, GFP_ATOMIC, "mount"); - WARN_ON(error != -EPERM); - } - - read_unlock_irqrestore(&sd_lock, lockflags); - - return error; -} - -static int subdomain_umount(struct vfsmount *mnt, int flags) -{ - int error = 0; - struct subdomain *sd; - unsigned long lockflags; - - if (!current->security) - return 0; - - read_lock_irqsave(&sd_lock, lockflags); - - sd = SD_SUBDOMAIN(current->security); - - if (__sd_is_confined(sd)) { - error = sd_audit_syscallreject(sd, GFP_ATOMIC, "umount"); - WARN_ON(error != -EPERM); - } - - read_unlock_irqrestore(&sd_lock, lockflags); - - return error; -} - -static int subdomain_inode_mkdir(struct inode *inode, struct dentry *dentry, - int mask) -{ - struct subdomain sdcopy, *sd; - int error; - - if (!current->security) - return 0; - - sd = get_sdcopy(&sdcopy); - - error = sd_perm_dir(sd, dentry, SD_DIR_MKDIR); - - put_sdcopy(sd); - - return error; -} - -static int subdomain_inode_rmdir(struct inode *inode, struct dentry *dentry) -{ - struct subdomain sdcopy, *sd; - int error; - - if (!current->security) - return 0; - - sd = get_sdcopy(&sdcopy); - - error = sd_perm_dir(sd, dentry, SD_DIR_RMDIR); - - put_sdcopy(sd); - - return error; -} - -static int subdomain_inode_create(struct inode *inode, struct dentry *dentry, - int mask) -{ - struct subdomain sdcopy, *sd; - int error; - - if (!current->security) - return 0; - - sd = get_sdcopy(&sdcopy); - - /* At a minimum, need write perm to create */ - error = sd_perm_dentry(sd, dentry, MAY_WRITE); - - put_sdcopy(sd); - - return error; -} - -static int subdomain_inode_link(struct dentry *old_dentry, struct inode *inode, - struct dentry *new_dentry) -{ - int error = 0; - struct subdomain sdcopy, *sd; - - if (!current->security) - return 0; - - sd = get_sdcopy(&sdcopy); - error = sd_link(sd, new_dentry, old_dentry); - put_sdcopy(sd); - - return error; -} - -static int subdomain_inode_unlink(struct inode *inode, struct dentry *dentry) -{ - struct subdomain sdcopy, *sd; - int error; - - if (!current->security) - return 0; - - sd = get_sdcopy(&sdcopy); - - error = sd_perm_dentry(sd, dentry, MAY_WRITE); - - put_sdcopy(sd); - - return error; -} - -static int subdomain_inode_mknod(struct inode *inode, struct dentry *dentry, - int mode, dev_t dev) -{ - struct subdomain sdcopy, *sd; - int error = 0; - - if (!current->security) - return 0; - - sd = get_sdcopy(&sdcopy); - - error = sd_perm_dentry(sd, dentry, MAY_WRITE); - - put_sdcopy(sd); - - return error; -} - -static int subdomain_inode_rename(struct inode *old_inode, - struct dentry *old_dentry, - struct inode *new_inode, - struct dentry *new_dentry) -{ - struct subdomain sdcopy, *sd; - int error = 0; - - if (!current->security) - return 0; - - sd = get_sdcopy(&sdcopy); - - error = sd_perm_dentry(sd, old_dentry, - MAY_READ | MAY_WRITE); - - if (!error) - error = sd_perm_dentry(sd, new_dentry, MAY_WRITE); - - put_sdcopy(sd); - - return error; -} - -static int subdomain_inode_permission(struct inode *inode, int mask, - struct nameidata *nd) -{ - int error = 0; - - /* Do not perform check on pipes or sockets - * Same as subdomain_file_permission - */ - if (current->security && VALID_FSTYPE(inode)) { - struct subdomain sdcopy, *sd; - - sd = get_sdcopy(&sdcopy); - error = sd_perm_nameidata(sd, nd, mask); - put_sdcopy(sd); - } - - return error; -} - -static int subdomain_inode_setattr(struct dentry *dentry, struct iattr *iattr) -{ - struct subdomain sdcopy, *sd; - int error = 0; - - if (current->security && VALID_FSTYPE(dentry->d_inode)) { - - sd = get_sdcopy(&sdcopy); - - /* - * Mediate any attempt to change attributes of a file - * (chmod, chown, chgrp, etc) - */ - error = sd_attr(sd, dentry, iattr); - - put_sdcopy(sd); - } - - return error; -} - -static int subdomain_inode_setxattr(struct dentry *dentry, char *name, - void *value, size_t size, int flags) -{ - int error = 0; - - if (current->security && VALID_FSTYPE(dentry->d_inode)) { - struct subdomain sdcopy, *sd; - - sd = get_sdcopy(&sdcopy); - error = sd_xattr(sd, dentry, name, SD_XATTR_SET); - put_sdcopy(sd); - } - - return error; -} - -static int subdomain_inode_getxattr(struct dentry *dentry, char *name) -{ - int error = 0; - - if (current->security && VALID_FSTYPE(dentry->d_inode)) { - struct subdomain sdcopy, *sd; - - sd = get_sdcopy(&sdcopy); - error = sd_xattr(sd, dentry, name, SD_XATTR_GET); - put_sdcopy(sd); - } - - return error; -} -static int subdomain_inode_listxattr(struct dentry *dentry) -{ - int error = 0; - - if (current->security && VALID_FSTYPE(dentry->d_inode)) { - struct subdomain sdcopy, *sd; - - sd = get_sdcopy(&sdcopy); - error = sd_xattr(sd, dentry, NULL, SD_XATTR_LIST); - put_sdcopy(sd); - } - - return error; -} - -static int subdomain_inode_removexattr(struct dentry *dentry, char *name) -{ - int error = 0; - - if (current->security && VALID_FSTYPE(dentry->d_inode)) { - struct subdomain sdcopy, *sd; - - sd = get_sdcopy(&sdcopy); - error = sd_xattr(sd, dentry, name, SD_XATTR_REMOVE); - put_sdcopy(sd); - } - - return error; -} - -static int subdomain_file_permission(struct file *file, int mask) -{ - struct subdomain sdcopy, *sd; - struct sdfile *sdf; - int error = 0; - - if (!current->security || - !(sdf = (struct sdfile *)file->f_security) || - !VALID_FSTYPE(file->f_dentry->d_inode)) - return 0; - - sd = get_sdcopy(&sdcopy); - - if (__sd_is_confined(sd) && sdf->profile != sd->active) - error = sd_perm(sd, file->f_dentry, file->f_vfsmnt, - mask & (MAY_EXEC | MAY_WRITE | MAY_READ)); - - put_sdcopy(sd); - - return error; -} - -static int subdomain_file_alloc_security(struct file *file) -{ - struct subdomain sdcopy, *sd; - int error = 0; - - if (!current->security) - return 0; - - sd = get_sdcopy(&sdcopy); - - if (__sd_is_confined(sd)) { - struct sdfile *sdf; - - sdf = kmalloc(sizeof(struct sdfile), GFP_KERNEL); - - if (sdf) { - sdf->type = sd_file_default; - sdf->profile = get_sdprofile(sd->active); - } else { - error = -ENOMEM; - } - - file->f_security = sdf; - } - - put_sdcopy(sd); - - return error; -} - -static void subdomain_file_free_security(struct file *file) -{ - struct sdfile *sdf = (struct sdfile *)file->f_security; - - if (sdf) { - put_sdprofile(sdf->profile); - kfree(sdf); - } -} - -static inline int sd_mmap(struct file *file, unsigned long prot, - unsigned long flags) -{ - int error = 0, mask = 0; - struct subdomain sdcopy, *sd; - struct sdfile *sdf; - - if (!current->security || !file || - !(sdf = (struct sdfile *)file->f_security) || - sdf->type == sd_file_shmem) - return 0; - - sd = get_sdcopy(&sdcopy); - - if (prot & PROT_READ) - mask |= MAY_READ; - - /* Private mappings don't require write perms since they don't - * write back to the files */ - if (prot & PROT_WRITE && !(flags & MAP_PRIVATE)) - mask |= MAY_WRITE; - - if (prot & PROT_EXEC) - mask |= SD_EXEC_MMAP; - - SD_DEBUG("%s: 0x%x\n", __FUNCTION__, mask); - - if (mask) - error = sd_perm(sd, file->f_dentry, file->f_vfsmnt, mask); - - put_sdcopy(sd); - - return error; -} - -static int subdomain_file_mmap(struct file *file, unsigned long reqprot, - unsigned long prot, unsigned long flags) -{ - return sd_mmap(file, prot, flags); -} - -static int subdomain_file_mprotect(struct vm_area_struct* vma, - unsigned long reqprot, unsigned long prot) -{ - return sd_mmap(vma->vm_file, prot, - !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); -} - -static int subdomain_task_alloc_security(struct task_struct *p) -{ - return sd_fork(p); -} - -static void subdomain_task_free_security(struct task_struct *p) -{ - if (p->security) - sd_release(p); -} - -static int subdomain_task_post_setuid(uid_t id0, uid_t id1, uid_t id2, - int flags) -{ - return cap_task_post_setuid(id0, id1, id2, flags); -} - -static void subdomain_task_reparent_to_init(struct task_struct *p) -{ - cap_task_reparent_to_init(p); - return; -} - -static int subdomain_shm_shmat(struct shmid_kernel* shp, char __user *shmaddr, - int shmflg) -{ - struct sdfile *sdf = (struct sdfile *)shp->shm_file->f_security; - - if (sdf) - sdf->type = sd_file_shmem; - - return 0; -} - -static int subdomain_getprocattr(struct task_struct *p, char *name, void *value, - size_t size) -{ - int error; - struct subdomain sdcopy, *sd; - char *str = value; - unsigned long flags; - - /* Subdomain only supports the "current" process attribute */ - if (strcmp(name, "current") != 0) { - error = -EINVAL; - goto out; - } - - if (!size) { - error = -ERANGE; - goto out; - } - - /* must be task querying itself or admin */ - if (current != p && !capable(CAP_SYS_ADMIN)) { - error = -EPERM; - goto out; - } - - read_lock_irqsave(&sd_lock, flags); - - sd = __get_sdcopy(&sdcopy, p); - - read_unlock_irqrestore(&sd_lock, flags); - - error = sd_getprocattr(sd, str, size); - put_sdcopy(sd); - -out: - return error; -} - -static int subdomain_setprocattr(struct task_struct *p, char *name, void *value, - size_t size) -{ - const char *cmd_changehat = "changehat ", - *cmd_setprofile = "setprofile "; - - int error = -EACCES; /* default to a perm denied */ - char *cmd = (char *)value; - - /* only support messages to current */ - if (strcmp(name, "current") != 0) { - error = -EINVAL; - goto out; - } - - if (!size) { - error = -ERANGE; - goto out; - } - - /* CHANGE HAT */ - if (size > strlen(cmd_changehat) && - strncmp(cmd, cmd_changehat, strlen(cmd_changehat)) == 0) { - char *hatinfo = cmd + strlen(cmd_changehat); - size_t infosize = size - strlen(cmd_changehat); - - /* Only the current process may change it's hat */ - if (current != p) { - SD_WARN("%s: Attempt by foreign task %s(%d) " - "[user %d] to changehat of task %s(%d)\n", - __FUNCTION__, - current->comm, - current->pid, - current->uid, - p->comm, - p->pid); - - error = -EACCES; - goto out; - } - - error = sd_setprocattr_changehat(hatinfo, infosize); - if (error == 0) - /* success, set return to #bytes in orig request */ - error = size; - - /* SET NEW PROFILE */ - } else if (size > strlen(cmd_setprofile) && - strncmp(cmd, cmd_setprofile, strlen(cmd_setprofile)) == 0) { - int confined; - unsigned long flags; - - /* only an unconfined process with admin capabilities - * may change the profile of another task - */ - - if (!capable(CAP_SYS_ADMIN)) { - SD_WARN("%s: Unprivileged attempt by task %s(%d) " - "[user %d] to assign profile to task %s(%d)\n", - __FUNCTION__, - current->comm, - current->pid, - current->uid, - p->comm, - p->pid); - error = -EACCES; - goto out; - } - - read_lock_irqsave(&sd_lock, flags); - confined = sd_is_confined(); - read_unlock_irqrestore(&sd_lock, flags); - - if (!confined) { - char *profile = cmd + strlen(cmd_setprofile); - size_t profilesize = size - strlen(cmd_setprofile); - - error = sd_setprocattr_setprofile(p, profile, profilesize); - if (error == 0) - /* success, - * set return to #bytes in orig request - */ - error = size; - } else { - SD_WARN("%s: Attempt by confined task %s(%d) " - "[user %d] to assign profile to task %s(%d)\n", - __FUNCTION__, - current->comm, - current->pid, - current->uid, - p->comm, - p->pid); - - error = -EACCES; - } - } else { - /* unknown operation */ - SD_WARN("%s: Unknown setprocattr command '%.*s' by task %s(%d) " - "[user %d] for task %s(%d)\n", - __FUNCTION__, - size < 16 ? (int)size : 16, - cmd, - current->comm, - current->pid, - current->uid, - p->comm, - p->pid); - - error = -EINVAL; - } - -out: - return error; -} - -struct security_operations subdomain_ops = { - .ptrace = subdomain_ptrace, - .capget = subdomain_capget, - .capset_check = subdomain_capset_check, - .capset_set = subdomain_capset_set, - .sysctl = subdomain_sysctl, - .capable = subdomain_capable, - .syslog = subdomain_syslog, - - .netlink_send = subdomain_netlink_send, - .netlink_recv = subdomain_netlink_recv, - - .bprm_apply_creds = subdomain_bprm_apply_creds, - .bprm_set_security = subdomain_bprm_set_security, - .bprm_secureexec = subdomain_bprm_secureexec, - - .sb_mount = subdomain_sb_mount, - .sb_umount = subdomain_umount, - - .inode_mkdir = subdomain_inode_mkdir, - .inode_rmdir = subdomain_inode_rmdir, - .inode_create = subdomain_inode_create, - .inode_link = subdomain_inode_link, - .inode_unlink = subdomain_inode_unlink, - .inode_mknod = subdomain_inode_mknod, - .inode_rename = subdomain_inode_rename, - .inode_permission = subdomain_inode_permission, - .inode_setattr = subdomain_inode_setattr, - .inode_setxattr = subdomain_inode_setxattr, - .inode_getxattr = subdomain_inode_getxattr, - .inode_listxattr = subdomain_inode_listxattr, - .inode_removexattr = subdomain_inode_removexattr, - .file_permission = subdomain_file_permission, - .file_alloc_security = subdomain_file_alloc_security, - .file_free_security = subdomain_file_free_security, - .file_mmap = subdomain_file_mmap, - .file_mprotect = subdomain_file_mprotect, - - .task_alloc_security = subdomain_task_alloc_security, - .task_free_security = subdomain_task_free_security, - .task_post_setuid = subdomain_task_post_setuid, - .task_reparent_to_init = subdomain_task_reparent_to_init, - - .shm_shmat = subdomain_shm_shmat, - - .getprocattr = subdomain_getprocattr, - .setprocattr = subdomain_setprocattr, -}; - -static int __init subdomain_init(void) -{ - int error = 0; - const char *complainmsg = ": complainmode enabled"; - - if (!create_subdomainfs()) { - SD_ERROR("Unable to activate AppArmor filesystem\n"); - error = -ENOENT; - goto createfs_out; - } - - if (!alloc_nullprofiles()){ - SD_ERROR("Unable to allocate null profiles\n"); - error = -ENOMEM; - goto alloc_out; - } - - if ((error = register_security(&subdomain_ops))) { - SD_WARN("Unable to load AppArmor\n"); - goto register_security_out; - } - - SD_INFO("AppArmor (version %s) initialized%s\n", - apparmor_version(), - subdomain_complain ? complainmsg : ""); - sd_audit_message(NULL, GFP_KERNEL, 0, - "AppArmor (version %s) initialized%s\n", - apparmor_version(), - subdomain_complain ? complainmsg : ""); - - return error; - -register_security_out: - free_nullprofiles(); - -alloc_out: - (void)destroy_subdomainfs(); - -createfs_out: - return error; - -} - -static int subdomain_exit_removeall_iter(struct subdomain *sd, void *cookie) -{ - /* write_lock(&sd_lock) held here */ - - if (__sd_is_confined(sd)) { - SD_DEBUG("%s: Dropping profiles %s(%d) " - "profile %s(%p) active %s(%p)\n", - __FUNCTION__, - sd->task->comm, sd->task->pid, - sd->profile->name, sd->profile, - sd->active->name, sd->active); - sd_switch_unconfined(sd); - } - - return 0; -} - -static void __exit subdomain_exit(void) -{ - unsigned long flags; - - /* Remove profiles from the global profile list. - * This is just for tidyness as there is no way to reference this - * list once the AppArmor lsm hooks are detached (below) - */ - sd_profilelist_release(); - - /* Remove profiles from active tasks - * If this is not done, if module is reloaded after being removed, - * old profiles (still refcounted in memory) will become 'magically' - * reattached - */ - - write_lock_irqsave(&sd_lock, flags); - sd_subdomainlist_iterate(subdomain_exit_removeall_iter, NULL); - write_unlock_irqrestore(&sd_lock, flags); - - /* Free up list of active subdomain */ - sd_subdomainlist_release(); - - free_nullprofiles(); - - if (!destroy_subdomainfs()) - SD_WARN("Unable to properly deactivate AppArmor fs\n"); - - if (unregister_security(&subdomain_ops)) - SD_WARN("Unable to properly unregister AppArmor\n"); - - SD_INFO("AppArmor protection removed\n"); - sd_audit_message(NULL, GFP_KERNEL, 0, - "AppArmor protection removed\n"); -} - -module_init(subdomain_init); -module_exit(subdomain_exit); - -MODULE_DESCRIPTION("AppArmor process confinement"); -MODULE_AUTHOR("Tony Jones "); -MODULE_LICENSE("GPL"); diff --git a/module-nextgen/main.c b/module-nextgen/main.c deleted file mode 100644 index 49802bdb0..000000000 --- a/module-nextgen/main.c +++ /dev/null @@ -1,1691 +0,0 @@ -/* - * Copyright (C) 2002-2005 Novell/SUSE - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. - * - * AppArmor Core - */ - -#include -#include -#include - -#include "apparmor.h" -#include "aamatch/match.h" - -#include "inline.h" - -/* NULL profile - * - * Used when an attempt is made to changehat into a non-existant - * subhat. In the NULL profile, no file access is allowed - * (currently full network access is allowed). Using a NULL - * profile ensures that active is always non zero. - * - * Leaving the NULL profile is by either successfully changehatting - * into a sibling hat, or changehatting back to the parent (NULL hat). - */ -struct sdprofile *null_profile; - -/* NULL complain profile - * - * Used when in complain mode, to emit Permitting messages for non-existant - * profiles and hats. This is necessary because of selective mode, in which - * case we need a complain null_profile and enforce null_profile - * - * The null_complain_profile cannot be statically allocated, because it - * can be associated to files which keep their reference even if subdomain is - * unloaded - */ -struct sdprofile *null_complain_profile; - -/*************************** - * PRIVATE UTILITY FUNCTIONS - **************************/ - -/** - * dentry_xlate_error - * @dentry: pointer to dentry - * @error: error number - * @dtype: type of dentry - * - * Display error message when a dentry translation error occured - */ -static void dentry_xlate_error(struct dentry *dentry, int error, char *dtype) -{ - const unsigned int len = 16; - char buf[len]; - - if (dentry->d_inode) { - snprintf(buf, len, "%lu", dentry->d_inode->i_ino); - } else { - strncpy(buf, "", len); - buf[len-1]=0; - } - - SD_ERROR("An error occured while translating %s %p " - "inode# %s to a pathname. Error %d\n", - dtype, - dentry, - buf, - error); -} - -/** - * sd_taskattr_access: - * @name: name of file to check permission - * @mask: permission mask requested for file - * - * Determine if request is for write access to /proc/self/attr/current - */ -static inline int sd_taskattr_access(const char *procrelname) -{ -/* - * assumes a 32bit pid, which requires max 10 decimal digits to represent - * sizeof includes trailing \0 - */ - char buf[sizeof("/attr/current") + 10]; - const int maxbuflen = sizeof(buf); - - snprintf(buf, maxbuflen, "%d/attr/current", current->pid); - buf[maxbuflen - 1] = 0; - - return strcmp(buf, procrelname) == 0; -} - -/** - * sd_file_mode - get full mode for file entry from profile - * @profile: profile - * @name: filename - */ -static inline int sd_file_mode(struct sdprofile *profile, const char *name) -{ - struct sd_entry *entry; - int mode = 0; - - SD_DEBUG("%s: %s\n", __FUNCTION__, name); - if (!name) { - SD_DEBUG("%s: no name\n", __FUNCTION__); - goto out; - } - - if (!profile) { - SD_DEBUG("%s: no profile\n", __FUNCTION__); - goto out; - } - list_for_each_entry(entry, &profile->file_entry, list) { - if (sdmatch_match(name, entry->filename, - entry->entry_type, entry->extradata)) - mode |= entry->mode; - } -out: - return mode; -} - -/** - * sd_get_execmode - calculate what qualifier to apply to an exec - * @sd: subdomain to search - * @name: name of file to exec - * @xmod: pointer to a execution mode bit for the rule that was matched - * if the rule has no execuition qualifier {pui} then - * SD_MAY_EXEC is returned indicating a naked x - * if the has an exec qualifier then only the qualifier bit {pui} - * is returned (SD_MAY_EXEC) is not set. - * @unsafe: true if secure_exec should be overridden - * - * Returns 0 (false): - * if unable to find profile or there are conflicting pattern matches. - * *xmod - is not modified - * *unsafe - is not modified - * - * Returns 1 (true): - * if not confined - * *xmod = SD_MAY_EXEC - * *unsafe = 0 - * if exec rule matched - * if the rule has an execution mode qualifier {pui} then - * *xmod = the execution qualifier of the rule {pui} - * else - * *xmod = SD_MAY_EXEC - * unsafe = presence of unsafe flag - */ -static inline int sd_get_execmode(struct subdomain *sd, const char *name, - int *xmod, int *unsafe) -{ - struct sdprofile *profile; - struct sd_entry *entry; - struct sd_entry *match = NULL; - - int pattern_match_invalid = 0, rc = 0; - - /* not confined */ - if (!__sd_is_confined(sd)) { - SD_DEBUG("%s: not confined\n", __FUNCTION__); - goto not_confined; - } - - profile = sd->active; - - /* search list of profiles with 'x' permission - * this will also include entries with 'p', 'u' and 'i' - * qualifiers. - * - * If we find a pattern match we will keep looking for an exact match - * If we find conflicting pattern matches we will flag (while still - * looking for an exact match). If all we have is a conflict, FALSE - * is returned. - */ - - list_for_each_entry(entry, &profile->file_entryp[POS_SD_MAY_EXEC], - listp[POS_SD_MAY_EXEC]) { - if (!pattern_match_invalid && - entry->entry_type == sd_entry_pattern && - sdmatch_match(name, entry->filename, - entry->entry_type, entry->extradata)) { - if (match && - SD_EXEC_UNSAFE_MASK(entry->mode) != - SD_EXEC_UNSAFE_MASK(match->mode)) - pattern_match_invalid = 1; - else - /* keep searching for an exact match */ - match = entry; - } else if ((entry->entry_type == sd_entry_literal || - (!pattern_match_invalid && - entry->entry_type == sd_entry_tailglob)) && - sdmatch_match(name, entry->filename, - entry->entry_type, - entry->extradata)) { - if (entry->entry_type == sd_entry_literal) { - /* got an exact match -- there can be only - * one, asserted at profile load time - */ - match = entry; - pattern_match_invalid = 0; - break; - } else { - if (match && - SD_EXEC_UNSAFE_MASK(entry->mode) != - SD_EXEC_UNSAFE_MASK(match->mode)) - pattern_match_invalid = 1; - else - /* got a tailglob match, keep searching - * for an exact match - */ - match = entry; - } - } - - } - - rc = match && !pattern_match_invalid; - - if (rc) { - int mode = SD_EXEC_MASK(match->mode); - - /* check for qualifiers, if present - * we just return the qualifier - */ - if (mode & ~SD_MAY_EXEC) - mode = mode & ~SD_MAY_EXEC; - - *xmod = mode; - *unsafe = (match->mode & SD_EXEC_UNSAFE); - } else if (!match) { - SD_DEBUG("%s: Unable to find execute entry in profile " - "for image '%s'\n", - __FUNCTION__, - name); - } else if (pattern_match_invalid) { - SD_WARN("%s: Inconsistency in profile %s. " - "Two (or more) patterns specify conflicting exec " - "qualifiers ('u', 'i' or 'p') for image %s\n", - __FUNCTION__, - sd->active->name, - name); - } - - return rc; - -not_confined: - *xmod = SD_MAY_EXEC; - *unsafe = 0; - return 1; -} - -/** - * sd_filter_mask - * @mask: requested mask - * @inode: potential directory inode - * - * This fn performs pre-verification of the requested mask - * We ignore append. Previously we required 'w' on a dir to add a file. - * No longer. Now we require 'w' on just the file itself. Traversal 'x' is - * also ignored for directories. - * - * Returned value of 0 indicates no need to perform a perm check. - */ -static inline int sd_filter_mask(int mask, struct inode *inode) -{ - if (mask) { - int elim = MAY_APPEND; - - if (inode && S_ISDIR(inode->i_mode)) - elim |= (MAY_EXEC | MAY_WRITE); - - mask &= ~elim; - } - - return mask; -} - -static inline void sd_permerror2result(int perm_result, struct sd_audit *sa) -{ - if (perm_result == 0) { /* success */ - sa->result = 1; - sa->errorcode = 0; - } else { /* -ve internal error code or +ve mask of denied perms */ - sa->result = 0; - sa->errorcode = perm_result; - } -} - -/************************* - * MAIN INTERNAL FUNCTIONS - ************************/ - -/** - * sd_file_perm - calculate access mode for file - * @subdomain: current subdomain - * @name: name of file to calculate mode for - * @mask: permission mask requested for file - * - * Search the sd_entry list in @profile. - * Search looking to verify all permissions passed in mask. - * Perform the search by looking at the partitioned list of entries, one - * partition per permission bit. - * - * Return 0 on success, else mask of non-allowed permissions - */ -static unsigned int sd_file_perm(struct subdomain *sd, const char *name, - int mask) -{ - struct sdprofile *profile; - int i, error = 0, mode; - -#define PROCPFX "/proc/" -#define PROCLEN sizeof(PROCPFX) - 1 - - SD_DEBUG("%s: %s 0x%x\n", __FUNCTION__, name, mask); - - /* should not enter with other than R/W/M/X/L */ - BUG_ON(mask & - ~(SD_MAY_READ | SD_MAY_WRITE | SD_MAY_EXEC | - SD_EXEC_MMAP | SD_MAY_LINK)); - - /* not confined */ - if (!__sd_is_confined(sd)) { - /* exit with access allowed */ - SD_DEBUG("%s: not confined\n", __FUNCTION__); - goto done; - } - - /* Special case access to /proc/self/attr/current - * Currently we only allow access if opened O_WRONLY - */ - if (mask == MAY_WRITE && strncmp(PROCPFX, name, PROCLEN) == 0 && - (!list_empty(&sd->profile->sub) || SUBDOMAIN_COMPLAIN(sd)) && - sd_taskattr_access(name + PROCLEN)) - goto done; - - profile = sd->active; - - mode = 0; - - /* iterate over partition, one permission bit at a time */ - for (i = 0; i <= POS_SD_FILE_MAX; i++) { - struct sd_entry *entry; - - /* do we have to accumulate this bit? - * or have we already accumulated it (shortcut below)? */ - if (!(mask & (1 << i)) || mode & (1 << i)) - continue; - - list_for_each_entry(entry, &profile->file_entryp[i], - listp[i]) { - if (sdmatch_match(name, entry->filename, - entry->entry_type, entry->extradata)) { - /* Shortcut, accumulate all bits present */ - mode |= entry->mode; - - /* - * Mask bits are overloaded - * MAY_{EXEC,WRITE,READ,APPEND} are used by - * kernel, other values are used locally only. - */ - if ((mode & mask) == mask) { - SD_DEBUG("MATCH! %s=0x%x [total mode=0x%x]\n", - name, mask, mode); - - goto done; - } - } - } - } - - /* return permissions not satisfied */ - error = mask & ~mode; - -done: - return error; -} - -/** - * sd_link_perm - test permission to link to a file - * @sd: current subdomain - * @link: name of link being created - * @target: name of target to be linked to - * - * Look up permission mode on both @link and @target. @link must have same - * permission mode as @target. At least @link must have the link bit enabled. - * Return 0 on success, error otherwise. - */ -static int sd_link_perm(struct subdomain *sd, - const char *link, const char *target) -{ - int l_mode, t_mode, ret; - struct sdprofile *profile = sd->active; - - l_mode = sd_file_mode(profile, link); - if (l_mode & SD_MAY_LINK) { - /* mask off link bit */ - l_mode &= ~SD_MAY_LINK; - - t_mode = sd_file_mode(profile, target); - t_mode &= ~SD_MAY_LINK; - - ret = (l_mode == t_mode); - } else { - ret = 0; - } - - return ret; -} - -/** - * _sd_perm_dentry - * @sd: current subdomain - * @dentry: requested dentry - * @mask: mask of requested operations - * @pname: pointer to hold matched pathname (if any) - * - * Helper function. Obtain pathname for specified dentry. Verify if profile - * authorizes mask operations on pathname (due to lack of vfsmnt it is sadly - * necessary to search mountpoints in namespace -- when nameidata is passed - * more fully, this code can go away). If more than one mountpoint matches - * but none satisfy the profile, only the first pathname (mountpoint) is - * returned for subsequent logging. - * - * Return 0 (success), +ve (mask of permissions not satisfied) or -ve (system - * error, most likely -ENOMEM). - */ -static int _sd_perm_dentry(struct subdomain *sd, struct dentry *dentry, - int mask, const char **pname) -{ - char *name = NULL, *failed_name = NULL; - struct sd_path_data data; - int error = 0, failed_error = 0, sdpath_error, - sdcomplain = SUBDOMAIN_COMPLAIN(sd); - - /* search all paths to dentry */ - - sd_path_begin(dentry, &data); - do { - name = sd_path_getname(&data); - if (name) { - /* error here is 0 (success) or +ve (mask of perms) */ - error = sd_file_perm(sd, name, mask); - - /* access via any path is enough */ - if (sdcomplain || error == 0) - break; /* Caller must free name */ - - /* Already have an path that failed? */ - if (failed_name) { - sd_put_name(name); - } else { - failed_name = name; - failed_error = error; - } - } - } while (name); - - if ((sdpath_error = sd_path_end(&data)) != 0) { - dentry_xlate_error(dentry, sdpath_error, "dentry"); - - WARN_ON(name); /* name should not be set if error */ - error = sdpath_error; - name = NULL; - } else if (name) { - if (failed_name) - sd_put_name(failed_name); - } else { - name = failed_name; - error = failed_error; - } - - *pname = name; - - return error; -} - -/************************** - * GLOBAL UTILITY FUNCTIONS - *************************/ - -/** - * alloc_nullprofiles - Allocate null profiles - */ -int alloc_nullprofiles(void) -{ - null_profile = alloc_sdprofile(); - null_complain_profile = alloc_sdprofile(); - - if (!null_profile || !null_complain_profile) - goto fail; - - null_profile->name = kstrdup("null-profile", GFP_KERNEL); - null_complain_profile->name = - kstrdup("null-complain-profile", GFP_KERNEL); - - if (!null_profile->name || - !null_complain_profile->name) - goto fail; - - get_sdprofile(null_profile); - get_sdprofile(null_complain_profile); - null_complain_profile->flags.complain = 1; - - return 1; - -fail: - /* free_sdprofile is safe for freeing partially constructed objects */ - free_sdprofile(null_profile); - free_sdprofile(null_complain_profile); - null_profile = null_complain_profile = NULL; - return 0; -} - -/** - * free_nullprofiles - Free null profiles - */ -void free_nullprofiles(void) -{ - put_sdprofile(null_complain_profile); - put_sdprofile(null_profile); - null_profile = null_complain_profile = NULL; -} - -/** - * sd_audit_message - Log a message to the audit subsystem - * @sd: current subdomain - * @gfp: allocation flags - * @flags: audit flags - * @fmt: varargs fmt - */ -int sd_audit_message(struct subdomain *sd, unsigned int gfp, int flags, - const char *fmt, ...) -{ - int ret; - struct sd_audit sa; - - sa.type = SD_AUDITTYPE_MSG; - sa.name = fmt; - va_start(sa.vaval, fmt); - sa.flags = flags; - sa.gfp_mask = gfp; - sa.errorcode = 0; - sa.result = 0; /* fake failure: force message to be logged */ - - ret = sd_audit(sd, &sa); - - va_end(sa.vaval); - - return ret; -} - -/** - * sd_audit_syscallreject - Log a syscall rejection to the audit subsystem - * @sd: current subdomain - * @msg: string describing syscall being rejected - * @gfp: memory allocation flags - */ -int sd_audit_syscallreject(struct subdomain *sd, unsigned int gfp, - const char *msg) -{ - struct sd_audit sa; - - sa.type = SD_AUDITTYPE_SYSCALL; - sa.name = msg; - sa.flags = 0; - sa.gfp_mask = gfp; - sa.errorcode = 0; - sa.result = 0; /* failure */ - - return sd_audit(sd, &sa); -} - -/** - * sd_audit - Log an audit event to the audit subsystem - * @sd: current subdomain - * @sa: audit event - */ -int sd_audit(struct subdomain *sd, const struct sd_audit *sa) -{ - struct audit_buffer *ab = NULL; - struct audit_context *ctx; - - const char *logcls; - unsigned int flags; - int sdaudit = 0, - sdcomplain = 0, - error = -EINVAL, - opspec_error = -EACCES; - - const unsigned int gfp_mask = sa->gfp_mask; - - WARN_ON(sa->type >= SD_AUDITTYPE__END); - - /* - * sa->result: 1 success, 0 failure - * sa->errorcode: success: 0 - * failure: +ve mask of failed permissions or -ve - * system error - */ - - if (likely(sa->result)) { - if (likely(!SUBDOMAIN_AUDIT(sd))) { - /* nothing to log */ - error = 0; - goto out; - } else { - sdaudit = 1; - logcls = "AUDITING"; - } - } else if (sa->errorcode < 0) { - audit_log(current->audit_context, gfp_mask, AUDIT_SD, - "Internal error auditing event type %d (error %d)", - sa->type, sa->errorcode); - SD_ERROR("Internal error auditing event type %d (error %d)\n", - sa->type, sa->errorcode); - error = sa->errorcode; - goto out; - } else if (sa->type == SD_AUDITTYPE_SYSCALL) { - /* Currently SD_AUDITTYPE_SYSCALL is for rejects only. - * Values set by sd_audit_syscallreject will get us here. - */ - logcls = "REJECTING"; - } else { - sdcomplain = SUBDOMAIN_COMPLAIN(sd); - logcls = sdcomplain ? "PERMITTING" : "REJECTING"; - } - - /* In future extend w/ per-profile flags - * (flags |= sa->active->flags) - */ - flags = sa->flags; - if (subdomain_logsyscall) - flags |= SD_AUDITFLAG_AUDITSS_SYSCALL; - - - /* Force full audit syscall logging regardless of global setting if - * we are rejecting a syscall - */ - if (sa->type == SD_AUDITTYPE_SYSCALL) { - ctx = current->audit_context; - } else { - ctx = (flags & SD_AUDITFLAG_AUDITSS_SYSCALL) ? - current->audit_context : NULL; - } - - ab = audit_log_start(ctx, gfp_mask, AUDIT_SD); - - if (!ab) { - SD_ERROR("Unable to log event (%d) to audit subsys\n", - sa->type); - if (sdcomplain) - error = 0; - goto out; - } - - /* messages get special handling */ - if (sa->type == SD_AUDITTYPE_MSG) { - audit_log_vformat(ab, sa->name, sa->vaval); - audit_log_end(ab); - error = 0; - goto out; - } - - /* log operation */ - - audit_log_format(ab, "%s ", logcls); /* REJECTING/ALLOWING/etc */ - - if (sa->type == SD_AUDITTYPE_FILE) { - int perm = sdaudit ? sa->ival : sa->errorcode; - - audit_log_format(ab, "%s%s%s%s%s access to %s ", - perm & SD_EXEC_MMAP ? "m" : "", - perm & SD_MAY_READ ? "r" : "", - perm & SD_MAY_WRITE ? "w" : "", - perm & SD_MAY_EXEC ? "x" : "", - perm & SD_MAY_LINK ? "l" : "", - sa->name); - - opspec_error = -EPERM; - - } else if (sa->type == SD_AUDITTYPE_DIR) { - audit_log_format(ab, "%s on %s ", - sa->ival == SD_DIR_MKDIR ? "mkdir" : "rmdir", - sa->name); - - } else if (sa->type == SD_AUDITTYPE_ATTR) { - struct iattr *iattr = (struct iattr*)sa->pval; - - audit_log_format(ab, - "attribute (%s%s%s%s%s%s%s) change to %s ", - iattr->ia_valid & ATTR_MODE ? "mode," : "", - iattr->ia_valid & ATTR_UID ? "uid," : "", - iattr->ia_valid & ATTR_GID ? "gid," : "", - iattr->ia_valid & ATTR_SIZE ? "size," : "", - ((iattr->ia_valid & ATTR_ATIME_SET) || - (iattr->ia_valid & ATTR_ATIME)) ? "atime," : "", - ((iattr->ia_valid & ATTR_MTIME_SET) || - (iattr->ia_valid & ATTR_MTIME)) ? "mtime," : "", - iattr->ia_valid & ATTR_CTIME ? "ctime," : "", - sa->name); - - } else if (sa->type == SD_AUDITTYPE_XATTR) { - const char *fmt; - switch (sa->ival) { - case SD_XATTR_GET: - fmt = "xattr get"; - break; - case SD_XATTR_SET: - fmt = "xattr set"; - break; - case SD_XATTR_LIST: - fmt = "xattr list"; - break; - case SD_XATTR_REMOVE: - fmt = "xattr remove"; - break; - default: - fmt = "xattr "; - break; - } - - audit_log_format(ab, "%s on %s ", fmt, sa->name); - - } else if (sa->type == SD_AUDITTYPE_LINK) { - audit_log_format(ab, - "link access from %s to %s ", - sa->name, - (char*)sa->pval); - - } else if (sa->type == SD_AUDITTYPE_CAP) { - audit_log_format(ab, - "access to capability '%s' ", - capability_to_name(sa->ival)); - - opspec_error = -EPERM; - } else if (sa->type == SD_AUDITTYPE_SYSCALL) { - audit_log_format(ab, "access to syscall '%s' ", sa->name); - - opspec_error = -EPERM; - } else { - /* -EINVAL -- will WARN_ON above */ - goto out; - } - - audit_log_format(ab, "(%s(%d) profile %s active %s)", - current->comm, current->pid, - sd->profile->name, sd->active->name); - - audit_log_end(ab); - - if (sdcomplain) - error = 0; - else - error = sa->result ? 0 : opspec_error; - -out: - return error; -} - -/** - * sd_get_name - retrieve fully qualified path name - * @dentry: relative path element - * @mnt: where in tree - * - * Returns fully qualified path name on sucess, NULL on failure. - * sd_put_name must be used to free allocated buffer. - */ -char *sd_get_name(struct dentry *dentry, struct vfsmount *mnt) -{ - char *page, *name; - - page = (char *)__get_free_page(GFP_KERNEL); - if (!page) { - name = ERR_PTR(-ENOMEM); - goto out; - } - - name = d_path(dentry, mnt, page, PAGE_SIZE); - - /* check for (deleted) that d_path appends to pathnames if the dentry - * has been removed from the cache. - * The size > deleted_size and strcmp checks are redundant safe guards. - */ - if (IS_ERR(name)) { - free_page((unsigned long)page); - } else { - const char deleted_str[] = " (deleted)"; - const size_t deleted_size = sizeof(deleted_str) - 1; - size_t size; - size = strlen(name); - if (!IS_ROOT(dentry) && d_unhashed(dentry) && - size > deleted_size && - strcmp(name + size - deleted_size, deleted_str) == 0) - name[size - deleted_size] = '\0'; - - SD_DEBUG("%s: full_path=%s\n", __FUNCTION__, name); - } - -out: - return name; -} - -/*********************************** - * GLOBAL PERMISSION CHECK FUNCTIONS - ***********************************/ - -/** - * sd_attr - check whether attribute change allowed - * @sd: subdomain to check against to check against - * @dentry: file to check - * @iattr: attribute changes requested - */ -int sd_attr(struct subdomain *sd, struct dentry *dentry, struct iattr *iattr) -{ - int error = 0, permerror; - struct sd_audit sa; - - if (!__sd_is_confined(sd)) - goto out; - - sa.type = SD_AUDITTYPE_ATTR; - sa.pval = iattr; - sa.flags = 0; - sa.gfp_mask = GFP_KERNEL; - - permerror = _sd_perm_dentry(sd, dentry, MAY_WRITE, &sa.name); - sd_permerror2result(permerror, &sa); - - error = sd_audit(sd, &sa); - - sd_put_name(sa.name); - -out: - return error; -} - -int sd_xattr(struct subdomain *sd, struct dentry *dentry, const char *xattr, - int xattroptype) -{ - int error = 0, permerror, mask = 0; - struct sd_audit sa; - - /* if not confined or empty mask permission granted */ - if (!__sd_is_confined(sd)) - goto out; - - if (xattroptype == SD_XATTR_GET || xattroptype == SD_XATTR_LIST) - mask = MAY_READ; - else if (xattroptype == SD_XATTR_SET || xattroptype == SD_XATTR_REMOVE) - mask = MAY_WRITE; - - sa.type = SD_AUDITTYPE_XATTR; - sa.ival = xattroptype; - sa.pval = xattr; - sa.flags = 0; - sa.gfp_mask = GFP_KERNEL; - - permerror = _sd_perm_dentry(sd, dentry, mask, &sa.name); - sd_permerror2result(permerror, &sa); - - error = sd_audit(sd, &sa); - - sd_put_name(sa.name); - -out: - return error; -} - -/** - * sd_perm - basic subdomain permissions check - * @sd: subdomain to check against - * @dentry: dentry - * @mnt: mountpoint - * @mask: access mode requested - * - * Determine if access (mask) for dentry is authorized by subdomain sd. - * Result, 0 (success), -ve (error) - */ -int sd_perm(struct subdomain *sd, struct dentry *dentry, struct vfsmount *mnt, - int mask) -{ - int error = 0, permerror; - struct sd_audit sa; - - if (!__sd_is_confined(sd)) - goto out; - - if ((mask = sd_filter_mask(mask, dentry->d_inode)) == 0) - goto out; - - sa.type = SD_AUDITTYPE_FILE; - sa.name = sd_get_name(dentry, mnt); - sa.ival = mask; - sa.flags = 0; - sa.gfp_mask = GFP_KERNEL; - - if (IS_ERR(sa.name)) { - permerror = PTR_ERR(sa.name); - sa.name = NULL; - } else { - permerror = sd_file_perm(sd, sa.name, mask); - } - - sd_permerror2result(permerror, &sa); - - error = sd_audit(sd, &sa); - - sd_put_name(sa.name); - -out: - return error; -} - -/** - * sd_perm_nameidata: interface to sd_perm accepting nameidata - * @sd: subdomain to check against - * @nd: namespace data (for vfsmnt and dentry) - * @mask: access mode requested - */ -int sd_perm_nameidata(struct subdomain *sd, struct nameidata *nd, int mask) -{ - int error = 0; - - if (nd) - error = sd_perm(sd, nd->dentry, nd->mnt, mask); - - return error; -} - -/** - * sd_perm_dentry - file permissions interface when no vfsmnt available - * @sd: current subdomain - * @dentry: requested dentry - * @mask: access mode requested - * - * Determine if access (mask) for dentry is authorized by subdomain sd. - * Result, 0 (success), -ve (error) - */ -int sd_perm_dentry(struct subdomain *sd, struct dentry *dentry, int mask) -{ - int error = 0, permerror; - struct sd_audit sa; - - if (!__sd_is_confined(sd)) - goto out; - - if ((mask = sd_filter_mask(mask, dentry->d_inode)) == 0) - goto out; - - sa.type = SD_AUDITTYPE_FILE; - sa.ival = mask; - sa.flags = 0; - sa.gfp_mask = GFP_KERNEL; - - permerror = _sd_perm_dentry(sd, dentry, mask, &sa.name); - sd_permerror2result(permerror, &sa); - - error = sd_audit(sd, &sa); - - sd_put_name(sa.name); - -out: - return error; -} - -/** - * sd_perm_dir - * @sd: current subdomain - * @dentry: requested dentry - * @mode: SD_DIR_MKDIR or SD_DIR_RMDIR - * - * Determine if directory operation (make/remove) for dentry is authorized - * by subdomain sd. - * Result, 0 (success), -ve (error) - */ -int sd_perm_dir(struct subdomain *sd, struct dentry *dentry, int diroptype) -{ - int error = 0, permerror, mask; - struct sd_audit sa; - - BUG_ON(diroptype != SD_DIR_MKDIR && diroptype != SD_DIR_RMDIR); - - if (!__sd_is_confined(sd)) - goto out; - - mask = MAY_WRITE; - - sa.type = SD_AUDITTYPE_DIR; - sa.ival = diroptype; - sa.flags = 0; - sa.gfp_mask = GFP_KERNEL; - - permerror = _sd_perm_dentry(sd, dentry, mask, &sa.name); - sd_permerror2result(permerror, &sa); - - error = sd_audit(sd, &sa); - - sd_put_name(sa.name); - -out: - return error; -} - -/** - * sd_capability - test permission to use capability - * @sd: subdomain to check against - * @cap: capability to be tested - * - * Look up capability in active profile capability set. - * Return 0 (success), -EPERM (error) - */ -int sd_capability(struct subdomain *sd, int cap) -{ - int error = 0; - - if (__sd_is_confined(sd)) { - struct sd_audit sa; - - sa.type = SD_AUDITTYPE_CAP; - sa.name = NULL; - sa.ival = cap; - sa.flags = 0; - sa.errorcode = 0; - sa.result = cap_raised(sd->active->capabilities, cap); - sa.gfp_mask = GFP_ATOMIC; - - error = sd_audit(sd, &sa); - } - - return error; -} - -/** - * sd_link - hard link check - * @link: dentry for link being created - * @target: dentry for link target - * @sd: subdomain to check against - * - * Checks link permissions for all possible name combinations. This is - * particularly ugly. Returns 0 on sucess, error otherwise. - */ -int sd_link(struct subdomain *sd, struct dentry *link, struct dentry *target) -{ - char *iname = NULL, *oname = NULL, - *failed_iname = NULL, *failed_oname = NULL; - unsigned int result = 0; - int error, sdpath_error, errorcode = 0, match = 0, - sdcomplain = SUBDOMAIN_COMPLAIN(sd); - struct sd_path_data idata, odata; - struct sd_audit sa; - - if (!__sd_is_confined(sd)) - return 0; - - /* Perform nested lookup for names. - * This is necessary in the case where /dev/block is mounted - * multiple times, i.e /dev/block->/a and /dev/block->/b - * This allows us to detect links where src/dest are on different - * mounts. N.B no support yet for links across bind mounts of - * the form mount -bind /mnt/subpath /mnt2 - * - * Getting direct access to vfsmounts (via nameidata) for link and - * target would allow all this uglyness to go away. - * - * If more than one mountpoint matches but none satisfy the profile, - * only the first pathname (mountpoint) is logged. - */ - - sd_path_begin2(target, link, &odata); - do { - oname = sd_path_getname(&odata); - if (oname) { - sd_path_begin(target, &idata); - do { - iname = sd_path_getname(&idata); - if (iname) { - result = sd_link_perm(sd, oname, iname); - - /* access via any path is enough */ - if (result || sdcomplain) { - match = 1; - break; - } - - /* Already have an path that failed? */ - if (failed_iname) { - sd_put_name(iname); - } else { - failed_iname = iname; - failed_oname = oname; - } - } - } while (iname && !match); - - /* should not be possible if we matched */ - if ((sdpath_error = sd_path_end(&idata)) != 0) { - dentry_xlate_error(target, sdpath_error, - "inner dentry [link]"); - - /* name should not be set if error */ - WARN_ON(iname); - - errorcode = sdpath_error; - } - - /* don't release if we're saving it */ - if (!match && failed_oname != oname) - sd_put_name(oname); - } - } while (oname && !match); - - if (errorcode != 0) { - /* inner error */ - (void)sd_path_end(&odata); - } else if ((sdpath_error = sd_path_end(&odata)) != 0) { - dentry_xlate_error(link, sdpath_error, "outer dentry [link]"); - - errorcode = sdpath_error; - } - - if (errorcode != 0) { - /* inner or outer error */ - result = 0; - } else if (match) { - result = 1; - } else { - /* failed to match */ - WARN_ON(iname); - WARN_ON(oname); - - result = 0; - iname = failed_iname; - oname = failed_oname; - } - - sa.type = SD_AUDITTYPE_LINK; - sa.name = oname; /* link */ - sa.pval = iname; /* target */ - sa.flags = 0; - sa.errorcode = errorcode; - sa.result = result; - sa.gfp_mask = GFP_KERNEL; - - error = sd_audit(sd, &sa); - - if (failed_oname != oname) - sd_put_name(failed_oname); - if (failed_iname != iname) - sd_put_name(failed_iname); - - sd_put_name(oname); - sd_put_name(iname); - - return error; -} - -/********************************** - * GLOBAL PROCESS RELATED FUNCTIONS - *********************************/ - -/** - * sd_fork - create a new subdomain - * @p: new process - * - * Create a new subdomain for newly created process @p if it's parent - * is already confined. Otherwise a subdomain will be lazily allocated - * for the child if it subsequently execs (in sd_register). - * Return 0 on sucess. - */ - -int sd_fork(struct task_struct *p) -{ - struct subdomain *sd = SD_SUBDOMAIN(current->security); - struct subdomain *newsd = NULL; - - SD_DEBUG("%s\n", __FUNCTION__); - - if (__sd_is_confined(sd)) { - unsigned long flags; - - newsd = alloc_subdomain(p); - - if (!newsd) - return -ENOMEM; - - /* Can get away with a read rather than write lock here - * as we just allocated newsd above, so we can guarantee - * that it's active/profile are null and therefore a replace - * cannot happen. - */ - read_lock_irqsave(&sd_lock, flags); - sd_switch(newsd, sd->profile, sd->active); - newsd->sd_hat_magic = sd->sd_hat_magic; - read_unlock_irqrestore(&sd_lock, flags); - - if (SUBDOMAIN_COMPLAIN(sd) && - sd->active == null_complain_profile) - LOG_HINT(sd, GFP_KERNEL, HINT_FORK, - "pid=%d child=%d\n", - current->pid, p->pid); - } - p->security = newsd; - return 0; -} - -/** - * sd_register - register a new program - * @filp: file of program being registered - * - * Try to register a new program during execve(). This should give the - * new program a valid subdomain. - * - * This _used_ to be a really simple piece of code :-( - * - */ -int sd_register(struct linux_binprm *bprm) -{ - char *filename; - struct file *filp = bprm->file; - struct subdomain *sd, sdcopy; - struct sdprofile *newprofile = NULL, unconstrained_flag; - int error = -ENOMEM, - exec_mode = 0, - findprofile = 0, - findprofile_mandatory = 0, - unsafe_exec = 0, - complain = 0; - - SD_DEBUG("%s\n", __FUNCTION__); - - sd = get_sdcopy(&sdcopy); - - filename = sd_get_name(filp->f_dentry, filp->f_vfsmnt); - if (IS_ERR(filename)) { - SD_WARN("%s: Failed to get filename\n", __FUNCTION__); - goto out; - } - - error = 0; - - if (!__sd_is_confined(sd)) { - /* Unconfined task, load profile if it exists */ - findprofile = 1; - goto find_profile; - } - - complain = SUBDOMAIN_COMPLAIN(sd); - - /* Confined task, determine what mode inherit, unconstrained or - * mandatory to load new profile - */ - if (sd_get_execmode(sd, filename, &exec_mode, &unsafe_exec)) { - switch (exec_mode) { - case SD_EXEC_INHERIT: - /* do nothing - setting of profile - * already handed in sd_fork - */ - SD_DEBUG("%s: INHERIT %s\n", - __FUNCTION__, - filename); - break; - - case SD_EXEC_UNCONSTRAINED: - SD_DEBUG("%s: UNCONSTRAINED %s\n", - __FUNCTION__, - filename); - - /* unload profile */ - newprofile = &unconstrained_flag; - break; - - case SD_EXEC_PROFILE: - SD_DEBUG("%s: PROFILE %s\n", - __FUNCTION__, - filename); - - findprofile = 1; - findprofile_mandatory = 1; - break; - - case SD_MAY_EXEC: - /* this should not happen, entries - * with just EXEC only should be - * rejected at profile load time - */ - SD_ERROR("%s: Rejecting exec(2) of image '%s'. " - "SD_MAY_EXEC without exec qualifier invalid " - "(%s(%d) profile %s active %s\n", - __FUNCTION__, - filename, - current->comm, current->pid, - sd->profile->name, sd->active->name); - error = -EPERM; - break; - - default: - SD_ERROR("%s: Rejecting exec(2) of image '%s'. " - "Unknown exec qualifier %x " - "(%s (pid %d) profile %s active %s)\n", - __FUNCTION__, - filename, - exec_mode, - current->comm, current->pid, - sd->profile->name, sd->active->name); - error = -EPERM; - break; - } - - } else if (complain) { - /* There was no entry in calling profile - * describing mode to execute image in. - * Drop into null-profile (disabling secure exec). - */ - newprofile = get_sdprofile(null_complain_profile); - unsafe_exec = 1; - } else { - SD_WARN("%s: Rejecting exec(2) of image '%s'. " - "Unable to determine exec qualifier " - "(%s (pid %d) profile %s active %s)\n", - __FUNCTION__, - filename, - current->comm, current->pid, - sd->profile->name, sd->active->name); - error = -EPERM; - } - - -find_profile: - if (!findprofile) - goto apply_profile; - - /* Locate new profile */ - newprofile = sd_profilelist_find(filename); - if (newprofile) { - SD_DEBUG("%s: setting profile %s\n", - __FUNCTION__, newprofile->name); - } else if (findprofile_mandatory) { - /* Profile (mandatory) could not be found */ - - if (complain) { - LOG_HINT(sd, GFP_KERNEL, HINT_MANDPROF, - "image=%s pid=%d profile=%s active=%s\n", - filename, - current->pid, - sd->profile->name, - sd->active->name); - - newprofile = get_sdprofile(null_complain_profile); - } else { - SD_WARN("REJECTING exec(2) of image '%s'. " - "Profile mandatory and not found " - "(%s(%d) profile %s active %s)\n", - filename, - current->comm, current->pid, - sd->profile->name, sd->active->name); - error = -EPERM; - } - } else { - /* Profile (non-mandatory) could not be found */ - - /* Only way we can get into this code is if task - * is unconstrained. - */ - - BUG_ON(__sd_is_confined(sd)); - - SD_DEBUG("%s: No profile found for exec image %s\n", - __FUNCTION__, - filename); - } /* newprofile */ - - -apply_profile: - /* Apply profile if necessary */ - if (newprofile) { - struct subdomain *latest_sd, *lazy_sd = NULL; - unsigned long flags; - - if (newprofile == &unconstrained_flag) - newprofile = NULL; - - /* grab a write lock - * - * - Task may be presently unconfined (have no sd). In which - * case we have to lazily allocate one. Note we may be raced - * to this allocation by a setprofile. - * - * - sd is a refcounted copy of the subdomain (get_sdcopy) and - * not the actual subdomain. This allows us to not have to - * hold a read lock around all this code. However, we need to - * change the actual subdomain, not the copy. - * - * - If newprofile points to an actual profile (result of - * sd_profilelist_find above), this profile may have been - * replaced. We need to fix it up. Doing this to avoid - * having to hold a write lock around all this code. - */ - - if (!sd) { - lazy_sd = alloc_subdomain(current); - } - - write_lock_irqsave(&sd_lock, flags); - - latest_sd = SD_SUBDOMAIN(current->security); - - if (latest_sd) { - if (lazy_sd) { - /* raced by setprofile (created latest_sd) */ - free_subdomain(lazy_sd); - lazy_sd = NULL; - } - } else { - if (lazy_sd) { - latest_sd = lazy_sd; - current->security = lazy_sd; - } else { - SD_ERROR("%s: Failed to allocate subdomain\n", - __FUNCTION__); - - error = -ENOMEM; - write_unlock_irqrestore(&sd_lock, flags); - goto done; - } - } - - /* Determine if profile we found earlier is stale. - * If so, reobtain it. N.B stale flag should never be - * set on null_complain profile. - */ - if (newprofile && unlikely(newprofile->isstale)) { - BUG_ON(newprofile == null_complain_profile); - - /* drop refcnt obtained from earlier get_sdprofile */ - put_sdprofile(newprofile); - - newprofile = sd_profilelist_find(filename); - - if (!newprofile) { - /* Race, profile was removed, not replaced. - * Redo with error checking - */ - write_unlock_irqrestore(&sd_lock, flags); - goto find_profile; - } - } - - /* Handle confined exec. - * Can be at this point for the following reasons: - * 1. unconfined switching to confined - * 2. confined switching to different confinement - * 3. confined switching to unconfined - * - * Cases 2 and 3 are marked as requiring secure exec - * (unless policy specified "unsafe exec") - */ - if (__sd_is_confined(latest_sd) && !unsafe_exec) { - unsigned long bprm_flags; - - bprm_flags = SD_SECURE_EXEC_NEEDED; - bprm->security = (void*) - ((unsigned long)bprm->security | bprm_flags); - } - - sd_switch(latest_sd, newprofile, newprofile); - put_sdprofile(newprofile); - - if (complain && newprofile == null_complain_profile) - LOG_HINT(latest_sd, GFP_ATOMIC, HINT_CHGPROF, - "pid=%d\n", - current->pid); - - write_unlock_irqrestore(&sd_lock, flags); - } - -done: - sd_put_name(filename); - - if (sd) - put_sdcopy(sd); - -out: - return error; -} - -/** - * sd_release - release the task's subdomain - * @p: task being released - * - * This is called after a task has exited and the parent has reaped it. - * p->security must be !NULL. The @p->security blob is freed. - */ -void sd_release(struct task_struct *p) -{ - struct subdomain *sd = SD_SUBDOMAIN(p->security); - p->security = NULL; - - sd_subdomainlist_remove(sd); - - /* release profiles */ - put_sdprofile(sd->profile); - put_sdprofile(sd->active); - - kfree(sd); -} - -/***************************** - * GLOBAL SUBPROFILE FUNCTIONS - ****************************/ - -/** - * do_change_hat - actually switch hats - * @name: name of hat to swtich to - * @sd: current subdomain - * - * Switch to a new hat. Return 0 on success, error otherwise. - */ -static inline int do_change_hat(const char *hat_name, struct subdomain *sd) -{ - struct sdprofile *sub; - struct sdprofile *p = sd->active; - int error = 0; - - sub = __sd_find_profile(hat_name, &sd->profile->sub); - - if (sub) { - /* change hat */ - sd->active = sub; - } else { - /* There is no such subprofile change to a NULL profile. - * The NULL profile grants no file access. - * - * This feature is used by changehat_apache. - * - * N.B from the null-profile the task can still changehat back - * out to the parent profile (assuming magic != NULL) - */ - if (SUBDOMAIN_COMPLAIN(sd)) { - LOG_HINT(sd, GFP_ATOMIC, HINT_UNKNOWN_HAT, - "%s pid=%d " - "profile=%s active=%s\n", - hat_name, - current->pid, - sd->profile->name, - sd->active->name); - sd->active = get_sdprofile(null_complain_profile); - } else { - SD_DEBUG("%s: Unknown hatname '%s'. " - "Changing to NULL profile " - "(%s(%d) profile %s active %s)\n", - __FUNCTION__, - hat_name, - current->comm, current->pid, - sd->profile->name, sd->active->name); - - sd->active = get_sdprofile(null_profile); - error = -EACCES; - } - } - put_sdprofile(p); - - return error; -} - -/** - * sd_change_hat - change hat to/from subprofile - * @hat_name: specifies hat to change to - * @hat_magic: token to validate hat change - * - * Change to new @hat_name when current hat is top level profile, and store - * the @hat_magic in the current subdomain. If the new @hat_name is - * NULL, and the @hat_magic matches that stored in the current subdomain - * return to original top level profile. Returns 0 on success, error - * otherwise. - */ -#define IN_SUBPROFILE(sd) ((sd)->profile != (sd)->active) -int sd_change_hat(const char *hat_name, __u32 hat_magic) -{ - struct subdomain *sd = SD_SUBDOMAIN(current->security); - int error = 0; - - SD_DEBUG("%s: %p, 0x%x (pid %d)\n", - __FUNCTION__, - hat_name, hat_magic, - current->pid); - - /* Dump out above debugging in WARN mode if we are in AUDIT mode */ - if (SUBDOMAIN_AUDIT(sd)) { - SD_WARN("%s: %s, 0x%x (pid %d)\n", - __FUNCTION__, hat_name ? hat_name : "NULL", - hat_magic, current->pid); - } - - /* no subdomain: changehat into the null_profile, since the process - has no subdomain do_change_hat won't find a match which will cause - a changehat to null_profile. We could short circuit this but since - the subdprofile (hat) list is empty we would save very little. */ - - /* check to see if an unconfined process is doing a changehat. */ - if (!__sd_is_confined(sd)) { - error = -EACCES; - goto out; - } - - /* Check whether current domain is parent - * or one of the sibling children - */ - if (sd->profile == sd->active) { - /* - * parent - */ - if (hat_name) { - SD_DEBUG("%s: switching to %s, 0x%x\n", - __FUNCTION__, - hat_name, - hat_magic); - - /* - * N.B hat_magic == 0 has a special meaning - * this indicates that the task may never changehat - * back to it's parent, it will stay in this subhat - * (or null-profile, if the hat doesn't exist) until - * the task terminates - */ - sd->sd_hat_magic = hat_magic; - error = do_change_hat(hat_name, sd); - } else { - /* Got here via changehat(NULL, magic) - * - * We used to simply update the magic cookie. - * That's an odd behaviour, so just do nothing. - */ - } - } else { - /* - * child -- check to make sure magic is same as what was - * passed when we switched into this profile, - * Handle special casing of NULL magic which confines task - * to subprofile and prohibits further changehats - */ - if (hat_magic == sd->sd_hat_magic && sd->sd_hat_magic) { - if (!hat_name) { - /* - * Got here via changehat(NULL, magic) - * Return from subprofile, back to parent - */ - put_sdprofile(sd->active); - sd->active = get_sdprofile(sd->profile); - - /* Reset hat_magic to zero. - * New value will be passed on next changehat - */ - sd->sd_hat_magic = 0; - } else { - /* change to another (sibling) profile */ - error = do_change_hat(hat_name, sd); - } - } else if (sd->sd_hat_magic) { - SD_ERROR("KILLING process %s(%d) " - "Invalid change_hat() magic# 0x%x " - "(hatname %s profile %s active %s)\n", - current->comm, current->pid, - hat_magic, - hat_name ? hat_name : "NULL", - sd->profile->name, sd->active->name); - - /* terminate current process */ - (void)send_sig_info(SIGKILL, NULL, current); - } else { /* sd->sd_hat_magic == NULL */ - SD_ERROR("KILLING process %s(%d) " - "Task was confined to current subprofile " - "(profile %s active %s)\n", - current->comm, current->pid, - sd->profile->name, sd->active->name); - - /* terminate current process */ - (void)send_sig_info(SIGKILL, NULL, current); - } - - } - -out: - return error; -} diff --git a/module-nextgen/module_interface.c b/module-nextgen/module_interface.c deleted file mode 100644 index 5ca7bb643..000000000 --- a/module-nextgen/module_interface.c +++ /dev/null @@ -1,712 +0,0 @@ -/* - * Copyright (C) 1998-2005 Novell/SUSE - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. - * - * AppArmor userspace policy interface - */ - -#include - -#include "apparmor.h" -#include "inline.h" -#include "module_interface.h" -#include "aamatch/match.h" - -/* sd_code defined in module_interface.h */ - -const int sdcode_datasize[] = { 1, 2, 4, 8, 2, 2, 4, 0, 0, 0, 0, 0, 0 }; - -struct sd_taskreplace_data { - struct sdprofile *old_profile; - struct sdprofile *new_profile; -}; - -/* inlines must be forward of there use in newer version of gcc, - just forward declaring with a prototype won't work anymore */ - -static inline void free_sd_entry(struct sd_entry *entry) -{ - if (entry) { - kfree(entry->filename); - sdmatch_free(entry->extradata); - kfree(entry); - } -} - -/** - * alloc_sd_entry - create new empty sd_entry - * - * This routine allocates, initializes, and returns a new subdomain - * file entry structure. Structure is zeroed. Returns new structure on - * success, NULL on failure. - */ -static inline struct sd_entry *alloc_sd_entry(void) -{ - struct sd_entry *entry; - - SD_DEBUG("%s\n", __FUNCTION__); - entry = kmalloc(sizeof(struct sd_entry), GFP_KERNEL); - if (entry) { - int i; - memset(entry, 0, sizeof(struct sd_entry)); - INIT_LIST_HEAD(&entry->list); - for (i = 0; i <= POS_SD_FILE_MAX; i++) { - INIT_LIST_HEAD(&entry->listp[i]); - } - } - return entry; -} - -/** - * free_sdprofile - free sdprofile structure - */ -void free_sdprofile(struct sdprofile *profile) -{ - struct sd_entry *sdent, *tmp; - struct sdprofile *p, *ptmp; - - SD_DEBUG("%s(%p)\n", __FUNCTION__, profile); - - if (!profile) - return; - - /* profile is still on global profile list -- invalid */ - if (!list_empty(&profile->list)) { - SD_ERROR("%s: internal error, " - "profile '%s' still on global list\n", - __FUNCTION__, - profile->name); - BUG(); - } - - list_for_each_entry_safe(sdent, tmp, &profile->file_entry, list) { - if (sdent->filename) - SD_DEBUG("freeing sd_entry: %p %s\n", - sdent->filename, sdent->filename); - list_del_init(&sdent->list); - free_sd_entry(sdent); - } - - list_for_each_entry_safe(p, ptmp, &profile->sub, list) { - list_del_init(&p->list); - put_sdprofile(p); - } - - if (profile->name) { - SD_DEBUG("%s: %s\n", __FUNCTION__, profile->name); - kfree(profile->name); - } - - kfree(profile); -} - -/** task_remove - * - * remove profile in a task's subdomain leaving the task unconfined - * - * @sd: task's subdomain - */ -static inline void task_remove(struct subdomain *sd) -{ - /* write_lock(&sd_lock) held here */ - SD_DEBUG("%s: removing profile from task %s(%d) profile %s active %s\n", - __FUNCTION__, - sd->task->comm, - sd->task->pid, - sd->profile->name, - sd->active->name); - - sd_switch_unconfined(sd); -} - -/** taskremove_iter - * - * Iterate over all subdomains. - * - * If any matches old_profile, then call task_remove to remove it. - * This leaves the task (subdomain) unconfined. - */ -static int taskremove_iter(struct subdomain *sd, void *cookie) -{ - struct sdprofile *old_profile = (struct sdprofile *)cookie; - unsigned long flags; - - write_lock_irqsave(&sd_lock, flags); - - if (__sd_is_confined(sd) && sd->profile == old_profile) - task_remove(sd); - - write_unlock_irqrestore(&sd_lock, flags); - - return 0; -} - -/** task_replace - * - * replace profile in a task's subdomain with newly loaded profile - * - * @sd: task's subdomain - * @new: old profile - */ -static inline void task_replace(struct subdomain *sd, struct sdprofile *new) -{ - struct sdprofile *nactive = NULL; - - SD_DEBUG("%s: replacing profile for task %s(%d) " - "profile=%s (%p) active=%s (%p)\n", - __FUNCTION__, - sd->task->comm, sd->task->pid, - sd->profile->name, sd->profile, - sd->active->name, sd->active); - - if (sd->profile == sd->active) - nactive = get_sdprofile(new); - else if (sd->active) { - /* old in hat, new profile has hats */ - nactive = __sd_find_profile(sd->active->name, &new->sub); - - if (!nactive) { - if (new->flags.complain) - nactive = get_sdprofile(null_complain_profile); - else - nactive = get_sdprofile(null_profile); - } - } - sd_switch(sd, new, nactive); - - put_sdprofile(nactive); -} - -/** taskreplace_iter - * - * Iterate over all subdomains. - * - * If any matches old_profile, then call task_replace to replace with - * new_profile - */ -static int taskreplace_iter(struct subdomain *sd, void *cookie) -{ - struct sd_taskreplace_data *data = (struct sd_taskreplace_data *)cookie; - unsigned long flags; - - write_lock_irqsave(&sd_lock, flags); - - if (__sd_is_confined(sd) && sd->profile == data->old_profile) - task_replace(sd, data->new_profile); - - write_unlock_irqrestore(&sd_lock, flags); - - return 0; -} - -static inline int sd_inbounds(struct sd_ext *e, size_t size) -{ - return (e->pos + size <= e->end); -} - -/** - * sdconvert - for codes that have a trailing value, convert that value - * and put it in dest. - * if a code does not have a trailing value nop - * @code: type code - * @dest: pointer to object to receive the converted value - * @src: pointer to value to convert - */ -static void sdconvert(enum sd_code code, void *dest, void *src) -{ - switch (code) { - case SD_U8: - *(u8 *)dest = *(u8 *) src; - break; - case SD_U16: - case SD_NAME: - case SD_DYN_STRING: - *(u16 *)dest = le16_to_cpu(get_unaligned((u16 *)src)); - break; - case SD_U32: - case SD_STATIC_BLOB: - *(u32 *)dest = le32_to_cpu(get_unaligned((u32 *)src)); - break; - case SD_U64: - *(u64 *)dest = le64_to_cpu(get_unaligned((u64 *)src)); - break; - default: - /* nop - all other type codes do not have a trailing value */ - ; - } -} - -/** - * sd_is_X - check if the next element is of type X and if it is within - * bounds. If it is put the associated value in data. - * @e: extent information - * @code: type code - * @data: object located at @e->pos (of type @code) is written into @data - * if @data is non-null. if data is null it means skip this - * entry - * return the size of bytes associated with the returned data - * for complex object like blob and string a pointer to the allocated - * data is returned in data, but the size of the blob or string is - * returned. - */ -static u32 sd_is_X(struct sd_ext *e, enum sd_code code, void *data) -{ - void *pos = e->pos; - int ret = 0; - if (!sd_inbounds(e, SD_CODE_BYTE + sdcode_datasize[code])) - goto fail; - if (code != *(u8 *)e->pos) - goto out; - e->pos += SD_CODE_BYTE; - if (code == SD_NAME) { - u16 size; - /* name codes are followed by X bytes */ - size = le16_to_cpu(get_unaligned((u16 *)e->pos)); - if (!sd_inbounds(e, (size_t) size)) - goto fail; - if (data) - *(u16 *)data = size; - e->pos += sdcode_datasize[code]; - ret = 1 + sdcode_datasize[code]; - } else if (code == SD_DYN_STRING) { - u16 size; - char *str; - /* strings codes are followed by X bytes */ - size = le16_to_cpu(get_unaligned((u16 *)e->pos)); - e->pos += sdcode_datasize[code]; - if (!sd_inbounds(e, (size_t) size)) - goto fail; - if (data) { - * (char **)data = NULL; - str = kmalloc(size, GFP_KERNEL); - if (!str) - goto fail; - memcpy(str, e->pos, (size_t) size); - str[size-1] = '\0'; - * (char **)data = str; - } - e->pos += size; - ret = size; - } else if (code == SD_STATIC_BLOB) { - u32 size; - /* blobs are followed by X bytes, that can be 2^32 */ - size = le32_to_cpu(get_unaligned((u32 *)e->pos)); - e->pos += sdcode_datasize[code]; - if (!sd_inbounds(e, (size_t) size)) - goto fail; - if (data) - memcpy(data, e->pos, (size_t) size); - e->pos += size; - ret = size; - } else { - if (data) - sdconvert(code, data, e->pos); - e->pos += sdcode_datasize[code]; - ret = 1 + sdcode_datasize[code]; - } -out: - return ret; -fail: - e->pos = pos; - return 0; -} - -/* sd_is_nameX - check is the next element is X, and its tag is name. - * if the code matches and name (if specified) matches then the packed data - * is unpacked into *data. (Note for strings this is the size, and the next - * data in the stream is the string data) - * returns 0 if either match failes - */ -static int sd_is_nameX(struct sd_ext *e, enum sd_code code, void *data, - const char *name) -{ - void *pos = e->pos; - u16 size; - u32 ret; - /* check for presence of a tagname, and if present name size - * SD_NAME tag value is a u16 */ - if (sd_is_X(e, SD_NAME, &size)) { - /* if a name is specified it must match. otherwise skip tag */ - if (name && ((strlen(name) != size-1) || - strncmp(name, (char *)e->pos, (size_t)size-1))) - goto fail; - e->pos += size; - } - /* now check if data actually matches */ - ret = sd_is_X(e, code, data); - if (!ret) - goto fail; - return ret; - -fail: - e->pos = pos; - return 0; -} - -/* macro to wrap error case to make a block of reads look nicer */ -#define SD_READ_X(E, C, D, N) \ - do { \ - u32 __ret; \ - __ret = sd_is_nameX((E), (C), (D), (N)); \ - if (!__ret) \ - goto fail; \ - } while (0) - -/** - * sd_activate_net_entry - ignores/skips net entries if the they are present - * in the data stream. - * @e: extent information - */ -static inline int sd_activate_net_entry(struct sd_ext *e) -{ - SD_READ_X(e, SD_STRUCT, NULL, "ne"); - SD_READ_X(e, SD_U32, NULL, NULL); - SD_READ_X(e, SD_U32, NULL, NULL); - SD_READ_X(e, SD_U32, NULL, NULL); - SD_READ_X(e, SD_U16, NULL, NULL); - SD_READ_X(e, SD_U16, NULL, NULL); - SD_READ_X(e, SD_U32, NULL, NULL); - SD_READ_X(e, SD_U32, NULL, NULL); - SD_READ_X(e, SD_U16, NULL, NULL); - SD_READ_X(e, SD_U16, NULL, NULL); - /* interface name is optional so just ignore return code */ - sd_is_nameX(e, SD_DYN_STRING, NULL, NULL); - SD_READ_X(e, SD_STRUCTEND, NULL, NULL); - - return 1; -fail: - return 0; -} - -static inline struct sd_entry *sd_activate_file_entry(struct sd_ext *e) -{ - struct sd_entry *entry = NULL; - - if (!(entry = alloc_sd_entry())) - goto fail; - - SD_READ_X(e, SD_STRUCT, NULL, "fe"); - SD_READ_X(e, SD_DYN_STRING, &entry->filename, NULL); - SD_READ_X(e, SD_U32, &entry->mode, "file.mode"); - SD_READ_X(e, SD_U32, &entry->entry_type, "file.pattern_type"); - - entry->extradata = sdmatch_alloc(entry->entry_type); - if (IS_ERR(entry->extradata)) { - entry->extradata = NULL; - goto fail; - } - - if (entry->extradata && - sdmatch_serialize(entry->extradata, e, sd_is_nameX) != 0) { - goto fail; - } - SD_READ_X(e, SD_STRUCTEND, NULL, NULL); - - switch (entry->entry_type) { - case sd_entry_literal: - SD_DEBUG("%s: %s [no pattern] mode=0x%x\n", - __FUNCTION__, - entry->filename, - entry->mode); - break; - case sd_entry_tailglob: - SD_DEBUG("%s: %s [tailglob] mode=0x%x\n", - __FUNCTION__, - entry->filename, - entry->mode); - break; - case sd_entry_pattern: - SD_DEBUG("%s: %s mode=0x%x\n", - __FUNCTION__, - entry->filename, - entry->mode); - break; - default: - SD_WARN("%s: INVALID entry_type %d\n", - __FUNCTION__, - (int)entry->entry_type); - goto fail; - } - - return entry; - -fail: - sdmatch_free(entry->extradata); - free_sd_entry(entry); - return NULL; -} - -static inline int check_rule_and_add(struct sd_entry *file_entry, - struct sdprofile *profile, - const char **message) -{ - /* verify consistency of x, px, ix, ux for entry against - possible duplicates for this entry */ - int mode = SD_EXEC_MODIFIER_MASK(file_entry->mode); - int i; - - if (mode && !(SD_MAY_EXEC & file_entry->mode)) { - *message = "inconsistent rule, x modifiers without x"; - goto out; - } - - /* check that only 1 of the modifiers is set */ - if (mode && (mode & (mode - 1))) { - *message = "inconsistent rule, multiple x modifiers"; - goto out; - } - - /* ix -> m (required so that target exec binary may map itself) */ - if (mode & SD_EXEC_INHERIT) - file_entry->mode |= SD_EXEC_MMAP; - - list_add(&file_entry->list, &profile->file_entry); - profile->num_file_entries++; - - mode = file_entry->mode; - - /* Handle partitioned lists - * Chain entries onto sublists based on individual - * permission bits. This allows more rapid searching. - */ - for (i = 0; i <= POS_SD_FILE_MAX; i++) { - if (mode & (1 << i)) - /* profile->file_entryp[i] initially set to - * NULL in alloc_sdprofile() */ - list_add(&file_entry->listp[i], - &profile->file_entryp[i]); - } - - return 1; - -out: - free_sd_entry(file_entry); - return 0; -} - -#define SD_ENTRY_LIST(NAME) \ - do { \ - if (sd_is_nameX(e, SD_LIST, NULL, (NAME))) { \ - rulename = ""; \ - error_string = "Invalid file entry"; \ - while (!sd_is_nameX(e, SD_LISTEND, NULL, NULL)) { \ - struct sd_entry *file_entry; \ - file_entry = sd_activate_file_entry(e); \ - if (!file_entry) \ - goto fail; \ - if (!check_rule_and_add(file_entry, profile, \ - &error_string)) { \ - rulename = file_entry->filename; \ - goto fail; \ - } \ - } \ - } \ - } while (0) - -struct sdprofile *sd_activate_profile(struct sd_ext *e, ssize_t *error) -{ - struct sdprofile *profile = NULL; - const char *rulename = ""; - const char *error_string = "Invalid Profile"; - - *error = -EPROTO; - - profile = alloc_sdprofile(); - if (!profile) { - error_string = "Could not allocate profile"; - *error = -ENOMEM; - goto fail; - } - - /* check that we have the right struct being passed */ - SD_READ_X(e, SD_STRUCT, NULL, "profile"); - SD_READ_X(e, SD_DYN_STRING, &profile->name, NULL); - - error_string = "Invalid flags"; - /* per profile debug flags (debug, complain, audit) */ - SD_READ_X(e, SD_STRUCT, NULL, "flags"); - SD_READ_X(e, SD_U32, &(profile->flags.debug), "profile.flags.debug"); - SD_READ_X(e, SD_U32, &(profile->flags.complain), - "profile.flags.complain"); - SD_READ_X(e, SD_U32, &(profile->flags.audit), "profile.flags.audit"); - SD_READ_X(e, SD_STRUCTEND, NULL, NULL); - - error_string = "Invalid capabilities"; - SD_READ_X(e, SD_U32, &(profile->capabilities), "profile.capabilities"); - - /* get the file entries. */ - SD_ENTRY_LIST("pgent"); /* pcre rules */ - SD_ENTRY_LIST("sgent"); /* simple globs */ - SD_ENTRY_LIST("fent"); /* regular file entries */ - - /* get the net entries */ - if (sd_is_nameX(e, SD_LIST, NULL, "net")) { - error_string = "Invalid net entry"; - while (!sd_is_nameX(e, SD_LISTEND, NULL, NULL)) { - if (!sd_activate_net_entry(e)) - goto fail; - } - } - rulename = ""; - - /* get subprofiles */ - if (sd_is_nameX(e, SD_LIST, NULL, "hats")) { - error_string = "Invalid profile hat"; - while (!sd_is_nameX(e, SD_LISTEND, NULL, NULL)) { - struct sdprofile *subprofile; - subprofile = sd_activate_profile(e, error); - if (!subprofile) - goto fail; - get_sdprofile(subprofile); - list_add(&subprofile->list, &profile->sub); - } - } - - error_string = "Invalid end of profile"; - SD_READ_X(e, SD_STRUCTEND, NULL, NULL); - - return profile; - -fail: - SD_WARN("%s: %s %s in profile %s\n", INTERFACE_ID, rulename, - error_string, profile && profile->name ? profile->name - : "unknown"); - - if (profile) { - free_sdprofile(profile); - profile = NULL; - } - - return NULL; -} - -void *sd_activate_top_profile(struct sd_ext *e, ssize_t *error) -{ - /* get the interface version */ - if (!sd_is_nameX(e, SD_U32, &e->version, "version")) { - SD_WARN("%s: version missing\n", INTERFACE_ID); - *error = -EPROTONOSUPPORT; - goto out; - } - - /* check that the interface version is currently supported */ - if (e->version != 2) { - SD_WARN("%s: unsupported interface version (%d)\n", - INTERFACE_ID, e->version); - *error = -EPROTONOSUPPORT; - goto out; - } - - return sd_activate_profile(e, error); -out: - return NULL; -} - -ssize_t sd_file_prof_add(void *data, size_t size) -{ - struct sdprofile *profile = NULL; - - struct sd_ext e = { data, data + size, data }; - ssize_t error; - - profile = sd_activate_top_profile(&e, &error); - if (!profile) { - SD_DEBUG("couldn't activate profile\n"); - return error; - } - - if (!sd_profilelist_add(profile)) { - SD_WARN("trying to add profile (%s) that already exists.\n", - profile->name); - free_sdprofile(profile); - return -EEXIST; - } - - return size; -} - -ssize_t sd_file_prof_repl(void *udata, size_t size) -{ - struct sd_taskreplace_data data; - struct sd_ext e = { udata, udata + size, udata }; - ssize_t error; - - data.new_profile = sd_activate_top_profile(&e, &error); - if (!data.new_profile) { - SD_DEBUG("couldn't activate profile\n"); - return error; - } - /* Grab reference to close race window (see comment below) */ - get_sdprofile(data.new_profile); - - /* Replace the profile on the global profile list. - * This list is used by all new exec's to find the correct profile. - * If there was a previous profile, it is returned, else NULL. - * - * N.B sd_profilelist_replace does not drop the refcnt on - * old_profile when removing it from the global list, otherwise it - * could reach zero and be automatically free'd. We nust manually - * drop it at the end of this function when we are finished with it. - */ - data.old_profile = sd_profilelist_replace(data.new_profile); - - /* RACE window here. - * At this point another task could preempt us trying to replace - * the SAME profile. If it makes it to this point, it has removed - * the original tasks new_profile from the global list and holds a - * reference of 1 to it in it's old_profile. If the new task - * reaches the end of the function it will put old_profile causing - * the profile to be deleted. - * When the original task is rescheduled it will continue calling - * sd_subdomainlist_iterate relabelling tasks with a profile - * which points to free'd memory. - */ - - /* If there was an old profile, find all currently executing tasks - * using this profile and replace the old profile with the new. - */ - if (data.old_profile) { - SD_DEBUG("%s: try to replace profile (%p)%s\n", - __FUNCTION__, - data.old_profile, - data.old_profile->name); - - sd_subdomainlist_iterate(taskreplace_iter, (void *)&data); - - /* it's off global list, and we are done replacing */ - put_sdprofile(data.old_profile); - } - - /* Free reference obtained above */ - put_sdprofile(data.new_profile); - - return size; -} - -ssize_t sd_file_prof_remove(const char *name, size_t size) -{ - struct sdprofile *old_profile; - - /* if the old profile exists it will be removed from the list and - * a reference is returned. - */ - old_profile = sd_profilelist_remove(name); - - if (old_profile) { - /* remove profile from any tasks using it */ - sd_subdomainlist_iterate(taskremove_iter, (void *)old_profile); - - /* drop reference obtained by sd_profilelist_remove */ - put_sdprofile(old_profile); - } else { - SD_WARN("%s: trying to remove profile (%s) that " - "doesn't exist - skipping.\n", __FUNCTION__, name); - return -ENOENT; - } - - return size; -} diff --git a/module-nextgen/module_interface.h b/module-nextgen/module_interface.h deleted file mode 100644 index 812e76b37..000000000 --- a/module-nextgen/module_interface.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef __MODULEINTERFACE_H -#define __MODULEINTERFACE_H - -/* Codes of the types of basic structures that are understood */ -#define SD_CODE_BYTE (sizeof(u8)) -#define INTERFACE_ID "INTERFACE" - -#define SUBDOMAIN_INTERFACE_VERSION 2 - -enum sd_code { - SD_U8, - SD_U16, - SD_U32, - SD_U64, - SD_NAME, /* same as string except it is items name */ - SD_DYN_STRING, - SD_STATIC_BLOB, - SD_STRUCT, - SD_STRUCTEND, - SD_LIST, - SD_LISTEND, - SD_OFFSET, - SD_BAD -}; - -/* sd_ext tracks the kernel buffer and read position in it. The interface - * data is copied into a kernel buffer in subdomainfs and then handed off to - * the activate routines. - */ -struct sd_ext { - void *start; - void *end; - void *pos; /* pointer to current position in the buffer */ - u32 version; -}; - -#endif /* __MODULEINTERFACE_H */ diff --git a/module-nextgen/shared.h b/module-nextgen/shared.h deleted file mode 100644 index e986e7ce9..000000000 --- a/module-nextgen/shared.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2000, 2001, 2004, 2005 Novell/SUSE - * - * Immunix AppArmor LSM - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. - */ - -#ifndef _SHARED_H -#define _SHARED_H - -/* start of system offsets */ -#define POS_SD_FILE_MIN 0 -#define POS_SD_MAY_EXEC POS_SD_FILE_MIN -#define POS_SD_MAY_WRITE (POS_SD_MAY_EXEC + 1) -#define POS_SD_MAY_READ (POS_SD_MAY_WRITE + 1) -/* not used by Subdomain */ -#define POS_SD_MAY_APPEND (POS_SD_MAY_READ + 1) -/* end of system offsets */ - -#define POS_SD_MAY_LINK (POS_SD_MAY_APPEND + 1) -#define POS_SD_EXEC_INHERIT (POS_SD_MAY_LINK + 1) -#define POS_SD_EXEC_UNCONSTRAINED (POS_SD_EXEC_INHERIT + 1) -#define POS_SD_EXEC_PROFILE (POS_SD_EXEC_UNCONSTRAINED + 1) -#define POS_SD_EXEC_MMAP (POS_SD_EXEC_PROFILE + 1) -#define POS_SD_EXEC_UNSAFE (POS_SD_EXEC_MMAP + 1) -#define POS_SD_FILE_MAX POS_SD_EXEC_UNSAFE - -/* Modeled after MAY_READ, MAY_WRITE, MAY_EXEC def'ns */ -#define SD_MAY_EXEC (0x01 << POS_SD_MAY_EXEC) -#define SD_MAY_WRITE (0x01 << POS_SD_MAY_WRITE) -#define SD_MAY_READ (0x01 << POS_SD_MAY_READ) -#define SD_MAY_LINK (0x01 << POS_SD_MAY_LINK) -#define SD_EXEC_INHERIT (0x01 << POS_SD_EXEC_INHERIT) -#define SD_EXEC_UNCONSTRAINED (0x01 << POS_SD_EXEC_UNCONSTRAINED) -#define SD_EXEC_PROFILE (0x01 << POS_SD_EXEC_PROFILE) -#define SD_EXEC_MMAP (0x01 << POS_SD_EXEC_MMAP) -#define SD_EXEC_UNSAFE (0x01 << POS_SD_EXEC_UNSAFE) - -#define SD_EXEC_MODIFIERS (SD_EXEC_INHERIT | \ - SD_EXEC_UNCONSTRAINED | \ - SD_EXEC_PROFILE) - -#endif /* _SHARED_H */