mirror of
https://gitlab.com/apparmor/apparmor.git
synced 2025-03-04 16:35:02 +01:00
Import nextgen branch of AppArmor
This commit is contained in:
parent
145432c805
commit
1d152eecb8
31 changed files with 4827 additions and 4928 deletions
|
@ -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
|
|
@ -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 */
|
|
@ -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 */
|
9
module-nextgen/apparmor/Kconfig
Normal file
9
module-nextgen/apparmor/Kconfig
Normal 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.
|
|
@ -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
|
325
module-nextgen/apparmor/apparmor.h
Normal file
325
module-nextgen/apparmor/apparmor.h
Normal 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 */
|
432
module-nextgen/apparmor/apparmorfs.c
Normal file
432
module-nextgen/apparmor/apparmorfs.c
Normal 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();
|
||||
}
|
|
@ -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;
|
||||
}
|
333
module-nextgen/apparmor/inline.h
Normal file
333
module-nextgen/apparmor/inline.h
Normal 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__ */
|
|
@ -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,
|
840
module-nextgen/apparmor/lsm.c
Normal file
840
module-nextgen/apparmor/lsm.c
Normal 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");
|
1618
module-nextgen/apparmor/main.c
Normal file
1618
module-nextgen/apparmor/main.c
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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
|
132
module-nextgen/apparmor/match/match.h
Normal file
132
module-nextgen/apparmor/match/match.h
Normal 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 */
|
57
module-nextgen/apparmor/match/match_default.c
Normal file
57
module-nextgen/apparmor/match/match_default.c
Normal 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");
|
|
@ -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>");
|
840
module-nextgen/apparmor/module_interface.c
Normal file
840
module-nextgen/apparmor/module_interface.c
Normal 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);
|
||||
}
|
37
module-nextgen/apparmor/module_interface.h
Normal file
37
module-nextgen/apparmor/module_interface.h
Normal 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 */
|
|
@ -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,14 +204,11 @@ 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) {
|
||||
SD_WARN("%s: Unable to switch task %s(%d) to profile '%s'. "
|
||||
"No such profile.\n",
|
||||
AA_WARN("%s: Unable to switch task %s(%d) to profile"
|
||||
"'%s'. No such profile.\n",
|
||||
__FUNCTION__,
|
||||
p->comm, p->pid,
|
||||
name);
|
||||
|
@ -217,25 +216,25 @@ int sd_setprocattr_setprofile(struct task_struct *p, char *profilename,
|
|||
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);
|
||||
|
41
module-nextgen/apparmor/shared.h
Normal file
41
module-nextgen/apparmor/shared.h
Normal 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 */
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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__ */
|
|
@ -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
|
@ -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;
|
||||
}
|
|
@ -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 */
|
|
@ -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 */
|
Loading…
Add table
Reference in a new issue