Import nextgen branch of AppArmor

This commit is contained in:
John Johansen 2006-08-30 00:27:59 +00:00
parent 145432c805
commit 1d152eecb8
31 changed files with 4827 additions and 4928 deletions

View file

@ -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

View file

@ -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 */

View file

@ -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 <linux/fs.h>
/* defn of linux_binprm */
#include <linux/binfmts.h>
#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 */

View file

@ -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
<http://forge.novell.com/modules/xfmod/project/?apparmor>
If you are unsure how to answer this question, answer N.

View file

@ -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

View file

@ -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 <linux/fs.h> /* Include for defn of iattr */
#include <linux/rcupdate.h>
#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 <linux/pipe_fs_i.h>
/* 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 */

View file

@ -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 <linux/security.h>
#include <linux/vmalloc.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#include <asm/uaccess.h>
#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();
}

View file

@ -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;
}

View file

@ -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 <linux/namespace.h>
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__ */

View file

@ -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,

View file

@ -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 <linux/security.h>
#include <linux/module.h>
#include <linux/mman.h>
#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 <tonyj@suse.de>");
MODULE_LICENSE("GPL");

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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 */

View file

@ -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 <linux/module.h>
#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 <tonyj@suse.de>");
MODULE_LICENSE("GPL");

View file

@ -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 <tonyj@suse.de>");

View file

@ -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 <asm/unaligned.h>
#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);
}

View file

@ -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 */

View file

@ -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);

View file

@ -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 */

View file

@ -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 <linux/module.h>
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;
}

View file

@ -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 <linux/security.h>
#include <linux/vmalloc.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#include <asm/uaccess.h>
#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;
}

View file

@ -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 <linux/namespace.h>
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__ */

View file

@ -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 <linux/security.h>
#include <linux/module.h>
#include <linux/mm.h>
/* superblock types */
/* PIPEFS_MAGIC */
#include <linux/pipe_fs_i.h>
/* 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 <asm/mman.h>
#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 <tonyj@suse.de>");
MODULE_LICENSE("GPL");

File diff suppressed because it is too large Load diff

View file

@ -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 <asm/unaligned.h>
#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;
}

View file

@ -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 */

View file

@ -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 */