Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/security-testing-2.6
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/security-testing-2.6: (90 commits) AppArmor: fix build warnings for non-const use of get_task_cred selinux: convert the policy type_attr_map to flex_array AppArmor: Enable configuring and building of the AppArmor security module TOMOYO: Use pathname specified by policy rather than execve() AppArmor: update path_truncate method to latest version AppArmor: core policy routines AppArmor: policy routines for loading and unpacking policy AppArmor: mediation of non file objects AppArmor: LSM interface, and security module initialization AppArmor: Enable configuring and building of the AppArmor security module AppArmor: update Maintainer and Documentation AppArmor: functions for domain transitions AppArmor: file enforcement routines AppArmor: userspace interfaces AppArmor: dfa match engine AppArmor: contexts used in attaching policy to system objects AppArmor: basic auditing infrastructure. AppArmor: misc. base functions and defines TOMOYO: Update version to 2.3.0 TOMOYO: Fix quota check. ...
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
--- What is AppArmor? ---
|
||||
|
||||
AppArmor is MAC style security extension for the Linux kernel. It implements
|
||||
a task centered policy, with task "profiles" being created and loaded
|
||||
from user space. Tasks on the system that do not have a profile defined for
|
||||
them run in an unconfined state which is equivalent to standard Linux DAC
|
||||
permissions.
|
||||
|
||||
--- How to enable/disable ---
|
||||
|
||||
set CONFIG_SECURITY_APPARMOR=y
|
||||
|
||||
If AppArmor should be selected as the default security module then
|
||||
set CONFIG_DEFAULT_SECURITY="apparmor"
|
||||
and CONFIG_SECURITY_APPARMOR_BOOTPARAM_VALUE=1
|
||||
|
||||
Build the kernel
|
||||
|
||||
If AppArmor is not the default security module it can be enabled by passing
|
||||
security=apparmor on the kernel's command line.
|
||||
|
||||
If AppArmor is the default security module it can be disabled by passing
|
||||
apparmor=0, security=XXXX (where XXX is valid security module), on the
|
||||
kernel's command line
|
||||
|
||||
For AppArmor to enforce any restrictions beyond standard Linux DAC permissions
|
||||
policy must be loaded into the kernel from user space (see the Documentation
|
||||
and tools links).
|
||||
|
||||
--- Documentation ---
|
||||
|
||||
Documentation can be found on the wiki.
|
||||
|
||||
--- Links ---
|
||||
|
||||
Mailing List - apparmor@lists.ubuntu.com
|
||||
Wiki - http://apparmor.wiki.kernel.org/
|
||||
User space tools - https://launchpad.net/apparmor
|
||||
Kernel module - git://git.kernel.org/pub/scm/linux/kernel/git/jj/apparmor-dev.git
|
||||
@@ -93,6 +93,7 @@ parameter is applicable:
|
||||
Documentation/scsi/.
|
||||
SECURITY Different security models are enabled.
|
||||
SELINUX SELinux support is enabled.
|
||||
APPARMOR AppArmor support is enabled.
|
||||
SERIAL Serial support is enabled.
|
||||
SH SuperH architecture is enabled.
|
||||
SMP The kernel is an SMP kernel.
|
||||
@@ -2312,6 +2313,13 @@ and is between 256 and 4096 characters. It is defined in the file
|
||||
If enabled at boot time, /selinux/disable can be used
|
||||
later to disable prior to initial policy load.
|
||||
|
||||
apparmor= [APPARMOR] Disable or enable AppArmor at boot time
|
||||
Format: { "0" | "1" }
|
||||
See security/apparmor/Kconfig help text
|
||||
0 -- disable.
|
||||
1 -- enable.
|
||||
Default value is set via kernel config option.
|
||||
|
||||
serialnumber [BUGS=X86-32]
|
||||
|
||||
shapers= [NET]
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
TOMOYO is a name-based MAC extension (LSM module) for the Linux kernel.
|
||||
|
||||
LiveCD-based tutorials are available at
|
||||
http://tomoyo.sourceforge.jp/en/1.6.x/1st-step/ubuntu8.04-live/
|
||||
http://tomoyo.sourceforge.jp/en/1.6.x/1st-step/centos5-live/ .
|
||||
http://tomoyo.sourceforge.jp/1.7/1st-step/ubuntu10.04-live/
|
||||
http://tomoyo.sourceforge.jp/1.7/1st-step/centos5-live/ .
|
||||
Though these tutorials use non-LSM version of TOMOYO, they are useful for you
|
||||
to know what TOMOYO is.
|
||||
|
||||
@@ -13,12 +13,12 @@ to know what TOMOYO is.
|
||||
Build the kernel with CONFIG_SECURITY_TOMOYO=y and pass "security=tomoyo" on
|
||||
kernel's command line.
|
||||
|
||||
Please see http://tomoyo.sourceforge.jp/en/2.2.x/ for details.
|
||||
Please see http://tomoyo.sourceforge.jp/2.3/ for details.
|
||||
|
||||
--- Where is documentation? ---
|
||||
|
||||
User <-> Kernel interface documentation is available at
|
||||
http://tomoyo.sourceforge.jp/en/2.2.x/policy-reference.html .
|
||||
http://tomoyo.sourceforge.jp/2.3/policy-reference.html .
|
||||
|
||||
Materials we prepared for seminars and symposiums are available at
|
||||
http://sourceforge.jp/projects/tomoyo/docs/?category_id=532&language_id=1 .
|
||||
@@ -50,6 +50,6 @@ multiple LSM modules at the same time. We feel sorry that you have to give up
|
||||
SELinux/SMACK/AppArmor etc. when you want to use TOMOYO.
|
||||
|
||||
We hope that LSM becomes stackable in future. Meanwhile, you can use non-LSM
|
||||
version of TOMOYO, available at http://tomoyo.sourceforge.jp/en/1.6.x/ .
|
||||
version of TOMOYO, available at http://tomoyo.sourceforge.jp/1.7/ .
|
||||
LSM version of TOMOYO is a subset of non-LSM version of TOMOYO. We are planning
|
||||
to port non-LSM version's functionalities to LSM versions.
|
||||
|
||||
+9
-1
@@ -5061,6 +5061,14 @@ S: Supported
|
||||
F: include/linux/selinux*
|
||||
F: security/selinux/
|
||||
|
||||
APPARMOR SECURITY MODULE
|
||||
M: John Johansen <john.johansen@canonical.com>
|
||||
L: apparmor@lists.ubuntu.com (subscribers-only, general discussion)
|
||||
W: apparmor.wiki.kernel.org
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/jj/apparmor-dev.git
|
||||
S: Supported
|
||||
F: security/apparmor/
|
||||
|
||||
SENSABLE PHANTOM
|
||||
M: Jiri Slaby <jirislaby@gmail.com>
|
||||
S: Maintained
|
||||
@@ -5605,7 +5613,7 @@ L: tomoyo-users-en@lists.sourceforge.jp (subscribers-only, for developers and us
|
||||
L: tomoyo-dev@lists.sourceforge.jp (subscribers-only, for developers in Japanese)
|
||||
L: tomoyo-users@lists.sourceforge.jp (subscribers-only, for users in Japanese)
|
||||
W: http://tomoyo.sourceforge.jp/
|
||||
T: quilt http://svn.sourceforge.jp/svnroot/tomoyo/trunk/2.2.x/tomoyo-lsm/patches/
|
||||
T: quilt http://svn.sourceforge.jp/svnroot/tomoyo/trunk/2.3.x/tomoyo-lsm/patches/
|
||||
S: Maintained
|
||||
F: security/tomoyo/
|
||||
|
||||
|
||||
+1
-1
@@ -1016,7 +1016,7 @@ static int fuse_permission(struct inode *inode, int mask)
|
||||
exist. So if permissions are revoked this won't be
|
||||
noticed immediately, only after the attribute
|
||||
timeout has expired */
|
||||
} else if (mask & MAY_ACCESS) {
|
||||
} else if (mask & (MAY_ACCESS | MAY_CHDIR)) {
|
||||
err = fuse_access(inode, mask);
|
||||
} else if ((mask & MAY_EXEC) && S_ISREG(inode->i_mode)) {
|
||||
if (!(inode->i_mode & S_IXUGO)) {
|
||||
|
||||
+2
-4
@@ -282,8 +282,7 @@ int inode_permission(struct inode *inode, int mask)
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
return security_inode_permission(inode,
|
||||
mask & (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND));
|
||||
return security_inode_permission(inode, mask);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1484,8 +1483,7 @@ static int handle_truncate(struct path *path)
|
||||
*/
|
||||
error = locks_verify_locked(inode);
|
||||
if (!error)
|
||||
error = security_path_truncate(path, 0,
|
||||
ATTR_MTIME|ATTR_CTIME|ATTR_OPEN);
|
||||
error = security_path_truncate(path);
|
||||
if (!error) {
|
||||
error = do_truncate(path->dentry, 0,
|
||||
ATTR_MTIME|ATTR_CTIME|ATTR_OPEN,
|
||||
|
||||
+1
-1
@@ -1953,7 +1953,7 @@ int nfs_permission(struct inode *inode, int mask)
|
||||
if ((mask & (MAY_READ | MAY_WRITE | MAY_EXEC)) == 0)
|
||||
goto out;
|
||||
/* Is this sys_access() ? */
|
||||
if (mask & MAY_ACCESS)
|
||||
if (mask & (MAY_ACCESS | MAY_CHDIR))
|
||||
goto force_lookup;
|
||||
|
||||
switch (inode->i_mode & S_IFMT) {
|
||||
|
||||
@@ -110,7 +110,7 @@ static long do_sys_truncate(const char __user *pathname, loff_t length)
|
||||
|
||||
error = locks_verify_truncate(inode, NULL, length);
|
||||
if (!error)
|
||||
error = security_path_truncate(&path, length, 0);
|
||||
error = security_path_truncate(&path);
|
||||
if (!error)
|
||||
error = do_truncate(path.dentry, length, 0, NULL);
|
||||
|
||||
@@ -165,8 +165,7 @@ static long do_sys_ftruncate(unsigned int fd, loff_t length, int small)
|
||||
|
||||
error = locks_verify_truncate(inode, file, length);
|
||||
if (!error)
|
||||
error = security_path_truncate(&file->f_path, length,
|
||||
ATTR_MTIME|ATTR_CTIME);
|
||||
error = security_path_truncate(&file->f_path);
|
||||
if (!error)
|
||||
error = do_truncate(dentry, length, ATTR_MTIME|ATTR_CTIME, file);
|
||||
out_putf:
|
||||
@@ -367,7 +366,7 @@ SYSCALL_DEFINE1(chdir, const char __user *, filename)
|
||||
if (error)
|
||||
goto out;
|
||||
|
||||
error = inode_permission(path.dentry->d_inode, MAY_EXEC | MAY_ACCESS);
|
||||
error = inode_permission(path.dentry->d_inode, MAY_EXEC | MAY_CHDIR);
|
||||
if (error)
|
||||
goto dput_and_out;
|
||||
|
||||
@@ -396,7 +395,7 @@ SYSCALL_DEFINE1(fchdir, unsigned int, fd)
|
||||
if (!S_ISDIR(inode->i_mode))
|
||||
goto out_putf;
|
||||
|
||||
error = inode_permission(inode, MAY_EXEC | MAY_ACCESS);
|
||||
error = inode_permission(inode, MAY_EXEC | MAY_CHDIR);
|
||||
if (!error)
|
||||
set_fs_pwd(current->fs, &file->f_path);
|
||||
out_putf:
|
||||
@@ -414,7 +413,7 @@ SYSCALL_DEFINE1(chroot, const char __user *, filename)
|
||||
if (error)
|
||||
goto out;
|
||||
|
||||
error = inode_permission(path.dentry->d_inode, MAY_EXEC | MAY_ACCESS);
|
||||
error = inode_permission(path.dentry->d_inode, MAY_EXEC | MAY_CHDIR);
|
||||
if (error)
|
||||
goto dput_and_out;
|
||||
|
||||
|
||||
@@ -49,9 +49,6 @@ typedef struct __user_cap_data_struct {
|
||||
} __user *cap_user_data_t;
|
||||
|
||||
|
||||
#define XATTR_CAPS_SUFFIX "capability"
|
||||
#define XATTR_NAME_CAPS XATTR_SECURITY_PREFIX XATTR_CAPS_SUFFIX
|
||||
|
||||
#define VFS_CAP_REVISION_MASK 0xFF000000
|
||||
#define VFS_CAP_REVISION_SHIFT 24
|
||||
#define VFS_CAP_FLAGS_MASK ~VFS_CAP_REVISION_MASK
|
||||
|
||||
@@ -53,6 +53,7 @@ struct inodes_stat_t {
|
||||
#define MAY_APPEND 8
|
||||
#define MAY_ACCESS 16
|
||||
#define MAY_OPEN 32
|
||||
#define MAY_CHDIR 64
|
||||
|
||||
/*
|
||||
* flags in file.f_mode. Note that FMODE_READ and FMODE_WRITE must correspond
|
||||
|
||||
@@ -90,9 +90,41 @@ struct common_audit_data {
|
||||
u32 requested;
|
||||
u32 audited;
|
||||
u32 denied;
|
||||
/*
|
||||
* auditdeny is a bit tricky and unintuitive. See the
|
||||
* comments in avc.c for it's meaning and usage.
|
||||
*/
|
||||
u32 auditdeny;
|
||||
struct av_decision *avd;
|
||||
int result;
|
||||
} selinux_audit_data;
|
||||
#endif
|
||||
#ifdef CONFIG_SECURITY_APPARMOR
|
||||
struct {
|
||||
int error;
|
||||
int op;
|
||||
int type;
|
||||
void *profile;
|
||||
const char *name;
|
||||
const char *info;
|
||||
union {
|
||||
void *target;
|
||||
struct {
|
||||
long pos;
|
||||
void *target;
|
||||
} iface;
|
||||
struct {
|
||||
int rlim;
|
||||
unsigned long max;
|
||||
} rlim;
|
||||
struct {
|
||||
const char *target;
|
||||
u32 request;
|
||||
u32 denied;
|
||||
uid_t ouid;
|
||||
} fs;
|
||||
};
|
||||
} apparmor_audit_data;
|
||||
#endif
|
||||
};
|
||||
/* these callback will be implemented by a specific LSM */
|
||||
|
||||
@@ -470,8 +470,6 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
|
||||
* @path_truncate:
|
||||
* Check permission before truncating a file.
|
||||
* @path contains the path structure for the file.
|
||||
* @length is the new length of the file.
|
||||
* @time_attrs is the flags passed to do_truncate().
|
||||
* Return 0 if permission is granted.
|
||||
* @inode_getattr:
|
||||
* Check permission before obtaining file attributes.
|
||||
@@ -1412,8 +1410,7 @@ struct security_operations {
|
||||
int (*path_rmdir) (struct path *dir, struct dentry *dentry);
|
||||
int (*path_mknod) (struct path *dir, struct dentry *dentry, int mode,
|
||||
unsigned int dev);
|
||||
int (*path_truncate) (struct path *path, loff_t length,
|
||||
unsigned int time_attrs);
|
||||
int (*path_truncate) (struct path *path);
|
||||
int (*path_symlink) (struct path *dir, struct dentry *dentry,
|
||||
const char *old_name);
|
||||
int (*path_link) (struct dentry *old_dentry, struct path *new_dir,
|
||||
@@ -2806,8 +2803,7 @@ int security_path_mkdir(struct path *dir, struct dentry *dentry, int mode);
|
||||
int security_path_rmdir(struct path *dir, struct dentry *dentry);
|
||||
int security_path_mknod(struct path *dir, struct dentry *dentry, int mode,
|
||||
unsigned int dev);
|
||||
int security_path_truncate(struct path *path, loff_t length,
|
||||
unsigned int time_attrs);
|
||||
int security_path_truncate(struct path *path);
|
||||
int security_path_symlink(struct path *dir, struct dentry *dentry,
|
||||
const char *old_name);
|
||||
int security_path_link(struct dentry *old_dentry, struct path *new_dir,
|
||||
@@ -2841,8 +2837,7 @@ static inline int security_path_mknod(struct path *dir, struct dentry *dentry,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int security_path_truncate(struct path *path, loff_t length,
|
||||
unsigned int time_attrs)
|
||||
static inline int security_path_truncate(struct path *path)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,20 @@
|
||||
#define XATTR_USER_PREFIX "user."
|
||||
#define XATTR_USER_PREFIX_LEN (sizeof (XATTR_USER_PREFIX) - 1)
|
||||
|
||||
/* Security namespace */
|
||||
#define XATTR_SELINUX_SUFFIX "selinux"
|
||||
#define XATTR_NAME_SELINUX XATTR_SECURITY_PREFIX XATTR_SELINUX_SUFFIX
|
||||
|
||||
#define XATTR_SMACK_SUFFIX "SMACK64"
|
||||
#define XATTR_SMACK_IPIN "SMACK64IPIN"
|
||||
#define XATTR_SMACK_IPOUT "SMACK64IPOUT"
|
||||
#define XATTR_NAME_SMACK XATTR_SECURITY_PREFIX XATTR_SMACK_SUFFIX
|
||||
#define XATTR_NAME_SMACKIPIN XATTR_SECURITY_PREFIX XATTR_SMACK_IPIN
|
||||
#define XATTR_NAME_SMACKIPOUT XATTR_SECURITY_PREFIX XATTR_SMACK_IPOUT
|
||||
|
||||
#define XATTR_CAPS_SUFFIX "capability"
|
||||
#define XATTR_NAME_CAPS XATTR_SECURITY_PREFIX XATTR_CAPS_SUFFIX
|
||||
|
||||
struct inode;
|
||||
struct dentry;
|
||||
|
||||
|
||||
@@ -140,6 +140,7 @@ config LSM_MMAP_MIN_ADDR
|
||||
source security/selinux/Kconfig
|
||||
source security/smack/Kconfig
|
||||
source security/tomoyo/Kconfig
|
||||
source security/apparmor/Kconfig
|
||||
|
||||
source security/integrity/ima/Kconfig
|
||||
|
||||
@@ -148,6 +149,7 @@ choice
|
||||
default DEFAULT_SECURITY_SELINUX if SECURITY_SELINUX
|
||||
default DEFAULT_SECURITY_SMACK if SECURITY_SMACK
|
||||
default DEFAULT_SECURITY_TOMOYO if SECURITY_TOMOYO
|
||||
default DEFAULT_SECURITY_APPARMOR if SECURITY_APPARMOR
|
||||
default DEFAULT_SECURITY_DAC
|
||||
|
||||
help
|
||||
@@ -163,6 +165,9 @@ choice
|
||||
config DEFAULT_SECURITY_TOMOYO
|
||||
bool "TOMOYO" if SECURITY_TOMOYO=y
|
||||
|
||||
config DEFAULT_SECURITY_APPARMOR
|
||||
bool "AppArmor" if SECURITY_APPARMOR=y
|
||||
|
||||
config DEFAULT_SECURITY_DAC
|
||||
bool "Unix Discretionary Access Controls"
|
||||
|
||||
@@ -173,6 +178,7 @@ config DEFAULT_SECURITY
|
||||
default "selinux" if DEFAULT_SECURITY_SELINUX
|
||||
default "smack" if DEFAULT_SECURITY_SMACK
|
||||
default "tomoyo" if DEFAULT_SECURITY_TOMOYO
|
||||
default "apparmor" if DEFAULT_SECURITY_APPARMOR
|
||||
default "" if DEFAULT_SECURITY_DAC
|
||||
|
||||
endmenu
|
||||
|
||||
@@ -6,6 +6,7 @@ obj-$(CONFIG_KEYS) += keys/
|
||||
subdir-$(CONFIG_SECURITY_SELINUX) += selinux
|
||||
subdir-$(CONFIG_SECURITY_SMACK) += smack
|
||||
subdir-$(CONFIG_SECURITY_TOMOYO) += tomoyo
|
||||
subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor
|
||||
|
||||
# always enable default capabilities
|
||||
obj-y += commoncap.o
|
||||
@@ -19,6 +20,7 @@ obj-$(CONFIG_SECURITY_SELINUX) += selinux/built-in.o
|
||||
obj-$(CONFIG_SECURITY_SMACK) += smack/built-in.o
|
||||
obj-$(CONFIG_AUDIT) += lsm_audit.o
|
||||
obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/built-in.o
|
||||
obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/built-in.o
|
||||
obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o
|
||||
|
||||
# Object integrity file lists
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# Generated include files
|
||||
#
|
||||
af_names.h
|
||||
capability_names.h
|
||||
@@ -0,0 +1,31 @@
|
||||
config SECURITY_APPARMOR
|
||||
bool "AppArmor support"
|
||||
depends on SECURITY
|
||||
select AUDIT
|
||||
select SECURITY_PATH
|
||||
select SECURITYFS
|
||||
select SECURITY_NETWORK
|
||||
default 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://apparmor.wiki.kernel.org
|
||||
|
||||
If you are unsure how to answer this question, answer N.
|
||||
|
||||
config SECURITY_APPARMOR_BOOTPARAM_VALUE
|
||||
int "AppArmor boot parameter default value"
|
||||
depends on SECURITY_APPARMOR
|
||||
range 0 1
|
||||
default 1
|
||||
help
|
||||
This option sets the default value for the kernel parameter
|
||||
'apparmor', which allows AppArmor to be enabled or disabled
|
||||
at boot. If this option is set to 0 (zero), the AppArmor
|
||||
kernel parameter will default to 0, disabling AppArmor at
|
||||
boot. If this option is set to 1 (one), the AppArmor
|
||||
kernel parameter will default to 1, enabling AppArmor at
|
||||
boot.
|
||||
|
||||
If you are unsure how to answer this question, answer 1.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Makefile for AppArmor Linux Security Module
|
||||
#
|
||||
obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
|
||||
|
||||
apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \
|
||||
path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \
|
||||
resource.o sid.o file.o
|
||||
|
||||
clean-files: capability_names.h af_names.h
|
||||
|
||||
quiet_cmd_make-caps = GEN $@
|
||||
cmd_make-caps = echo "static const char *capability_names[] = {" > $@ ; sed -n -e "/CAP_FS_MASK/d" -e "s/^\#define[ \\t]\\+CAP_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\$$/[\\2] = \"\\1\",/p" $< | tr A-Z a-z >> $@ ; echo "};" >> $@
|
||||
|
||||
quiet_cmd_make-rlim = GEN $@
|
||||
cmd_make-rlim = echo "static const char *rlim_names[] = {" > $@ ; sed -n --e "/AF_MAX/d" -e "s/^\# \\?define[ \\t]\\+RLIMIT_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\\(.*\\)\$$/[\\2] = \"\\1\",/p" $< | tr A-Z a-z >> $@ ; echo "};" >> $@ ; echo "static const int rlim_map[] = {" >> $@ ; sed -n -e "/AF_MAX/d" -e "s/^\# \\?define[ \\t]\\+\\(RLIMIT_[A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\\(.*\\)\$$/\\1,/p" $< >> $@ ; echo "};" >> $@
|
||||
|
||||
$(obj)/capability.o : $(obj)/capability_names.h
|
||||
$(obj)/resource.o : $(obj)/rlim_names.h
|
||||
$(obj)/capability_names.h : $(srctree)/include/linux/capability.h
|
||||
$(call cmd,make-caps)
|
||||
$(obj)/af_names.h : $(srctree)/include/linux/socket.h
|
||||
$(call cmd,make-af)
|
||||
$(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h
|
||||
$(call cmd,make-rlim)
|
||||
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor /sys/kernel/security/apparmor interface functions
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/security.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/namei.h>
|
||||
|
||||
#include "include/apparmor.h"
|
||||
#include "include/apparmorfs.h"
|
||||
#include "include/audit.h"
|
||||
#include "include/context.h"
|
||||
#include "include/policy.h"
|
||||
|
||||
/**
|
||||
* aa_simple_write_to_buffer - common routine for getting policy from user
|
||||
* @op: operation doing the user buffer copy
|
||||
* @userbuf: user buffer to copy data from (NOT NULL)
|
||||
* @alloc_size: size of user buffer
|
||||
* @copy_size: size of data to copy from user buffer
|
||||
* @pos: position write is at in the file (NOT NULL)
|
||||
*
|
||||
* Returns: kernel buffer containing copy of user buffer data or an
|
||||
* ERR_PTR on failure.
|
||||
*/
|
||||
static char *aa_simple_write_to_buffer(int op, const char __user *userbuf,
|
||||
size_t alloc_size, size_t copy_size,
|
||||
loff_t *pos)
|
||||
{
|
||||
char *data;
|
||||
|
||||
if (*pos != 0)
|
||||
/* only writes from pos 0, that is complete writes */
|
||||
return ERR_PTR(-ESPIPE);
|
||||
|
||||
/*
|
||||
* Don't allow profile load/replace/remove from profiles that don't
|
||||
* have CAP_MAC_ADMIN
|
||||
*/
|
||||
if (!aa_may_manage_policy(op))
|
||||
return ERR_PTR(-EACCES);
|
||||
|
||||
/* freed by caller to simple_write_to_buffer */
|
||||
data = kvmalloc(alloc_size);
|
||||
if (data == NULL)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
if (copy_from_user(data, userbuf, copy_size)) {
|
||||
kvfree(data);
|
||||
return ERR_PTR(-EFAULT);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
/* .load file hook fn to load policy */
|
||||
static ssize_t 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(OP_PROF_LOAD, buf, size, size, pos);
|
||||
|
||||
error = PTR_ERR(data);
|
||||
if (!IS_ERR(data)) {
|
||||
error = aa_replace_profiles(data, size, PROF_ADD);
|
||||
kvfree(data);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static const struct file_operations aa_fs_profile_load = {
|
||||
.write = profile_load
|
||||
};
|
||||
|
||||
/* .replace file hook fn to load and/or replace policy */
|
||||
static ssize_t 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(OP_PROF_REPL, buf, size, size, pos);
|
||||
error = PTR_ERR(data);
|
||||
if (!IS_ERR(data)) {
|
||||
error = aa_replace_profiles(data, size, PROF_REPLACE);
|
||||
kvfree(data);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static const struct file_operations aa_fs_profile_replace = {
|
||||
.write = profile_replace
|
||||
};
|
||||
|
||||
/* .remove file hook fn to remove loaded policy */
|
||||
static ssize_t profile_remove(struct file *f, const char __user *buf,
|
||||
size_t size, loff_t *pos)
|
||||
{
|
||||
char *data;
|
||||
ssize_t error;
|
||||
|
||||
/*
|
||||
* aa_remove_profile needs a null terminated string so 1 extra
|
||||
* byte is allocated and the copied data is null terminated.
|
||||
*/
|
||||
data = aa_simple_write_to_buffer(OP_PROF_RM, buf, size + 1, size, pos);
|
||||
|
||||
error = PTR_ERR(data);
|
||||
if (!IS_ERR(data)) {
|
||||
data[size] = 0;
|
||||
error = aa_remove_profiles(data, size);
|
||||
kvfree(data);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static const struct file_operations aa_fs_profile_remove = {
|
||||
.write = profile_remove
|
||||
};
|
||||
|
||||
/** Base file system setup **/
|
||||
|
||||
static struct dentry *aa_fs_dentry __initdata;
|
||||
|
||||
static void __init aafs_remove(const char *name)
|
||||
{
|
||||
struct dentry *dentry;
|
||||
|
||||
dentry = lookup_one_len(name, aa_fs_dentry, strlen(name));
|
||||
if (!IS_ERR(dentry)) {
|
||||
securityfs_remove(dentry);
|
||||
dput(dentry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* aafs_create - create an entry in the apparmor filesystem
|
||||
* @name: name of the entry (NOT NULL)
|
||||
* @mask: file permission mask of the file
|
||||
* @fops: file operations for the file (NOT NULL)
|
||||
*
|
||||
* Used aafs_remove to remove entries created with this fn.
|
||||
*/
|
||||
static int __init aafs_create(const char *name, int mask,
|
||||
const struct file_operations *fops)
|
||||
{
|
||||
struct dentry *dentry;
|
||||
|
||||
dentry = securityfs_create_file(name, S_IFREG | mask, aa_fs_dentry,
|
||||
NULL, fops);
|
||||
|
||||
return IS_ERR(dentry) ? PTR_ERR(dentry) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_destroy_aafs - cleanup and free aafs
|
||||
*
|
||||
* releases dentries allocated by aa_create_aafs
|
||||
*/
|
||||
void __init aa_destroy_aafs(void)
|
||||
{
|
||||
if (aa_fs_dentry) {
|
||||
aafs_remove(".remove");
|
||||
aafs_remove(".replace");
|
||||
aafs_remove(".load");
|
||||
|
||||
securityfs_remove(aa_fs_dentry);
|
||||
aa_fs_dentry = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_create_aafs - create the apparmor security filesystem
|
||||
*
|
||||
* dentries created here are released by aa_destroy_aafs
|
||||
*
|
||||
* Returns: error on failure
|
||||
*/
|
||||
int __init aa_create_aafs(void)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (!apparmor_initialized)
|
||||
return 0;
|
||||
|
||||
if (aa_fs_dentry) {
|
||||
AA_ERROR("%s: AppArmor securityfs already exists\n", __func__);
|
||||
return -EEXIST;
|
||||
}
|
||||
|
||||
aa_fs_dentry = securityfs_create_dir("apparmor", NULL);
|
||||
if (IS_ERR(aa_fs_dentry)) {
|
||||
error = PTR_ERR(aa_fs_dentry);
|
||||
aa_fs_dentry = NULL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
error = aafs_create(".load", 0640, &aa_fs_profile_load);
|
||||
if (error)
|
||||
goto error;
|
||||
error = aafs_create(".replace", 0640, &aa_fs_profile_replace);
|
||||
if (error)
|
||||
goto error;
|
||||
error = aafs_create(".remove", 0640, &aa_fs_profile_remove);
|
||||
if (error)
|
||||
goto error;
|
||||
|
||||
/* TODO: add support for apparmorfs_null and apparmorfs_mnt */
|
||||
|
||||
/* Report that AppArmor fs is enabled */
|
||||
aa_info_message("AppArmor Filesystem Enabled");
|
||||
return 0;
|
||||
|
||||
error:
|
||||
aa_destroy_aafs();
|
||||
AA_ERROR("Error creating AppArmor securityfs\n");
|
||||
return error;
|
||||
}
|
||||
|
||||
fs_initcall(aa_create_aafs);
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor auditing functions
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/audit.h>
|
||||
#include <linux/socket.h>
|
||||
|
||||
#include "include/apparmor.h"
|
||||
#include "include/audit.h"
|
||||
#include "include/policy.h"
|
||||
|
||||
const char *op_table[] = {
|
||||
"null",
|
||||
|
||||
"sysctl",
|
||||
"capable",
|
||||
|
||||
"unlink",
|
||||
"mkdir",
|
||||
"rmdir",
|
||||
"mknod",
|
||||
"truncate",
|
||||
"link",
|
||||
"symlink",
|
||||
"rename_src",
|
||||
"rename_dest",
|
||||
"chmod",
|
||||
"chown",
|
||||
"getattr",
|
||||
"open",
|
||||
|
||||
"file_perm",
|
||||
"file_lock",
|
||||
"file_mmap",
|
||||
"file_mprotect",
|
||||
|
||||
"create",
|
||||
"post_create",
|
||||
"bind",
|
||||
"connect",
|
||||
"listen",
|
||||
"accept",
|
||||
"sendmsg",
|
||||
"recvmsg",
|
||||
"getsockname",
|
||||
"getpeername",
|
||||
"getsockopt",
|
||||
"setsockopt",
|
||||
"socket_shutdown",
|
||||
|
||||
"ptrace",
|
||||
|
||||
"exec",
|
||||
"change_hat",
|
||||
"change_profile",
|
||||
"change_onexec",
|
||||
|
||||
"setprocattr",
|
||||
"setrlimit",
|
||||
|
||||
"profile_replace",
|
||||
"profile_load",
|
||||
"profile_remove"
|
||||
};
|
||||
|
||||
const char *audit_mode_names[] = {
|
||||
"normal",
|
||||
"quiet_denied",
|
||||
"quiet",
|
||||
"noquiet",
|
||||
"all"
|
||||
};
|
||||
|
||||
static char *aa_audit_type[] = {
|
||||
"AUDIT",
|
||||
"ALLOWED",
|
||||
"DENIED",
|
||||
"HINT",
|
||||
"STATUS",
|
||||
"ERROR",
|
||||
"KILLED"
|
||||
};
|
||||
|
||||
/*
|
||||
* Currently AppArmor auditing is fed straight into the audit framework.
|
||||
*
|
||||
* TODO:
|
||||
* netlink interface for complain mode
|
||||
* user auditing, - send user auditing to netlink interface
|
||||
* system control of whether user audit messages go to system log
|
||||
*/
|
||||
|
||||
/**
|
||||
* audit_base - core AppArmor function.
|
||||
* @ab: audit buffer to fill (NOT NULL)
|
||||
* @ca: audit structure containing data to audit (NOT NULL)
|
||||
*
|
||||
* Record common AppArmor audit data from @sa
|
||||
*/
|
||||
static void audit_pre(struct audit_buffer *ab, void *ca)
|
||||
{
|
||||
struct common_audit_data *sa = ca;
|
||||
struct task_struct *tsk = sa->tsk ? sa->tsk : current;
|
||||
|
||||
if (aa_g_audit_header) {
|
||||
audit_log_format(ab, "apparmor=");
|
||||
audit_log_string(ab, aa_audit_type[sa->aad.type]);
|
||||
}
|
||||
|
||||
if (sa->aad.op) {
|
||||
audit_log_format(ab, " operation=");
|
||||
audit_log_string(ab, op_table[sa->aad.op]);
|
||||
}
|
||||
|
||||
if (sa->aad.info) {
|
||||
audit_log_format(ab, " info=");
|
||||
audit_log_string(ab, sa->aad.info);
|
||||
if (sa->aad.error)
|
||||
audit_log_format(ab, " error=%d", sa->aad.error);
|
||||
}
|
||||
|
||||
if (sa->aad.profile) {
|
||||
struct aa_profile *profile = sa->aad.profile;
|
||||
pid_t pid;
|
||||
rcu_read_lock();
|
||||
pid = tsk->real_parent->pid;
|
||||
rcu_read_unlock();
|
||||
audit_log_format(ab, " parent=%d", pid);
|
||||
if (profile->ns != root_ns) {
|
||||
audit_log_format(ab, " namespace=");
|
||||
audit_log_untrustedstring(ab, profile->ns->base.hname);
|
||||
}
|
||||
audit_log_format(ab, " profile=");
|
||||
audit_log_untrustedstring(ab, profile->base.hname);
|
||||
}
|
||||
|
||||
if (sa->aad.name) {
|
||||
audit_log_format(ab, " name=");
|
||||
audit_log_untrustedstring(ab, sa->aad.name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_audit_msg - Log a message to the audit subsystem
|
||||
* @sa: audit event structure (NOT NULL)
|
||||
* @cb: optional callback fn for type specific fields (MAYBE NULL)
|
||||
*/
|
||||
void aa_audit_msg(int type, struct common_audit_data *sa,
|
||||
void (*cb) (struct audit_buffer *, void *))
|
||||
{
|
||||
sa->aad.type = type;
|
||||
sa->lsm_pre_audit = audit_pre;
|
||||
sa->lsm_post_audit = cb;
|
||||
common_lsm_audit(sa);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_audit - Log a profile based audit event to the audit subsystem
|
||||
* @type: audit type for the message
|
||||
* @profile: profile to check against (NOT NULL)
|
||||
* @gfp: allocation flags to use
|
||||
* @sa: audit event (NOT NULL)
|
||||
* @cb: optional callback fn for type specific fields (MAYBE NULL)
|
||||
*
|
||||
* Handle default message switching based off of audit mode flags
|
||||
*
|
||||
* Returns: error on failure
|
||||
*/
|
||||
int aa_audit(int type, struct aa_profile *profile, gfp_t gfp,
|
||||
struct common_audit_data *sa,
|
||||
void (*cb) (struct audit_buffer *, void *))
|
||||
{
|
||||
BUG_ON(!profile);
|
||||
|
||||
if (type == AUDIT_APPARMOR_AUTO) {
|
||||
if (likely(!sa->aad.error)) {
|
||||
if (AUDIT_MODE(profile) != AUDIT_ALL)
|
||||
return 0;
|
||||
type = AUDIT_APPARMOR_AUDIT;
|
||||
} else if (COMPLAIN_MODE(profile))
|
||||
type = AUDIT_APPARMOR_ALLOWED;
|
||||
else
|
||||
type = AUDIT_APPARMOR_DENIED;
|
||||
}
|
||||
if (AUDIT_MODE(profile) == AUDIT_QUIET ||
|
||||
(type == AUDIT_APPARMOR_DENIED &&
|
||||
AUDIT_MODE(profile) == AUDIT_QUIET))
|
||||
return sa->aad.error;
|
||||
|
||||
if (KILL_MODE(profile) && type == AUDIT_APPARMOR_DENIED)
|
||||
type = AUDIT_APPARMOR_KILL;
|
||||
|
||||
if (!unconfined(profile))
|
||||
sa->aad.profile = profile;
|
||||
|
||||
aa_audit_msg(type, sa, cb);
|
||||
|
||||
if (sa->aad.type == AUDIT_APPARMOR_KILL)
|
||||
(void)send_sig_info(SIGKILL, NULL, sa->tsk ? sa->tsk : current);
|
||||
|
||||
if (sa->aad.type == AUDIT_APPARMOR_ALLOWED)
|
||||
return complain_error(sa->aad.error);
|
||||
|
||||
return sa->aad.error;
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor capability mediation functions
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/capability.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/gfp.h>
|
||||
|
||||
#include "include/apparmor.h"
|
||||
#include "include/capability.h"
|
||||
#include "include/context.h"
|
||||
#include "include/policy.h"
|
||||
#include "include/audit.h"
|
||||
|
||||
/*
|
||||
* Table of capability names: we generate it from capabilities.h.
|
||||
*/
|
||||
#include "capability_names.h"
|
||||
|
||||
struct audit_cache {
|
||||
struct aa_profile *profile;
|
||||
kernel_cap_t caps;
|
||||
};
|
||||
|
||||
static DEFINE_PER_CPU(struct audit_cache, audit_cache);
|
||||
|
||||
/**
|
||||
* audit_cb - call back for capability components of audit struct
|
||||
* @ab - audit buffer (NOT NULL)
|
||||
* @va - audit struct to audit data from (NOT NULL)
|
||||
*/
|
||||
static void audit_cb(struct audit_buffer *ab, void *va)
|
||||
{
|
||||
struct common_audit_data *sa = va;
|
||||
audit_log_format(ab, " capname=");
|
||||
audit_log_untrustedstring(ab, capability_names[sa->u.cap]);
|
||||
}
|
||||
|
||||
/**
|
||||
* audit_caps - audit a capability
|
||||
* @profile: profile confining task (NOT NULL)
|
||||
* @task: task capability test was performed against (NOT NULL)
|
||||
* @cap: capability tested
|
||||
* @error: error code returned by test
|
||||
*
|
||||
* Do auditing of capability and handle, audit/complain/kill modes switching
|
||||
* and duplicate message elimination.
|
||||
*
|
||||
* Returns: 0 or sa->error on success, error code on failure
|
||||
*/
|
||||
static int audit_caps(struct aa_profile *profile, struct task_struct *task,
|
||||
int cap, int error)
|
||||
{
|
||||
struct audit_cache *ent;
|
||||
int type = AUDIT_APPARMOR_AUTO;
|
||||
struct common_audit_data sa;
|
||||
COMMON_AUDIT_DATA_INIT(&sa, CAP);
|
||||
sa.tsk = task;
|
||||
sa.u.cap = cap;
|
||||
sa.aad.op = OP_CAPABLE;
|
||||
sa.aad.error = error;
|
||||
|
||||
if (likely(!error)) {
|
||||
/* test if auditing is being forced */
|
||||
if (likely((AUDIT_MODE(profile) != AUDIT_ALL) &&
|
||||
!cap_raised(profile->caps.audit, cap)))
|
||||
return 0;
|
||||
type = AUDIT_APPARMOR_AUDIT;
|
||||
} else if (KILL_MODE(profile) ||
|
||||
cap_raised(profile->caps.kill, cap)) {
|
||||
type = AUDIT_APPARMOR_KILL;
|
||||
} else if (cap_raised(profile->caps.quiet, cap) &&
|
||||
AUDIT_MODE(profile) != AUDIT_NOQUIET &&
|
||||
AUDIT_MODE(profile) != AUDIT_ALL) {
|
||||
/* quiet auditing */
|
||||
return error;
|
||||
}
|
||||
|
||||
/* Do simple duplicate message elimination */
|
||||
ent = &get_cpu_var(audit_cache);
|
||||
if (profile == ent->profile && cap_raised(ent->caps, cap)) {
|
||||
put_cpu_var(audit_cache);
|
||||
if (COMPLAIN_MODE(profile))
|
||||
return complain_error(error);
|
||||
return error;
|
||||
} else {
|
||||
aa_put_profile(ent->profile);
|
||||
ent->profile = aa_get_profile(profile);
|
||||
cap_raise(ent->caps, cap);
|
||||
}
|
||||
put_cpu_var(audit_cache);
|
||||
|
||||
return aa_audit(type, profile, GFP_ATOMIC, &sa, audit_cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* profile_capable - test if profile allows use of capability @cap
|
||||
* @profile: profile being enforced (NOT NULL, NOT unconfined)
|
||||
* @cap: capability to test if allowed
|
||||
*
|
||||
* Returns: 0 if allowed else -EPERM
|
||||
*/
|
||||
static int profile_capable(struct aa_profile *profile, int cap)
|
||||
{
|
||||
return cap_raised(profile->caps.allow, cap) ? 0 : -EPERM;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_capable - test permission to use capability
|
||||
* @task: task doing capability test against (NOT NULL)
|
||||
* @profile: profile confining @task (NOT NULL)
|
||||
* @cap: capability to be tested
|
||||
* @audit: whether an audit record should be generated
|
||||
*
|
||||
* Look up capability in profile capability set.
|
||||
*
|
||||
* Returns: 0 on success, or else an error code.
|
||||
*/
|
||||
int aa_capable(struct task_struct *task, struct aa_profile *profile, int cap,
|
||||
int audit)
|
||||
{
|
||||
int error = profile_capable(profile, cap);
|
||||
|
||||
if (!audit) {
|
||||
if (COMPLAIN_MODE(profile))
|
||||
return complain_error(error);
|
||||
return error;
|
||||
}
|
||||
|
||||
return audit_caps(profile, task, cap, error);
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor functions used to manipulate object security
|
||||
* contexts.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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 sets confinement on every task, via the the aa_task_cxt and
|
||||
* the aa_task_cxt.profile, both of which are required and are not allowed
|
||||
* to be NULL. The aa_task_cxt is not reference counted and is unique
|
||||
* to each cred (which is reference count). The profile pointed to by
|
||||
* the task_cxt is reference counted.
|
||||
*
|
||||
* TODO
|
||||
* If a task uses change_hat it currently does not return to the old
|
||||
* cred or task context but instead creates a new one. Ideally the task
|
||||
* should return to the previous cred if it has not been modified.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "include/context.h"
|
||||
#include "include/policy.h"
|
||||
|
||||
/**
|
||||
* aa_alloc_task_context - allocate a new task_cxt
|
||||
* @flags: gfp flags for allocation
|
||||
*
|
||||
* Returns: allocated buffer or NULL on failure
|
||||
*/
|
||||
struct aa_task_cxt *aa_alloc_task_context(gfp_t flags)
|
||||
{
|
||||
return kzalloc(sizeof(struct aa_task_cxt), flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_free_task_context - free a task_cxt
|
||||
* @cxt: task_cxt to free (MAYBE NULL)
|
||||
*/
|
||||
void aa_free_task_context(struct aa_task_cxt *cxt)
|
||||
{
|
||||
if (cxt) {
|
||||
aa_put_profile(cxt->profile);
|
||||
aa_put_profile(cxt->previous);
|
||||
aa_put_profile(cxt->onexec);
|
||||
|
||||
kzfree(cxt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_dup_task_context - duplicate a task context, incrementing reference counts
|
||||
* @new: a blank task context (NOT NULL)
|
||||
* @old: the task context to copy (NOT NULL)
|
||||
*/
|
||||
void aa_dup_task_context(struct aa_task_cxt *new, const struct aa_task_cxt *old)
|
||||
{
|
||||
*new = *old;
|
||||
aa_get_profile(new->profile);
|
||||
aa_get_profile(new->previous);
|
||||
aa_get_profile(new->onexec);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_replace_current_profile - replace the current tasks profiles
|
||||
* @profile: new profile (NOT NULL)
|
||||
*
|
||||
* Returns: 0 or error on failure
|
||||
*/
|
||||
int aa_replace_current_profile(struct aa_profile *profile)
|
||||
{
|
||||
struct aa_task_cxt *cxt = current_cred()->security;
|
||||
struct cred *new;
|
||||
BUG_ON(!profile);
|
||||
|
||||
if (cxt->profile == profile)
|
||||
return 0;
|
||||
|
||||
new = prepare_creds();
|
||||
if (!new)
|
||||
return -ENOMEM;
|
||||
|
||||
cxt = new->security;
|
||||
if (unconfined(profile) || (cxt->profile->ns != profile->ns)) {
|
||||
/* if switching to unconfined or a different profile namespace
|
||||
* clear out context state
|
||||
*/
|
||||
aa_put_profile(cxt->previous);
|
||||
aa_put_profile(cxt->onexec);
|
||||
cxt->previous = NULL;
|
||||
cxt->onexec = NULL;
|
||||
cxt->token = 0;
|
||||
}
|
||||
/* be careful switching cxt->profile, when racing replacement it
|
||||
* is possible that cxt->profile->replacedby is the reference keeping
|
||||
* @profile valid, so make sure to get its reference before dropping
|
||||
* the reference on cxt->profile */
|
||||
aa_get_profile(profile);
|
||||
aa_put_profile(cxt->profile);
|
||||
cxt->profile = profile;
|
||||
|
||||
commit_creds(new);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_set_current_onexec - set the tasks change_profile to happen onexec
|
||||
* @profile: system profile to set at exec (MAYBE NULL to clear value)
|
||||
*
|
||||
* Returns: 0 or error on failure
|
||||
*/
|
||||
int aa_set_current_onexec(struct aa_profile *profile)
|
||||
{
|
||||
struct aa_task_cxt *cxt;
|
||||
struct cred *new = prepare_creds();
|
||||
if (!new)
|
||||
return -ENOMEM;
|
||||
|
||||
cxt = new->security;
|
||||
aa_get_profile(profile);
|
||||
aa_put_profile(cxt->onexec);
|
||||
cxt->onexec = profile;
|
||||
|
||||
commit_creds(new);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_set_current_hat - set the current tasks hat
|
||||
* @profile: profile to set as the current hat (NOT NULL)
|
||||
* @token: token value that must be specified to change from the hat
|
||||
*
|
||||
* Do switch of tasks hat. If the task is currently in a hat
|
||||
* validate the token to match.
|
||||
*
|
||||
* Returns: 0 or error on failure
|
||||
*/
|
||||
int aa_set_current_hat(struct aa_profile *profile, u64 token)
|
||||
{
|
||||
struct aa_task_cxt *cxt;
|
||||
struct cred *new = prepare_creds();
|
||||
if (!new)
|
||||
return -ENOMEM;
|
||||
BUG_ON(!profile);
|
||||
|
||||
cxt = new->security;
|
||||
if (!cxt->previous) {
|
||||
/* transfer refcount */
|
||||
cxt->previous = cxt->profile;
|
||||
cxt->token = token;
|
||||
} else if (cxt->token == token) {
|
||||
aa_put_profile(cxt->profile);
|
||||
} else {
|
||||
/* previous_profile && cxt->token != token */
|
||||
abort_creds(new);
|
||||
return -EACCES;
|
||||
}
|
||||
cxt->profile = aa_get_profile(aa_newest_version(profile));
|
||||
/* clear exec on switching context */
|
||||
aa_put_profile(cxt->onexec);
|
||||
cxt->onexec = NULL;
|
||||
|
||||
commit_creds(new);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_restore_previous_profile - exit from hat context restoring the profile
|
||||
* @token: the token that must be matched to exit hat context
|
||||
*
|
||||
* Attempt to return out of a hat to the previous profile. The token
|
||||
* must match the stored token value.
|
||||
*
|
||||
* Returns: 0 or error of failure
|
||||
*/
|
||||
int aa_restore_previous_profile(u64 token)
|
||||
{
|
||||
struct aa_task_cxt *cxt;
|
||||
struct cred *new = prepare_creds();
|
||||
if (!new)
|
||||
return -ENOMEM;
|
||||
|
||||
cxt = new->security;
|
||||
if (cxt->token != token) {
|
||||
abort_creds(new);
|
||||
return -EACCES;
|
||||
}
|
||||
/* ignore restores when there is no saved profile */
|
||||
if (!cxt->previous) {
|
||||
abort_creds(new);
|
||||
return 0;
|
||||
}
|
||||
|
||||
aa_put_profile(cxt->profile);
|
||||
cxt->profile = aa_newest_version(cxt->previous);
|
||||
BUG_ON(!cxt->profile);
|
||||
if (unlikely(cxt->profile != cxt->previous)) {
|
||||
aa_get_profile(cxt->profile);
|
||||
aa_put_profile(cxt->previous);
|
||||
}
|
||||
/* clear exec && prev information when restoring to previous context */
|
||||
cxt->previous = NULL;
|
||||
cxt->token = 0;
|
||||
aa_put_profile(cxt->onexec);
|
||||
cxt->onexec = NULL;
|
||||
|
||||
commit_creds(new);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,823 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor policy attachment and domain transitions
|
||||
*
|
||||
* Copyright (C) 2002-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/fdtable.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/syscalls.h>
|
||||
#include <linux/tracehook.h>
|
||||
#include <linux/personality.h>
|
||||
|
||||
#include "include/audit.h"
|
||||
#include "include/apparmorfs.h"
|
||||
#include "include/context.h"
|
||||
#include "include/domain.h"
|
||||
#include "include/file.h"
|
||||
#include "include/ipc.h"
|
||||
#include "include/match.h"
|
||||
#include "include/path.h"
|
||||
#include "include/policy.h"
|
||||
|
||||
/**
|
||||
* aa_free_domain_entries - free entries in a domain table
|
||||
* @domain: the domain table to free (MAYBE NULL)
|
||||
*/
|
||||
void aa_free_domain_entries(struct aa_domain *domain)
|
||||
{
|
||||
int i;
|
||||
if (domain) {
|
||||
if (!domain->table)
|
||||
return;
|
||||
|
||||
for (i = 0; i < domain->size; i++)
|
||||
kzfree(domain->table[i]);
|
||||
kzfree(domain->table);
|
||||
domain->table = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* may_change_ptraced_domain - check if can change profile on ptraced task
|
||||
* @task: task we want to change profile of (NOT NULL)
|
||||
* @to_profile: profile to change to (NOT NULL)
|
||||
*
|
||||
* Check if the task is ptraced and if so if the tracing task is allowed
|
||||
* to trace the new domain
|
||||
*
|
||||
* Returns: %0 or error if change not allowed
|
||||
*/
|
||||
static int may_change_ptraced_domain(struct task_struct *task,
|
||||
struct aa_profile *to_profile)
|
||||
{
|
||||
struct task_struct *tracer;
|
||||
const struct cred *cred = NULL;
|
||||
struct aa_profile *tracerp = NULL;
|
||||
int error = 0;
|
||||
|
||||
rcu_read_lock();
|
||||
tracer = tracehook_tracer_task(task);
|
||||
if (tracer) {
|
||||
/* released below */
|
||||
cred = get_task_cred(tracer);
|
||||
tracerp = aa_cred_profile(cred);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
|
||||
/* not ptraced */
|
||||
if (!tracer || unconfined(tracerp))
|
||||
goto out;
|
||||
|
||||
error = aa_may_ptrace(tracer, tracerp, to_profile, PTRACE_MODE_ATTACH);
|
||||
|
||||
out:
|
||||
if (cred)
|
||||
put_cred(cred);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* change_profile_perms - find permissions for change_profile
|
||||
* @profile: the current profile (NOT NULL)
|
||||
* @ns: the namespace being switched to (NOT NULL)
|
||||
* @name: the name of the profile to change to (NOT NULL)
|
||||
* @request: requested perms
|
||||
* @start: state to start matching in
|
||||
*
|
||||
* Returns: permission set
|
||||
*/
|
||||
static struct file_perms change_profile_perms(struct aa_profile *profile,
|
||||
struct aa_namespace *ns,
|
||||
const char *name, u32 request,
|
||||
unsigned int start)
|
||||
{
|
||||
struct file_perms perms;
|
||||
struct path_cond cond = { };
|
||||
unsigned int state;
|
||||
|
||||
if (unconfined(profile)) {
|
||||
perms.allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC;
|
||||
perms.audit = perms.quiet = perms.kill = 0;
|
||||
return perms;
|
||||
} else if (!profile->file.dfa) {
|
||||
return nullperms;
|
||||
} else if ((ns == profile->ns)) {
|
||||
/* try matching against rules with out namespace prepended */
|
||||
aa_str_perms(profile->file.dfa, start, name, &cond, &perms);
|
||||
if (COMBINED_PERM_MASK(perms) & request)
|
||||
return perms;
|
||||
}
|
||||
|
||||
/* try matching with namespace name and then profile */
|
||||
state = aa_dfa_match(profile->file.dfa, start, ns->base.name);
|
||||
state = aa_dfa_match_len(profile->file.dfa, state, ":", 1);
|
||||
aa_str_perms(profile->file.dfa, state, name, &cond, &perms);
|
||||
|
||||
return perms;
|
||||
}
|
||||
|
||||
/**
|
||||
* __attach_match_ - find an attachment match
|
||||
* @name - to match against (NOT NULL)
|
||||
* @head - profile list to walk (NOT NULL)
|
||||
*
|
||||
* Do a linear search on the profiles in the list. There is a matching
|
||||
* preference where an exact match is preferred over a name which uses
|
||||
* expressions to match, and matching expressions with the greatest
|
||||
* xmatch_len are preferred.
|
||||
*
|
||||
* Requires: @head not be shared or have appropriate locks held
|
||||
*
|
||||
* Returns: profile or NULL if no match found
|
||||
*/
|
||||
static struct aa_profile *__attach_match(const char *name,
|
||||
struct list_head *head)
|
||||
{
|
||||
int len = 0;
|
||||
struct aa_profile *profile, *candidate = NULL;
|
||||
|
||||
list_for_each_entry(profile, head, base.list) {
|
||||
if (profile->flags & PFLAG_NULL)
|
||||
continue;
|
||||
if (profile->xmatch && profile->xmatch_len > len) {
|
||||
unsigned int state = aa_dfa_match(profile->xmatch,
|
||||
DFA_START, name);
|
||||
u32 perm = dfa_user_allow(profile->xmatch, state);
|
||||
/* any accepting state means a valid match. */
|
||||
if (perm & MAY_EXEC) {
|
||||
candidate = profile;
|
||||
len = profile->xmatch_len;
|
||||
}
|
||||
} else if (!strcmp(profile->base.name, name))
|
||||
/* exact non-re match, no more searching required */
|
||||
return profile;
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
/**
|
||||
* find_attach - do attachment search for unconfined processes
|
||||
* @ns: the current namespace (NOT NULL)
|
||||
* @list: list to search (NOT NULL)
|
||||
* @name: the executable name to match against (NOT NULL)
|
||||
*
|
||||
* Returns: profile or NULL if no match found
|
||||
*/
|
||||
static struct aa_profile *find_attach(struct aa_namespace *ns,
|
||||
struct list_head *list, const char *name)
|
||||
{
|
||||
struct aa_profile *profile;
|
||||
|
||||
read_lock(&ns->lock);
|
||||
profile = aa_get_profile(__attach_match(name, list));
|
||||
read_unlock(&ns->lock);
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* separate_fqname - separate the namespace and profile names
|
||||
* @fqname: the fqname name to split (NOT NULL)
|
||||
* @ns_name: the namespace name if it exists (NOT NULL)
|
||||
*
|
||||
* This is the xtable equivalent routine of aa_split_fqname. It finds the
|
||||
* split in an xtable fqname which contains an embedded \0 instead of a :
|
||||
* if a namespace is specified. This is done so the xtable is constant and
|
||||
* isn't re-split on every lookup.
|
||||
*
|
||||
* Either the profile or namespace name may be optional but if the namespace
|
||||
* is specified the profile name termination must be present. This results
|
||||
* in the following possible encodings:
|
||||
* profile_name\0
|
||||
* :ns_name\0profile_name\0
|
||||
* :ns_name\0\0
|
||||
*
|
||||
* NOTE: the xtable fqname is pre-validated at load time in unpack_trans_table
|
||||
*
|
||||
* Returns: profile name if it is specified else NULL
|
||||
*/
|
||||
static const char *separate_fqname(const char *fqname, const char **ns_name)
|
||||
{
|
||||
const char *name;
|
||||
|
||||
if (fqname[0] == ':') {
|
||||
/* In this case there is guaranteed to be two \0 terminators
|
||||
* in the string. They are verified at load time by
|
||||
* by unpack_trans_table
|
||||
*/
|
||||
*ns_name = fqname + 1; /* skip : */
|
||||
name = *ns_name + strlen(*ns_name) + 1;
|
||||
if (!*name)
|
||||
name = NULL;
|
||||
} else {
|
||||
*ns_name = NULL;
|
||||
name = fqname;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
static const char *next_name(int xtype, const char *name)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* x_table_lookup - lookup an x transition name via transition table
|
||||
* @profile: current profile (NOT NULL)
|
||||
* @xindex: index into x transition table
|
||||
*
|
||||
* Returns: refcounted profile, or NULL on failure (MAYBE NULL)
|
||||
*/
|
||||
static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex)
|
||||
{
|
||||
struct aa_profile *new_profile = NULL;
|
||||
struct aa_namespace *ns = profile->ns;
|
||||
u32 xtype = xindex & AA_X_TYPE_MASK;
|
||||
int index = xindex & AA_X_INDEX_MASK;
|
||||
const char *name;
|
||||
|
||||
/* index is guaranteed to be in range, validated at load time */
|
||||
for (name = profile->file.trans.table[index]; !new_profile && name;
|
||||
name = next_name(xtype, name)) {
|
||||
struct aa_namespace *new_ns;
|
||||
const char *xname = NULL;
|
||||
|
||||
new_ns = NULL;
|
||||
if (xindex & AA_X_CHILD) {
|
||||
/* release by caller */
|
||||
new_profile = aa_find_child(profile, name);
|
||||
continue;
|
||||
} else if (*name == ':') {
|
||||
/* switching namespace */
|
||||
const char *ns_name;
|
||||
xname = name = separate_fqname(name, &ns_name);
|
||||
if (!xname)
|
||||
/* no name so use profile name */
|
||||
xname = profile->base.hname;
|
||||
if (*ns_name == '@') {
|
||||
/* TODO: variable support */
|
||||
;
|
||||
}
|
||||
/* released below */
|
||||
new_ns = aa_find_namespace(ns, ns_name);
|
||||
if (!new_ns)
|
||||
continue;
|
||||
} else if (*name == '@') {
|
||||
/* TODO: variable support */
|
||||
continue;
|
||||
} else {
|
||||
/* basic namespace lookup */
|
||||
xname = name;
|
||||
}
|
||||
|
||||
/* released by caller */
|
||||
new_profile = aa_lookup_profile(new_ns ? new_ns : ns, xname);
|
||||
aa_put_namespace(new_ns);
|
||||
}
|
||||
|
||||
/* released by caller */
|
||||
return new_profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* x_to_profile - get target profile for a given xindex
|
||||
* @profile: current profile (NOT NULL)
|
||||
* @name: name to lookup (NOT NULL)
|
||||
* @xindex: index into x transition table
|
||||
*
|
||||
* find profile for a transition index
|
||||
*
|
||||
* Returns: refcounted profile or NULL if not found available
|
||||
*/
|
||||
static struct aa_profile *x_to_profile(struct aa_profile *profile,
|
||||
const char *name, u32 xindex)
|
||||
{
|
||||
struct aa_profile *new_profile = NULL;
|
||||
struct aa_namespace *ns = profile->ns;
|
||||
u32 xtype = xindex & AA_X_TYPE_MASK;
|
||||
|
||||
switch (xtype) {
|
||||
case AA_X_NONE:
|
||||
/* fail exec unless ix || ux fallback - handled by caller */
|
||||
return NULL;
|
||||
case AA_X_NAME:
|
||||
if (xindex & AA_X_CHILD)
|
||||
/* released by caller */
|
||||
new_profile = find_attach(ns, &profile->base.profiles,
|
||||
name);
|
||||
else
|
||||
/* released by caller */
|
||||
new_profile = find_attach(ns, &ns->base.profiles,
|
||||
name);
|
||||
break;
|
||||
case AA_X_TABLE:
|
||||
/* released by caller */
|
||||
new_profile = x_table_lookup(profile, xindex);
|
||||
break;
|
||||
}
|
||||
|
||||
/* released by caller */
|
||||
return new_profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* apparmor_bprm_set_creds - set the new creds on the bprm struct
|
||||
* @bprm: binprm for the exec (NOT NULL)
|
||||
*
|
||||
* Returns: %0 or error on failure
|
||||
*/
|
||||
int apparmor_bprm_set_creds(struct linux_binprm *bprm)
|
||||
{
|
||||
struct aa_task_cxt *cxt;
|
||||
struct aa_profile *profile, *new_profile = NULL;
|
||||
struct aa_namespace *ns;
|
||||
char *buffer = NULL;
|
||||
unsigned int state;
|
||||
struct file_perms perms = {};
|
||||
struct path_cond cond = {
|
||||
bprm->file->f_path.dentry->d_inode->i_uid,
|
||||
bprm->file->f_path.dentry->d_inode->i_mode
|
||||
};
|
||||
const char *name = NULL, *target = NULL, *info = NULL;
|
||||
int error = cap_bprm_set_creds(bprm);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
if (bprm->cred_prepared)
|
||||
return 0;
|
||||
|
||||
cxt = bprm->cred->security;
|
||||
BUG_ON(!cxt);
|
||||
|
||||
profile = aa_get_profile(aa_newest_version(cxt->profile));
|
||||
/*
|
||||
* get the namespace from the replacement profile as replacement
|
||||
* can change the namespace
|
||||
*/
|
||||
ns = profile->ns;
|
||||
state = profile->file.start;
|
||||
|
||||
/* buffer freed below, name is pointer into buffer */
|
||||
error = aa_get_name(&bprm->file->f_path, profile->path_flags, &buffer,
|
||||
&name);
|
||||
if (error) {
|
||||
if (profile->flags &
|
||||
(PFLAG_IX_ON_NAME_ERROR | PFLAG_UNCONFINED))
|
||||
error = 0;
|
||||
info = "Exec failed name resolution";
|
||||
name = bprm->filename;
|
||||
goto audit;
|
||||
}
|
||||
|
||||
/* Test for onexec first as onexec directives override other
|
||||
* x transitions.
|
||||
*/
|
||||
if (unconfined(profile)) {
|
||||
/* unconfined task */
|
||||
if (cxt->onexec)
|
||||
/* change_profile on exec already been granted */
|
||||
new_profile = aa_get_profile(cxt->onexec);
|
||||
else
|
||||
new_profile = find_attach(ns, &ns->base.profiles, name);
|
||||
if (!new_profile)
|
||||
goto cleanup;
|
||||
goto apply;
|
||||
}
|
||||
|
||||
/* find exec permissions for name */
|
||||
state = aa_str_perms(profile->file.dfa, state, name, &cond, &perms);
|
||||
if (cxt->onexec) {
|
||||
struct file_perms cp;
|
||||
info = "change_profile onexec";
|
||||
if (!(perms.allow & AA_MAY_ONEXEC))
|
||||
goto audit;
|
||||
|
||||
/* test if this exec can be paired with change_profile onexec.
|
||||
* onexec permission is linked to exec with a standard pairing
|
||||
* exec\0change_profile
|
||||
*/
|
||||
state = aa_dfa_null_transition(profile->file.dfa, state);
|
||||
cp = change_profile_perms(profile, cxt->onexec->ns, name,
|
||||
AA_MAY_ONEXEC, state);
|
||||
|
||||
if (!(cp.allow & AA_MAY_ONEXEC))
|
||||
goto audit;
|
||||
new_profile = aa_get_profile(aa_newest_version(cxt->onexec));
|
||||
goto apply;
|
||||
}
|
||||
|
||||
if (perms.allow & MAY_EXEC) {
|
||||
/* exec permission determine how to transition */
|
||||
new_profile = x_to_profile(profile, name, perms.xindex);
|
||||
if (!new_profile) {
|
||||
if (perms.xindex & AA_X_INHERIT) {
|
||||
/* (p|c|n)ix - don't change profile but do
|
||||
* use the newest version, which was picked
|
||||
* up above when getting profile
|
||||
*/
|
||||
info = "ix fallback";
|
||||
new_profile = aa_get_profile(profile);
|
||||
goto x_clear;
|
||||
} else if (perms.xindex & AA_X_UNCONFINED) {
|
||||
new_profile = aa_get_profile(ns->unconfined);
|
||||
info = "ux fallback";
|
||||
} else {
|
||||
error = -ENOENT;
|
||||
info = "profile not found";
|
||||
}
|
||||
}
|
||||
} else if (COMPLAIN_MODE(profile)) {
|
||||
/* no exec permission - are we in learning mode */
|
||||
new_profile = aa_new_null_profile(profile, 0);
|
||||
if (!new_profile) {
|
||||
error = -ENOMEM;
|
||||
info = "could not create null profile";
|
||||
} else {
|
||||
error = -EACCES;
|
||||
target = new_profile->base.hname;
|
||||
}
|
||||
perms.xindex |= AA_X_UNSAFE;
|
||||
} else
|
||||
/* fail exec */
|
||||
error = -EACCES;
|
||||
|
||||
if (!new_profile)
|
||||
goto audit;
|
||||
|
||||
if (bprm->unsafe & LSM_UNSAFE_SHARE) {
|
||||
/* FIXME: currently don't mediate shared state */
|
||||
;
|
||||
}
|
||||
|
||||
if (bprm->unsafe & (LSM_UNSAFE_PTRACE | LSM_UNSAFE_PTRACE_CAP)) {
|
||||
error = may_change_ptraced_domain(current, new_profile);
|
||||
if (error) {
|
||||
aa_put_profile(new_profile);
|
||||
goto audit;
|
||||
}
|
||||
}
|
||||
|
||||
/* Determine if secure exec is needed.
|
||||
* Can be at this point for the following reasons:
|
||||
* 1. unconfined switching to confined
|
||||
* 2. confined switching to different confinement
|
||||
* 3. confined switching to unconfined
|
||||
*
|
||||
* Cases 2 and 3 are marked as requiring secure exec
|
||||
* (unless policy specified "unsafe exec")
|
||||
*
|
||||
* bprm->unsafe is used to cache the AA_X_UNSAFE permission
|
||||
* to avoid having to recompute in secureexec
|
||||
*/
|
||||
if (!(perms.xindex & AA_X_UNSAFE)) {
|
||||
AA_DEBUG("scrubbing environment variables for %s profile=%s\n",
|
||||
name, new_profile->base.hname);
|
||||
bprm->unsafe |= AA_SECURE_X_NEEDED;
|
||||
}
|
||||
apply:
|
||||
target = new_profile->base.hname;
|
||||
/* when transitioning profiles clear unsafe personality bits */
|
||||
bprm->per_clear |= PER_CLEAR_ON_SETID;
|
||||
|
||||
x_clear:
|
||||
aa_put_profile(cxt->profile);
|
||||
/* transfer new profile reference will be released when cxt is freed */
|
||||
cxt->profile = new_profile;
|
||||
|
||||
/* clear out all temporary/transitional state from the context */
|
||||
aa_put_profile(cxt->previous);
|
||||
aa_put_profile(cxt->onexec);
|
||||
cxt->previous = NULL;
|
||||
cxt->onexec = NULL;
|
||||
cxt->token = 0;
|
||||
|
||||
audit:
|
||||
error = aa_audit_file(profile, &perms, GFP_KERNEL, OP_EXEC, MAY_EXEC,
|
||||
name, target, cond.uid, info, error);
|
||||
|
||||
cleanup:
|
||||
aa_put_profile(profile);
|
||||
kfree(buffer);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* apparmor_bprm_secureexec - determine if secureexec is needed
|
||||
* @bprm: binprm for exec (NOT NULL)
|
||||
*
|
||||
* Returns: %1 if secureexec is needed else %0
|
||||
*/
|
||||
int apparmor_bprm_secureexec(struct linux_binprm *bprm)
|
||||
{
|
||||
int ret = cap_bprm_secureexec(bprm);
|
||||
|
||||
/* the decision to use secure exec is computed in set_creds
|
||||
* and stored in bprm->unsafe.
|
||||
*/
|
||||
if (!ret && (bprm->unsafe & AA_SECURE_X_NEEDED))
|
||||
ret = 1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* apparmor_bprm_committing_creds - do task cleanup on committing new creds
|
||||
* @bprm: binprm for the exec (NOT NULL)
|
||||
*/
|
||||
void apparmor_bprm_committing_creds(struct linux_binprm *bprm)
|
||||
{
|
||||
struct aa_profile *profile = __aa_current_profile();
|
||||
struct aa_task_cxt *new_cxt = bprm->cred->security;
|
||||
|
||||
/* bail out if unconfined or not changing profile */
|
||||
if ((new_cxt->profile == profile) ||
|
||||
(unconfined(new_cxt->profile)))
|
||||
return;
|
||||
|
||||
current->pdeath_signal = 0;
|
||||
|
||||
/* reset soft limits and set hard limits for the new profile */
|
||||
__aa_transition_rlimits(profile, new_cxt->profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* apparmor_bprm_commited_cred - do cleanup after new creds committed
|
||||
* @bprm: binprm for the exec (NOT NULL)
|
||||
*/
|
||||
void apparmor_bprm_committed_creds(struct linux_binprm *bprm)
|
||||
{
|
||||
/* TODO: cleanup signals - ipc mediation */
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Functions for self directed profile change
|
||||
*/
|
||||
|
||||
/**
|
||||
* new_compound_name - create an hname with @n2 appended to @n1
|
||||
* @n1: base of hname (NOT NULL)
|
||||
* @n2: name to append (NOT NULL)
|
||||
*
|
||||
* Returns: new name or NULL on error
|
||||
*/
|
||||
static char *new_compound_name(const char *n1, const char *n2)
|
||||
{
|
||||
char *name = kmalloc(strlen(n1) + strlen(n2) + 3, GFP_KERNEL);
|
||||
if (name)
|
||||
sprintf(name, "%s//%s", n1, n2);
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_change_hat - change hat to/from subprofile
|
||||
* @hats: vector of hat names to try changing into (MAYBE NULL if @count == 0)
|
||||
* @count: number of hat names in @hats
|
||||
* @token: magic value to validate the hat change
|
||||
* @permtest: true if this is just a permission test
|
||||
*
|
||||
* Change to the first profile specified in @hats that exists, and store
|
||||
* the @hat_magic in the current task context. If the count == 0 and the
|
||||
* @token matches that stored in the current task context, return to the
|
||||
* top level profile.
|
||||
*
|
||||
* Returns %0 on success, error otherwise.
|
||||
*/
|
||||
int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
|
||||
{
|
||||
const struct cred *cred;
|
||||
struct aa_task_cxt *cxt;
|
||||
struct aa_profile *profile, *previous_profile, *hat = NULL;
|
||||
char *name = NULL;
|
||||
int i;
|
||||
struct file_perms perms = {};
|
||||
const char *target = NULL, *info = NULL;
|
||||
int error = 0;
|
||||
|
||||
/* released below */
|
||||
cred = get_current_cred();
|
||||
cxt = cred->security;
|
||||
profile = aa_cred_profile(cred);
|
||||
previous_profile = cxt->previous;
|
||||
|
||||
if (unconfined(profile)) {
|
||||
info = "unconfined";
|
||||
error = -EPERM;
|
||||
goto audit;
|
||||
}
|
||||
|
||||
if (count) {
|
||||
/* attempting to change into a new hat or switch to a sibling */
|
||||
struct aa_profile *root;
|
||||
root = PROFILE_IS_HAT(profile) ? profile->parent : profile;
|
||||
|
||||
/* find first matching hat */
|
||||
for (i = 0; i < count && !hat; i++)
|
||||
/* released below */
|
||||
hat = aa_find_child(root, hats[i]);
|
||||
if (!hat) {
|
||||
if (!COMPLAIN_MODE(root) || permtest) {
|
||||
if (list_empty(&root->base.profiles))
|
||||
error = -ECHILD;
|
||||
else
|
||||
error = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* In complain mode and failed to match any hats.
|
||||
* Audit the failure is based off of the first hat
|
||||
* supplied. This is done due how userspace
|
||||
* interacts with change_hat.
|
||||
*
|
||||
* TODO: Add logging of all failed hats
|
||||
*/
|
||||
|
||||
/* freed below */
|
||||
name = new_compound_name(root->base.hname, hats[0]);
|
||||
target = name;
|
||||
/* released below */
|
||||
hat = aa_new_null_profile(profile, 1);
|
||||
if (!hat) {
|
||||
info = "failed null profile create";
|
||||
error = -ENOMEM;
|
||||
goto audit;
|
||||
}
|
||||
} else {
|
||||
target = hat->base.hname;
|
||||
if (!PROFILE_IS_HAT(hat)) {
|
||||
info = "target not hat";
|
||||
error = -EPERM;
|
||||
goto audit;
|
||||
}
|
||||
}
|
||||
|
||||
error = may_change_ptraced_domain(current, hat);
|
||||
if (error) {
|
||||
info = "ptraced";
|
||||
error = -EPERM;
|
||||
goto audit;
|
||||
}
|
||||
|
||||
if (!permtest) {
|
||||
error = aa_set_current_hat(hat, token);
|
||||
if (error == -EACCES)
|
||||
/* kill task in case of brute force attacks */
|
||||
perms.kill = AA_MAY_CHANGEHAT;
|
||||
else if (name && !error)
|
||||
/* reset error for learning of new hats */
|
||||
error = -ENOENT;
|
||||
}
|
||||
} else if (previous_profile) {
|
||||
/* Return to saved profile. Kill task if restore fails
|
||||
* to avoid brute force attacks
|
||||
*/
|
||||
target = previous_profile->base.hname;
|
||||
error = aa_restore_previous_profile(token);
|
||||
perms.kill = AA_MAY_CHANGEHAT;
|
||||
} else
|
||||
/* ignore restores when there is no saved profile */
|
||||
goto out;
|
||||
|
||||
audit:
|
||||
if (!permtest)
|
||||
error = aa_audit_file(profile, &perms, GFP_KERNEL,
|
||||
OP_CHANGE_HAT, AA_MAY_CHANGEHAT, NULL,
|
||||
target, 0, info, error);
|
||||
|
||||
out:
|
||||
aa_put_profile(hat);
|
||||
kfree(name);
|
||||
put_cred(cred);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_change_profile - perform a one-way profile transition
|
||||
* @ns_name: name of the profile namespace to change to (MAYBE NULL)
|
||||
* @hname: name of profile to change to (MAYBE NULL)
|
||||
* @onexec: whether this transition is to take place immediately or at exec
|
||||
* @permtest: true if this is just a permission test
|
||||
*
|
||||
* Change to new profile @name. Unlike with hats, there is no way
|
||||
* to change back. If @name isn't specified the current profile name is
|
||||
* used.
|
||||
* If @onexec then the transition is delayed until
|
||||
* the next exec.
|
||||
*
|
||||
* Returns %0 on success, error otherwise.
|
||||
*/
|
||||
int aa_change_profile(const char *ns_name, const char *hname, bool onexec,
|
||||
bool permtest)
|
||||
{
|
||||
const struct cred *cred;
|
||||
struct aa_task_cxt *cxt;
|
||||
struct aa_profile *profile, *target = NULL;
|
||||
struct aa_namespace *ns = NULL;
|
||||
struct file_perms perms = {};
|
||||
const char *name = NULL, *info = NULL;
|
||||
int op, error = 0;
|
||||
u32 request;
|
||||
|
||||
if (!hname && !ns_name)
|
||||
return -EINVAL;
|
||||
|
||||
if (onexec) {
|
||||
request = AA_MAY_ONEXEC;
|
||||
op = OP_CHANGE_ONEXEC;
|
||||
} else {
|
||||
request = AA_MAY_CHANGE_PROFILE;
|
||||
op = OP_CHANGE_PROFILE;
|
||||
}
|
||||
|
||||
cred = get_current_cred();
|
||||
cxt = cred->security;
|
||||
profile = aa_cred_profile(cred);
|
||||
|
||||
if (ns_name) {
|
||||
/* released below */
|
||||
ns = aa_find_namespace(profile->ns, ns_name);
|
||||
if (!ns) {
|
||||
/* we don't create new namespace in complain mode */
|
||||
name = ns_name;
|
||||
info = "namespace not found";
|
||||
error = -ENOENT;
|
||||
goto audit;
|
||||
}
|
||||
} else
|
||||
/* released below */
|
||||
ns = aa_get_namespace(profile->ns);
|
||||
|
||||
/* if the name was not specified, use the name of the current profile */
|
||||
if (!hname) {
|
||||
if (unconfined(profile))
|
||||
hname = ns->unconfined->base.hname;
|
||||
else
|
||||
hname = profile->base.hname;
|
||||
}
|
||||
|
||||
perms = change_profile_perms(profile, ns, hname, request,
|
||||
profile->file.start);
|
||||
if (!(perms.allow & request)) {
|
||||
error = -EACCES;
|
||||
goto audit;
|
||||
}
|
||||
|
||||
/* released below */
|
||||
target = aa_lookup_profile(ns, hname);
|
||||
if (!target) {
|
||||
info = "profile not found";
|
||||
error = -ENOENT;
|
||||
if (permtest || !COMPLAIN_MODE(profile))
|
||||
goto audit;
|
||||
/* released below */
|
||||
target = aa_new_null_profile(profile, 0);
|
||||
if (!target) {
|
||||
info = "failed null profile create";
|
||||
error = -ENOMEM;
|
||||
goto audit;
|
||||
}
|
||||
}
|
||||
|
||||
/* check if tracing task is allowed to trace target domain */
|
||||
error = may_change_ptraced_domain(current, target);
|
||||
if (error) {
|
||||
info = "ptrace prevents transition";
|
||||
goto audit;
|
||||
}
|
||||
|
||||
if (permtest)
|
||||
goto audit;
|
||||
|
||||
if (onexec)
|
||||
error = aa_set_current_onexec(target);
|
||||
else
|
||||
error = aa_replace_current_profile(target);
|
||||
|
||||
audit:
|
||||
if (!permtest)
|
||||
error = aa_audit_file(profile, &perms, GFP_KERNEL, op, request,
|
||||
name, hname, 0, info, error);
|
||||
|
||||
aa_put_namespace(ns);
|
||||
aa_put_profile(target);
|
||||
put_cred(cred);
|
||||
|
||||
return error;
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor mediation of files
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "include/apparmor.h"
|
||||
#include "include/audit.h"
|
||||
#include "include/file.h"
|
||||
#include "include/match.h"
|
||||
#include "include/path.h"
|
||||
#include "include/policy.h"
|
||||
|
||||
struct file_perms nullperms;
|
||||
|
||||
|
||||
/**
|
||||
* audit_file_mask - convert mask to permission string
|
||||
* @buffer: buffer to write string to (NOT NULL)
|
||||
* @mask: permission mask to convert
|
||||
*/
|
||||
static void audit_file_mask(struct audit_buffer *ab, u32 mask)
|
||||
{
|
||||
char str[10];
|
||||
|
||||
char *m = str;
|
||||
|
||||
if (mask & AA_EXEC_MMAP)
|
||||
*m++ = 'm';
|
||||
if (mask & (MAY_READ | AA_MAY_META_READ))
|
||||
*m++ = 'r';
|
||||
if (mask & (MAY_WRITE | AA_MAY_META_WRITE | AA_MAY_CHMOD |
|
||||
AA_MAY_CHOWN))
|
||||
*m++ = 'w';
|
||||
else if (mask & MAY_APPEND)
|
||||
*m++ = 'a';
|
||||
if (mask & AA_MAY_CREATE)
|
||||
*m++ = 'c';
|
||||
if (mask & AA_MAY_DELETE)
|
||||
*m++ = 'd';
|
||||
if (mask & AA_MAY_LINK)
|
||||
*m++ = 'l';
|
||||
if (mask & AA_MAY_LOCK)
|
||||
*m++ = 'k';
|
||||
if (mask & MAY_EXEC)
|
||||
*m++ = 'x';
|
||||
*m = '\0';
|
||||
|
||||
audit_log_string(ab, str);
|
||||
}
|
||||
|
||||
/**
|
||||
* file_audit_cb - call back for file specific audit fields
|
||||
* @ab: audit_buffer (NOT NULL)
|
||||
* @va: audit struct to audit values of (NOT NULL)
|
||||
*/
|
||||
static void file_audit_cb(struct audit_buffer *ab, void *va)
|
||||
{
|
||||
struct common_audit_data *sa = va;
|
||||
uid_t fsuid = current_fsuid();
|
||||
|
||||
if (sa->aad.fs.request & AA_AUDIT_FILE_MASK) {
|
||||
audit_log_format(ab, " requested_mask=");
|
||||
audit_file_mask(ab, sa->aad.fs.request);
|
||||
}
|
||||
if (sa->aad.fs.denied & AA_AUDIT_FILE_MASK) {
|
||||
audit_log_format(ab, " denied_mask=");
|
||||
audit_file_mask(ab, sa->aad.fs.denied);
|
||||
}
|
||||
if (sa->aad.fs.request & AA_AUDIT_FILE_MASK) {
|
||||
audit_log_format(ab, " fsuid=%d", fsuid);
|
||||
audit_log_format(ab, " ouid=%d", sa->aad.fs.ouid);
|
||||
}
|
||||
|
||||
if (sa->aad.fs.target) {
|
||||
audit_log_format(ab, " target=");
|
||||
audit_log_untrustedstring(ab, sa->aad.fs.target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_audit_file - handle the auditing of file operations
|
||||
* @profile: the profile being enforced (NOT NULL)
|
||||
* @perms: the permissions computed for the request (NOT NULL)
|
||||
* @gfp: allocation flags
|
||||
* @op: operation being mediated
|
||||
* @request: permissions requested
|
||||
* @name: name of object being mediated (MAYBE NULL)
|
||||
* @target: name of target (MAYBE NULL)
|
||||
* @ouid: object uid
|
||||
* @info: extra information message (MAYBE NULL)
|
||||
* @error: 0 if operation allowed else failure error code
|
||||
*
|
||||
* Returns: %0 or error on failure
|
||||
*/
|
||||
int aa_audit_file(struct aa_profile *profile, struct file_perms *perms,
|
||||
gfp_t gfp, int op, u32 request, const char *name,
|
||||
const char *target, uid_t ouid, const char *info, int error)
|
||||
{
|
||||
int type = AUDIT_APPARMOR_AUTO;
|
||||
struct common_audit_data sa;
|
||||
COMMON_AUDIT_DATA_INIT(&sa, NONE);
|
||||
sa.aad.op = op,
|
||||
sa.aad.fs.request = request;
|
||||
sa.aad.name = name;
|
||||
sa.aad.fs.target = target;
|
||||
sa.aad.fs.ouid = ouid;
|
||||
sa.aad.info = info;
|
||||
sa.aad.error = error;
|
||||
|
||||
if (likely(!sa.aad.error)) {
|
||||
u32 mask = perms->audit;
|
||||
|
||||
if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL))
|
||||
mask = 0xffff;
|
||||
|
||||
/* mask off perms that are not being force audited */
|
||||
sa.aad.fs.request &= mask;
|
||||
|
||||
if (likely(!sa.aad.fs.request))
|
||||
return 0;
|
||||
type = AUDIT_APPARMOR_AUDIT;
|
||||
} else {
|
||||
/* only report permissions that were denied */
|
||||
sa.aad.fs.request = sa.aad.fs.request & ~perms->allow;
|
||||
|
||||
if (sa.aad.fs.request & perms->kill)
|
||||
type = AUDIT_APPARMOR_KILL;
|
||||
|
||||
/* quiet known rejects, assumes quiet and kill do not overlap */
|
||||
if ((sa.aad.fs.request & perms->quiet) &&
|
||||
AUDIT_MODE(profile) != AUDIT_NOQUIET &&
|
||||
AUDIT_MODE(profile) != AUDIT_ALL)
|
||||
sa.aad.fs.request &= ~perms->quiet;
|
||||
|
||||
if (!sa.aad.fs.request)
|
||||
return COMPLAIN_MODE(profile) ? 0 : sa.aad.error;
|
||||
}
|
||||
|
||||
sa.aad.fs.denied = sa.aad.fs.request & ~perms->allow;
|
||||
return aa_audit(type, profile, gfp, &sa, file_audit_cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* map_old_perms - map old file perms layout to the new layout
|
||||
* @old: permission set in old mapping
|
||||
*
|
||||
* Returns: new permission mapping
|
||||
*/
|
||||
static u32 map_old_perms(u32 old)
|
||||
{
|
||||
u32 new = old & 0xf;
|
||||
if (old & MAY_READ)
|
||||
new |= AA_MAY_META_READ;
|
||||
if (old & MAY_WRITE)
|
||||
new |= AA_MAY_META_WRITE | AA_MAY_CREATE | AA_MAY_DELETE |
|
||||
AA_MAY_CHMOD | AA_MAY_CHOWN;
|
||||
if (old & 0x10)
|
||||
new |= AA_MAY_LINK;
|
||||
/* the old mapping lock and link_subset flags where overlaid
|
||||
* and use was determined by part of a pair that they were in
|
||||
*/
|
||||
if (old & 0x20)
|
||||
new |= AA_MAY_LOCK | AA_LINK_SUBSET;
|
||||
if (old & 0x40) /* AA_EXEC_MMAP */
|
||||
new |= AA_EXEC_MMAP;
|
||||
|
||||
new |= AA_MAY_META_READ;
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
/**
|
||||
* compute_perms - convert dfa compressed perms to internal perms
|
||||
* @dfa: dfa to compute perms for (NOT NULL)
|
||||
* @state: state in dfa
|
||||
* @cond: conditions to consider (NOT NULL)
|
||||
*
|
||||
* TODO: convert from dfa + state to permission entry, do computation conversion
|
||||
* at load time.
|
||||
*
|
||||
* Returns: computed permission set
|
||||
*/
|
||||
static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state,
|
||||
struct path_cond *cond)
|
||||
{
|
||||
struct file_perms perms;
|
||||
|
||||
/* FIXME: change over to new dfa format
|
||||
* currently file perms are encoded in the dfa, new format
|
||||
* splits the permissions from the dfa. This mapping can be
|
||||
* done at profile load
|
||||
*/
|
||||
perms.kill = 0;
|
||||
|
||||
if (current_fsuid() == cond->uid) {
|
||||
perms.allow = map_old_perms(dfa_user_allow(dfa, state));
|
||||
perms.audit = map_old_perms(dfa_user_audit(dfa, state));
|
||||
perms.quiet = map_old_perms(dfa_user_quiet(dfa, state));
|
||||
perms.xindex = dfa_user_xindex(dfa, state);
|
||||
} else {
|
||||
perms.allow = map_old_perms(dfa_other_allow(dfa, state));
|
||||
perms.audit = map_old_perms(dfa_other_audit(dfa, state));
|
||||
perms.quiet = map_old_perms(dfa_other_quiet(dfa, state));
|
||||
perms.xindex = dfa_other_xindex(dfa, state);
|
||||
}
|
||||
|
||||
/* change_profile wasn't determined by ownership in old mapping */
|
||||
if (ACCEPT_TABLE(dfa)[state] & 0x80000000)
|
||||
perms.allow |= AA_MAY_CHANGE_PROFILE;
|
||||
|
||||
return perms;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_str_perms - find permission that match @name
|
||||
* @dfa: to match against (MAYBE NULL)
|
||||
* @state: state to start matching in
|
||||
* @name: string to match against dfa (NOT NULL)
|
||||
* @cond: conditions to consider for permission set computation (NOT NULL)
|
||||
* @perms: Returns - the permissions found when matching @name
|
||||
*
|
||||
* Returns: the final state in @dfa when beginning @start and walking @name
|
||||
*/
|
||||
unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
|
||||
const char *name, struct path_cond *cond,
|
||||
struct file_perms *perms)
|
||||
{
|
||||
unsigned int state;
|
||||
if (!dfa) {
|
||||
*perms = nullperms;
|
||||
return DFA_NOMATCH;
|
||||
}
|
||||
|
||||
state = aa_dfa_match(dfa, start, name);
|
||||
*perms = compute_perms(dfa, state, cond);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* is_deleted - test if a file has been completely unlinked
|
||||
* @dentry: dentry of file to test for deletion (NOT NULL)
|
||||
*
|
||||
* Returns: %1 if deleted else %0
|
||||
*/
|
||||
static inline bool is_deleted(struct dentry *dentry)
|
||||
{
|
||||
if (d_unlinked(dentry) && dentry->d_inode->i_nlink == 0)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_path_perm - do permissions check & audit for @path
|
||||
* @op: operation being checked
|
||||
* @profile: profile being enforced (NOT NULL)
|
||||
* @path: path to check permissions of (NOT NULL)
|
||||
* @flags: any additional path flags beyond what the profile specifies
|
||||
* @request: requested permissions
|
||||
* @cond: conditional info for this request (NOT NULL)
|
||||
*
|
||||
* Returns: %0 else error if access denied or other error
|
||||
*/
|
||||
int aa_path_perm(int op, struct aa_profile *profile, struct path *path,
|
||||
int flags, u32 request, struct path_cond *cond)
|
||||
{
|
||||
char *buffer = NULL;
|
||||
struct file_perms perms = {};
|
||||
const char *name, *info = NULL;
|
||||
int error;
|
||||
|
||||
flags |= profile->path_flags | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0);
|
||||
error = aa_get_name(path, flags, &buffer, &name);
|
||||
if (error) {
|
||||
if (error == -ENOENT && is_deleted(path->dentry)) {
|
||||
/* Access to open files that are deleted are
|
||||
* give a pass (implicit delegation)
|
||||
*/
|
||||
error = 0;
|
||||
perms.allow = request;
|
||||
} else if (error == -ENOENT)
|
||||
info = "Failed name lookup - deleted entry";
|
||||
else if (error == -ESTALE)
|
||||
info = "Failed name lookup - disconnected path";
|
||||
else if (error == -ENAMETOOLONG)
|
||||
info = "Failed name lookup - name too long";
|
||||
else
|
||||
info = "Failed name lookup";
|
||||
} else {
|
||||
aa_str_perms(profile->file.dfa, profile->file.start, name, cond,
|
||||
&perms);
|
||||
if (request & ~perms.allow)
|
||||
error = -EACCES;
|
||||
}
|
||||
error = aa_audit_file(profile, &perms, GFP_KERNEL, op, request, name,
|
||||
NULL, cond->uid, info, error);
|
||||
kfree(buffer);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* xindex_is_subset - helper for aa_path_link
|
||||
* @link: link permission set
|
||||
* @target: target permission set
|
||||
*
|
||||
* test target x permissions are equal OR a subset of link x permissions
|
||||
* this is done as part of the subset test, where a hardlink must have
|
||||
* a subset of permissions that the target has.
|
||||
*
|
||||
* Returns: %1 if subset else %0
|
||||
*/
|
||||
static inline bool xindex_is_subset(u32 link, u32 target)
|
||||
{
|
||||
if (((link & ~AA_X_UNSAFE) != (target & ~AA_X_UNSAFE)) ||
|
||||
((link & AA_X_UNSAFE) && !(target & AA_X_UNSAFE)))
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_path_link - Handle hard link permission check
|
||||
* @profile: the profile being enforced (NOT NULL)
|
||||
* @old_dentry: the target dentry (NOT NULL)
|
||||
* @new_dir: directory the new link will be created in (NOT NULL)
|
||||
* @new_dentry: the link being created (NOT NULL)
|
||||
*
|
||||
* Handle the permission test for a link & target pair. Permission
|
||||
* is encoded as a pair where the link permission is determined
|
||||
* first, and if allowed, the target is tested. The target test
|
||||
* is done from the point of the link match (not start of DFA)
|
||||
* making the target permission dependent on the link permission match.
|
||||
*
|
||||
* The subset test if required forces that permissions granted
|
||||
* on link are a subset of the permission granted to target.
|
||||
*
|
||||
* Returns: %0 if allowed else error
|
||||
*/
|
||||
int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
|
||||
struct path *new_dir, struct dentry *new_dentry)
|
||||
{
|
||||
struct path link = { new_dir->mnt, new_dentry };
|
||||
struct path target = { new_dir->mnt, old_dentry };
|
||||
struct path_cond cond = {
|
||||
old_dentry->d_inode->i_uid,
|
||||
old_dentry->d_inode->i_mode
|
||||
};
|
||||
char *buffer = NULL, *buffer2 = NULL;
|
||||
const char *lname, *tname = NULL, *info = NULL;
|
||||
struct file_perms lperms, perms;
|
||||
u32 request = AA_MAY_LINK;
|
||||
unsigned int state;
|
||||
int error;
|
||||
|
||||
lperms = nullperms;
|
||||
|
||||
/* buffer freed below, lname is pointer in buffer */
|
||||
error = aa_get_name(&link, profile->path_flags, &buffer, &lname);
|
||||
if (error)
|
||||
goto audit;
|
||||
|
||||
/* buffer2 freed below, tname is pointer in buffer2 */
|
||||
error = aa_get_name(&target, profile->path_flags, &buffer2, &tname);
|
||||
if (error)
|
||||
goto audit;
|
||||
|
||||
error = -EACCES;
|
||||
/* aa_str_perms - handles the case of the dfa being NULL */
|
||||
state = aa_str_perms(profile->file.dfa, profile->file.start, lname,
|
||||
&cond, &lperms);
|
||||
|
||||
if (!(lperms.allow & AA_MAY_LINK))
|
||||
goto audit;
|
||||
|
||||
/* test to see if target can be paired with link */
|
||||
state = aa_dfa_null_transition(profile->file.dfa, state);
|
||||
aa_str_perms(profile->file.dfa, state, tname, &cond, &perms);
|
||||
|
||||
/* force audit/quiet masks for link are stored in the second entry
|
||||
* in the link pair.
|
||||
*/
|
||||
lperms.audit = perms.audit;
|
||||
lperms.quiet = perms.quiet;
|
||||
lperms.kill = perms.kill;
|
||||
|
||||
if (!(perms.allow & AA_MAY_LINK)) {
|
||||
info = "target restricted";
|
||||
goto audit;
|
||||
}
|
||||
|
||||
/* done if link subset test is not required */
|
||||
if (!(perms.allow & AA_LINK_SUBSET))
|
||||
goto done_tests;
|
||||
|
||||
/* Do link perm subset test requiring allowed permission on link are a
|
||||
* subset of the allowed permissions on target.
|
||||
*/
|
||||
aa_str_perms(profile->file.dfa, profile->file.start, tname, &cond,
|
||||
&perms);
|
||||
|
||||
/* AA_MAY_LINK is not considered in the subset test */
|
||||
request = lperms.allow & ~AA_MAY_LINK;
|
||||
lperms.allow &= perms.allow | AA_MAY_LINK;
|
||||
|
||||
request |= AA_AUDIT_FILE_MASK & (lperms.allow & ~perms.allow);
|
||||
if (request & ~lperms.allow) {
|
||||
goto audit;
|
||||
} else if ((lperms.allow & MAY_EXEC) &&
|
||||
!xindex_is_subset(lperms.xindex, perms.xindex)) {
|
||||
lperms.allow &= ~MAY_EXEC;
|
||||
request |= MAY_EXEC;
|
||||
info = "link not subset of target";
|
||||
goto audit;
|
||||
}
|
||||
|
||||
done_tests:
|
||||
error = 0;
|
||||
|
||||
audit:
|
||||
error = aa_audit_file(profile, &lperms, GFP_KERNEL, OP_LINK, request,
|
||||
lname, tname, cond.uid, info, error);
|
||||
kfree(buffer);
|
||||
kfree(buffer2);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_file_perm - do permission revalidation check & audit for @file
|
||||
* @op: operation being checked
|
||||
* @profile: profile being enforced (NOT NULL)
|
||||
* @file: file to revalidate access permissions on (NOT NULL)
|
||||
* @request: requested permissions
|
||||
*
|
||||
* Returns: %0 if access allowed else error
|
||||
*/
|
||||
int aa_file_perm(int op, struct aa_profile *profile, struct file *file,
|
||||
u32 request)
|
||||
{
|
||||
struct path_cond cond = {
|
||||
.uid = file->f_path.dentry->d_inode->i_uid,
|
||||
.mode = file->f_path.dentry->d_inode->i_mode
|
||||
};
|
||||
|
||||
return aa_path_perm(op, profile, &file->f_path, PATH_DELEGATE_DELETED,
|
||||
request, &cond);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor basic global and lib definitions
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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 __APPARMOR_H
|
||||
#define __APPARMOR_H
|
||||
|
||||
#include <linux/fs.h>
|
||||
|
||||
#include "match.h"
|
||||
|
||||
/* Control parameters settable through module/boot flags */
|
||||
extern enum audit_mode aa_g_audit;
|
||||
extern int aa_g_audit_header;
|
||||
extern int aa_g_debug;
|
||||
extern int aa_g_lock_policy;
|
||||
extern int aa_g_logsyscall;
|
||||
extern int aa_g_paranoid_load;
|
||||
extern unsigned int aa_g_path_max;
|
||||
|
||||
/*
|
||||
* 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 (aa_g_debug && printk_ratelimit()) \
|
||||
printk(KERN_DEBUG "AppArmor: " fmt, ##args); \
|
||||
} while (0)
|
||||
|
||||
#define AA_ERROR(fmt, args...) \
|
||||
do { \
|
||||
if (printk_ratelimit()) \
|
||||
printk(KERN_ERR "AppArmor: " fmt, ##args); \
|
||||
} while (0)
|
||||
|
||||
/* Flag indicating whether initialization completed */
|
||||
extern int apparmor_initialized __initdata;
|
||||
|
||||
/* fn's in lib */
|
||||
char *aa_split_fqname(char *args, char **ns_name);
|
||||
void aa_info_message(const char *str);
|
||||
void *kvmalloc(size_t size);
|
||||
void kvfree(void *buffer);
|
||||
|
||||
|
||||
/**
|
||||
* aa_strneq - compare null terminated @str to a non null terminated substring
|
||||
* @str: a null terminated string
|
||||
* @sub: a substring, not necessarily null terminated
|
||||
* @len: length of @sub to compare
|
||||
*
|
||||
* The @str string must be full consumed for this to be considered a match
|
||||
*/
|
||||
static inline bool aa_strneq(const char *str, const char *sub, int len)
|
||||
{
|
||||
return !strncmp(str, sub, len) && !str[len];
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_dfa_null_transition - step to next state after null character
|
||||
* @dfa: the dfa to match against
|
||||
* @start: the state of the dfa to start matching in
|
||||
*
|
||||
* aa_dfa_null_transition transitions to the next state after a null
|
||||
* character which is not used in standard matching and is only
|
||||
* used to separate pairs.
|
||||
*/
|
||||
static inline unsigned int aa_dfa_null_transition(struct aa_dfa *dfa,
|
||||
unsigned int start)
|
||||
{
|
||||
/* the null transition only needs the string's null terminator byte */
|
||||
return aa_dfa_match_len(dfa, start, "", 1);
|
||||
}
|
||||
|
||||
static inline bool mediated_filesystem(struct inode *inode)
|
||||
{
|
||||
return !(inode->i_sb->s_flags & MS_NOUSER);
|
||||
}
|
||||
|
||||
#endif /* __APPARMOR_H */
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor filesystem definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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 __AA_APPARMORFS_H
|
||||
#define __AA_APPARMORFS_H
|
||||
|
||||
extern void __init aa_destroy_aafs(void);
|
||||
|
||||
#endif /* __AA_APPARMORFS_H */
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor auditing function definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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 __AA_AUDIT_H
|
||||
#define __AA_AUDIT_H
|
||||
|
||||
#include <linux/audit.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/lsm_audit.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "file.h"
|
||||
|
||||
struct aa_profile;
|
||||
|
||||
extern const char *audit_mode_names[];
|
||||
#define AUDIT_MAX_INDEX 5
|
||||
|
||||
#define AUDIT_APPARMOR_AUTO 0 /* auto choose audit message type */
|
||||
|
||||
enum audit_mode {
|
||||
AUDIT_NORMAL, /* follow normal auditing of accesses */
|
||||
AUDIT_QUIET_DENIED, /* quiet all denied access messages */
|
||||
AUDIT_QUIET, /* quiet all messages */
|
||||
AUDIT_NOQUIET, /* do not quiet audit messages */
|
||||
AUDIT_ALL /* audit all accesses */
|
||||
};
|
||||
|
||||
enum audit_type {
|
||||
AUDIT_APPARMOR_AUDIT,
|
||||
AUDIT_APPARMOR_ALLOWED,
|
||||
AUDIT_APPARMOR_DENIED,
|
||||
AUDIT_APPARMOR_HINT,
|
||||
AUDIT_APPARMOR_STATUS,
|
||||
AUDIT_APPARMOR_ERROR,
|
||||
AUDIT_APPARMOR_KILL
|
||||
};
|
||||
|
||||
extern const char *op_table[];
|
||||
enum aa_ops {
|
||||
OP_NULL,
|
||||
|
||||
OP_SYSCTL,
|
||||
OP_CAPABLE,
|
||||
|
||||
OP_UNLINK,
|
||||
OP_MKDIR,
|
||||
OP_RMDIR,
|
||||
OP_MKNOD,
|
||||
OP_TRUNC,
|
||||
OP_LINK,
|
||||
OP_SYMLINK,
|
||||
OP_RENAME_SRC,
|
||||
OP_RENAME_DEST,
|
||||
OP_CHMOD,
|
||||
OP_CHOWN,
|
||||
OP_GETATTR,
|
||||
OP_OPEN,
|
||||
|
||||
OP_FPERM,
|
||||
OP_FLOCK,
|
||||
OP_FMMAP,
|
||||
OP_FMPROT,
|
||||
|
||||
OP_CREATE,
|
||||
OP_POST_CREATE,
|
||||
OP_BIND,
|
||||
OP_CONNECT,
|
||||
OP_LISTEN,
|
||||
OP_ACCEPT,
|
||||
OP_SENDMSG,
|
||||
OP_RECVMSG,
|
||||
OP_GETSOCKNAME,
|
||||
OP_GETPEERNAME,
|
||||
OP_GETSOCKOPT,
|
||||
OP_SETSOCKOPT,
|
||||
OP_SOCK_SHUTDOWN,
|
||||
|
||||
OP_PTRACE,
|
||||
|
||||
OP_EXEC,
|
||||
OP_CHANGE_HAT,
|
||||
OP_CHANGE_PROFILE,
|
||||
OP_CHANGE_ONEXEC,
|
||||
|
||||
OP_SETPROCATTR,
|
||||
OP_SETRLIMIT,
|
||||
|
||||
OP_PROF_REPL,
|
||||
OP_PROF_LOAD,
|
||||
OP_PROF_RM,
|
||||
};
|
||||
|
||||
|
||||
/* define a short hand for apparmor_audit_data portion of common_audit_data */
|
||||
#define aad apparmor_audit_data
|
||||
|
||||
void aa_audit_msg(int type, struct common_audit_data *sa,
|
||||
void (*cb) (struct audit_buffer *, void *));
|
||||
int aa_audit(int type, struct aa_profile *profile, gfp_t gfp,
|
||||
struct common_audit_data *sa,
|
||||
void (*cb) (struct audit_buffer *, void *));
|
||||
|
||||
static inline int complain_error(int error)
|
||||
{
|
||||
if (error == -EPERM || error == -EACCES)
|
||||
return 0;
|
||||
return error;
|
||||
}
|
||||
|
||||
#endif /* __AA_AUDIT_H */
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor capability mediation definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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 __AA_CAPABILITY_H
|
||||
#define __AA_CAPABILITY_H
|
||||
|
||||
#include <linux/sched.h>
|
||||
|
||||
struct aa_profile;
|
||||
|
||||
/* aa_caps - confinement data for capabilities
|
||||
* @allowed: capabilities mask
|
||||
* @audit: caps that are to be audited
|
||||
* @quiet: caps that should not be audited
|
||||
* @kill: caps that when requested will result in the task being killed
|
||||
* @extended: caps that are subject finer grained mediation
|
||||
*/
|
||||
struct aa_caps {
|
||||
kernel_cap_t allow;
|
||||
kernel_cap_t audit;
|
||||
kernel_cap_t quiet;
|
||||
kernel_cap_t kill;
|
||||
kernel_cap_t extended;
|
||||
};
|
||||
|
||||
int aa_capable(struct task_struct *task, struct aa_profile *profile, int cap,
|
||||
int audit);
|
||||
|
||||
static inline void aa_free_cap_rules(struct aa_caps *caps)
|
||||
{
|
||||
/* NOP */
|
||||
}
|
||||
|
||||
#endif /* __AA_CAPBILITY_H */
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor contexts used to associate "labels" to objects.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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 __AA_CONTEXT_H
|
||||
#define __AA_CONTEXT_H
|
||||
|
||||
#include <linux/cred.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#include "policy.h"
|
||||
|
||||
/* struct aa_file_cxt - the AppArmor context the file was opened in
|
||||
* @perms: the permission the file was opened with
|
||||
*
|
||||
* The file_cxt could currently be directly stored in file->f_security
|
||||
* as the profile reference is now stored in the f_cred. However the
|
||||
* cxt struct will expand in the future so we keep the struct.
|
||||
*/
|
||||
struct aa_file_cxt {
|
||||
u16 allow;
|
||||
};
|
||||
|
||||
/**
|
||||
* aa_alloc_file_context - allocate file_cxt
|
||||
* @gfp: gfp flags for allocation
|
||||
*
|
||||
* Returns: file_cxt or NULL on failure
|
||||
*/
|
||||
static inline struct aa_file_cxt *aa_alloc_file_context(gfp_t gfp)
|
||||
{
|
||||
return kzalloc(sizeof(struct aa_file_cxt), gfp);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_free_file_context - free a file_cxt
|
||||
* @cxt: file_cxt to free (MAYBE_NULL)
|
||||
*/
|
||||
static inline void aa_free_file_context(struct aa_file_cxt *cxt)
|
||||
{
|
||||
if (cxt)
|
||||
kzfree(cxt);
|
||||
}
|
||||
|
||||
/**
|
||||
* struct aa_task_cxt - primary label for confined tasks
|
||||
* @profile: the current profile (NOT NULL)
|
||||
* @exec: profile to transition to on next exec (MAYBE NULL)
|
||||
* @previous: profile the task may return to (MAYBE NULL)
|
||||
* @token: magic value the task must know for returning to @previous_profile
|
||||
*
|
||||
* Contains the task's current profile (which could change due to
|
||||
* change_hat). Plus the hat_magic needed during change_hat.
|
||||
*
|
||||
* TODO: make so a task can be confined by a stack of contexts
|
||||
*/
|
||||
struct aa_task_cxt {
|
||||
struct aa_profile *profile;
|
||||
struct aa_profile *onexec;
|
||||
struct aa_profile *previous;
|
||||
u64 token;
|
||||
};
|
||||
|
||||
struct aa_task_cxt *aa_alloc_task_context(gfp_t flags);
|
||||
void aa_free_task_context(struct aa_task_cxt *cxt);
|
||||
void aa_dup_task_context(struct aa_task_cxt *new,
|
||||
const struct aa_task_cxt *old);
|
||||
int aa_replace_current_profile(struct aa_profile *profile);
|
||||
int aa_set_current_onexec(struct aa_profile *profile);
|
||||
int aa_set_current_hat(struct aa_profile *profile, u64 token);
|
||||
int aa_restore_previous_profile(u64 cookie);
|
||||
|
||||
/**
|
||||
* __aa_task_is_confined - determine if @task has any confinement
|
||||
* @task: task to check confinement of (NOT NULL)
|
||||
*
|
||||
* If @task != current needs to be called in RCU safe critical section
|
||||
*/
|
||||
static inline bool __aa_task_is_confined(struct task_struct *task)
|
||||
{
|
||||
struct aa_task_cxt *cxt = __task_cred(task)->security;
|
||||
|
||||
BUG_ON(!cxt || !cxt->profile);
|
||||
if (unconfined(aa_newest_version(cxt->profile)))
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_cred_profile - obtain cred's profiles
|
||||
* @cred: cred to obtain profiles from (NOT NULL)
|
||||
*
|
||||
* Returns: confining profile
|
||||
*
|
||||
* does NOT increment reference count
|
||||
*/
|
||||
static inline struct aa_profile *aa_cred_profile(const struct cred *cred)
|
||||
{
|
||||
struct aa_task_cxt *cxt = cred->security;
|
||||
BUG_ON(!cxt || !cxt->profile);
|
||||
return aa_newest_version(cxt->profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* __aa_current_profile - find the current tasks confining profile
|
||||
*
|
||||
* Returns: up to date confining profile or the ns unconfined profile (NOT NULL)
|
||||
*
|
||||
* This fn will not update the tasks cred to the most up to date version
|
||||
* of the profile so it is safe to call when inside of locks.
|
||||
*/
|
||||
static inline struct aa_profile *__aa_current_profile(void)
|
||||
{
|
||||
return aa_cred_profile(current_cred());
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_current_profile - find the current tasks confining profile and do updates
|
||||
*
|
||||
* Returns: up to date confining profile or the ns unconfined profile (NOT NULL)
|
||||
*
|
||||
* This fn will update the tasks cred structure if the profile has been
|
||||
* replaced. Not safe to call inside locks
|
||||
*/
|
||||
static inline struct aa_profile *aa_current_profile(void)
|
||||
{
|
||||
const struct aa_task_cxt *cxt = current_cred()->security;
|
||||
struct aa_profile *profile;
|
||||
BUG_ON(!cxt || !cxt->profile);
|
||||
|
||||
profile = aa_newest_version(cxt->profile);
|
||||
/*
|
||||
* Whether or not replacement succeeds, use newest profile so
|
||||
* there is no need to update it after replacement.
|
||||
*/
|
||||
if (unlikely((cxt->profile != profile)))
|
||||
aa_replace_current_profile(profile);
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
#endif /* __AA_CONTEXT_H */
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor security domain transition function definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/binfmts.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#ifndef __AA_DOMAIN_H
|
||||
#define __AA_DOMAIN_H
|
||||
|
||||
struct aa_domain {
|
||||
int size;
|
||||
char **table;
|
||||
};
|
||||
|
||||
int apparmor_bprm_set_creds(struct linux_binprm *bprm);
|
||||
int apparmor_bprm_secureexec(struct linux_binprm *bprm);
|
||||
void apparmor_bprm_committing_creds(struct linux_binprm *bprm);
|
||||
void apparmor_bprm_committed_creds(struct linux_binprm *bprm);
|
||||
|
||||
void aa_free_domain_entries(struct aa_domain *domain);
|
||||
int aa_change_hat(const char *hats[], int count, u64 token, bool permtest);
|
||||
int aa_change_profile(const char *ns_name, const char *name, bool onexec,
|
||||
bool permtest);
|
||||
|
||||
#endif /* __AA_DOMAIN_H */
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor file mediation function definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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 __AA_FILE_H
|
||||
#define __AA_FILE_H
|
||||
|
||||
#include <linux/path.h>
|
||||
|
||||
#include "domain.h"
|
||||
#include "match.h"
|
||||
|
||||
struct aa_profile;
|
||||
|
||||
/*
|
||||
* We use MAY_EXEC, MAY_WRITE, MAY_READ, MAY_APPEND and the following flags
|
||||
* for profile permissions
|
||||
*/
|
||||
#define AA_MAY_CREATE 0x0010
|
||||
#define AA_MAY_DELETE 0x0020
|
||||
#define AA_MAY_META_WRITE 0x0040
|
||||
#define AA_MAY_META_READ 0x0080
|
||||
|
||||
#define AA_MAY_CHMOD 0x0100
|
||||
#define AA_MAY_CHOWN 0x0200
|
||||
#define AA_MAY_LOCK 0x0400
|
||||
#define AA_EXEC_MMAP 0x0800
|
||||
|
||||
#define AA_MAY_LINK 0x1000
|
||||
#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */
|
||||
#define AA_MAY_ONEXEC 0x40000000 /* exec allows onexec */
|
||||
#define AA_MAY_CHANGE_PROFILE 0x80000000
|
||||
#define AA_MAY_CHANGEHAT 0x80000000 /* ctrl auditing only */
|
||||
|
||||
#define AA_AUDIT_FILE_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | MAY_APPEND |\
|
||||
AA_MAY_CREATE | AA_MAY_DELETE | \
|
||||
AA_MAY_META_READ | AA_MAY_META_WRITE | \
|
||||
AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_LOCK | \
|
||||
AA_EXEC_MMAP | AA_MAY_LINK)
|
||||
|
||||
/*
|
||||
* The xindex is broken into 3 parts
|
||||
* - index - an index into either the exec name table or the variable table
|
||||
* - exec type - which determines how the executable name and index are used
|
||||
* - flags - which modify how the destination name is applied
|
||||
*/
|
||||
#define AA_X_INDEX_MASK 0x03ff
|
||||
|
||||
#define AA_X_TYPE_MASK 0x0c00
|
||||
#define AA_X_TYPE_SHIFT 10
|
||||
#define AA_X_NONE 0x0000
|
||||
#define AA_X_NAME 0x0400 /* use executable name px */
|
||||
#define AA_X_TABLE 0x0800 /* use a specified name ->n# */
|
||||
|
||||
#define AA_X_UNSAFE 0x1000
|
||||
#define AA_X_CHILD 0x2000 /* make >AA_X_NONE apply to children */
|
||||
#define AA_X_INHERIT 0x4000
|
||||
#define AA_X_UNCONFINED 0x8000
|
||||
|
||||
/* AA_SECURE_X_NEEDED - is passed in the bprm->unsafe field */
|
||||
#define AA_SECURE_X_NEEDED 0x8000
|
||||
|
||||
/* need to make conditional which ones are being set */
|
||||
struct path_cond {
|
||||
uid_t uid;
|
||||
umode_t mode;
|
||||
};
|
||||
|
||||
/* struct file_perms - file permission
|
||||
* @allow: mask of permissions that are allowed
|
||||
* @audit: mask of permissions to force an audit message for
|
||||
* @quiet: mask of permissions to quiet audit messages for
|
||||
* @kill: mask of permissions that when matched will kill the task
|
||||
* @xindex: exec transition index if @allow contains MAY_EXEC
|
||||
*
|
||||
* The @audit and @queit mask should be mutually exclusive.
|
||||
*/
|
||||
struct file_perms {
|
||||
u32 allow;
|
||||
u32 audit;
|
||||
u32 quiet;
|
||||
u32 kill;
|
||||
u16 xindex;
|
||||
};
|
||||
|
||||
extern struct file_perms nullperms;
|
||||
|
||||
#define COMBINED_PERM_MASK(X) ((X).allow | (X).audit | (X).quiet | (X).kill)
|
||||
|
||||
/* FIXME: split perms from dfa and match this to description
|
||||
* also add delegation info.
|
||||
*/
|
||||
static inline u16 dfa_map_xindex(u16 mask)
|
||||
{
|
||||
u16 old_index = (mask >> 10) & 0xf;
|
||||
u16 index = 0;
|
||||
|
||||
if (mask & 0x100)
|
||||
index |= AA_X_UNSAFE;
|
||||
if (mask & 0x200)
|
||||
index |= AA_X_INHERIT;
|
||||
if (mask & 0x80)
|
||||
index |= AA_X_UNCONFINED;
|
||||
|
||||
if (old_index == 1) {
|
||||
index |= AA_X_UNCONFINED;
|
||||
} else if (old_index == 2) {
|
||||
index |= AA_X_NAME;
|
||||
} else if (old_index == 3) {
|
||||
index |= AA_X_NAME | AA_X_CHILD;
|
||||
} else {
|
||||
index |= AA_X_TABLE;
|
||||
index |= old_index - 4;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/*
|
||||
* map old dfa inline permissions to new format
|
||||
*/
|
||||
#define dfa_user_allow(dfa, state) (((ACCEPT_TABLE(dfa)[state]) & 0x7f) | \
|
||||
((ACCEPT_TABLE(dfa)[state]) & 0x80000000))
|
||||
#define dfa_user_audit(dfa, state) ((ACCEPT_TABLE2(dfa)[state]) & 0x7f)
|
||||
#define dfa_user_quiet(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 7) & 0x7f)
|
||||
#define dfa_user_xindex(dfa, state) \
|
||||
(dfa_map_xindex(ACCEPT_TABLE(dfa)[state] & 0x3fff))
|
||||
|
||||
#define dfa_other_allow(dfa, state) ((((ACCEPT_TABLE(dfa)[state]) >> 14) & \
|
||||
0x7f) | \
|
||||
((ACCEPT_TABLE(dfa)[state]) & 0x80000000))
|
||||
#define dfa_other_audit(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 14) & 0x7f)
|
||||
#define dfa_other_quiet(dfa, state) \
|
||||
((((ACCEPT_TABLE2(dfa)[state]) >> 7) >> 14) & 0x7f)
|
||||
#define dfa_other_xindex(dfa, state) \
|
||||
dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff)
|
||||
|
||||
int aa_audit_file(struct aa_profile *profile, struct file_perms *perms,
|
||||
gfp_t gfp, int op, u32 request, const char *name,
|
||||
const char *target, uid_t ouid, const char *info, int error);
|
||||
|
||||
/**
|
||||
* struct aa_file_rules - components used for file rule permissions
|
||||
* @dfa: dfa to match path names and conditionals against
|
||||
* @perms: permission table indexed by the matched state accept entry of @dfa
|
||||
* @trans: transition table for indexed by named x transitions
|
||||
*
|
||||
* File permission are determined by matching a path against @dfa and then
|
||||
* then using the value of the accept entry for the matching state as
|
||||
* an index into @perms. If a named exec transition is required it is
|
||||
* looked up in the transition table.
|
||||
*/
|
||||
struct aa_file_rules {
|
||||
unsigned int start;
|
||||
struct aa_dfa *dfa;
|
||||
/* struct perms perms; */
|
||||
struct aa_domain trans;
|
||||
/* TODO: add delegate table */
|
||||
};
|
||||
|
||||
unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
|
||||
const char *name, struct path_cond *cond,
|
||||
struct file_perms *perms);
|
||||
|
||||
int aa_path_perm(int op, struct aa_profile *profile, struct path *path,
|
||||
int flags, u32 request, struct path_cond *cond);
|
||||
|
||||
int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
|
||||
struct path *new_dir, struct dentry *new_dentry);
|
||||
|
||||
int aa_file_perm(int op, struct aa_profile *profile, struct file *file,
|
||||
u32 request);
|
||||
|
||||
static inline void aa_free_file_rules(struct aa_file_rules *rules)
|
||||
{
|
||||
aa_put_dfa(rules->dfa);
|
||||
aa_free_domain_entries(&rules->trans);
|
||||
}
|
||||
|
||||
#define ACC_FMODE(x) (("\000\004\002\006"[(x)&O_ACCMODE]) | (((x) << 1) & 0x40))
|
||||
|
||||
/* from namei.c */
|
||||
#define MAP_OPEN_FLAGS(x) ((((x) + 1) & O_ACCMODE) ? (x) + 1 : (x))
|
||||
|
||||
/**
|
||||
* aa_map_file_perms - map file flags to AppArmor permissions
|
||||
* @file: open file to map flags to AppArmor permissions
|
||||
*
|
||||
* Returns: apparmor permission set for the file
|
||||
*/
|
||||
static inline u32 aa_map_file_to_perms(struct file *file)
|
||||
{
|
||||
int flags = MAP_OPEN_FLAGS(file->f_flags);
|
||||
u32 perms = ACC_FMODE(file->f_mode);
|
||||
|
||||
if ((flags & O_APPEND) && (perms & MAY_WRITE))
|
||||
perms = (perms & ~MAY_WRITE) | MAY_APPEND;
|
||||
/* trunc implies write permission */
|
||||
if (flags & O_TRUNC)
|
||||
perms |= MAY_WRITE;
|
||||
if (flags & O_CREAT)
|
||||
perms |= AA_MAY_CREATE;
|
||||
|
||||
return perms;
|
||||
}
|
||||
|
||||
#endif /* __AA_FILE_H */
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor ipc mediation function definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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 __AA_IPC_H
|
||||
#define __AA_IPC_H
|
||||
|
||||
#include <linux/sched.h>
|
||||
|
||||
struct aa_profile;
|
||||
|
||||
int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer,
|
||||
struct aa_profile *tracee, unsigned int mode);
|
||||
|
||||
int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,
|
||||
unsigned int mode);
|
||||
|
||||
#endif /* __AA_IPC_H */
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor policy dfa matching engine definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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 __AA_MATCH_H
|
||||
#define __AA_MATCH_H
|
||||
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#define DFA_NOMATCH 0
|
||||
#define DFA_START 1
|
||||
|
||||
#define DFA_VALID_PERM_MASK 0xffffffff
|
||||
#define DFA_VALID_PERM2_MASK 0xffffffff
|
||||
|
||||
/**
|
||||
* The format used for transition tables is based on the GNU flex table
|
||||
* file format (--tables-file option; see Table File Format in the flex
|
||||
* info pages and the flex sources for documentation). The magic number
|
||||
* used in the header is 0x1B5E783D insted of 0xF13C57B1 though, because
|
||||
* the YY_ID_CHK (check) and YY_ID_DEF (default) tables are used
|
||||
* slightly differently (see the apparmor-parser package).
|
||||
*/
|
||||
|
||||
#define YYTH_MAGIC 0x1B5E783D
|
||||
#define YYTH_DEF_RECURSE 0x1 /* DEF Table is recursive */
|
||||
|
||||
struct table_set_header {
|
||||
u32 th_magic; /* YYTH_MAGIC */
|
||||
u32 th_hsize;
|
||||
u32 th_ssize;
|
||||
u16 th_flags;
|
||||
char th_version[];
|
||||
};
|
||||
|
||||
/* The YYTD_ID are one less than flex table mappings. The flex id
|
||||
* has 1 subtracted at table load time, this allows us to directly use the
|
||||
* ID's as indexes.
|
||||
*/
|
||||
#define YYTD_ID_ACCEPT 0
|
||||
#define YYTD_ID_BASE 1
|
||||
#define YYTD_ID_CHK 2
|
||||
#define YYTD_ID_DEF 3
|
||||
#define YYTD_ID_EC 4
|
||||
#define YYTD_ID_META 5
|
||||
#define YYTD_ID_ACCEPT2 6
|
||||
#define YYTD_ID_NXT 7
|
||||
#define YYTD_ID_TSIZE 8
|
||||
|
||||
#define YYTD_DATA8 1
|
||||
#define YYTD_DATA16 2
|
||||
#define YYTD_DATA32 4
|
||||
#define YYTD_DATA64 8
|
||||
|
||||
/* Each ACCEPT2 table gets 6 dedicated flags, YYTD_DATAX define the
|
||||
* first flags
|
||||
*/
|
||||
#define ACCEPT1_FLAGS(X) ((X) & 0x3f)
|
||||
#define ACCEPT2_FLAGS(X) ACCEPT1_FLAGS((X) >> YYTD_ID_ACCEPT2)
|
||||
#define TO_ACCEPT1_FLAG(X) ACCEPT1_FLAGS(X)
|
||||
#define TO_ACCEPT2_FLAG(X) (ACCEPT1_FLAGS(X) << YYTD_ID_ACCEPT2)
|
||||
#define DFA_FLAG_VERIFY_STATES 0x1000
|
||||
|
||||
struct table_header {
|
||||
u16 td_id;
|
||||
u16 td_flags;
|
||||
u32 td_hilen;
|
||||
u32 td_lolen;
|
||||
char td_data[];
|
||||
};
|
||||
|
||||
#define DEFAULT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_DEF]->td_data))
|
||||
#define BASE_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_BASE]->td_data))
|
||||
#define NEXT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_NXT]->td_data))
|
||||
#define CHECK_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_CHK]->td_data))
|
||||
#define EQUIV_TABLE(DFA) ((u8 *)((DFA)->tables[YYTD_ID_EC]->td_data))
|
||||
#define ACCEPT_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT]->td_data))
|
||||
#define ACCEPT_TABLE2(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT2]->td_data))
|
||||
|
||||
struct aa_dfa {
|
||||
struct kref count;
|
||||
u16 flags;
|
||||
struct table_header *tables[YYTD_ID_TSIZE];
|
||||
};
|
||||
|
||||
#define byte_to_byte(X) (X)
|
||||
|
||||
#define UNPACK_ARRAY(TABLE, BLOB, LEN, TYPE, NTOHX) \
|
||||
do { \
|
||||
typeof(LEN) __i; \
|
||||
TYPE *__t = (TYPE *) TABLE; \
|
||||
TYPE *__b = (TYPE *) BLOB; \
|
||||
for (__i = 0; __i < LEN; __i++) { \
|
||||
__t[__i] = NTOHX(__b[__i]); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static inline size_t table_size(size_t len, size_t el_size)
|
||||
{
|
||||
return ALIGN(sizeof(struct table_header) + len * el_size, 8);
|
||||
}
|
||||
|
||||
struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags);
|
||||
unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
|
||||
const char *str, int len);
|
||||
unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start,
|
||||
const char *str);
|
||||
void aa_dfa_free_kref(struct kref *kref);
|
||||
|
||||
/**
|
||||
* aa_put_dfa - put a dfa refcount
|
||||
* @dfa: dfa to put refcount (MAYBE NULL)
|
||||
*
|
||||
* Requires: if @dfa != NULL that a valid refcount be held
|
||||
*/
|
||||
static inline void aa_put_dfa(struct aa_dfa *dfa)
|
||||
{
|
||||
if (dfa)
|
||||
kref_put(&dfa->count, aa_dfa_free_kref);
|
||||
}
|
||||
|
||||
#endif /* __AA_MATCH_H */
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor basic path manipulation function definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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 __AA_PATH_H
|
||||
#define __AA_PATH_H
|
||||
|
||||
|
||||
enum path_flags {
|
||||
PATH_IS_DIR = 0x1, /* path is a directory */
|
||||
PATH_CONNECT_PATH = 0x4, /* connect disconnected paths to / */
|
||||
PATH_CHROOT_REL = 0x8, /* do path lookup relative to chroot */
|
||||
PATH_CHROOT_NSCONNECT = 0x10, /* connect paths that are at ns root */
|
||||
|
||||
PATH_DELEGATE_DELETED = 0x08000, /* delegate deleted files */
|
||||
PATH_MEDIATE_DELETED = 0x10000, /* mediate deleted paths */
|
||||
};
|
||||
|
||||
int aa_get_name(struct path *path, int flags, char **buffer, const char **name);
|
||||
|
||||
#endif /* __AA_PATH_H */
|
||||
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor policy definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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 __AA_POLICY_H
|
||||
#define __AA_POLICY_H
|
||||
|
||||
#include <linux/capability.h>
|
||||
#include <linux/cred.h>
|
||||
#include <linux/kref.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/socket.h>
|
||||
|
||||
#include "apparmor.h"
|
||||
#include "audit.h"
|
||||
#include "capability.h"
|
||||
#include "domain.h"
|
||||
#include "file.h"
|
||||
#include "resource.h"
|
||||
|
||||
extern const char *profile_mode_names[];
|
||||
#define APPARMOR_NAMES_MAX_INDEX 3
|
||||
|
||||
#define COMPLAIN_MODE(_profile) \
|
||||
((aa_g_profile_mode == APPARMOR_COMPLAIN) || \
|
||||
((_profile)->mode == APPARMOR_COMPLAIN))
|
||||
|
||||
#define KILL_MODE(_profile) \
|
||||
((aa_g_profile_mode == APPARMOR_KILL) || \
|
||||
((_profile)->mode == APPARMOR_KILL))
|
||||
|
||||
#define PROFILE_IS_HAT(_profile) ((_profile)->flags & PFLAG_HAT)
|
||||
|
||||
/*
|
||||
* FIXME: currently need a clean way to replace and remove profiles as a
|
||||
* set. It should be done at the namespace level.
|
||||
* Either, with a set of profiles loaded at the namespace level or via
|
||||
* a mark and remove marked interface.
|
||||
*/
|
||||
enum profile_mode {
|
||||
APPARMOR_ENFORCE, /* enforce access rules */
|
||||
APPARMOR_COMPLAIN, /* allow and log access violations */
|
||||
APPARMOR_KILL, /* kill task on access violation */
|
||||
};
|
||||
|
||||
enum profile_flags {
|
||||
PFLAG_HAT = 1, /* profile is a hat */
|
||||
PFLAG_UNCONFINED = 2, /* profile is an unconfined profile */
|
||||
PFLAG_NULL = 4, /* profile is null learning profile */
|
||||
PFLAG_IX_ON_NAME_ERROR = 8, /* fallback to ix on name lookup fail */
|
||||
PFLAG_IMMUTABLE = 0x10, /* don't allow changes/replacement */
|
||||
PFLAG_USER_DEFINED = 0x20, /* user based profile - lower privs */
|
||||
PFLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */
|
||||
PFLAG_OLD_NULL_TRANS = 0x100, /* use // as the null transition */
|
||||
|
||||
/* These flags must correspond with PATH_flags */
|
||||
PFLAG_MEDIATE_DELETED = 0x10000, /* mediate instead delegate deleted */
|
||||
};
|
||||
|
||||
struct aa_profile;
|
||||
|
||||
/* struct aa_policy - common part of both namespaces and profiles
|
||||
* @name: name of the object
|
||||
* @hname - The hierarchical name
|
||||
* @count: reference count of the obj
|
||||
* @list: list policy object is on
|
||||
* @profiles: head of the profiles list contained in the object
|
||||
*/
|
||||
struct aa_policy {
|
||||
char *name;
|
||||
char *hname;
|
||||
struct kref count;
|
||||
struct list_head list;
|
||||
struct list_head profiles;
|
||||
};
|
||||
|
||||
/* struct aa_ns_acct - accounting of profiles in namespace
|
||||
* @max_size: maximum space allowed for all profiles in namespace
|
||||
* @max_count: maximum number of profiles that can be in this namespace
|
||||
* @size: current size of profiles
|
||||
* @count: current count of profiles (includes null profiles)
|
||||
*/
|
||||
struct aa_ns_acct {
|
||||
int max_size;
|
||||
int max_count;
|
||||
int size;
|
||||
int count;
|
||||
};
|
||||
|
||||
/* struct aa_namespace - namespace for a set of profiles
|
||||
* @base: common policy
|
||||
* @parent: parent of namespace
|
||||
* @lock: lock for modifying the object
|
||||
* @acct: accounting for the namespace
|
||||
* @unconfined: special unconfined profile for the namespace
|
||||
* @sub_ns: list of namespaces under the current namespace.
|
||||
*
|
||||
* An aa_namespace defines the set profiles that are searched to determine
|
||||
* which profile to attach to a task. Profiles can not be shared between
|
||||
* aa_namespaces and profile names within a namespace are guaranteed to be
|
||||
* unique. When profiles in separate namespaces have the same name they
|
||||
* are NOT considered to be equivalent.
|
||||
*
|
||||
* Namespaces are hierarchical and only namespaces and profiles below the
|
||||
* current namespace are visible.
|
||||
*
|
||||
* Namespace names must be unique and can not contain the characters :/\0
|
||||
*
|
||||
* FIXME TODO: add vserver support of namespaces (can it all be done in
|
||||
* userspace?)
|
||||
*/
|
||||
struct aa_namespace {
|
||||
struct aa_policy base;
|
||||
struct aa_namespace *parent;
|
||||
rwlock_t lock;
|
||||
struct aa_ns_acct acct;
|
||||
struct aa_profile *unconfined;
|
||||
struct list_head sub_ns;
|
||||
};
|
||||
|
||||
/* struct aa_profile - basic confinement data
|
||||
* @base - base components of the profile (name, refcount, lists, lock ...)
|
||||
* @parent: parent of profile
|
||||
* @ns: namespace the profile is in
|
||||
* @replacedby: is set to the profile that replaced this profile
|
||||
* @rename: optional profile name that this profile renamed
|
||||
* @xmatch: optional extended matching for unconfined executables names
|
||||
* @xmatch_len: xmatch prefix len, used to determine xmatch priority
|
||||
* @sid: the unique security id number of this profile
|
||||
* @audit: the auditing mode of the profile
|
||||
* @mode: the enforcement mode of the profile
|
||||
* @flags: flags controlling profile behavior
|
||||
* @path_flags: flags controlling path generation behavior
|
||||
* @size: the memory consumed by this profiles rules
|
||||
* @file: The set of rules governing basic file access and domain transitions
|
||||
* @caps: capabilities for the profile
|
||||
* @rlimits: rlimits for the profile
|
||||
*
|
||||
* The AppArmor profile contains the basic confinement data. Each profile
|
||||
* has a name, and exists in a namespace. The @name and @exec_match are
|
||||
* used to determine profile attachment against unconfined tasks. All other
|
||||
* attachments are determined by profile X transition rules.
|
||||
*
|
||||
* The @replacedby field is write protected by the profile lock. Reads
|
||||
* are assumed to be atomic, and are done without locking.
|
||||
*
|
||||
* Profiles have a hierarchy where hats and children profiles keep
|
||||
* a reference to their parent.
|
||||
*
|
||||
* Profile names can not begin with a : and can not contain the \0
|
||||
* character. If a profile name begins with / it will be considered when
|
||||
* determining profile attachment on "unconfined" tasks.
|
||||
*/
|
||||
struct aa_profile {
|
||||
struct aa_policy base;
|
||||
struct aa_profile *parent;
|
||||
|
||||
struct aa_namespace *ns;
|
||||
struct aa_profile *replacedby;
|
||||
const char *rename;
|
||||
|
||||
struct aa_dfa *xmatch;
|
||||
int xmatch_len;
|
||||
u32 sid;
|
||||
enum audit_mode audit;
|
||||
enum profile_mode mode;
|
||||
u32 flags;
|
||||
u32 path_flags;
|
||||
int size;
|
||||
|
||||
struct aa_file_rules file;
|
||||
struct aa_caps caps;
|
||||
struct aa_rlimit rlimits;
|
||||
};
|
||||
|
||||
extern struct aa_namespace *root_ns;
|
||||
extern enum profile_mode aa_g_profile_mode;
|
||||
|
||||
void aa_add_profile(struct aa_policy *common, struct aa_profile *profile);
|
||||
|
||||
bool aa_ns_visible(struct aa_namespace *curr, struct aa_namespace *view);
|
||||
const char *aa_ns_name(struct aa_namespace *parent, struct aa_namespace *child);
|
||||
int aa_alloc_root_ns(void);
|
||||
void aa_free_root_ns(void);
|
||||
void aa_free_namespace_kref(struct kref *kref);
|
||||
|
||||
struct aa_namespace *aa_find_namespace(struct aa_namespace *root,
|
||||
const char *name);
|
||||
|
||||
static inline struct aa_policy *aa_get_common(struct aa_policy *c)
|
||||
{
|
||||
if (c)
|
||||
kref_get(&c->count);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_get_namespace - increment references count on @ns
|
||||
* @ns: namespace to increment reference count of (MAYBE NULL)
|
||||
*
|
||||
* Returns: pointer to @ns, if @ns is NULL returns NULL
|
||||
* Requires: @ns must be held with valid refcount when called
|
||||
*/
|
||||
static inline struct aa_namespace *aa_get_namespace(struct aa_namespace *ns)
|
||||
{
|
||||
if (ns)
|
||||
kref_get(&(ns->base.count));
|
||||
|
||||
return ns;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_put_namespace - decrement refcount on @ns
|
||||
* @ns: namespace to put reference of
|
||||
*
|
||||
* Decrement reference count of @ns and if no longer in use free it
|
||||
*/
|
||||
static inline void aa_put_namespace(struct aa_namespace *ns)
|
||||
{
|
||||
if (ns)
|
||||
kref_put(&ns->base.count, aa_free_namespace_kref);
|
||||
}
|
||||
|
||||
struct aa_profile *aa_alloc_profile(const char *name);
|
||||
struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat);
|
||||
void aa_free_profile_kref(struct kref *kref);
|
||||
struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name);
|
||||
struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *name);
|
||||
struct aa_profile *aa_match_profile(struct aa_namespace *ns, const char *name);
|
||||
|
||||
ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace);
|
||||
ssize_t aa_remove_profiles(char *name, size_t size);
|
||||
|
||||
#define PROF_ADD 1
|
||||
#define PROF_REPLACE 0
|
||||
|
||||
#define unconfined(X) ((X)->flags & PFLAG_UNCONFINED)
|
||||
|
||||
/**
|
||||
* aa_newest_version - find the newest version of @profile
|
||||
* @profile: the profile to check for newer versions of (NOT NULL)
|
||||
*
|
||||
* Returns: newest version of @profile, if @profile is the newest version
|
||||
* return @profile.
|
||||
*
|
||||
* NOTE: the profile returned is not refcounted, The refcount on @profile
|
||||
* must be held until the caller decides what to do with the returned newest
|
||||
* version.
|
||||
*/
|
||||
static inline struct aa_profile *aa_newest_version(struct aa_profile *profile)
|
||||
{
|
||||
while (profile->replacedby)
|
||||
profile = profile->replacedby;
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_get_profile - increment refcount on profile @p
|
||||
* @p: profile (MAYBE NULL)
|
||||
*
|
||||
* Returns: pointer to @p if @p is NULL will return NULL
|
||||
* Requires: @p must be held with valid refcount when called
|
||||
*/
|
||||
static inline struct aa_profile *aa_get_profile(struct aa_profile *p)
|
||||
{
|
||||
if (p)
|
||||
kref_get(&(p->base.count));
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_put_profile - decrement refcount on profile @p
|
||||
* @p: profile (MAYBE NULL)
|
||||
*/
|
||||
static inline void aa_put_profile(struct aa_profile *p)
|
||||
{
|
||||
if (p)
|
||||
kref_put(&p->base.count, aa_free_profile_kref);
|
||||
}
|
||||
|
||||
static inline int AUDIT_MODE(struct aa_profile *profile)
|
||||
{
|
||||
if (aa_g_audit != AUDIT_NORMAL)
|
||||
return aa_g_audit;
|
||||
|
||||
return profile->audit;
|
||||
}
|
||||
|
||||
bool aa_may_manage_policy(int op);
|
||||
|
||||
#endif /* __AA_POLICY_H */
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor policy loading interface function definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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 __POLICY_INTERFACE_H
|
||||
#define __POLICY_INTERFACE_H
|
||||
|
||||
struct aa_profile *aa_unpack(void *udata, size_t size, const char **ns);
|
||||
|
||||
#endif /* __POLICY_INTERFACE_H */
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor /proc/<pid>/attr/ interface function definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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 __AA_PROCATTR_H
|
||||
#define __AA_PROCATTR_H
|
||||
|
||||
#define AA_DO_TEST 1
|
||||
#define AA_ONEXEC 1
|
||||
|
||||
int aa_getprocattr(struct aa_profile *profile, char **string);
|
||||
int aa_setprocattr_changehat(char *args, size_t size, int test);
|
||||
int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test);
|
||||
int aa_setprocattr_permipc(char *fqname);
|
||||
|
||||
#endif /* __AA_PROCATTR_H */
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor resource limits function definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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 __AA_RESOURCE_H
|
||||
#define __AA_RESOURCE_H
|
||||
|
||||
#include <linux/resource.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
struct aa_profile;
|
||||
|
||||
/* struct aa_rlimit - rlimit settings for the profile
|
||||
* @mask: which hard limits to set
|
||||
* @limits: rlimit values that override task limits
|
||||
*
|
||||
* AppArmor rlimits are used to set confined task rlimits. Only the
|
||||
* limits specified in @mask will be controlled by apparmor.
|
||||
*/
|
||||
struct aa_rlimit {
|
||||
unsigned int mask;
|
||||
struct rlimit limits[RLIM_NLIMITS];
|
||||
};
|
||||
|
||||
int aa_map_resource(int resource);
|
||||
int aa_task_setrlimit(struct aa_profile *profile, unsigned int resource,
|
||||
struct rlimit *new_rlim);
|
||||
|
||||
void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new);
|
||||
|
||||
static inline void aa_free_rlimit_rules(struct aa_rlimit *rlims)
|
||||
{
|
||||
/* NOP */
|
||||
}
|
||||
|
||||
#endif /* __AA_RESOURCE_H */
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor security identifier (sid) definitions
|
||||
*
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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 __AA_SID_H
|
||||
#define __AA_SID_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
struct aa_profile;
|
||||
|
||||
u32 aa_alloc_sid(void);
|
||||
void aa_free_sid(u32 sid);
|
||||
|
||||
#endif /* __AA_SID_H */
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor ipc mediation
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/ptrace.h>
|
||||
|
||||
#include "include/audit.h"
|
||||
#include "include/capability.h"
|
||||
#include "include/context.h"
|
||||
#include "include/policy.h"
|
||||
|
||||
/* call back to audit ptrace fields */
|
||||
static void audit_cb(struct audit_buffer *ab, void *va)
|
||||
{
|
||||
struct common_audit_data *sa = va;
|
||||
audit_log_format(ab, " target=");
|
||||
audit_log_untrustedstring(ab, sa->aad.target);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_audit_ptrace - do auditing for ptrace
|
||||
* @profile: profile being enforced (NOT NULL)
|
||||
* @target: profile being traced (NOT NULL)
|
||||
* @error: error condition
|
||||
*
|
||||
* Returns: %0 or error code
|
||||
*/
|
||||
static int aa_audit_ptrace(struct aa_profile *profile,
|
||||
struct aa_profile *target, int error)
|
||||
{
|
||||
struct common_audit_data sa;
|
||||
COMMON_AUDIT_DATA_INIT(&sa, NONE);
|
||||
sa.aad.op = OP_PTRACE;
|
||||
sa.aad.target = target;
|
||||
sa.aad.error = error;
|
||||
|
||||
return aa_audit(AUDIT_APPARMOR_AUTO, profile, GFP_ATOMIC, &sa,
|
||||
audit_cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_may_ptrace - test if tracer task can trace the tracee
|
||||
* @tracer_task: task who will do the tracing (NOT NULL)
|
||||
* @tracer: profile of the task doing the tracing (NOT NULL)
|
||||
* @tracee: task to be traced
|
||||
* @mode: whether PTRACE_MODE_READ || PTRACE_MODE_ATTACH
|
||||
*
|
||||
* Returns: %0 else error code if permission denied or error
|
||||
*/
|
||||
int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer,
|
||||
struct aa_profile *tracee, unsigned int mode)
|
||||
{
|
||||
/* TODO: currently only based on capability, not extended ptrace
|
||||
* rules,
|
||||
* Test mode for PTRACE_MODE_READ || PTRACE_MODE_ATTACH
|
||||
*/
|
||||
|
||||
if (unconfined(tracer) || tracer == tracee)
|
||||
return 0;
|
||||
/* log this capability request */
|
||||
return aa_capable(tracer_task, tracer, CAP_SYS_PTRACE, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_ptrace - do ptrace permission check and auditing
|
||||
* @tracer: task doing the tracing (NOT NULL)
|
||||
* @tracee: task being traced (NOT NULL)
|
||||
* @mode: ptrace mode either PTRACE_MODE_READ || PTRACE_MODE_ATTACH
|
||||
*
|
||||
* Returns: %0 else error code if permission denied or error
|
||||
*/
|
||||
int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,
|
||||
unsigned int mode)
|
||||
{
|
||||
/*
|
||||
* tracer can ptrace tracee when
|
||||
* - tracer is unconfined ||
|
||||
* - tracer is in complain mode
|
||||
* - tracer has rules allowing it to trace tracee currently this is:
|
||||
* - confined by the same profile ||
|
||||
* - tracer profile has CAP_SYS_PTRACE
|
||||
*/
|
||||
|
||||
struct aa_profile *tracer_p;
|
||||
/* cred released below */
|
||||
const struct cred *cred = get_task_cred(tracer);
|
||||
int error = 0;
|
||||
tracer_p = aa_cred_profile(cred);
|
||||
|
||||
if (!unconfined(tracer_p)) {
|
||||
/* lcred released below */
|
||||
const struct cred *lcred = get_task_cred(tracee);
|
||||
struct aa_profile *tracee_p = aa_cred_profile(lcred);
|
||||
|
||||
error = aa_may_ptrace(tracer, tracer_p, tracee_p, mode);
|
||||
error = aa_audit_ptrace(tracer_p, tracee_p, error);
|
||||
|
||||
put_cred(lcred);
|
||||
}
|
||||
put_cred(cred);
|
||||
|
||||
return error;
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains basic common functions used in AppArmor
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/vmalloc.h>
|
||||
|
||||
#include "include/audit.h"
|
||||
|
||||
|
||||
/**
|
||||
* aa_split_fqname - split a fqname into a profile and namespace name
|
||||
* @fqname: a full qualified name in namespace profile format (NOT NULL)
|
||||
* @ns_name: pointer to portion of the string containing the ns name (NOT NULL)
|
||||
*
|
||||
* Returns: profile name or NULL if one is not specified
|
||||
*
|
||||
* Split a namespace name from a profile name (see policy.c for naming
|
||||
* description). If a portion of the name is missing it returns NULL for
|
||||
* that portion.
|
||||
*
|
||||
* NOTE: may modify the @fqname string. The pointers returned point
|
||||
* into the @fqname string.
|
||||
*/
|
||||
char *aa_split_fqname(char *fqname, char **ns_name)
|
||||
{
|
||||
char *name = strim(fqname);
|
||||
|
||||
*ns_name = NULL;
|
||||
if (name[0] == ':') {
|
||||
char *split = strchr(&name[1], ':');
|
||||
if (split) {
|
||||
/* overwrite ':' with \0 */
|
||||
*split = 0;
|
||||
name = skip_spaces(split + 1);
|
||||
} else
|
||||
/* a ns name without a following profile is allowed */
|
||||
name = NULL;
|
||||
*ns_name = &name[1];
|
||||
}
|
||||
if (name && *name == 0)
|
||||
name = NULL;
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_info_message - log a none profile related status message
|
||||
* @str: message to log
|
||||
*/
|
||||
void aa_info_message(const char *str)
|
||||
{
|
||||
if (audit_enabled) {
|
||||
struct common_audit_data sa;
|
||||
COMMON_AUDIT_DATA_INIT(&sa, NONE);
|
||||
sa.aad.info = str;
|
||||
aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, NULL);
|
||||
}
|
||||
printk(KERN_INFO "AppArmor: %s\n", str);
|
||||
}
|
||||
|
||||
/**
|
||||
* kvmalloc - do allocation preferring kmalloc but falling back to vmalloc
|
||||
* @size: size of allocation
|
||||
*
|
||||
* Return: allocated buffer or NULL if failed
|
||||
*
|
||||
* It is possible that policy being loaded from the user is larger than
|
||||
* what can be allocated by kmalloc, in those cases fall back to vmalloc.
|
||||
*/
|
||||
void *kvmalloc(size_t size)
|
||||
{
|
||||
void *buffer = NULL;
|
||||
|
||||
if (size == 0)
|
||||
return NULL;
|
||||
|
||||
/* do not attempt kmalloc if we need more than 16 pages at once */
|
||||
if (size <= (16*PAGE_SIZE))
|
||||
buffer = kmalloc(size, GFP_NOIO | __GFP_NOWARN);
|
||||
if (!buffer) {
|
||||
/* see kvfree for why size must be at least work_struct size
|
||||
* when allocated via vmalloc
|
||||
*/
|
||||
if (size < sizeof(struct work_struct))
|
||||
size = sizeof(struct work_struct);
|
||||
buffer = vmalloc(size);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* do_vfree - workqueue routine for freeing vmalloced memory
|
||||
* @work: data to be freed
|
||||
*
|
||||
* The work_struct is overlaid to the data being freed, as at the point
|
||||
* the work is scheduled the data is no longer valid, be its freeing
|
||||
* needs to be delayed until safe.
|
||||
*/
|
||||
static void do_vfree(struct work_struct *work)
|
||||
{
|
||||
vfree(work);
|
||||
}
|
||||
|
||||
/**
|
||||
* kvfree - free an allocation do by kvmalloc
|
||||
* @buffer: buffer to free (MAYBE_NULL)
|
||||
*
|
||||
* Free a buffer allocated by kvmalloc
|
||||
*/
|
||||
void kvfree(void *buffer)
|
||||
{
|
||||
if (is_vmalloc_addr(buffer)) {
|
||||
/* Data is no longer valid so just use the allocated space
|
||||
* as the work_struct
|
||||
*/
|
||||
struct work_struct *work = (struct work_struct *) buffer;
|
||||
INIT_WORK(work, do_vfree);
|
||||
schedule_work(work);
|
||||
} else
|
||||
kfree(buffer);
|
||||
}
|
||||
@@ -0,0 +1,938 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor LSM hooks.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/security.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/mman.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/ptrace.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/sysctl.h>
|
||||
#include <linux/audit.h>
|
||||
#include <net/sock.h>
|
||||
|
||||
#include "include/apparmor.h"
|
||||
#include "include/apparmorfs.h"
|
||||
#include "include/audit.h"
|
||||
#include "include/capability.h"
|
||||
#include "include/context.h"
|
||||
#include "include/file.h"
|
||||
#include "include/ipc.h"
|
||||
#include "include/path.h"
|
||||
#include "include/policy.h"
|
||||
#include "include/procattr.h"
|
||||
|
||||
/* Flag indicating whether initialization completed */
|
||||
int apparmor_initialized __initdata;
|
||||
|
||||
/*
|
||||
* LSM hook functions
|
||||
*/
|
||||
|
||||
/*
|
||||
* free the associated aa_task_cxt and put its profiles
|
||||
*/
|
||||
static void apparmor_cred_free(struct cred *cred)
|
||||
{
|
||||
aa_free_task_context(cred->security);
|
||||
cred->security = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* allocate the apparmor part of blank credentials
|
||||
*/
|
||||
static int apparmor_cred_alloc_blank(struct cred *cred, gfp_t gfp)
|
||||
{
|
||||
/* freed by apparmor_cred_free */
|
||||
struct aa_task_cxt *cxt = aa_alloc_task_context(gfp);
|
||||
if (!cxt)
|
||||
return -ENOMEM;
|
||||
|
||||
cred->security = cxt;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* prepare new aa_task_cxt for modification by prepare_cred block
|
||||
*/
|
||||
static int apparmor_cred_prepare(struct cred *new, const struct cred *old,
|
||||
gfp_t gfp)
|
||||
{
|
||||
/* freed by apparmor_cred_free */
|
||||
struct aa_task_cxt *cxt = aa_alloc_task_context(gfp);
|
||||
if (!cxt)
|
||||
return -ENOMEM;
|
||||
|
||||
aa_dup_task_context(cxt, old->security);
|
||||
new->security = cxt;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* transfer the apparmor data to a blank set of creds
|
||||
*/
|
||||
static void apparmor_cred_transfer(struct cred *new, const struct cred *old)
|
||||
{
|
||||
const struct aa_task_cxt *old_cxt = old->security;
|
||||
struct aa_task_cxt *new_cxt = new->security;
|
||||
|
||||
aa_dup_task_context(new_cxt, old_cxt);
|
||||
}
|
||||
|
||||
static int apparmor_ptrace_access_check(struct task_struct *child,
|
||||
unsigned int mode)
|
||||
{
|
||||
int error = cap_ptrace_access_check(child, mode);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
return aa_ptrace(current, child, mode);
|
||||
}
|
||||
|
||||
static int apparmor_ptrace_traceme(struct task_struct *parent)
|
||||
{
|
||||
int error = cap_ptrace_traceme(parent);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
return aa_ptrace(parent, current, PTRACE_MODE_ATTACH);
|
||||
}
|
||||
|
||||
/* Derived from security/commoncap.c:cap_capget */
|
||||
static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective,
|
||||
kernel_cap_t *inheritable, kernel_cap_t *permitted)
|
||||
{
|
||||
struct aa_profile *profile;
|
||||
const struct cred *cred;
|
||||
|
||||
rcu_read_lock();
|
||||
cred = __task_cred(target);
|
||||
profile = aa_cred_profile(cred);
|
||||
|
||||
*effective = cred->cap_effective;
|
||||
*inheritable = cred->cap_inheritable;
|
||||
*permitted = cred->cap_permitted;
|
||||
|
||||
if (!unconfined(profile)) {
|
||||
*effective = cap_intersect(*effective, profile->caps.allow);
|
||||
*permitted = cap_intersect(*permitted, profile->caps.allow);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apparmor_capable(struct task_struct *task, const struct cred *cred,
|
||||
int cap, int audit)
|
||||
{
|
||||
struct aa_profile *profile;
|
||||
/* cap_capable returns 0 on success, else -EPERM */
|
||||
int error = cap_capable(task, cred, cap, audit);
|
||||
if (!error) {
|
||||
profile = aa_cred_profile(cred);
|
||||
if (!unconfined(profile))
|
||||
error = aa_capable(task, profile, cap, audit);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* common_perm - basic common permission check wrapper fn for paths
|
||||
* @op: operation being checked
|
||||
* @path: path to check permission of (NOT NULL)
|
||||
* @mask: requested permissions mask
|
||||
* @cond: conditional info for the permission request (NOT NULL)
|
||||
*
|
||||
* Returns: %0 else error code if error or permission denied
|
||||
*/
|
||||
static int common_perm(int op, struct path *path, u32 mask,
|
||||
struct path_cond *cond)
|
||||
{
|
||||
struct aa_profile *profile;
|
||||
int error = 0;
|
||||
|
||||
profile = __aa_current_profile();
|
||||
if (!unconfined(profile))
|
||||
error = aa_path_perm(op, profile, path, 0, mask, cond);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* common_perm_dir_dentry - common permission wrapper when path is dir, dentry
|
||||
* @op: operation being checked
|
||||
* @dir: directory of the dentry (NOT NULL)
|
||||
* @dentry: dentry to check (NOT NULL)
|
||||
* @mask: requested permissions mask
|
||||
* @cond: conditional info for the permission request (NOT NULL)
|
||||
*
|
||||
* Returns: %0 else error code if error or permission denied
|
||||
*/
|
||||
static int common_perm_dir_dentry(int op, struct path *dir,
|
||||
struct dentry *dentry, u32 mask,
|
||||
struct path_cond *cond)
|
||||
{
|
||||
struct path path = { dir->mnt, dentry };
|
||||
|
||||
return common_perm(op, &path, mask, cond);
|
||||
}
|
||||
|
||||
/**
|
||||
* common_perm_mnt_dentry - common permission wrapper when mnt, dentry
|
||||
* @op: operation being checked
|
||||
* @mnt: mount point of dentry (NOT NULL)
|
||||
* @dentry: dentry to check (NOT NULL)
|
||||
* @mask: requested permissions mask
|
||||
*
|
||||
* Returns: %0 else error code if error or permission denied
|
||||
*/
|
||||
static int common_perm_mnt_dentry(int op, struct vfsmount *mnt,
|
||||
struct dentry *dentry, u32 mask)
|
||||
{
|
||||
struct path path = { mnt, dentry };
|
||||
struct path_cond cond = { dentry->d_inode->i_uid,
|
||||
dentry->d_inode->i_mode
|
||||
};
|
||||
|
||||
return common_perm(op, &path, mask, &cond);
|
||||
}
|
||||
|
||||
/**
|
||||
* common_perm_rm - common permission wrapper for operations doing rm
|
||||
* @op: operation being checked
|
||||
* @dir: directory that the dentry is in (NOT NULL)
|
||||
* @dentry: dentry being rm'd (NOT NULL)
|
||||
* @mask: requested permission mask
|
||||
*
|
||||
* Returns: %0 else error code if error or permission denied
|
||||
*/
|
||||
static int common_perm_rm(int op, struct path *dir,
|
||||
struct dentry *dentry, u32 mask)
|
||||
{
|
||||
struct inode *inode = dentry->d_inode;
|
||||
struct path_cond cond = { };
|
||||
|
||||
if (!inode || !dir->mnt || !mediated_filesystem(inode))
|
||||
return 0;
|
||||
|
||||
cond.uid = inode->i_uid;
|
||||
cond.mode = inode->i_mode;
|
||||
|
||||
return common_perm_dir_dentry(op, dir, dentry, mask, &cond);
|
||||
}
|
||||
|
||||
/**
|
||||
* common_perm_create - common permission wrapper for operations doing create
|
||||
* @op: operation being checked
|
||||
* @dir: directory that dentry will be created in (NOT NULL)
|
||||
* @dentry: dentry to create (NOT NULL)
|
||||
* @mask: request permission mask
|
||||
* @mode: created file mode
|
||||
*
|
||||
* Returns: %0 else error code if error or permission denied
|
||||
*/
|
||||
static int common_perm_create(int op, struct path *dir, struct dentry *dentry,
|
||||
u32 mask, umode_t mode)
|
||||
{
|
||||
struct path_cond cond = { current_fsuid(), mode };
|
||||
|
||||
if (!dir->mnt || !mediated_filesystem(dir->dentry->d_inode))
|
||||
return 0;
|
||||
|
||||
return common_perm_dir_dentry(op, dir, dentry, mask, &cond);
|
||||
}
|
||||
|
||||
static int apparmor_path_unlink(struct path *dir, struct dentry *dentry)
|
||||
{
|
||||
return common_perm_rm(OP_UNLINK, dir, dentry, AA_MAY_DELETE);
|
||||
}
|
||||
|
||||
static int apparmor_path_mkdir(struct path *dir, struct dentry *dentry,
|
||||
int mode)
|
||||
{
|
||||
return common_perm_create(OP_MKDIR, dir, dentry, AA_MAY_CREATE,
|
||||
S_IFDIR);
|
||||
}
|
||||
|
||||
static int apparmor_path_rmdir(struct path *dir, struct dentry *dentry)
|
||||
{
|
||||
return common_perm_rm(OP_RMDIR, dir, dentry, AA_MAY_DELETE);
|
||||
}
|
||||
|
||||
static int apparmor_path_mknod(struct path *dir, struct dentry *dentry,
|
||||
int mode, unsigned int dev)
|
||||
{
|
||||
return common_perm_create(OP_MKNOD, dir, dentry, AA_MAY_CREATE, mode);
|
||||
}
|
||||
|
||||
static int apparmor_path_truncate(struct path *path)
|
||||
{
|
||||
struct path_cond cond = { path->dentry->d_inode->i_uid,
|
||||
path->dentry->d_inode->i_mode
|
||||
};
|
||||
|
||||
if (!path->mnt || !mediated_filesystem(path->dentry->d_inode))
|
||||
return 0;
|
||||
|
||||
return common_perm(OP_TRUNC, path, MAY_WRITE | AA_MAY_META_WRITE,
|
||||
&cond);
|
||||
}
|
||||
|
||||
static int apparmor_path_symlink(struct path *dir, struct dentry *dentry,
|
||||
const char *old_name)
|
||||
{
|
||||
return common_perm_create(OP_SYMLINK, dir, dentry, AA_MAY_CREATE,
|
||||
S_IFLNK);
|
||||
}
|
||||
|
||||
static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir,
|
||||
struct dentry *new_dentry)
|
||||
{
|
||||
struct aa_profile *profile;
|
||||
int error = 0;
|
||||
|
||||
if (!mediated_filesystem(old_dentry->d_inode))
|
||||
return 0;
|
||||
|
||||
profile = aa_current_profile();
|
||||
if (!unconfined(profile))
|
||||
error = aa_path_link(profile, old_dentry, new_dir, new_dentry);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
|
||||
struct path *new_dir, struct dentry *new_dentry)
|
||||
{
|
||||
struct aa_profile *profile;
|
||||
int error = 0;
|
||||
|
||||
if (!mediated_filesystem(old_dentry->d_inode))
|
||||
return 0;
|
||||
|
||||
profile = aa_current_profile();
|
||||
if (!unconfined(profile)) {
|
||||
struct path old_path = { old_dir->mnt, old_dentry };
|
||||
struct path new_path = { new_dir->mnt, new_dentry };
|
||||
struct path_cond cond = { old_dentry->d_inode->i_uid,
|
||||
old_dentry->d_inode->i_mode
|
||||
};
|
||||
|
||||
error = aa_path_perm(OP_RENAME_SRC, profile, &old_path, 0,
|
||||
MAY_READ | AA_MAY_META_READ | MAY_WRITE |
|
||||
AA_MAY_META_WRITE | AA_MAY_DELETE,
|
||||
&cond);
|
||||
if (!error)
|
||||
error = aa_path_perm(OP_RENAME_DEST, profile, &new_path,
|
||||
0, MAY_WRITE | AA_MAY_META_WRITE |
|
||||
AA_MAY_CREATE, &cond);
|
||||
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
static int apparmor_path_chmod(struct dentry *dentry, struct vfsmount *mnt,
|
||||
mode_t mode)
|
||||
{
|
||||
if (!mediated_filesystem(dentry->d_inode))
|
||||
return 0;
|
||||
|
||||
return common_perm_mnt_dentry(OP_CHMOD, mnt, dentry, AA_MAY_CHMOD);
|
||||
}
|
||||
|
||||
static int apparmor_path_chown(struct path *path, uid_t uid, gid_t gid)
|
||||
{
|
||||
struct path_cond cond = { path->dentry->d_inode->i_uid,
|
||||
path->dentry->d_inode->i_mode
|
||||
};
|
||||
|
||||
if (!mediated_filesystem(path->dentry->d_inode))
|
||||
return 0;
|
||||
|
||||
return common_perm(OP_CHOWN, path, AA_MAY_CHOWN, &cond);
|
||||
}
|
||||
|
||||
static int apparmor_inode_getattr(struct vfsmount *mnt, struct dentry *dentry)
|
||||
{
|
||||
if (!mediated_filesystem(dentry->d_inode))
|
||||
return 0;
|
||||
|
||||
return common_perm_mnt_dentry(OP_GETATTR, mnt, dentry,
|
||||
AA_MAY_META_READ);
|
||||
}
|
||||
|
||||
static int apparmor_dentry_open(struct file *file, const struct cred *cred)
|
||||
{
|
||||
struct aa_file_cxt *fcxt = file->f_security;
|
||||
struct aa_profile *profile;
|
||||
int error = 0;
|
||||
|
||||
if (!mediated_filesystem(file->f_path.dentry->d_inode))
|
||||
return 0;
|
||||
|
||||
/* If in exec, permission is handled by bprm hooks.
|
||||
* Cache permissions granted by the previous exec check, with
|
||||
* implicit read and executable mmap which are required to
|
||||
* actually execute the image.
|
||||
*/
|
||||
if (current->in_execve) {
|
||||
fcxt->allow = MAY_EXEC | MAY_READ | AA_EXEC_MMAP;
|
||||
return 0;
|
||||
}
|
||||
|
||||
profile = aa_cred_profile(cred);
|
||||
if (!unconfined(profile)) {
|
||||
struct inode *inode = file->f_path.dentry->d_inode;
|
||||
struct path_cond cond = { inode->i_uid, inode->i_mode };
|
||||
|
||||
error = aa_path_perm(OP_OPEN, profile, &file->f_path, 0,
|
||||
aa_map_file_to_perms(file), &cond);
|
||||
/* todo cache full allowed permissions set and state */
|
||||
fcxt->allow = aa_map_file_to_perms(file);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int apparmor_file_alloc_security(struct file *file)
|
||||
{
|
||||
/* freed by apparmor_file_free_security */
|
||||
file->f_security = aa_alloc_file_context(GFP_KERNEL);
|
||||
if (!file->f_security)
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static void apparmor_file_free_security(struct file *file)
|
||||
{
|
||||
struct aa_file_cxt *cxt = file->f_security;
|
||||
|
||||
aa_free_file_context(cxt);
|
||||
}
|
||||
|
||||
static int common_file_perm(int op, struct file *file, u32 mask)
|
||||
{
|
||||
struct aa_file_cxt *fcxt = file->f_security;
|
||||
struct aa_profile *profile, *fprofile = aa_cred_profile(file->f_cred);
|
||||
int error = 0;
|
||||
|
||||
BUG_ON(!fprofile);
|
||||
|
||||
if (!file->f_path.mnt ||
|
||||
!mediated_filesystem(file->f_path.dentry->d_inode))
|
||||
return 0;
|
||||
|
||||
profile = __aa_current_profile();
|
||||
|
||||
/* revalidate access, if task is unconfined, or the cached cred
|
||||
* doesn't match or if the request is for more permissions than
|
||||
* was granted.
|
||||
*
|
||||
* Note: the test for !unconfined(fprofile) is to handle file
|
||||
* delegation from unconfined tasks
|
||||
*/
|
||||
if (!unconfined(profile) && !unconfined(fprofile) &&
|
||||
((fprofile != profile) || (mask & ~fcxt->allow)))
|
||||
error = aa_file_perm(op, profile, file, mask);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int apparmor_file_permission(struct file *file, int mask)
|
||||
{
|
||||
return common_file_perm(OP_FPERM, file, mask);
|
||||
}
|
||||
|
||||
static int apparmor_file_lock(struct file *file, unsigned int cmd)
|
||||
{
|
||||
u32 mask = AA_MAY_LOCK;
|
||||
|
||||
if (cmd == F_WRLCK)
|
||||
mask |= MAY_WRITE;
|
||||
|
||||
return common_file_perm(OP_FLOCK, file, mask);
|
||||
}
|
||||
|
||||
static int common_mmap(int op, struct file *file, unsigned long prot,
|
||||
unsigned long flags)
|
||||
{
|
||||
struct dentry *dentry;
|
||||
int mask = 0;
|
||||
|
||||
if (!file || !file->f_security)
|
||||
return 0;
|
||||
|
||||
if (prot & PROT_READ)
|
||||
mask |= MAY_READ;
|
||||
/*
|
||||
* Private mappings don't require write perms since they don't
|
||||
* write back to the files
|
||||
*/
|
||||
if ((prot & PROT_WRITE) && !(flags & MAP_PRIVATE))
|
||||
mask |= MAY_WRITE;
|
||||
if (prot & PROT_EXEC)
|
||||
mask |= AA_EXEC_MMAP;
|
||||
|
||||
dentry = file->f_path.dentry;
|
||||
return common_file_perm(op, file, mask);
|
||||
}
|
||||
|
||||
static int apparmor_file_mmap(struct file *file, unsigned long reqprot,
|
||||
unsigned long prot, unsigned long flags,
|
||||
unsigned long addr, unsigned long addr_only)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
/* do DAC check */
|
||||
rc = cap_file_mmap(file, reqprot, prot, flags, addr, addr_only);
|
||||
if (rc || addr_only)
|
||||
return rc;
|
||||
|
||||
return common_mmap(OP_FMMAP, file, prot, flags);
|
||||
}
|
||||
|
||||
static int apparmor_file_mprotect(struct vm_area_struct *vma,
|
||||
unsigned long reqprot, unsigned long prot)
|
||||
{
|
||||
return common_mmap(OP_FMPROT, vma->vm_file, prot,
|
||||
!(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0);
|
||||
}
|
||||
|
||||
static int apparmor_getprocattr(struct task_struct *task, char *name,
|
||||
char **value)
|
||||
{
|
||||
int error = -ENOENT;
|
||||
struct aa_profile *profile;
|
||||
/* released below */
|
||||
const struct cred *cred = get_task_cred(task);
|
||||
struct aa_task_cxt *cxt = cred->security;
|
||||
profile = aa_cred_profile(cred);
|
||||
|
||||
if (strcmp(name, "current") == 0)
|
||||
error = aa_getprocattr(aa_newest_version(cxt->profile),
|
||||
value);
|
||||
else if (strcmp(name, "prev") == 0 && cxt->previous)
|
||||
error = aa_getprocattr(aa_newest_version(cxt->previous),
|
||||
value);
|
||||
else if (strcmp(name, "exec") == 0 && cxt->onexec)
|
||||
error = aa_getprocattr(aa_newest_version(cxt->onexec),
|
||||
value);
|
||||
else
|
||||
error = -EINVAL;
|
||||
|
||||
put_cred(cred);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int apparmor_setprocattr(struct task_struct *task, char *name,
|
||||
void *value, size_t size)
|
||||
{
|
||||
char *command, *args = value;
|
||||
size_t arg_size;
|
||||
int error;
|
||||
|
||||
if (size == 0)
|
||||
return -EINVAL;
|
||||
/* args points to a PAGE_SIZE buffer, AppArmor requires that
|
||||
* the buffer must be null terminated or have size <= PAGE_SIZE -1
|
||||
* so that AppArmor can null terminate them
|
||||
*/
|
||||
if (args[size - 1] != '\0') {
|
||||
if (size == PAGE_SIZE)
|
||||
return -EINVAL;
|
||||
args[size] = '\0';
|
||||
}
|
||||
|
||||
/* task can only write its own attributes */
|
||||
if (current != task)
|
||||
return -EACCES;
|
||||
|
||||
args = value;
|
||||
args = strim(args);
|
||||
command = strsep(&args, " ");
|
||||
if (!args)
|
||||
return -EINVAL;
|
||||
args = skip_spaces(args);
|
||||
if (!*args)
|
||||
return -EINVAL;
|
||||
|
||||
arg_size = size - (args - (char *) value);
|
||||
if (strcmp(name, "current") == 0) {
|
||||
if (strcmp(command, "changehat") == 0) {
|
||||
error = aa_setprocattr_changehat(args, arg_size,
|
||||
!AA_DO_TEST);
|
||||
} else if (strcmp(command, "permhat") == 0) {
|
||||
error = aa_setprocattr_changehat(args, arg_size,
|
||||
AA_DO_TEST);
|
||||
} else if (strcmp(command, "changeprofile") == 0) {
|
||||
error = aa_setprocattr_changeprofile(args, !AA_ONEXEC,
|
||||
!AA_DO_TEST);
|
||||
} else if (strcmp(command, "permprofile") == 0) {
|
||||
error = aa_setprocattr_changeprofile(args, !AA_ONEXEC,
|
||||
AA_DO_TEST);
|
||||
} else if (strcmp(command, "permipc") == 0) {
|
||||
error = aa_setprocattr_permipc(args);
|
||||
} else {
|
||||
struct common_audit_data sa;
|
||||
COMMON_AUDIT_DATA_INIT(&sa, NONE);
|
||||
sa.aad.op = OP_SETPROCATTR;
|
||||
sa.aad.info = name;
|
||||
sa.aad.error = -EINVAL;
|
||||
return aa_audit(AUDIT_APPARMOR_DENIED, NULL, GFP_KERNEL,
|
||||
&sa, NULL);
|
||||
}
|
||||
} else if (strcmp(name, "exec") == 0) {
|
||||
error = aa_setprocattr_changeprofile(args, AA_ONEXEC,
|
||||
!AA_DO_TEST);
|
||||
} else {
|
||||
/* only support the "current" and "exec" process attributes */
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!error)
|
||||
error = size;
|
||||
return error;
|
||||
}
|
||||
|
||||
static int apparmor_task_setrlimit(unsigned int resource,
|
||||
struct rlimit *new_rlim)
|
||||
{
|
||||
struct aa_profile *profile = aa_current_profile();
|
||||
int error = 0;
|
||||
|
||||
if (!unconfined(profile))
|
||||
error = aa_task_setrlimit(profile, resource, new_rlim);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static struct security_operations apparmor_ops = {
|
||||
.name = "apparmor",
|
||||
|
||||
.ptrace_access_check = apparmor_ptrace_access_check,
|
||||
.ptrace_traceme = apparmor_ptrace_traceme,
|
||||
.capget = apparmor_capget,
|
||||
.capable = apparmor_capable,
|
||||
|
||||
.path_link = apparmor_path_link,
|
||||
.path_unlink = apparmor_path_unlink,
|
||||
.path_symlink = apparmor_path_symlink,
|
||||
.path_mkdir = apparmor_path_mkdir,
|
||||
.path_rmdir = apparmor_path_rmdir,
|
||||
.path_mknod = apparmor_path_mknod,
|
||||
.path_rename = apparmor_path_rename,
|
||||
.path_chmod = apparmor_path_chmod,
|
||||
.path_chown = apparmor_path_chown,
|
||||
.path_truncate = apparmor_path_truncate,
|
||||
.dentry_open = apparmor_dentry_open,
|
||||
.inode_getattr = apparmor_inode_getattr,
|
||||
|
||||
.file_permission = apparmor_file_permission,
|
||||
.file_alloc_security = apparmor_file_alloc_security,
|
||||
.file_free_security = apparmor_file_free_security,
|
||||
.file_mmap = apparmor_file_mmap,
|
||||
.file_mprotect = apparmor_file_mprotect,
|
||||
.file_lock = apparmor_file_lock,
|
||||
|
||||
.getprocattr = apparmor_getprocattr,
|
||||
.setprocattr = apparmor_setprocattr,
|
||||
|
||||
.cred_alloc_blank = apparmor_cred_alloc_blank,
|
||||
.cred_free = apparmor_cred_free,
|
||||
.cred_prepare = apparmor_cred_prepare,
|
||||
.cred_transfer = apparmor_cred_transfer,
|
||||
|
||||
.bprm_set_creds = apparmor_bprm_set_creds,
|
||||
.bprm_committing_creds = apparmor_bprm_committing_creds,
|
||||
.bprm_committed_creds = apparmor_bprm_committed_creds,
|
||||
.bprm_secureexec = apparmor_bprm_secureexec,
|
||||
|
||||
.task_setrlimit = apparmor_task_setrlimit,
|
||||
};
|
||||
|
||||
/*
|
||||
* AppArmor sysfs module parameters
|
||||
*/
|
||||
|
||||
static int param_set_aabool(const char *val, struct kernel_param *kp);
|
||||
static int param_get_aabool(char *buffer, struct kernel_param *kp);
|
||||
#define param_check_aabool(name, p) __param_check(name, p, int)
|
||||
|
||||
static int param_set_aauint(const char *val, struct kernel_param *kp);
|
||||
static int param_get_aauint(char *buffer, struct kernel_param *kp);
|
||||
#define param_check_aauint(name, p) __param_check(name, p, int)
|
||||
|
||||
static int param_set_aalockpolicy(const char *val, struct kernel_param *kp);
|
||||
static int param_get_aalockpolicy(char *buffer, struct kernel_param *kp);
|
||||
#define param_check_aalockpolicy(name, p) __param_check(name, p, int)
|
||||
|
||||
static int param_set_audit(const char *val, struct kernel_param *kp);
|
||||
static int param_get_audit(char *buffer, struct kernel_param *kp);
|
||||
#define param_check_audit(name, p) __param_check(name, p, int)
|
||||
|
||||
static int param_set_mode(const char *val, struct kernel_param *kp);
|
||||
static int param_get_mode(char *buffer, struct kernel_param *kp);
|
||||
#define param_check_mode(name, p) __param_check(name, p, int)
|
||||
|
||||
/* Flag values, also controllable via /sys/module/apparmor/parameters
|
||||
* We define special types as we want to do additional mediation.
|
||||
*/
|
||||
|
||||
/* AppArmor global enforcement switch - complain, enforce, kill */
|
||||
enum profile_mode aa_g_profile_mode = APPARMOR_ENFORCE;
|
||||
module_param_call(mode, param_set_mode, param_get_mode,
|
||||
&aa_g_profile_mode, S_IRUSR | S_IWUSR);
|
||||
|
||||
/* Debug mode */
|
||||
int aa_g_debug;
|
||||
module_param_named(debug, aa_g_debug, aabool, S_IRUSR | S_IWUSR);
|
||||
|
||||
/* Audit mode */
|
||||
enum audit_mode aa_g_audit;
|
||||
module_param_call(audit, param_set_audit, param_get_audit,
|
||||
&aa_g_audit, S_IRUSR | S_IWUSR);
|
||||
|
||||
/* Determines if audit header is included in audited messages. This
|
||||
* provides more context if the audit daemon is not running
|
||||
*/
|
||||
int aa_g_audit_header = 1;
|
||||
module_param_named(audit_header, aa_g_audit_header, aabool,
|
||||
S_IRUSR | S_IWUSR);
|
||||
|
||||
/* lock out loading/removal of policy
|
||||
* TODO: add in at boot loading of policy, which is the only way to
|
||||
* load policy, if lock_policy is set
|
||||
*/
|
||||
int aa_g_lock_policy;
|
||||
module_param_named(lock_policy, aa_g_lock_policy, aalockpolicy,
|
||||
S_IRUSR | S_IWUSR);
|
||||
|
||||
/* Syscall logging mode */
|
||||
int aa_g_logsyscall;
|
||||
module_param_named(logsyscall, aa_g_logsyscall, aabool, S_IRUSR | S_IWUSR);
|
||||
|
||||
/* Maximum pathname length before accesses will start getting rejected */
|
||||
unsigned int aa_g_path_max = 2 * PATH_MAX;
|
||||
module_param_named(path_max, aa_g_path_max, aauint, S_IRUSR | S_IWUSR);
|
||||
|
||||
/* Determines how paranoid loading of policy is and how much verification
|
||||
* on the loaded policy is done.
|
||||
*/
|
||||
int aa_g_paranoid_load = 1;
|
||||
module_param_named(paranoid_load, aa_g_paranoid_load, aabool,
|
||||
S_IRUSR | S_IWUSR);
|
||||
|
||||
/* Boot time disable flag */
|
||||
static unsigned int apparmor_enabled = CONFIG_SECURITY_APPARMOR_BOOTPARAM_VALUE;
|
||||
module_param_named(enabled, apparmor_enabled, aabool, S_IRUSR);
|
||||
|
||||
static int __init apparmor_enabled_setup(char *str)
|
||||
{
|
||||
unsigned long enabled;
|
||||
int error = strict_strtoul(str, 0, &enabled);
|
||||
if (!error)
|
||||
apparmor_enabled = enabled ? 1 : 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
__setup("apparmor=", apparmor_enabled_setup);
|
||||
|
||||
/* set global flag turning off the ability to load policy */
|
||||
static int param_set_aalockpolicy(const char *val, struct kernel_param *kp)
|
||||
{
|
||||
if (!capable(CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
if (aa_g_lock_policy)
|
||||
return -EACCES;
|
||||
return param_set_bool(val, kp);
|
||||
}
|
||||
|
||||
static int param_get_aalockpolicy(char *buffer, struct kernel_param *kp)
|
||||
{
|
||||
if (!capable(CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
return param_get_bool(buffer, kp);
|
||||
}
|
||||
|
||||
static int param_set_aabool(const char *val, struct kernel_param *kp)
|
||||
{
|
||||
if (!capable(CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
return param_set_bool(val, kp);
|
||||
}
|
||||
|
||||
static int param_get_aabool(char *buffer, struct kernel_param *kp)
|
||||
{
|
||||
if (!capable(CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
return param_get_bool(buffer, kp);
|
||||
}
|
||||
|
||||
static int param_set_aauint(const char *val, struct kernel_param *kp)
|
||||
{
|
||||
if (!capable(CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
return param_set_uint(val, kp);
|
||||
}
|
||||
|
||||
static int param_get_aauint(char *buffer, struct kernel_param *kp)
|
||||
{
|
||||
if (!capable(CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
return param_get_uint(buffer, kp);
|
||||
}
|
||||
|
||||
static int param_get_audit(char *buffer, struct kernel_param *kp)
|
||||
{
|
||||
if (!capable(CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
if (!apparmor_enabled)
|
||||
return -EINVAL;
|
||||
|
||||
return sprintf(buffer, "%s", audit_mode_names[aa_g_audit]);
|
||||
}
|
||||
|
||||
static int param_set_audit(const char *val, struct kernel_param *kp)
|
||||
{
|
||||
int i;
|
||||
if (!capable(CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
if (!apparmor_enabled)
|
||||
return -EINVAL;
|
||||
|
||||
if (!val)
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < AUDIT_MAX_INDEX; i++) {
|
||||
if (strcmp(val, audit_mode_names[i]) == 0) {
|
||||
aa_g_audit = i;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int param_get_mode(char *buffer, struct kernel_param *kp)
|
||||
{
|
||||
if (!capable(CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
if (!apparmor_enabled)
|
||||
return -EINVAL;
|
||||
|
||||
return sprintf(buffer, "%s", profile_mode_names[aa_g_profile_mode]);
|
||||
}
|
||||
|
||||
static int param_set_mode(const char *val, struct kernel_param *kp)
|
||||
{
|
||||
int i;
|
||||
if (!capable(CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
if (!apparmor_enabled)
|
||||
return -EINVAL;
|
||||
|
||||
if (!val)
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < APPARMOR_NAMES_MAX_INDEX; i++) {
|
||||
if (strcmp(val, profile_mode_names[i]) == 0) {
|
||||
aa_g_profile_mode = i;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* AppArmor init functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* set_init_cxt - set a task context and profile on the first task.
|
||||
*
|
||||
* TODO: allow setting an alternate profile than unconfined
|
||||
*/
|
||||
static int __init set_init_cxt(void)
|
||||
{
|
||||
struct cred *cred = (struct cred *)current->real_cred;
|
||||
struct aa_task_cxt *cxt;
|
||||
|
||||
cxt = aa_alloc_task_context(GFP_KERNEL);
|
||||
if (!cxt)
|
||||
return -ENOMEM;
|
||||
|
||||
cxt->profile = aa_get_profile(root_ns->unconfined);
|
||||
cred->security = cxt;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init apparmor_init(void)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (!apparmor_enabled || !security_module_enable(&apparmor_ops)) {
|
||||
aa_info_message("AppArmor disabled by boot time parameter");
|
||||
apparmor_enabled = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
error = aa_alloc_root_ns();
|
||||
if (error) {
|
||||
AA_ERROR("Unable to allocate default profile namespace\n");
|
||||
goto alloc_out;
|
||||
}
|
||||
|
||||
error = set_init_cxt();
|
||||
if (error) {
|
||||
AA_ERROR("Failed to set context on init task\n");
|
||||
goto register_security_out;
|
||||
}
|
||||
|
||||
error = register_security(&apparmor_ops);
|
||||
if (error) {
|
||||
AA_ERROR("Unable to register AppArmor\n");
|
||||
goto register_security_out;
|
||||
}
|
||||
|
||||
/* Report that AppArmor successfully initialized */
|
||||
apparmor_initialized = 1;
|
||||
if (aa_g_profile_mode == APPARMOR_COMPLAIN)
|
||||
aa_info_message("AppArmor initialized: complain mode enabled");
|
||||
else if (aa_g_profile_mode == APPARMOR_KILL)
|
||||
aa_info_message("AppArmor initialized: kill mode enabled");
|
||||
else
|
||||
aa_info_message("AppArmor initialized");
|
||||
|
||||
return error;
|
||||
|
||||
register_security_out:
|
||||
aa_free_root_ns();
|
||||
|
||||
alloc_out:
|
||||
aa_destroy_aafs();
|
||||
|
||||
apparmor_enabled = 0;
|
||||
return error;
|
||||
|
||||
}
|
||||
|
||||
security_initcall(apparmor_init);
|
||||
@@ -0,0 +1,353 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor dfa based regular expression matching engine
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/kref.h>
|
||||
|
||||
#include "include/apparmor.h"
|
||||
#include "include/match.h"
|
||||
|
||||
/**
|
||||
* unpack_table - unpack a dfa table (one of accept, default, base, next check)
|
||||
* @blob: data to unpack (NOT NULL)
|
||||
* @bsize: size of blob
|
||||
*
|
||||
* Returns: pointer to table else NULL on failure
|
||||
*
|
||||
* NOTE: must be freed by kvfree (not kmalloc)
|
||||
*/
|
||||
static struct table_header *unpack_table(char *blob, size_t bsize)
|
||||
{
|
||||
struct table_header *table = NULL;
|
||||
struct table_header th;
|
||||
size_t tsize;
|
||||
|
||||
if (bsize < sizeof(struct table_header))
|
||||
goto out;
|
||||
|
||||
/* loaded td_id's start at 1, subtract 1 now to avoid doing
|
||||
* it every time we use td_id as an index
|
||||
*/
|
||||
th.td_id = be16_to_cpu(*(u16 *) (blob)) - 1;
|
||||
th.td_flags = be16_to_cpu(*(u16 *) (blob + 2));
|
||||
th.td_lolen = be32_to_cpu(*(u32 *) (blob + 8));
|
||||
blob += sizeof(struct table_header);
|
||||
|
||||
if (!(th.td_flags == YYTD_DATA16 || th.td_flags == YYTD_DATA32 ||
|
||||
th.td_flags == YYTD_DATA8))
|
||||
goto out;
|
||||
|
||||
tsize = table_size(th.td_lolen, th.td_flags);
|
||||
if (bsize < tsize)
|
||||
goto out;
|
||||
|
||||
table = kvmalloc(tsize);
|
||||
if (table) {
|
||||
*table = th;
|
||||
if (th.td_flags == YYTD_DATA8)
|
||||
UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
|
||||
u8, byte_to_byte);
|
||||
else if (th.td_flags == YYTD_DATA16)
|
||||
UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
|
||||
u16, be16_to_cpu);
|
||||
else if (th.td_flags == YYTD_DATA32)
|
||||
UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
|
||||
u32, be32_to_cpu);
|
||||
else
|
||||
goto fail;
|
||||
}
|
||||
|
||||
out:
|
||||
/* if table was vmalloced make sure the page tables are synced
|
||||
* before it is used, as it goes live to all cpus.
|
||||
*/
|
||||
if (is_vmalloc_addr(table))
|
||||
vm_unmap_aliases();
|
||||
return table;
|
||||
fail:
|
||||
kvfree(table);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* verify_dfa - verify that transitions and states in the tables are in bounds.
|
||||
* @dfa: dfa to test (NOT NULL)
|
||||
* @flags: flags controlling what type of accept table are acceptable
|
||||
*
|
||||
* Assumes dfa has gone through the first pass verification done by unpacking
|
||||
* NOTE: this does not valid accept table values
|
||||
*
|
||||
* Returns: %0 else error code on failure to verify
|
||||
*/
|
||||
static int verify_dfa(struct aa_dfa *dfa, int flags)
|
||||
{
|
||||
size_t i, state_count, trans_count;
|
||||
int error = -EPROTO;
|
||||
|
||||
/* check that required tables exist */
|
||||
if (!(dfa->tables[YYTD_ID_DEF] &&
|
||||
dfa->tables[YYTD_ID_BASE] &&
|
||||
dfa->tables[YYTD_ID_NXT] && dfa->tables[YYTD_ID_CHK]))
|
||||
goto out;
|
||||
|
||||
/* accept.size == default.size == base.size */
|
||||
state_count = dfa->tables[YYTD_ID_BASE]->td_lolen;
|
||||
if (ACCEPT1_FLAGS(flags)) {
|
||||
if (!dfa->tables[YYTD_ID_ACCEPT])
|
||||
goto out;
|
||||
if (state_count != dfa->tables[YYTD_ID_ACCEPT]->td_lolen)
|
||||
goto out;
|
||||
}
|
||||
if (ACCEPT2_FLAGS(flags)) {
|
||||
if (!dfa->tables[YYTD_ID_ACCEPT2])
|
||||
goto out;
|
||||
if (state_count != dfa->tables[YYTD_ID_ACCEPT2]->td_lolen)
|
||||
goto out;
|
||||
}
|
||||
if (state_count != dfa->tables[YYTD_ID_DEF]->td_lolen)
|
||||
goto out;
|
||||
|
||||
/* next.size == chk.size */
|
||||
trans_count = dfa->tables[YYTD_ID_NXT]->td_lolen;
|
||||
if (trans_count != dfa->tables[YYTD_ID_CHK]->td_lolen)
|
||||
goto out;
|
||||
|
||||
/* if equivalence classes then its table size must be 256 */
|
||||
if (dfa->tables[YYTD_ID_EC] &&
|
||||
dfa->tables[YYTD_ID_EC]->td_lolen != 256)
|
||||
goto out;
|
||||
|
||||
if (flags & DFA_FLAG_VERIFY_STATES) {
|
||||
for (i = 0; i < state_count; i++) {
|
||||
if (DEFAULT_TABLE(dfa)[i] >= state_count)
|
||||
goto out;
|
||||
/* TODO: do check that DEF state recursion terminates */
|
||||
if (BASE_TABLE(dfa)[i] + 255 >= trans_count) {
|
||||
printk(KERN_ERR "AppArmor DFA next/check upper "
|
||||
"bounds error\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < trans_count; i++) {
|
||||
if (NEXT_TABLE(dfa)[i] >= state_count)
|
||||
goto out;
|
||||
if (CHECK_TABLE(dfa)[i] >= state_count)
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
error = 0;
|
||||
out:
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* dfa_free - free a dfa allocated by aa_dfa_unpack
|
||||
* @dfa: the dfa to free (MAYBE NULL)
|
||||
*
|
||||
* Requires: reference count to dfa == 0
|
||||
*/
|
||||
static void dfa_free(struct aa_dfa *dfa)
|
||||
{
|
||||
if (dfa) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(dfa->tables); i++) {
|
||||
kvfree(dfa->tables[i]);
|
||||
dfa->tables[i] = NULL;
|
||||
}
|
||||
kfree(dfa);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_dfa_free_kref - free aa_dfa by kref (called by aa_put_dfa)
|
||||
* @kr: kref callback for freeing of a dfa (NOT NULL)
|
||||
*/
|
||||
void aa_dfa_free_kref(struct kref *kref)
|
||||
{
|
||||
struct aa_dfa *dfa = container_of(kref, struct aa_dfa, count);
|
||||
dfa_free(dfa);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_dfa_unpack - unpack the binary tables of a serialized dfa
|
||||
* @blob: aligned serialized stream of data to unpack (NOT NULL)
|
||||
* @size: size of data to unpack
|
||||
* @flags: flags controlling what type of accept tables are acceptable
|
||||
*
|
||||
* Unpack a dfa that has been serialized. To find information on the dfa
|
||||
* format look in Documentation/apparmor.txt
|
||||
* Assumes the dfa @blob stream has been aligned on a 8 byte boundry
|
||||
*
|
||||
* Returns: an unpacked dfa ready for matching or ERR_PTR on failure
|
||||
*/
|
||||
struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags)
|
||||
{
|
||||
int hsize;
|
||||
int error = -ENOMEM;
|
||||
char *data = blob;
|
||||
struct table_header *table = NULL;
|
||||
struct aa_dfa *dfa = kzalloc(sizeof(struct aa_dfa), GFP_KERNEL);
|
||||
if (!dfa)
|
||||
goto fail;
|
||||
|
||||
kref_init(&dfa->count);
|
||||
|
||||
error = -EPROTO;
|
||||
|
||||
/* get dfa table set header */
|
||||
if (size < sizeof(struct table_set_header))
|
||||
goto fail;
|
||||
|
||||
if (ntohl(*(u32 *) data) != YYTH_MAGIC)
|
||||
goto fail;
|
||||
|
||||
hsize = ntohl(*(u32 *) (data + 4));
|
||||
if (size < hsize)
|
||||
goto fail;
|
||||
|
||||
dfa->flags = ntohs(*(u16 *) (data + 12));
|
||||
data += hsize;
|
||||
size -= hsize;
|
||||
|
||||
while (size > 0) {
|
||||
table = unpack_table(data, size);
|
||||
if (!table)
|
||||
goto fail;
|
||||
|
||||
switch (table->td_id) {
|
||||
case YYTD_ID_ACCEPT:
|
||||
if (!(table->td_flags & ACCEPT1_FLAGS(flags)))
|
||||
goto fail;
|
||||
break;
|
||||
case YYTD_ID_ACCEPT2:
|
||||
if (!(table->td_flags & ACCEPT2_FLAGS(flags)))
|
||||
goto fail;
|
||||
break;
|
||||
case YYTD_ID_BASE:
|
||||
if (table->td_flags != YYTD_DATA32)
|
||||
goto fail;
|
||||
break;
|
||||
case YYTD_ID_DEF:
|
||||
case YYTD_ID_NXT:
|
||||
case YYTD_ID_CHK:
|
||||
if (table->td_flags != YYTD_DATA16)
|
||||
goto fail;
|
||||
break;
|
||||
case YYTD_ID_EC:
|
||||
if (table->td_flags != YYTD_DATA8)
|
||||
goto fail;
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
/* check for duplicate table entry */
|
||||
if (dfa->tables[table->td_id])
|
||||
goto fail;
|
||||
dfa->tables[table->td_id] = table;
|
||||
data += table_size(table->td_lolen, table->td_flags);
|
||||
size -= table_size(table->td_lolen, table->td_flags);
|
||||
table = NULL;
|
||||
}
|
||||
|
||||
error = verify_dfa(dfa, flags);
|
||||
if (error)
|
||||
goto fail;
|
||||
|
||||
return dfa;
|
||||
|
||||
fail:
|
||||
kvfree(table);
|
||||
dfa_free(dfa);
|
||||
return ERR_PTR(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_dfa_match_len - traverse @dfa to find state @str stops at
|
||||
* @dfa: the dfa to match @str against (NOT NULL)
|
||||
* @start: the state of the dfa to start matching in
|
||||
* @str: the string of bytes to match against the dfa (NOT NULL)
|
||||
* @len: length of the string of bytes to match
|
||||
*
|
||||
* aa_dfa_match_len will match @str against the dfa and return the state it
|
||||
* finished matching in. The final state can be used to look up the accepting
|
||||
* label, or as the start state of a continuing match.
|
||||
*
|
||||
* This function will happily match again the 0 byte and only finishes
|
||||
* when @len input is consumed.
|
||||
*
|
||||
* Returns: final state reached after input is consumed
|
||||
*/
|
||||
unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
|
||||
const char *str, int len)
|
||||
{
|
||||
u16 *def = DEFAULT_TABLE(dfa);
|
||||
u32 *base = BASE_TABLE(dfa);
|
||||
u16 *next = NEXT_TABLE(dfa);
|
||||
u16 *check = CHECK_TABLE(dfa);
|
||||
unsigned int state = start, pos;
|
||||
|
||||
if (state == 0)
|
||||
return 0;
|
||||
|
||||
/* current state is <state>, matching character *str */
|
||||
if (dfa->tables[YYTD_ID_EC]) {
|
||||
/* Equivalence class table defined */
|
||||
u8 *equiv = EQUIV_TABLE(dfa);
|
||||
/* default is direct to next state */
|
||||
for (; len; len--) {
|
||||
pos = base[state] + equiv[(u8) *str++];
|
||||
if (check[pos] == state)
|
||||
state = next[pos];
|
||||
else
|
||||
state = def[state];
|
||||
}
|
||||
} else {
|
||||
/* default is direct to next state */
|
||||
for (; len; len--) {
|
||||
pos = base[state] + (u8) *str++;
|
||||
if (check[pos] == state)
|
||||
state = next[pos];
|
||||
else
|
||||
state = def[state];
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_dfa_next_state - traverse @dfa to find state @str stops at
|
||||
* @dfa: the dfa to match @str against (NOT NULL)
|
||||
* @start: the state of the dfa to start matching in
|
||||
* @str: the null terminated string of bytes to match against the dfa (NOT NULL)
|
||||
*
|
||||
* aa_dfa_next_state will match @str against the dfa and return the state it
|
||||
* finished matching in. The final state can be used to look up the accepting
|
||||
* label, or as the start state of a continuing match.
|
||||
*
|
||||
* Returns: final state reached after input is consumed
|
||||
*/
|
||||
unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start,
|
||||
const char *str)
|
||||
{
|
||||
return aa_dfa_match_len(dfa, start, str, strlen(str));
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor function for pathnames
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/magic.h>
|
||||
#include <linux/mnt_namespace.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/nsproxy.h>
|
||||
#include <linux/path.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/fs_struct.h>
|
||||
|
||||
#include "include/apparmor.h"
|
||||
#include "include/path.h"
|
||||
#include "include/policy.h"
|
||||
|
||||
|
||||
/* modified from dcache.c */
|
||||
static int prepend(char **buffer, int buflen, const char *str, int namelen)
|
||||
{
|
||||
buflen -= namelen;
|
||||
if (buflen < 0)
|
||||
return -ENAMETOOLONG;
|
||||
*buffer -= namelen;
|
||||
memcpy(*buffer, str, namelen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CHROOT_NSCONNECT (PATH_CHROOT_REL | PATH_CHROOT_NSCONNECT)
|
||||
|
||||
/**
|
||||
* d_namespace_path - lookup a name associated with a given path
|
||||
* @path: path to lookup (NOT NULL)
|
||||
* @buf: buffer to store path to (NOT NULL)
|
||||
* @buflen: length of @buf
|
||||
* @name: Returns - pointer for start of path name with in @buf (NOT NULL)
|
||||
* @flags: flags controlling path lookup
|
||||
*
|
||||
* Handle path name lookup.
|
||||
*
|
||||
* Returns: %0 else error code if path lookup fails
|
||||
* When no error the path name is returned in @name which points to
|
||||
* to a position in @buf
|
||||
*/
|
||||
static int d_namespace_path(struct path *path, char *buf, int buflen,
|
||||
char **name, int flags)
|
||||
{
|
||||
struct path root, tmp;
|
||||
char *res;
|
||||
int deleted, connected;
|
||||
int error = 0;
|
||||
|
||||
/* Get the root we want to resolve too */
|
||||
if (flags & PATH_CHROOT_REL) {
|
||||
/* resolve paths relative to chroot */
|
||||
read_lock(¤t->fs->lock);
|
||||
root = current->fs->root;
|
||||
/* released below */
|
||||
path_get(&root);
|
||||
read_unlock(¤t->fs->lock);
|
||||
} else {
|
||||
/* resolve paths relative to namespace */
|
||||
root.mnt = current->nsproxy->mnt_ns->root;
|
||||
root.dentry = root.mnt->mnt_root;
|
||||
/* released below */
|
||||
path_get(&root);
|
||||
}
|
||||
|
||||
spin_lock(&dcache_lock);
|
||||
/* There is a race window between path lookup here and the
|
||||
* need to strip the " (deleted) string that __d_path applies
|
||||
* Detect the race and relookup the path
|
||||
*
|
||||
* The stripping of (deleted) is a hack that could be removed
|
||||
* with an updated __d_path
|
||||
*/
|
||||
do {
|
||||
tmp = root;
|
||||
deleted = d_unlinked(path->dentry);
|
||||
res = __d_path(path, &tmp, buf, buflen);
|
||||
|
||||
} while (deleted != d_unlinked(path->dentry));
|
||||
spin_unlock(&dcache_lock);
|
||||
|
||||
*name = res;
|
||||
/* handle error conditions - and still allow a partial path to
|
||||
* be returned.
|
||||
*/
|
||||
if (IS_ERR(res)) {
|
||||
error = PTR_ERR(res);
|
||||
*name = buf;
|
||||
goto out;
|
||||
}
|
||||
if (deleted) {
|
||||
/* On some filesystems, newly allocated dentries appear to the
|
||||
* security_path hooks as a deleted dentry except without an
|
||||
* inode allocated.
|
||||
*
|
||||
* Remove the appended deleted text and return as string for
|
||||
* normal mediation, or auditing. The (deleted) string is
|
||||
* guaranteed to be added in this case, so just strip it.
|
||||
*/
|
||||
buf[buflen - 11] = 0; /* - (len(" (deleted)") +\0) */
|
||||
|
||||
if (path->dentry->d_inode && !(flags & PATH_MEDIATE_DELETED)) {
|
||||
error = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/* Determine if the path is connected to the expected root */
|
||||
connected = tmp.dentry == root.dentry && tmp.mnt == root.mnt;
|
||||
|
||||
/* If the path is not connected,
|
||||
* check if it is a sysctl and handle specially else remove any
|
||||
* leading / that __d_path may have returned.
|
||||
* Unless
|
||||
* specifically directed to connect the path,
|
||||
* OR
|
||||
* if in a chroot and doing chroot relative paths and the path
|
||||
* resolves to the namespace root (would be connected outside
|
||||
* of chroot) and specifically directed to connect paths to
|
||||
* namespace root.
|
||||
*/
|
||||
if (!connected) {
|
||||
/* is the disconnect path a sysctl? */
|
||||
if (tmp.dentry->d_sb->s_magic == PROC_SUPER_MAGIC &&
|
||||
strncmp(*name, "/sys/", 5) == 0) {
|
||||
/* TODO: convert over to using a per namespace
|
||||
* control instead of hard coded /proc
|
||||
*/
|
||||
error = prepend(name, *name - buf, "/proc", 5);
|
||||
} else if (!(flags & PATH_CONNECT_PATH) &&
|
||||
!(((flags & CHROOT_NSCONNECT) == CHROOT_NSCONNECT) &&
|
||||
(tmp.mnt == current->nsproxy->mnt_ns->root &&
|
||||
tmp.dentry == tmp.mnt->mnt_root))) {
|
||||
/* disconnected path, don't return pathname starting
|
||||
* with '/'
|
||||
*/
|
||||
error = -ESTALE;
|
||||
if (*res == '/')
|
||||
*name = res + 1;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
path_put(&root);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_name_to_buffer - get the pathname to a buffer ensure dir / is appended
|
||||
* @path: path to get name for (NOT NULL)
|
||||
* @flags: flags controlling path lookup
|
||||
* @buffer: buffer to put name in (NOT NULL)
|
||||
* @size: size of buffer
|
||||
* @name: Returns - contains position of path name in @buffer (NOT NULL)
|
||||
*
|
||||
* Returns: %0 else error on failure
|
||||
*/
|
||||
static int get_name_to_buffer(struct path *path, int flags, char *buffer,
|
||||
int size, char **name)
|
||||
{
|
||||
int adjust = (flags & PATH_IS_DIR) ? 1 : 0;
|
||||
int error = d_namespace_path(path, buffer, size - adjust, name, flags);
|
||||
|
||||
if (!error && (flags & PATH_IS_DIR) && (*name)[1] != '\0')
|
||||
/*
|
||||
* Append "/" to the pathname. The root directory is a special
|
||||
* case; it already ends in slash.
|
||||
*/
|
||||
strcpy(&buffer[size - 2], "/");
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_get_name - compute the pathname of a file
|
||||
* @path: path the file (NOT NULL)
|
||||
* @flags: flags controlling path name generation
|
||||
* @buffer: buffer that aa_get_name() allocated (NOT NULL)
|
||||
* @name: Returns - the generated path name if !error (NOT NULL)
|
||||
*
|
||||
* @name is a pointer to the beginning of the pathname (which usually differs
|
||||
* from the beginning of the buffer), or NULL. If there is an error @name
|
||||
* may contain a partial or invalid name that can be used for audit purposes,
|
||||
* but it can not be used for mediation.
|
||||
*
|
||||
* We need PATH_IS_DIR to indicate whether the file is a directory or not
|
||||
* because the file may not yet exist, and so we cannot check the inode's
|
||||
* file type.
|
||||
*
|
||||
* Returns: %0 else error code if could retrieve name
|
||||
*/
|
||||
int aa_get_name(struct path *path, int flags, char **buffer, const char **name)
|
||||
{
|
||||
char *buf, *str = NULL;
|
||||
int size = 256;
|
||||
int error;
|
||||
|
||||
*name = NULL;
|
||||
*buffer = NULL;
|
||||
for (;;) {
|
||||
/* freed by caller */
|
||||
buf = kmalloc(size, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
error = get_name_to_buffer(path, flags, buf, size, &str);
|
||||
if (error != -ENAMETOOLONG)
|
||||
break;
|
||||
|
||||
kfree(buf);
|
||||
size <<= 1;
|
||||
if (size > aa_g_path_max)
|
||||
return -ENAMETOOLONG;
|
||||
}
|
||||
*buffer = buf;
|
||||
*name = str;
|
||||
|
||||
return error;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,703 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor functions for unpacking policy loaded from
|
||||
* userspace.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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 uses a serialized binary format for loading policy.
|
||||
* To find policy format documentation look in Documentation/apparmor.txt
|
||||
* All policy is validated before it is used.
|
||||
*/
|
||||
|
||||
#include <asm/unaligned.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/errno.h>
|
||||
|
||||
#include "include/apparmor.h"
|
||||
#include "include/audit.h"
|
||||
#include "include/context.h"
|
||||
#include "include/match.h"
|
||||
#include "include/policy.h"
|
||||
#include "include/policy_unpack.h"
|
||||
#include "include/sid.h"
|
||||
|
||||
/*
|
||||
* The AppArmor interface treats data as a type byte followed by the
|
||||
* actual data. The interface has the notion of a a named entry
|
||||
* which has a name (AA_NAME typecode followed by name string) followed by
|
||||
* the entries typecode and data. Named types allow for optional
|
||||
* elements and extensions to be added and tested for without breaking
|
||||
* backwards compatibility.
|
||||
*/
|
||||
|
||||
enum aa_code {
|
||||
AA_U8,
|
||||
AA_U16,
|
||||
AA_U32,
|
||||
AA_U64,
|
||||
AA_NAME, /* same as string except it is items name */
|
||||
AA_STRING,
|
||||
AA_BLOB,
|
||||
AA_STRUCT,
|
||||
AA_STRUCTEND,
|
||||
AA_LIST,
|
||||
AA_LISTEND,
|
||||
AA_ARRAY,
|
||||
AA_ARRAYEND,
|
||||
};
|
||||
|
||||
/*
|
||||
* aa_ext is the read of the buffer containing the serialized profile. The
|
||||
* data is copied into a kernel buffer in apparmorfs and then handed off to
|
||||
* the unpack routines.
|
||||
*/
|
||||
struct aa_ext {
|
||||
void *start;
|
||||
void *end;
|
||||
void *pos; /* pointer to current position in the buffer */
|
||||
u32 version;
|
||||
};
|
||||
|
||||
/* audit callback for unpack fields */
|
||||
static void audit_cb(struct audit_buffer *ab, void *va)
|
||||
{
|
||||
struct common_audit_data *sa = va;
|
||||
if (sa->aad.iface.target) {
|
||||
struct aa_profile *name = sa->aad.iface.target;
|
||||
audit_log_format(ab, " name=");
|
||||
audit_log_untrustedstring(ab, name->base.hname);
|
||||
}
|
||||
if (sa->aad.iface.pos)
|
||||
audit_log_format(ab, " offset=%ld", sa->aad.iface.pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* audit_iface - do audit message for policy unpacking/load/replace/remove
|
||||
* @new: profile if it has been allocated (MAYBE NULL)
|
||||
* @name: name of the profile being manipulated (MAYBE NULL)
|
||||
* @info: any extra info about the failure (MAYBE NULL)
|
||||
* @e: buffer position info (NOT NULL)
|
||||
* @error: error code
|
||||
*
|
||||
* Returns: %0 or error
|
||||
*/
|
||||
static int audit_iface(struct aa_profile *new, const char *name,
|
||||
const char *info, struct aa_ext *e, int error)
|
||||
{
|
||||
struct aa_profile *profile = __aa_current_profile();
|
||||
struct common_audit_data sa;
|
||||
COMMON_AUDIT_DATA_INIT(&sa, NONE);
|
||||
sa.aad.iface.pos = e->pos - e->start;
|
||||
sa.aad.iface.target = new;
|
||||
sa.aad.name = name;
|
||||
sa.aad.info = info;
|
||||
sa.aad.error = error;
|
||||
|
||||
return aa_audit(AUDIT_APPARMOR_STATUS, profile, GFP_KERNEL, &sa,
|
||||
audit_cb);
|
||||
}
|
||||
|
||||
/* test if read will be in packed data bounds */
|
||||
static bool inbounds(struct aa_ext *e, size_t size)
|
||||
{
|
||||
return (size <= e->end - e->pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_u16_chunck - test and do bounds checking for a u16 size based chunk
|
||||
* @e: serialized data read head (NOT NULL)
|
||||
* @chunk: start address for chunk of data (NOT NULL)
|
||||
*
|
||||
* Returns: the size of chunk found with the read head at the end of the chunk.
|
||||
*/
|
||||
static size_t unpack_u16_chunk(struct aa_ext *e, char **chunk)
|
||||
{
|
||||
size_t size = 0;
|
||||
|
||||
if (!inbounds(e, sizeof(u16)))
|
||||
return 0;
|
||||
size = le16_to_cpu(get_unaligned((u16 *) e->pos));
|
||||
e->pos += sizeof(u16);
|
||||
if (!inbounds(e, size))
|
||||
return 0;
|
||||
*chunk = e->pos;
|
||||
e->pos += size;
|
||||
return size;
|
||||
}
|
||||
|
||||
/* unpack control byte */
|
||||
static bool unpack_X(struct aa_ext *e, enum aa_code code)
|
||||
{
|
||||
if (!inbounds(e, 1))
|
||||
return 0;
|
||||
if (*(u8 *) e->pos != code)
|
||||
return 0;
|
||||
e->pos++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* unpack_nameX - check is the next element is of type X with a name of @name
|
||||
* @e: serialized data extent information (NOT NULL)
|
||||
* @code: type code
|
||||
* @name: name to match to the serialized element. (MAYBE NULL)
|
||||
*
|
||||
* check that the next serialized data element is of type X and has a tag
|
||||
* name @name. If @name is specified then there must be a matching
|
||||
* name element in the stream. If @name is NULL any name element will be
|
||||
* skipped and only the typecode will be tested.
|
||||
*
|
||||
* Returns 1 on success (both type code and name tests match) and the read
|
||||
* head is advanced past the headers
|
||||
*
|
||||
* Returns: 0 if either match fails, the read head does not move
|
||||
*/
|
||||
static bool unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name)
|
||||
{
|
||||
/*
|
||||
* May need to reset pos if name or type doesn't match
|
||||
*/
|
||||
void *pos = e->pos;
|
||||
/*
|
||||
* Check for presence of a tagname, and if present name size
|
||||
* AA_NAME tag value is a u16.
|
||||
*/
|
||||
if (unpack_X(e, AA_NAME)) {
|
||||
char *tag = NULL;
|
||||
size_t size = unpack_u16_chunk(e, &tag);
|
||||
/* if a name is specified it must match. otherwise skip tag */
|
||||
if (name && (!size || strcmp(name, tag)))
|
||||
goto fail;
|
||||
} else if (name) {
|
||||
/* if a name is specified and there is no name tag fail */
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* now check if type code matches */
|
||||
if (unpack_X(e, code))
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
e->pos = pos;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name)
|
||||
{
|
||||
if (unpack_nameX(e, AA_U32, name)) {
|
||||
if (!inbounds(e, sizeof(u32)))
|
||||
return 0;
|
||||
if (data)
|
||||
*data = le32_to_cpu(get_unaligned((u32 *) e->pos));
|
||||
e->pos += sizeof(u32);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool unpack_u64(struct aa_ext *e, u64 *data, const char *name)
|
||||
{
|
||||
if (unpack_nameX(e, AA_U64, name)) {
|
||||
if (!inbounds(e, sizeof(u64)))
|
||||
return 0;
|
||||
if (data)
|
||||
*data = le64_to_cpu(get_unaligned((u64 *) e->pos));
|
||||
e->pos += sizeof(u64);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t unpack_array(struct aa_ext *e, const char *name)
|
||||
{
|
||||
if (unpack_nameX(e, AA_ARRAY, name)) {
|
||||
int size;
|
||||
if (!inbounds(e, sizeof(u16)))
|
||||
return 0;
|
||||
size = (int)le16_to_cpu(get_unaligned((u16 *) e->pos));
|
||||
e->pos += sizeof(u16);
|
||||
return size;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t unpack_blob(struct aa_ext *e, char **blob, const char *name)
|
||||
{
|
||||
if (unpack_nameX(e, AA_BLOB, name)) {
|
||||
u32 size;
|
||||
if (!inbounds(e, sizeof(u32)))
|
||||
return 0;
|
||||
size = le32_to_cpu(get_unaligned((u32 *) e->pos));
|
||||
e->pos += sizeof(u32);
|
||||
if (inbounds(e, (size_t) size)) {
|
||||
*blob = e->pos;
|
||||
e->pos += size;
|
||||
return size;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unpack_str(struct aa_ext *e, const char **string, const char *name)
|
||||
{
|
||||
char *src_str;
|
||||
size_t size = 0;
|
||||
void *pos = e->pos;
|
||||
*string = NULL;
|
||||
if (unpack_nameX(e, AA_STRING, name)) {
|
||||
size = unpack_u16_chunk(e, &src_str);
|
||||
if (size) {
|
||||
/* strings are null terminated, length is size - 1 */
|
||||
if (src_str[size - 1] != 0)
|
||||
goto fail;
|
||||
*string = src_str;
|
||||
}
|
||||
}
|
||||
return size;
|
||||
|
||||
fail:
|
||||
e->pos = pos;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unpack_strdup(struct aa_ext *e, char **string, const char *name)
|
||||
{
|
||||
const char *tmp;
|
||||
void *pos = e->pos;
|
||||
int res = unpack_str(e, &tmp, name);
|
||||
*string = NULL;
|
||||
|
||||
if (!res)
|
||||
return 0;
|
||||
|
||||
*string = kmemdup(tmp, res, GFP_KERNEL);
|
||||
if (!*string) {
|
||||
e->pos = pos;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* verify_accept - verify the accept tables of a dfa
|
||||
* @dfa: dfa to verify accept tables of (NOT NULL)
|
||||
* @flags: flags governing dfa
|
||||
*
|
||||
* Returns: 1 if valid accept tables else 0 if error
|
||||
*/
|
||||
static bool verify_accept(struct aa_dfa *dfa, int flags)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* verify accept permissions */
|
||||
for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) {
|
||||
int mode = ACCEPT_TABLE(dfa)[i];
|
||||
|
||||
if (mode & ~DFA_VALID_PERM_MASK)
|
||||
return 0;
|
||||
|
||||
if (ACCEPT_TABLE2(dfa)[i] & ~DFA_VALID_PERM2_MASK)
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* unpack_dfa - unpack a file rule dfa
|
||||
* @e: serialized data extent information (NOT NULL)
|
||||
*
|
||||
* returns dfa or ERR_PTR or NULL if no dfa
|
||||
*/
|
||||
static struct aa_dfa *unpack_dfa(struct aa_ext *e)
|
||||
{
|
||||
char *blob = NULL;
|
||||
size_t size;
|
||||
struct aa_dfa *dfa = NULL;
|
||||
|
||||
size = unpack_blob(e, &blob, "aadfa");
|
||||
if (size) {
|
||||
/*
|
||||
* The dfa is aligned with in the blob to 8 bytes
|
||||
* from the beginning of the stream.
|
||||
*/
|
||||
size_t sz = blob - (char *)e->start;
|
||||
size_t pad = ALIGN(sz, 8) - sz;
|
||||
int flags = TO_ACCEPT1_FLAG(YYTD_DATA32) |
|
||||
TO_ACCEPT2_FLAG(YYTD_DATA32);
|
||||
|
||||
|
||||
if (aa_g_paranoid_load)
|
||||
flags |= DFA_FLAG_VERIFY_STATES;
|
||||
|
||||
dfa = aa_dfa_unpack(blob + pad, size - pad, flags);
|
||||
|
||||
if (IS_ERR(dfa))
|
||||
return dfa;
|
||||
|
||||
if (!verify_accept(dfa, flags))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return dfa;
|
||||
|
||||
fail:
|
||||
aa_put_dfa(dfa);
|
||||
return ERR_PTR(-EPROTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* unpack_trans_table - unpack a profile transition table
|
||||
* @e: serialized data extent information (NOT NULL)
|
||||
* @profile: profile to add the accept table to (NOT NULL)
|
||||
*
|
||||
* Returns: 1 if table succesfully unpacked
|
||||
*/
|
||||
static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
|
||||
{
|
||||
void *pos = e->pos;
|
||||
|
||||
/* exec table is optional */
|
||||
if (unpack_nameX(e, AA_STRUCT, "xtable")) {
|
||||
int i, size;
|
||||
|
||||
size = unpack_array(e, NULL);
|
||||
/* currently 4 exec bits and entries 0-3 are reserved iupcx */
|
||||
if (size > 16 - 4)
|
||||
goto fail;
|
||||
profile->file.trans.table = kzalloc(sizeof(char *) * size,
|
||||
GFP_KERNEL);
|
||||
if (!profile->file.trans.table)
|
||||
goto fail;
|
||||
|
||||
profile->file.trans.size = size;
|
||||
for (i = 0; i < size; i++) {
|
||||
char *str;
|
||||
int c, j, size = unpack_strdup(e, &str, NULL);
|
||||
/* unpack_strdup verifies that the last character is
|
||||
* null termination byte.
|
||||
*/
|
||||
if (!size)
|
||||
goto fail;
|
||||
profile->file.trans.table[i] = str;
|
||||
/* verify that name doesn't start with space */
|
||||
if (isspace(*str))
|
||||
goto fail;
|
||||
|
||||
/* count internal # of internal \0 */
|
||||
for (c = j = 0; j < size - 2; j++) {
|
||||
if (!str[j])
|
||||
c++;
|
||||
}
|
||||
if (*str == ':') {
|
||||
/* beginning with : requires an embedded \0,
|
||||
* verify that exactly 1 internal \0 exists
|
||||
* trailing \0 already verified by unpack_strdup
|
||||
*/
|
||||
if (c != 1)
|
||||
goto fail;
|
||||
/* first character after : must be valid */
|
||||
if (!str[1])
|
||||
goto fail;
|
||||
} else if (c)
|
||||
/* fail - all other cases with embedded \0 */
|
||||
goto fail;
|
||||
}
|
||||
if (!unpack_nameX(e, AA_ARRAYEND, NULL))
|
||||
goto fail;
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
}
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
aa_free_domain_entries(&profile->file.trans);
|
||||
e->pos = pos;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile)
|
||||
{
|
||||
void *pos = e->pos;
|
||||
|
||||
/* rlimits are optional */
|
||||
if (unpack_nameX(e, AA_STRUCT, "rlimits")) {
|
||||
int i, size;
|
||||
u32 tmp = 0;
|
||||
if (!unpack_u32(e, &tmp, NULL))
|
||||
goto fail;
|
||||
profile->rlimits.mask = tmp;
|
||||
|
||||
size = unpack_array(e, NULL);
|
||||
if (size > RLIM_NLIMITS)
|
||||
goto fail;
|
||||
for (i = 0; i < size; i++) {
|
||||
u64 tmp = 0;
|
||||
int a = aa_map_resource(i);
|
||||
if (!unpack_u64(e, &tmp, NULL))
|
||||
goto fail;
|
||||
profile->rlimits.limits[a].rlim_max = tmp;
|
||||
}
|
||||
if (!unpack_nameX(e, AA_ARRAYEND, NULL))
|
||||
goto fail;
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
}
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
e->pos = pos;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* unpack_profile - unpack a serialized profile
|
||||
* @e: serialized data extent information (NOT NULL)
|
||||
*
|
||||
* NOTE: unpack profile sets audit struct if there is a failure
|
||||
*/
|
||||
static struct aa_profile *unpack_profile(struct aa_ext *e)
|
||||
{
|
||||
struct aa_profile *profile = NULL;
|
||||
const char *name = NULL;
|
||||
int error = -EPROTO;
|
||||
kernel_cap_t tmpcap;
|
||||
u32 tmp;
|
||||
|
||||
/* check that we have the right struct being passed */
|
||||
if (!unpack_nameX(e, AA_STRUCT, "profile"))
|
||||
goto fail;
|
||||
if (!unpack_str(e, &name, NULL))
|
||||
goto fail;
|
||||
|
||||
profile = aa_alloc_profile(name);
|
||||
if (!profile)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
/* profile renaming is optional */
|
||||
(void) unpack_str(e, &profile->rename, "rename");
|
||||
|
||||
/* xmatch is optional and may be NULL */
|
||||
profile->xmatch = unpack_dfa(e);
|
||||
if (IS_ERR(profile->xmatch)) {
|
||||
error = PTR_ERR(profile->xmatch);
|
||||
profile->xmatch = NULL;
|
||||
goto fail;
|
||||
}
|
||||
/* xmatch_len is not optional if xmatch is set */
|
||||
if (profile->xmatch) {
|
||||
if (!unpack_u32(e, &tmp, NULL))
|
||||
goto fail;
|
||||
profile->xmatch_len = tmp;
|
||||
}
|
||||
|
||||
/* per profile debug flags (complain, audit) */
|
||||
if (!unpack_nameX(e, AA_STRUCT, "flags"))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &tmp, NULL))
|
||||
goto fail;
|
||||
if (tmp)
|
||||
profile->flags |= PFLAG_HAT;
|
||||
if (!unpack_u32(e, &tmp, NULL))
|
||||
goto fail;
|
||||
if (tmp)
|
||||
profile->mode = APPARMOR_COMPLAIN;
|
||||
if (!unpack_u32(e, &tmp, NULL))
|
||||
goto fail;
|
||||
if (tmp)
|
||||
profile->audit = AUDIT_ALL;
|
||||
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
|
||||
/* path_flags is optional */
|
||||
if (unpack_u32(e, &profile->path_flags, "path_flags"))
|
||||
profile->path_flags |= profile->flags & PFLAG_MEDIATE_DELETED;
|
||||
else
|
||||
/* set a default value if path_flags field is not present */
|
||||
profile->path_flags = PFLAG_MEDIATE_DELETED;
|
||||
|
||||
if (!unpack_u32(e, &(profile->caps.allow.cap[0]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &(profile->caps.audit.cap[0]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &(profile->caps.quiet.cap[0]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &tmpcap.cap[0], NULL))
|
||||
goto fail;
|
||||
|
||||
if (unpack_nameX(e, AA_STRUCT, "caps64")) {
|
||||
/* optional upper half of 64 bit caps */
|
||||
if (!unpack_u32(e, &(profile->caps.allow.cap[1]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &(profile->caps.audit.cap[1]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &(profile->caps.quiet.cap[1]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &(tmpcap.cap[1]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (unpack_nameX(e, AA_STRUCT, "capsx")) {
|
||||
/* optional extended caps mediation mask */
|
||||
if (!unpack_u32(e, &(profile->caps.extended.cap[0]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &(profile->caps.extended.cap[1]), NULL))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!unpack_rlimits(e, profile))
|
||||
goto fail;
|
||||
|
||||
/* get file rules */
|
||||
profile->file.dfa = unpack_dfa(e);
|
||||
if (IS_ERR(profile->file.dfa)) {
|
||||
error = PTR_ERR(profile->file.dfa);
|
||||
profile->file.dfa = NULL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!unpack_u32(e, &profile->file.start, "dfa_start"))
|
||||
/* default start state */
|
||||
profile->file.start = DFA_START;
|
||||
|
||||
if (!unpack_trans_table(e, profile))
|
||||
goto fail;
|
||||
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
|
||||
return profile;
|
||||
|
||||
fail:
|
||||
if (profile)
|
||||
name = NULL;
|
||||
else if (!name)
|
||||
name = "unknown";
|
||||
audit_iface(profile, name, "failed to unpack profile", e, error);
|
||||
aa_put_profile(profile);
|
||||
|
||||
return ERR_PTR(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* verify_head - unpack serialized stream header
|
||||
* @e: serialized data read head (NOT NULL)
|
||||
* @ns: Returns - namespace if one is specified else NULL (NOT NULL)
|
||||
*
|
||||
* Returns: error or 0 if header is good
|
||||
*/
|
||||
static int verify_header(struct aa_ext *e, const char **ns)
|
||||
{
|
||||
int error = -EPROTONOSUPPORT;
|
||||
/* get the interface version */
|
||||
if (!unpack_u32(e, &e->version, "version")) {
|
||||
audit_iface(NULL, NULL, "invalid profile format", e, error);
|
||||
return error;
|
||||
}
|
||||
|
||||
/* check that the interface version is currently supported */
|
||||
if (e->version != 5) {
|
||||
audit_iface(NULL, NULL, "unsupported interface version", e,
|
||||
error);
|
||||
return error;
|
||||
}
|
||||
|
||||
/* read the namespace if present */
|
||||
if (!unpack_str(e, ns, "namespace"))
|
||||
*ns = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool verify_xindex(int xindex, int table_size)
|
||||
{
|
||||
int index, xtype;
|
||||
xtype = xindex & AA_X_TYPE_MASK;
|
||||
index = xindex & AA_X_INDEX_MASK;
|
||||
if (xtype == AA_X_TABLE && index > table_size)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* verify dfa xindexes are in range of transition tables */
|
||||
static bool verify_dfa_xindex(struct aa_dfa *dfa, int table_size)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) {
|
||||
if (!verify_xindex(dfa_user_xindex(dfa, i), table_size))
|
||||
return 0;
|
||||
if (!verify_xindex(dfa_other_xindex(dfa, i), table_size))
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* verify_profile - Do post unpack analysis to verify profile consistency
|
||||
* @profile: profile to verify (NOT NULL)
|
||||
*
|
||||
* Returns: 0 if passes verification else error
|
||||
*/
|
||||
static int verify_profile(struct aa_profile *profile)
|
||||
{
|
||||
if (aa_g_paranoid_load) {
|
||||
if (profile->file.dfa &&
|
||||
!verify_dfa_xindex(profile->file.dfa,
|
||||
profile->file.trans.size)) {
|
||||
audit_iface(profile, NULL, "Invalid named transition",
|
||||
NULL, -EPROTO);
|
||||
return -EPROTO;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_unpack - unpack packed binary profile data loaded from user space
|
||||
* @udata: user data copied to kmem (NOT NULL)
|
||||
* @size: the size of the user data
|
||||
* @ns: Returns namespace profile is in if specified else NULL (NOT NULL)
|
||||
*
|
||||
* Unpack user data and return refcounted allocated profile or ERR_PTR
|
||||
*
|
||||
* Returns: profile else error pointer if fails to unpack
|
||||
*/
|
||||
struct aa_profile *aa_unpack(void *udata, size_t size, const char **ns)
|
||||
{
|
||||
struct aa_profile *profile = NULL;
|
||||
int error;
|
||||
struct aa_ext e = {
|
||||
.start = udata,
|
||||
.end = udata + size,
|
||||
.pos = udata,
|
||||
};
|
||||
|
||||
error = verify_header(&e, ns);
|
||||
if (error)
|
||||
return ERR_PTR(error);
|
||||
|
||||
profile = unpack_profile(&e);
|
||||
if (IS_ERR(profile))
|
||||
return profile;
|
||||
|
||||
error = verify_profile(profile);
|
||||
if (error) {
|
||||
aa_put_profile(profile);
|
||||
profile = ERR_PTR(error);
|
||||
}
|
||||
|
||||
/* return refcount */
|
||||
return profile;
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor /proc/<pid>/attr/ interface functions
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "include/apparmor.h"
|
||||
#include "include/context.h"
|
||||
#include "include/policy.h"
|
||||
#include "include/domain.h"
|
||||
|
||||
|
||||
/**
|
||||
* aa_getprocattr - Return the profile information for @profile
|
||||
* @profile: the profile to print profile info about (NOT NULL)
|
||||
* @string: Returns - string containing the profile info (NOT NULL)
|
||||
*
|
||||
* Returns: length of @string on success else error on failure
|
||||
*
|
||||
* Requires: profile != NULL
|
||||
*
|
||||
* Creates a string containing the namespace_name://profile_name for
|
||||
* @profile.
|
||||
*
|
||||
* Returns: size of string placed in @string else error code on failure
|
||||
*/
|
||||
int aa_getprocattr(struct aa_profile *profile, char **string)
|
||||
{
|
||||
char *str;
|
||||
int len = 0, mode_len = 0, ns_len = 0, name_len;
|
||||
const char *mode_str = profile_mode_names[profile->mode];
|
||||
const char *ns_name = NULL;
|
||||
struct aa_namespace *ns = profile->ns;
|
||||
struct aa_namespace *current_ns = __aa_current_profile()->ns;
|
||||
char *s;
|
||||
|
||||
if (!aa_ns_visible(current_ns, ns))
|
||||
return -EACCES;
|
||||
|
||||
ns_name = aa_ns_name(current_ns, ns);
|
||||
ns_len = strlen(ns_name);
|
||||
|
||||
/* if the visible ns_name is > 0 increase size for : :// seperator */
|
||||
if (ns_len)
|
||||
ns_len += 4;
|
||||
|
||||
/* unconfined profiles don't have a mode string appended */
|
||||
if (!unconfined(profile))
|
||||
mode_len = strlen(mode_str) + 3; /* + 3 for _() */
|
||||
|
||||
name_len = strlen(profile->base.hname);
|
||||
len = mode_len + ns_len + name_len + 1; /* + 1 for \n */
|
||||
s = str = kmalloc(len + 1, GFP_KERNEL); /* + 1 \0 */
|
||||
if (!str)
|
||||
return -ENOMEM;
|
||||
|
||||
if (ns_len) {
|
||||
/* skip over prefix current_ns->base.hname and separating // */
|
||||
sprintf(s, ":%s://", ns_name);
|
||||
s += ns_len;
|
||||
}
|
||||
if (unconfined(profile))
|
||||
/* mode string not being appended */
|
||||
sprintf(s, "%s\n", profile->base.hname);
|
||||
else
|
||||
sprintf(s, "%s (%s)\n", profile->base.hname, mode_str);
|
||||
*string = str;
|
||||
|
||||
/* NOTE: len does not include \0 of string, not saved as part of file */
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* split_token_from_name - separate a string of form <token>^<name>
|
||||
* @op: operation being checked
|
||||
* @args: string to parse (NOT NULL)
|
||||
* @token: stores returned parsed token value (NOT NULL)
|
||||
*
|
||||
* Returns: start position of name after token else NULL on failure
|
||||
*/
|
||||
static char *split_token_from_name(int op, char *args, u64 * token)
|
||||
{
|
||||
char *name;
|
||||
|
||||
*token = simple_strtoull(args, &name, 16);
|
||||
if ((name == args) || *name != '^') {
|
||||
AA_ERROR("%s: Invalid input '%s'", op_table[op], args);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
name++; /* skip ^ */
|
||||
if (!*name)
|
||||
name = NULL;
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_setprocattr_chagnehat - handle procattr interface to change_hat
|
||||
* @args: args received from writing to /proc/<pid>/attr/current (NOT NULL)
|
||||
* @size: size of the args
|
||||
* @test: true if this is a test of change_hat permissions
|
||||
*
|
||||
* Returns: %0 or error code if change_hat fails
|
||||
*/
|
||||
int aa_setprocattr_changehat(char *args, size_t size, int test)
|
||||
{
|
||||
char *hat;
|
||||
u64 token;
|
||||
const char *hats[16]; /* current hard limit on # of names */
|
||||
int count = 0;
|
||||
|
||||
hat = split_token_from_name(OP_CHANGE_HAT, args, &token);
|
||||
if (IS_ERR(hat))
|
||||
return PTR_ERR(hat);
|
||||
|
||||
if (!hat && !token) {
|
||||
AA_ERROR("change_hat: Invalid input, NULL hat and NULL magic");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (hat) {
|
||||
/* set up hat name vector, args guaranteed null terminated
|
||||
* at args[size] by setprocattr.
|
||||
*
|
||||
* If there are multiple hat names in the buffer each is
|
||||
* separated by a \0. Ie. userspace writes them pre tokenized
|
||||
*/
|
||||
char *end = args + size;
|
||||
for (count = 0; (hat < end) && count < 16; ++count) {
|
||||
char *next = hat + strlen(hat) + 1;
|
||||
hats[count] = hat;
|
||||
hat = next;
|
||||
}
|
||||
}
|
||||
|
||||
AA_DEBUG("%s: Magic 0x%llx Hat '%s'\n",
|
||||
__func__, token, hat ? hat : NULL);
|
||||
|
||||
return aa_change_hat(hats, count, token, test);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_setprocattr_changeprofile - handle procattr interface to changeprofile
|
||||
* @fqname: args received from writting to /proc/<pid>/attr/current (NOT NULL)
|
||||
* @onexec: true if change_profile should be delayed until exec
|
||||
* @test: true if this is a test of change_profile permissions
|
||||
*
|
||||
* Returns: %0 or error code if change_profile fails
|
||||
*/
|
||||
int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test)
|
||||
{
|
||||
char *name, *ns_name;
|
||||
|
||||
name = aa_split_fqname(fqname, &ns_name);
|
||||
return aa_change_profile(ns_name, name, onexec, test);
|
||||
}
|
||||
|
||||
int aa_setprocattr_permipc(char *fqname)
|
||||
{
|
||||
/* TODO: add ipc permission querying */
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor resource mediation and attachment
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/audit.h>
|
||||
|
||||
#include "include/audit.h"
|
||||
#include "include/resource.h"
|
||||
#include "include/policy.h"
|
||||
|
||||
/*
|
||||
* Table of rlimit names: we generate it from resource.h.
|
||||
*/
|
||||
#include "rlim_names.h"
|
||||
|
||||
/* audit callback for resource specific fields */
|
||||
static void audit_cb(struct audit_buffer *ab, void *va)
|
||||
{
|
||||
struct common_audit_data *sa = va;
|
||||
|
||||
audit_log_format(ab, " rlimit=%s value=%lu",
|
||||
rlim_names[sa->aad.rlim.rlim], sa->aad.rlim.max);
|
||||
}
|
||||
|
||||
/**
|
||||
* audit_resource - audit setting resource limit
|
||||
* @profile: profile being enforced (NOT NULL)
|
||||
* @resoure: rlimit being auditing
|
||||
* @value: value being set
|
||||
* @error: error value
|
||||
*
|
||||
* Returns: 0 or sa->error else other error code on failure
|
||||
*/
|
||||
static int audit_resource(struct aa_profile *profile, unsigned int resource,
|
||||
unsigned long value, int error)
|
||||
{
|
||||
struct common_audit_data sa;
|
||||
|
||||
COMMON_AUDIT_DATA_INIT(&sa, NONE);
|
||||
sa.aad.op = OP_SETRLIMIT,
|
||||
sa.aad.rlim.rlim = resource;
|
||||
sa.aad.rlim.max = value;
|
||||
sa.aad.error = error;
|
||||
return aa_audit(AUDIT_APPARMOR_AUTO, profile, GFP_KERNEL, &sa,
|
||||
audit_cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_map_resouce - map compiled policy resource to internal #
|
||||
* @resource: flattened policy resource number
|
||||
*
|
||||
* Returns: resource # for the current architecture.
|
||||
*
|
||||
* rlimit resource can vary based on architecture, map the compiled policy
|
||||
* resource # to the internal representation for the architecture.
|
||||
*/
|
||||
int aa_map_resource(int resource)
|
||||
{
|
||||
return rlim_map[resource];
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_task_setrlimit - test permission to set an rlimit
|
||||
* @profile - profile confining the task (NOT NULL)
|
||||
* @resource - the resource being set
|
||||
* @new_rlim - the new resource limit (NOT NULL)
|
||||
*
|
||||
* Control raising the processes hard limit.
|
||||
*
|
||||
* Returns: 0 or error code if setting resource failed
|
||||
*/
|
||||
int aa_task_setrlimit(struct aa_profile *profile, unsigned int resource,
|
||||
struct rlimit *new_rlim)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
if (profile->rlimits.mask & (1 << resource) &&
|
||||
new_rlim->rlim_max > profile->rlimits.limits[resource].rlim_max)
|
||||
|
||||
error = audit_resource(profile, resource, new_rlim->rlim_max,
|
||||
-EACCES);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* __aa_transition_rlimits - apply new profile rlimits
|
||||
* @old: old profile on task (NOT NULL)
|
||||
* @new: new profile with rlimits to apply (NOT NULL)
|
||||
*/
|
||||
void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new)
|
||||
{
|
||||
unsigned int mask = 0;
|
||||
struct rlimit *rlim, *initrlim;
|
||||
int i;
|
||||
|
||||
/* for any rlimits the profile controlled reset the soft limit
|
||||
* to the less of the tasks hard limit and the init tasks soft limit
|
||||
*/
|
||||
if (old->rlimits.mask) {
|
||||
for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) {
|
||||
if (old->rlimits.mask & mask) {
|
||||
rlim = current->signal->rlim + i;
|
||||
initrlim = init_task.signal->rlim + i;
|
||||
rlim->rlim_cur = min(rlim->rlim_max,
|
||||
initrlim->rlim_cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* set any new hard limits as dictated by the new profile */
|
||||
if (!new->rlimits.mask)
|
||||
return;
|
||||
for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) {
|
||||
if (!(new->rlimits.mask & mask))
|
||||
continue;
|
||||
|
||||
rlim = current->signal->rlim + i;
|
||||
rlim->rlim_max = min(rlim->rlim_max,
|
||||
new->rlimits.limits[i].rlim_max);
|
||||
/* soft limit should not exceed hard limit */
|
||||
rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor security identifier (sid) manipulation fns
|
||||
*
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* 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 allocates a unique sid for every profile loaded. If a profile
|
||||
* is replaced it receives the sid of the profile it is replacing.
|
||||
*
|
||||
* The sid value of 0 is invalid.
|
||||
*/
|
||||
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
#include "include/sid.h"
|
||||
|
||||
/* global counter from which sids are allocated */
|
||||
static u32 global_sid;
|
||||
static DEFINE_SPINLOCK(sid_lock);
|
||||
|
||||
/* TODO FIXME: add sid to profile mapping, and sid recycling */
|
||||
|
||||
/**
|
||||
* aa_alloc_sid - allocate a new sid for a profile
|
||||
*/
|
||||
u32 aa_alloc_sid(void)
|
||||
{
|
||||
u32 sid;
|
||||
|
||||
/*
|
||||
* TODO FIXME: sid recycling - part of profile mapping table
|
||||
*/
|
||||
spin_lock(&sid_lock);
|
||||
sid = (++global_sid);
|
||||
spin_unlock(&sid_lock);
|
||||
return sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_free_sid - free a sid
|
||||
* @sid: sid to free
|
||||
*/
|
||||
void aa_free_sid(u32 sid)
|
||||
{
|
||||
; /* NOP ATM */
|
||||
}
|
||||
@@ -27,7 +27,7 @@ static int cap_quota_on(struct dentry *dentry)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cap_bprm_check_security (struct linux_binprm *bprm)
|
||||
static int cap_bprm_check_security(struct linux_binprm *bprm)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -268,8 +268,7 @@ static int cap_path_rename(struct path *old_path, struct dentry *old_dentry,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cap_path_truncate(struct path *path, loff_t length,
|
||||
unsigned int time_attrs)
|
||||
static int cap_path_truncate(struct path *path)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
+3
-1
@@ -86,7 +86,7 @@ static int mknod(struct inode *dir, struct dentry *dentry,
|
||||
int mode, dev_t dev)
|
||||
{
|
||||
struct inode *inode;
|
||||
int error = -EPERM;
|
||||
int error = -ENOMEM;
|
||||
|
||||
if (dentry->d_inode)
|
||||
return -EEXIST;
|
||||
@@ -166,6 +166,8 @@ static int create_by_name(const char *name, mode_t mode,
|
||||
error = mkdir(parent->d_inode, *dentry, mode);
|
||||
else
|
||||
error = create(parent->d_inode, *dentry, mode);
|
||||
if (error)
|
||||
dput(*dentry);
|
||||
} else
|
||||
error = PTR_ERR(*dentry);
|
||||
mutex_unlock(&parent->d_inode->i_mutex);
|
||||
|
||||
@@ -45,7 +45,8 @@ static ssize_t ima_show_htable_violations(struct file *filp,
|
||||
}
|
||||
|
||||
static const struct file_operations ima_htable_violations_ops = {
|
||||
.read = ima_show_htable_violations
|
||||
.read = ima_show_htable_violations,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
static ssize_t ima_show_measurements_count(struct file *filp,
|
||||
@@ -57,7 +58,8 @@ static ssize_t ima_show_measurements_count(struct file *filp,
|
||||
}
|
||||
|
||||
static const struct file_operations ima_measurements_count_ops = {
|
||||
.read = ima_show_measurements_count
|
||||
.read = ima_show_measurements_count,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
/* returns pointer to hlist_node */
|
||||
@@ -319,7 +321,8 @@ static int ima_release_policy(struct inode *inode, struct file *file)
|
||||
static const struct file_operations ima_measure_policy_ops = {
|
||||
.open = ima_open_policy,
|
||||
.write = ima_write_policy,
|
||||
.release = ima_release_policy
|
||||
.release = ima_release_policy,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
int __init ima_fs_init(void)
|
||||
|
||||
@@ -114,6 +114,10 @@ extern key_ref_t keyring_search_aux(key_ref_t keyring_ref,
|
||||
const void *description,
|
||||
key_match_func_t match);
|
||||
|
||||
extern key_ref_t search_my_process_keyrings(struct key_type *type,
|
||||
const void *description,
|
||||
key_match_func_t match,
|
||||
const struct cred *cred);
|
||||
extern key_ref_t search_process_keyrings(struct key_type *type,
|
||||
const void *description,
|
||||
key_match_func_t match,
|
||||
@@ -134,6 +138,7 @@ extern struct key *request_key_and_link(struct key_type *type,
|
||||
struct key *dest_keyring,
|
||||
unsigned long flags);
|
||||
|
||||
extern int lookup_user_key_possessed(const struct key *key, const void *target);
|
||||
extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags,
|
||||
key_perm_t perm);
|
||||
#define KEY_LOOKUP_CREATE 0x01
|
||||
|
||||
+21
-8
@@ -505,13 +505,11 @@ okay:
|
||||
|
||||
ret = snprintf(tmpbuf, PAGE_SIZE - 1,
|
||||
"%s;%d;%d;%08x;%s",
|
||||
key_ref_to_ptr(key_ref)->type->name,
|
||||
key_ref_to_ptr(key_ref)->uid,
|
||||
key_ref_to_ptr(key_ref)->gid,
|
||||
key_ref_to_ptr(key_ref)->perm,
|
||||
key_ref_to_ptr(key_ref)->description ?
|
||||
key_ref_to_ptr(key_ref)->description : ""
|
||||
);
|
||||
key->type->name,
|
||||
key->uid,
|
||||
key->gid,
|
||||
key->perm,
|
||||
key->description ?: "");
|
||||
|
||||
/* include a NUL char at the end of the data */
|
||||
if (ret > PAGE_SIZE - 1)
|
||||
@@ -1091,7 +1089,7 @@ error:
|
||||
long keyctl_set_timeout(key_serial_t id, unsigned timeout)
|
||||
{
|
||||
struct timespec now;
|
||||
struct key *key;
|
||||
struct key *key, *instkey;
|
||||
key_ref_t key_ref;
|
||||
time_t expiry;
|
||||
long ret;
|
||||
@@ -1099,10 +1097,25 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout)
|
||||
key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
|
||||
KEY_SETATTR);
|
||||
if (IS_ERR(key_ref)) {
|
||||
/* setting the timeout on a key under construction is permitted
|
||||
* if we have the authorisation token handy */
|
||||
if (PTR_ERR(key_ref) == -EACCES) {
|
||||
instkey = key_get_instantiation_authkey(id);
|
||||
if (!IS_ERR(instkey)) {
|
||||
key_put(instkey);
|
||||
key_ref = lookup_user_key(id,
|
||||
KEY_LOOKUP_PARTIAL,
|
||||
0);
|
||||
if (!IS_ERR(key_ref))
|
||||
goto okay;
|
||||
}
|
||||
}
|
||||
|
||||
ret = PTR_ERR(key_ref);
|
||||
goto error;
|
||||
}
|
||||
|
||||
okay:
|
||||
key = key_ref_to_ptr(key_ref);
|
||||
|
||||
/* make the changes with the locks held to prevent races */
|
||||
|
||||
+18
-2
@@ -184,20 +184,36 @@ static void proc_keys_stop(struct seq_file *p, void *v)
|
||||
|
||||
static int proc_keys_show(struct seq_file *m, void *v)
|
||||
{
|
||||
const struct cred *cred = current_cred();
|
||||
struct rb_node *_p = v;
|
||||
struct key *key = rb_entry(_p, struct key, serial_node);
|
||||
struct timespec now;
|
||||
unsigned long timo;
|
||||
key_ref_t key_ref, skey_ref;
|
||||
char xbuf[12];
|
||||
int rc;
|
||||
|
||||
key_ref = make_key_ref(key, 0);
|
||||
|
||||
/* determine if the key is possessed by this process (a test we can
|
||||
* skip if the key does not indicate the possessor can view it
|
||||
*/
|
||||
if (key->perm & KEY_POS_VIEW) {
|
||||
skey_ref = search_my_process_keyrings(key->type, key,
|
||||
lookup_user_key_possessed,
|
||||
cred);
|
||||
if (!IS_ERR(skey_ref)) {
|
||||
key_ref_put(skey_ref);
|
||||
key_ref = make_key_ref(key, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* check whether the current task is allowed to view the key (assuming
|
||||
* non-possession)
|
||||
* - the caller holds a spinlock, and thus the RCU read lock, making our
|
||||
* access to __current_cred() safe
|
||||
*/
|
||||
rc = key_task_permission(make_key_ref(key, 0), current_cred(),
|
||||
KEY_VIEW);
|
||||
rc = key_task_permission(key_ref, cred, KEY_VIEW);
|
||||
if (rc < 0)
|
||||
return 0;
|
||||
|
||||
|
||||
@@ -309,22 +309,19 @@ void key_fsgid_changed(struct task_struct *tsk)
|
||||
|
||||
/*****************************************************************************/
|
||||
/*
|
||||
* search the process keyrings for the first matching key
|
||||
* search only my process keyrings for the first matching key
|
||||
* - we use the supplied match function to see if the description (or other
|
||||
* feature of interest) matches
|
||||
* - we return -EAGAIN if we didn't find any matching key
|
||||
* - we return -ENOKEY if we found only negative matching keys
|
||||
*/
|
||||
key_ref_t search_process_keyrings(struct key_type *type,
|
||||
const void *description,
|
||||
key_match_func_t match,
|
||||
const struct cred *cred)
|
||||
key_ref_t search_my_process_keyrings(struct key_type *type,
|
||||
const void *description,
|
||||
key_match_func_t match,
|
||||
const struct cred *cred)
|
||||
{
|
||||
struct request_key_auth *rka;
|
||||
key_ref_t key_ref, ret, err;
|
||||
|
||||
might_sleep();
|
||||
|
||||
/* we want to return -EAGAIN or -ENOKEY if any of the keyrings were
|
||||
* searchable, but we failed to find a key or we found a negative key;
|
||||
* otherwise we want to return a sample error (probably -EACCES) if
|
||||
@@ -424,6 +421,36 @@ key_ref_t search_process_keyrings(struct key_type *type,
|
||||
}
|
||||
}
|
||||
|
||||
/* no key - decide on the error we're going to go for */
|
||||
key_ref = ret ? ret : err;
|
||||
|
||||
found:
|
||||
return key_ref;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/*
|
||||
* search the process keyrings for the first matching key
|
||||
* - we use the supplied match function to see if the description (or other
|
||||
* feature of interest) matches
|
||||
* - we return -EAGAIN if we didn't find any matching key
|
||||
* - we return -ENOKEY if we found only negative matching keys
|
||||
*/
|
||||
key_ref_t search_process_keyrings(struct key_type *type,
|
||||
const void *description,
|
||||
key_match_func_t match,
|
||||
const struct cred *cred)
|
||||
{
|
||||
struct request_key_auth *rka;
|
||||
key_ref_t key_ref, ret = ERR_PTR(-EACCES), err;
|
||||
|
||||
might_sleep();
|
||||
|
||||
key_ref = search_my_process_keyrings(type, description, match, cred);
|
||||
if (!IS_ERR(key_ref))
|
||||
goto found;
|
||||
err = key_ref;
|
||||
|
||||
/* if this process has an instantiation authorisation key, then we also
|
||||
* search the keyrings of the process mentioned there
|
||||
* - we don't permit access to request_key auth keys via this method
|
||||
@@ -446,24 +473,19 @@ key_ref_t search_process_keyrings(struct key_type *type,
|
||||
if (!IS_ERR(key_ref))
|
||||
goto found;
|
||||
|
||||
switch (PTR_ERR(key_ref)) {
|
||||
case -EAGAIN: /* no key */
|
||||
if (ret)
|
||||
break;
|
||||
case -ENOKEY: /* negative key */
|
||||
ret = key_ref;
|
||||
break;
|
||||
default:
|
||||
err = key_ref;
|
||||
break;
|
||||
}
|
||||
ret = key_ref;
|
||||
} else {
|
||||
up_read(&cred->request_key_auth->sem);
|
||||
}
|
||||
}
|
||||
|
||||
/* no key - decide on the error we're going to go for */
|
||||
key_ref = ret ? ret : err;
|
||||
if (err == ERR_PTR(-ENOKEY) || ret == ERR_PTR(-ENOKEY))
|
||||
key_ref = ERR_PTR(-ENOKEY);
|
||||
else if (err == ERR_PTR(-EACCES))
|
||||
key_ref = ret;
|
||||
else
|
||||
key_ref = err;
|
||||
|
||||
found:
|
||||
return key_ref;
|
||||
@@ -474,7 +496,7 @@ found:
|
||||
/*
|
||||
* see if the key we're looking at is the target key
|
||||
*/
|
||||
static int lookup_user_key_possessed(const struct key *key, const void *target)
|
||||
int lookup_user_key_possessed(const struct key *key, const void *target)
|
||||
{
|
||||
return key == target;
|
||||
|
||||
|
||||
@@ -144,6 +144,7 @@ static int call_sbin_request_key(struct key_construction *cons,
|
||||
prkey = 0;
|
||||
if (cred->tgcred->process_keyring)
|
||||
prkey = cred->tgcred->process_keyring->serial;
|
||||
sprintf(keyring_str[1], "%d", prkey);
|
||||
|
||||
rcu_read_lock();
|
||||
session = rcu_dereference(cred->tgcred->session_keyring);
|
||||
|
||||
+2
-3
@@ -417,12 +417,11 @@ int security_path_rename(struct path *old_dir, struct dentry *old_dentry,
|
||||
new_dentry);
|
||||
}
|
||||
|
||||
int security_path_truncate(struct path *path, loff_t length,
|
||||
unsigned int time_attrs)
|
||||
int security_path_truncate(struct path *path)
|
||||
{
|
||||
if (unlikely(IS_PRIVATE(path->dentry->d_inode)))
|
||||
return 0;
|
||||
return security_ops->path_truncate(path, length, time_attrs);
|
||||
return security_ops->path_truncate(path);
|
||||
}
|
||||
|
||||
int security_path_chmod(struct dentry *dentry, struct vfsmount *mnt,
|
||||
|
||||
+22
-3
@@ -288,7 +288,6 @@ static struct avc_node *avc_alloc_node(void)
|
||||
if (!node)
|
||||
goto out;
|
||||
|
||||
INIT_RCU_HEAD(&node->rhead);
|
||||
INIT_HLIST_NODE(&node->list);
|
||||
avc_cache_stats_incr(allocations);
|
||||
|
||||
@@ -489,9 +488,29 @@ void avc_audit(u32 ssid, u32 tsid,
|
||||
struct common_audit_data stack_data;
|
||||
u32 denied, audited;
|
||||
denied = requested & ~avd->allowed;
|
||||
if (denied)
|
||||
if (denied) {
|
||||
audited = denied & avd->auditdeny;
|
||||
else if (result)
|
||||
/*
|
||||
* a->selinux_audit_data.auditdeny is TRICKY! Setting a bit in
|
||||
* this field means that ANY denials should NOT be audited if
|
||||
* the policy contains an explicit dontaudit rule for that
|
||||
* permission. Take notice that this is unrelated to the
|
||||
* actual permissions that were denied. As an example lets
|
||||
* assume:
|
||||
*
|
||||
* denied == READ
|
||||
* avd.auditdeny & ACCESS == 0 (not set means explicit rule)
|
||||
* selinux_audit_data.auditdeny & ACCESS == 1
|
||||
*
|
||||
* We will NOT audit the denial even though the denied
|
||||
* permission was READ and the auditdeny checks were for
|
||||
* ACCESS
|
||||
*/
|
||||
if (a &&
|
||||
a->selinux_audit_data.auditdeny &&
|
||||
!(a->selinux_audit_data.auditdeny & avd->auditdeny))
|
||||
audited = 0;
|
||||
} else if (result)
|
||||
audited = denied = requested;
|
||||
else
|
||||
audited = requested & avd->auditallow;
|
||||
|
||||
+114
-178
@@ -87,9 +87,6 @@
|
||||
#include "netlabel.h"
|
||||
#include "audit.h"
|
||||
|
||||
#define XATTR_SELINUX_SUFFIX "selinux"
|
||||
#define XATTR_NAME_SELINUX XATTR_SECURITY_PREFIX XATTR_SELINUX_SUFFIX
|
||||
|
||||
#define NUM_SEL_MNT_OPTS 5
|
||||
|
||||
extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm);
|
||||
@@ -188,7 +185,7 @@ static inline u32 task_sid(const struct task_struct *task)
|
||||
*/
|
||||
static inline u32 current_sid(void)
|
||||
{
|
||||
const struct task_security_struct *tsec = current_cred()->security;
|
||||
const struct task_security_struct *tsec = current_security();
|
||||
|
||||
return tsec->sid;
|
||||
}
|
||||
@@ -279,32 +276,6 @@ static void superblock_free_security(struct super_block *sb)
|
||||
kfree(sbsec);
|
||||
}
|
||||
|
||||
static int sk_alloc_security(struct sock *sk, int family, gfp_t priority)
|
||||
{
|
||||
struct sk_security_struct *sksec;
|
||||
|
||||
sksec = kzalloc(sizeof(*sksec), priority);
|
||||
if (!sksec)
|
||||
return -ENOMEM;
|
||||
|
||||
sksec->peer_sid = SECINITSID_UNLABELED;
|
||||
sksec->sid = SECINITSID_UNLABELED;
|
||||
sk->sk_security = sksec;
|
||||
|
||||
selinux_netlbl_sk_security_reset(sksec);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sk_free_security(struct sock *sk)
|
||||
{
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
|
||||
sk->sk_security = NULL;
|
||||
selinux_netlbl_sk_security_free(sksec);
|
||||
kfree(sksec);
|
||||
}
|
||||
|
||||
/* The security server must be initialized before
|
||||
any labeling or access decisions can be provided. */
|
||||
extern int ss_initialized;
|
||||
@@ -1584,8 +1555,7 @@ static int may_create(struct inode *dir,
|
||||
struct dentry *dentry,
|
||||
u16 tclass)
|
||||
{
|
||||
const struct cred *cred = current_cred();
|
||||
const struct task_security_struct *tsec = cred->security;
|
||||
const struct task_security_struct *tsec = current_security();
|
||||
struct inode_security_struct *dsec;
|
||||
struct superblock_security_struct *sbsec;
|
||||
u32 sid, newsid;
|
||||
@@ -1806,27 +1776,9 @@ static inline u32 open_file_to_av(struct file *file)
|
||||
{
|
||||
u32 av = file_to_av(file);
|
||||
|
||||
if (selinux_policycap_openperm) {
|
||||
mode_t mode = file->f_path.dentry->d_inode->i_mode;
|
||||
/*
|
||||
* lnk files and socks do not really have an 'open'
|
||||
*/
|
||||
if (S_ISREG(mode))
|
||||
av |= FILE__OPEN;
|
||||
else if (S_ISCHR(mode))
|
||||
av |= CHR_FILE__OPEN;
|
||||
else if (S_ISBLK(mode))
|
||||
av |= BLK_FILE__OPEN;
|
||||
else if (S_ISFIFO(mode))
|
||||
av |= FIFO_FILE__OPEN;
|
||||
else if (S_ISDIR(mode))
|
||||
av |= DIR__OPEN;
|
||||
else if (S_ISSOCK(mode))
|
||||
av |= SOCK_FILE__OPEN;
|
||||
else
|
||||
printk(KERN_ERR "SELinux: WARNING: inside %s with "
|
||||
"unknown mode:%o\n", __func__, mode);
|
||||
}
|
||||
if (selinux_policycap_openperm)
|
||||
av |= FILE__OPEN;
|
||||
|
||||
return av;
|
||||
}
|
||||
|
||||
@@ -2183,8 +2135,7 @@ static int selinux_bprm_set_creds(struct linux_binprm *bprm)
|
||||
|
||||
static int selinux_bprm_secureexec(struct linux_binprm *bprm)
|
||||
{
|
||||
const struct cred *cred = current_cred();
|
||||
const struct task_security_struct *tsec = cred->security;
|
||||
const struct task_security_struct *tsec = current_security();
|
||||
u32 sid, osid;
|
||||
int atsecure = 0;
|
||||
|
||||
@@ -2559,8 +2510,7 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir,
|
||||
char **name, void **value,
|
||||
size_t *len)
|
||||
{
|
||||
const struct cred *cred = current_cred();
|
||||
const struct task_security_struct *tsec = cred->security;
|
||||
const struct task_security_struct *tsec = current_security();
|
||||
struct inode_security_struct *dsec;
|
||||
struct superblock_security_struct *sbsec;
|
||||
u32 sid, newsid, clen;
|
||||
@@ -2676,14 +2626,26 @@ static int selinux_inode_follow_link(struct dentry *dentry, struct nameidata *na
|
||||
static int selinux_inode_permission(struct inode *inode, int mask)
|
||||
{
|
||||
const struct cred *cred = current_cred();
|
||||
struct common_audit_data ad;
|
||||
u32 perms;
|
||||
bool from_access;
|
||||
|
||||
if (!mask) {
|
||||
/* No permission to check. Existence test. */
|
||||
from_access = mask & MAY_ACCESS;
|
||||
mask &= (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND);
|
||||
|
||||
/* No permission to check. Existence test. */
|
||||
if (!mask)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return inode_has_perm(cred, inode,
|
||||
file_mask_to_av(inode->i_mode, mask), NULL);
|
||||
COMMON_AUDIT_DATA_INIT(&ad, FS);
|
||||
ad.u.fs.inode = inode;
|
||||
|
||||
if (from_access)
|
||||
ad.selinux_audit_data.auditdeny |= FILE__AUDIT_ACCESS;
|
||||
|
||||
perms = file_mask_to_av(inode->i_mode, mask);
|
||||
|
||||
return inode_has_perm(cred, inode, perms, &ad);
|
||||
}
|
||||
|
||||
static int selinux_inode_setattr(struct dentry *dentry, struct iattr *iattr)
|
||||
@@ -3671,71 +3633,54 @@ static int selinux_skb_peerlbl_sid(struct sk_buff *skb, u16 family, u32 *sid)
|
||||
}
|
||||
|
||||
/* socket security operations */
|
||||
static int socket_has_perm(struct task_struct *task, struct socket *sock,
|
||||
u32 perms)
|
||||
|
||||
static u32 socket_sockcreate_sid(const struct task_security_struct *tsec)
|
||||
{
|
||||
struct inode_security_struct *isec;
|
||||
return tsec->sockcreate_sid ? : tsec->sid;
|
||||
}
|
||||
|
||||
static int sock_has_perm(struct task_struct *task, struct sock *sk, u32 perms)
|
||||
{
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct common_audit_data ad;
|
||||
u32 sid;
|
||||
int err = 0;
|
||||
u32 tsid = task_sid(task);
|
||||
|
||||
isec = SOCK_INODE(sock)->i_security;
|
||||
|
||||
if (isec->sid == SECINITSID_KERNEL)
|
||||
goto out;
|
||||
sid = task_sid(task);
|
||||
if (sksec->sid == SECINITSID_KERNEL)
|
||||
return 0;
|
||||
|
||||
COMMON_AUDIT_DATA_INIT(&ad, NET);
|
||||
ad.u.net.sk = sock->sk;
|
||||
err = avc_has_perm(sid, isec->sid, isec->sclass, perms, &ad);
|
||||
ad.u.net.sk = sk;
|
||||
|
||||
out:
|
||||
return err;
|
||||
return avc_has_perm(tsid, sksec->sid, sksec->sclass, perms, &ad);
|
||||
}
|
||||
|
||||
static int selinux_socket_create(int family, int type,
|
||||
int protocol, int kern)
|
||||
{
|
||||
const struct cred *cred = current_cred();
|
||||
const struct task_security_struct *tsec = cred->security;
|
||||
u32 sid, newsid;
|
||||
const struct task_security_struct *tsec = current_security();
|
||||
u32 newsid;
|
||||
u16 secclass;
|
||||
int err = 0;
|
||||
|
||||
if (kern)
|
||||
goto out;
|
||||
|
||||
sid = tsec->sid;
|
||||
newsid = tsec->sockcreate_sid ?: sid;
|
||||
return 0;
|
||||
|
||||
newsid = socket_sockcreate_sid(tsec);
|
||||
secclass = socket_type_to_security_class(family, type, protocol);
|
||||
err = avc_has_perm(sid, newsid, secclass, SOCKET__CREATE, NULL);
|
||||
|
||||
out:
|
||||
return err;
|
||||
return avc_has_perm(tsec->sid, newsid, secclass, SOCKET__CREATE, NULL);
|
||||
}
|
||||
|
||||
static int selinux_socket_post_create(struct socket *sock, int family,
|
||||
int type, int protocol, int kern)
|
||||
{
|
||||
const struct cred *cred = current_cred();
|
||||
const struct task_security_struct *tsec = cred->security;
|
||||
struct inode_security_struct *isec;
|
||||
const struct task_security_struct *tsec = current_security();
|
||||
struct inode_security_struct *isec = SOCK_INODE(sock)->i_security;
|
||||
struct sk_security_struct *sksec;
|
||||
u32 sid, newsid;
|
||||
int err = 0;
|
||||
|
||||
sid = tsec->sid;
|
||||
newsid = tsec->sockcreate_sid;
|
||||
|
||||
isec = SOCK_INODE(sock)->i_security;
|
||||
|
||||
if (kern)
|
||||
isec->sid = SECINITSID_KERNEL;
|
||||
else if (newsid)
|
||||
isec->sid = newsid;
|
||||
else
|
||||
isec->sid = sid;
|
||||
isec->sid = socket_sockcreate_sid(tsec);
|
||||
|
||||
isec->sclass = socket_type_to_security_class(family, type, protocol);
|
||||
isec->initialized = 1;
|
||||
@@ -3756,10 +3701,11 @@ static int selinux_socket_post_create(struct socket *sock, int family,
|
||||
|
||||
static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
u16 family;
|
||||
int err;
|
||||
|
||||
err = socket_has_perm(current, sock, SOCKET__BIND);
|
||||
err = sock_has_perm(current, sk, SOCKET__BIND);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
@@ -3768,19 +3714,16 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in
|
||||
* Multiple address binding for SCTP is not supported yet: we just
|
||||
* check the first address now.
|
||||
*/
|
||||
family = sock->sk->sk_family;
|
||||
family = sk->sk_family;
|
||||
if (family == PF_INET || family == PF_INET6) {
|
||||
char *addrp;
|
||||
struct inode_security_struct *isec;
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
struct common_audit_data ad;
|
||||
struct sockaddr_in *addr4 = NULL;
|
||||
struct sockaddr_in6 *addr6 = NULL;
|
||||
unsigned short snum;
|
||||
struct sock *sk = sock->sk;
|
||||
u32 sid, node_perm;
|
||||
|
||||
isec = SOCK_INODE(sock)->i_security;
|
||||
|
||||
if (family == PF_INET) {
|
||||
addr4 = (struct sockaddr_in *)address;
|
||||
snum = ntohs(addr4->sin_port);
|
||||
@@ -3804,15 +3747,15 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in
|
||||
COMMON_AUDIT_DATA_INIT(&ad, NET);
|
||||
ad.u.net.sport = htons(snum);
|
||||
ad.u.net.family = family;
|
||||
err = avc_has_perm(isec->sid, sid,
|
||||
isec->sclass,
|
||||
err = avc_has_perm(sksec->sid, sid,
|
||||
sksec->sclass,
|
||||
SOCKET__NAME_BIND, &ad);
|
||||
if (err)
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
switch (isec->sclass) {
|
||||
switch (sksec->sclass) {
|
||||
case SECCLASS_TCP_SOCKET:
|
||||
node_perm = TCP_SOCKET__NODE_BIND;
|
||||
break;
|
||||
@@ -3843,8 +3786,8 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in
|
||||
else
|
||||
ipv6_addr_copy(&ad.u.net.v6info.saddr, &addr6->sin6_addr);
|
||||
|
||||
err = avc_has_perm(isec->sid, sid,
|
||||
isec->sclass, node_perm, &ad);
|
||||
err = avc_has_perm(sksec->sid, sid,
|
||||
sksec->sclass, node_perm, &ad);
|
||||
if (err)
|
||||
goto out;
|
||||
}
|
||||
@@ -3855,19 +3798,18 @@ out:
|
||||
static int selinux_socket_connect(struct socket *sock, struct sockaddr *address, int addrlen)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct inode_security_struct *isec;
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
int err;
|
||||
|
||||
err = socket_has_perm(current, sock, SOCKET__CONNECT);
|
||||
err = sock_has_perm(current, sk, SOCKET__CONNECT);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/*
|
||||
* If a TCP or DCCP socket, check name_connect permission for the port.
|
||||
*/
|
||||
isec = SOCK_INODE(sock)->i_security;
|
||||
if (isec->sclass == SECCLASS_TCP_SOCKET ||
|
||||
isec->sclass == SECCLASS_DCCP_SOCKET) {
|
||||
if (sksec->sclass == SECCLASS_TCP_SOCKET ||
|
||||
sksec->sclass == SECCLASS_DCCP_SOCKET) {
|
||||
struct common_audit_data ad;
|
||||
struct sockaddr_in *addr4 = NULL;
|
||||
struct sockaddr_in6 *addr6 = NULL;
|
||||
@@ -3890,13 +3832,13 @@ static int selinux_socket_connect(struct socket *sock, struct sockaddr *address,
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
perm = (isec->sclass == SECCLASS_TCP_SOCKET) ?
|
||||
perm = (sksec->sclass == SECCLASS_TCP_SOCKET) ?
|
||||
TCP_SOCKET__NAME_CONNECT : DCCP_SOCKET__NAME_CONNECT;
|
||||
|
||||
COMMON_AUDIT_DATA_INIT(&ad, NET);
|
||||
ad.u.net.dport = htons(snum);
|
||||
ad.u.net.family = sk->sk_family;
|
||||
err = avc_has_perm(isec->sid, sid, isec->sclass, perm, &ad);
|
||||
err = avc_has_perm(sksec->sid, sid, sksec->sclass, perm, &ad);
|
||||
if (err)
|
||||
goto out;
|
||||
}
|
||||
@@ -3909,7 +3851,7 @@ out:
|
||||
|
||||
static int selinux_socket_listen(struct socket *sock, int backlog)
|
||||
{
|
||||
return socket_has_perm(current, sock, SOCKET__LISTEN);
|
||||
return sock_has_perm(current, sock->sk, SOCKET__LISTEN);
|
||||
}
|
||||
|
||||
static int selinux_socket_accept(struct socket *sock, struct socket *newsock)
|
||||
@@ -3918,7 +3860,7 @@ static int selinux_socket_accept(struct socket *sock, struct socket *newsock)
|
||||
struct inode_security_struct *isec;
|
||||
struct inode_security_struct *newisec;
|
||||
|
||||
err = socket_has_perm(current, sock, SOCKET__ACCEPT);
|
||||
err = sock_has_perm(current, sock->sk, SOCKET__ACCEPT);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
@@ -3935,30 +3877,30 @@ static int selinux_socket_accept(struct socket *sock, struct socket *newsock)
|
||||
static int selinux_socket_sendmsg(struct socket *sock, struct msghdr *msg,
|
||||
int size)
|
||||
{
|
||||
return socket_has_perm(current, sock, SOCKET__WRITE);
|
||||
return sock_has_perm(current, sock->sk, SOCKET__WRITE);
|
||||
}
|
||||
|
||||
static int selinux_socket_recvmsg(struct socket *sock, struct msghdr *msg,
|
||||
int size, int flags)
|
||||
{
|
||||
return socket_has_perm(current, sock, SOCKET__READ);
|
||||
return sock_has_perm(current, sock->sk, SOCKET__READ);
|
||||
}
|
||||
|
||||
static int selinux_socket_getsockname(struct socket *sock)
|
||||
{
|
||||
return socket_has_perm(current, sock, SOCKET__GETATTR);
|
||||
return sock_has_perm(current, sock->sk, SOCKET__GETATTR);
|
||||
}
|
||||
|
||||
static int selinux_socket_getpeername(struct socket *sock)
|
||||
{
|
||||
return socket_has_perm(current, sock, SOCKET__GETATTR);
|
||||
return sock_has_perm(current, sock->sk, SOCKET__GETATTR);
|
||||
}
|
||||
|
||||
static int selinux_socket_setsockopt(struct socket *sock, int level, int optname)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = socket_has_perm(current, sock, SOCKET__SETOPT);
|
||||
err = sock_has_perm(current, sock->sk, SOCKET__SETOPT);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
@@ -3968,68 +3910,58 @@ static int selinux_socket_setsockopt(struct socket *sock, int level, int optname
|
||||
static int selinux_socket_getsockopt(struct socket *sock, int level,
|
||||
int optname)
|
||||
{
|
||||
return socket_has_perm(current, sock, SOCKET__GETOPT);
|
||||
return sock_has_perm(current, sock->sk, SOCKET__GETOPT);
|
||||
}
|
||||
|
||||
static int selinux_socket_shutdown(struct socket *sock, int how)
|
||||
{
|
||||
return socket_has_perm(current, sock, SOCKET__SHUTDOWN);
|
||||
return sock_has_perm(current, sock->sk, SOCKET__SHUTDOWN);
|
||||
}
|
||||
|
||||
static int selinux_socket_unix_stream_connect(struct socket *sock,
|
||||
struct socket *other,
|
||||
struct sock *newsk)
|
||||
{
|
||||
struct sk_security_struct *sksec;
|
||||
struct inode_security_struct *isec;
|
||||
struct inode_security_struct *other_isec;
|
||||
struct sk_security_struct *sksec_sock = sock->sk->sk_security;
|
||||
struct sk_security_struct *sksec_other = other->sk->sk_security;
|
||||
struct sk_security_struct *sksec_new = newsk->sk_security;
|
||||
struct common_audit_data ad;
|
||||
int err;
|
||||
|
||||
isec = SOCK_INODE(sock)->i_security;
|
||||
other_isec = SOCK_INODE(other)->i_security;
|
||||
|
||||
COMMON_AUDIT_DATA_INIT(&ad, NET);
|
||||
ad.u.net.sk = other->sk;
|
||||
|
||||
err = avc_has_perm(isec->sid, other_isec->sid,
|
||||
isec->sclass,
|
||||
err = avc_has_perm(sksec_sock->sid, sksec_other->sid,
|
||||
sksec_other->sclass,
|
||||
UNIX_STREAM_SOCKET__CONNECTTO, &ad);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* connecting socket */
|
||||
sksec = sock->sk->sk_security;
|
||||
sksec->peer_sid = other_isec->sid;
|
||||
|
||||
/* server child socket */
|
||||
sksec = newsk->sk_security;
|
||||
sksec->peer_sid = isec->sid;
|
||||
err = security_sid_mls_copy(other_isec->sid, sksec->peer_sid, &sksec->sid);
|
||||
sksec_new->peer_sid = sksec_sock->sid;
|
||||
err = security_sid_mls_copy(sksec_other->sid, sksec_sock->sid,
|
||||
&sksec_new->sid);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return err;
|
||||
/* connecting socket */
|
||||
sksec_sock->peer_sid = sksec_new->sid;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int selinux_socket_unix_may_send(struct socket *sock,
|
||||
struct socket *other)
|
||||
{
|
||||
struct inode_security_struct *isec;
|
||||
struct inode_security_struct *other_isec;
|
||||
struct sk_security_struct *ssec = sock->sk->sk_security;
|
||||
struct sk_security_struct *osec = other->sk->sk_security;
|
||||
struct common_audit_data ad;
|
||||
int err;
|
||||
|
||||
isec = SOCK_INODE(sock)->i_security;
|
||||
other_isec = SOCK_INODE(other)->i_security;
|
||||
|
||||
COMMON_AUDIT_DATA_INIT(&ad, NET);
|
||||
ad.u.net.sk = other->sk;
|
||||
|
||||
err = avc_has_perm(isec->sid, other_isec->sid,
|
||||
isec->sclass, SOCKET__SENDTO, &ad);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
return avc_has_perm(ssec->sid, osec->sid, osec->sclass, SOCKET__SENDTO,
|
||||
&ad);
|
||||
}
|
||||
|
||||
static int selinux_inet_sys_rcv_skb(int ifindex, char *addrp, u16 family,
|
||||
@@ -4168,26 +4100,18 @@ static int selinux_socket_getpeersec_stream(struct socket *sock, char __user *op
|
||||
int err = 0;
|
||||
char *scontext;
|
||||
u32 scontext_len;
|
||||
struct sk_security_struct *sksec;
|
||||
struct inode_security_struct *isec;
|
||||
struct sk_security_struct *sksec = sock->sk->sk_security;
|
||||
u32 peer_sid = SECSID_NULL;
|
||||
|
||||
isec = SOCK_INODE(sock)->i_security;
|
||||
|
||||
if (isec->sclass == SECCLASS_UNIX_STREAM_SOCKET ||
|
||||
isec->sclass == SECCLASS_TCP_SOCKET) {
|
||||
sksec = sock->sk->sk_security;
|
||||
if (sksec->sclass == SECCLASS_UNIX_STREAM_SOCKET ||
|
||||
sksec->sclass == SECCLASS_TCP_SOCKET)
|
||||
peer_sid = sksec->peer_sid;
|
||||
}
|
||||
if (peer_sid == SECSID_NULL) {
|
||||
err = -ENOPROTOOPT;
|
||||
goto out;
|
||||
}
|
||||
if (peer_sid == SECSID_NULL)
|
||||
return -ENOPROTOOPT;
|
||||
|
||||
err = security_sid_to_context(peer_sid, &scontext, &scontext_len);
|
||||
|
||||
if (err)
|
||||
goto out;
|
||||
return err;
|
||||
|
||||
if (scontext_len > len) {
|
||||
err = -ERANGE;
|
||||
@@ -4200,9 +4124,7 @@ static int selinux_socket_getpeersec_stream(struct socket *sock, char __user *op
|
||||
out_len:
|
||||
if (put_user(scontext_len, optlen))
|
||||
err = -EFAULT;
|
||||
|
||||
kfree(scontext);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -4234,12 +4156,27 @@ out:
|
||||
|
||||
static int selinux_sk_alloc_security(struct sock *sk, int family, gfp_t priority)
|
||||
{
|
||||
return sk_alloc_security(sk, family, priority);
|
||||
struct sk_security_struct *sksec;
|
||||
|
||||
sksec = kzalloc(sizeof(*sksec), priority);
|
||||
if (!sksec)
|
||||
return -ENOMEM;
|
||||
|
||||
sksec->peer_sid = SECINITSID_UNLABELED;
|
||||
sksec->sid = SECINITSID_UNLABELED;
|
||||
selinux_netlbl_sk_security_reset(sksec);
|
||||
sk->sk_security = sksec;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void selinux_sk_free_security(struct sock *sk)
|
||||
{
|
||||
sk_free_security(sk);
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
|
||||
sk->sk_security = NULL;
|
||||
selinux_netlbl_sk_security_free(sksec);
|
||||
kfree(sksec);
|
||||
}
|
||||
|
||||
static void selinux_sk_clone_security(const struct sock *sk, struct sock *newsk)
|
||||
@@ -4399,8 +4336,7 @@ static int selinux_nlmsg_perm(struct sock *sk, struct sk_buff *skb)
|
||||
int err = 0;
|
||||
u32 perm;
|
||||
struct nlmsghdr *nlh;
|
||||
struct socket *sock = sk->sk_socket;
|
||||
struct inode_security_struct *isec = SOCK_INODE(sock)->i_security;
|
||||
struct sk_security_struct *sksec = sk->sk_security;
|
||||
|
||||
if (skb->len < NLMSG_SPACE(0)) {
|
||||
err = -EINVAL;
|
||||
@@ -4408,13 +4344,13 @@ static int selinux_nlmsg_perm(struct sock *sk, struct sk_buff *skb)
|
||||
}
|
||||
nlh = nlmsg_hdr(skb);
|
||||
|
||||
err = selinux_nlmsg_lookup(isec->sclass, nlh->nlmsg_type, &perm);
|
||||
err = selinux_nlmsg_lookup(sksec->sclass, nlh->nlmsg_type, &perm);
|
||||
if (err) {
|
||||
if (err == -EINVAL) {
|
||||
audit_log(current->audit_context, GFP_KERNEL, AUDIT_SELINUX_ERR,
|
||||
"SELinux: unrecognized netlink message"
|
||||
" type=%hu for sclass=%hu\n",
|
||||
nlh->nlmsg_type, isec->sclass);
|
||||
nlh->nlmsg_type, sksec->sclass);
|
||||
if (!selinux_enforcing || security_get_allow_unknown())
|
||||
err = 0;
|
||||
}
|
||||
@@ -4425,7 +4361,7 @@ static int selinux_nlmsg_perm(struct sock *sk, struct sk_buff *skb)
|
||||
goto out;
|
||||
}
|
||||
|
||||
err = socket_has_perm(current, sock, perm);
|
||||
err = sock_has_perm(current, sk, perm);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"getattr", "setattr", "lock", "relabelfrom", "relabelto", "append"
|
||||
|
||||
#define COMMON_FILE_PERMS COMMON_FILE_SOCK_PERMS, "unlink", "link", \
|
||||
"rename", "execute", "swapon", "quotaon", "mounton"
|
||||
"rename", "execute", "swapon", "quotaon", "mounton", "audit_access", \
|
||||
"open", "execmod"
|
||||
|
||||
#define COMMON_SOCK_PERMS COMMON_FILE_SOCK_PERMS, "bind", "connect", \
|
||||
"listen", "accept", "getopt", "setopt", "shutdown", "recvfrom", \
|
||||
@@ -43,22 +44,21 @@ struct security_class_mapping secclass_map[] = {
|
||||
"quotaget", NULL } },
|
||||
{ "file",
|
||||
{ COMMON_FILE_PERMS,
|
||||
"execute_no_trans", "entrypoint", "execmod", "open", NULL } },
|
||||
"execute_no_trans", "entrypoint", NULL } },
|
||||
{ "dir",
|
||||
{ COMMON_FILE_PERMS, "add_name", "remove_name",
|
||||
"reparent", "search", "rmdir", "open", NULL } },
|
||||
"reparent", "search", "rmdir", NULL } },
|
||||
{ "fd", { "use", NULL } },
|
||||
{ "lnk_file",
|
||||
{ COMMON_FILE_PERMS, NULL } },
|
||||
{ "chr_file",
|
||||
{ COMMON_FILE_PERMS,
|
||||
"execute_no_trans", "entrypoint", "execmod", "open", NULL } },
|
||||
{ COMMON_FILE_PERMS, NULL } },
|
||||
{ "blk_file",
|
||||
{ COMMON_FILE_PERMS, "open", NULL } },
|
||||
{ COMMON_FILE_PERMS, NULL } },
|
||||
{ "sock_file",
|
||||
{ COMMON_FILE_PERMS, "open", NULL } },
|
||||
{ COMMON_FILE_PERMS, NULL } },
|
||||
{ "fifo_file",
|
||||
{ COMMON_FILE_PERMS, "open", NULL } },
|
||||
{ COMMON_FILE_PERMS, NULL } },
|
||||
{ "socket",
|
||||
{ COMMON_SOCK_PERMS, NULL } },
|
||||
{ "tcp_socket",
|
||||
|
||||
@@ -183,8 +183,6 @@ static void sel_netnode_insert(struct sel_netnode *node)
|
||||
BUG();
|
||||
}
|
||||
|
||||
INIT_RCU_HEAD(&node->rcu);
|
||||
|
||||
/* we need to impose a limit on the growth of the hash table so check
|
||||
* this bucket to make sure it is within the specified bounds */
|
||||
list_add_rcu(&node->list, &sel_netnode_hash[idx].list);
|
||||
|
||||
@@ -184,6 +184,7 @@ out:
|
||||
static const struct file_operations sel_enforce_ops = {
|
||||
.read = sel_read_enforce,
|
||||
.write = sel_write_enforce,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
static ssize_t sel_read_handle_unknown(struct file *filp, char __user *buf,
|
||||
@@ -201,6 +202,7 @@ static ssize_t sel_read_handle_unknown(struct file *filp, char __user *buf,
|
||||
|
||||
static const struct file_operations sel_handle_unknown_ops = {
|
||||
.read = sel_read_handle_unknown,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_SECURITY_SELINUX_DISABLE
|
||||
@@ -251,6 +253,7 @@ out:
|
||||
|
||||
static const struct file_operations sel_disable_ops = {
|
||||
.write = sel_write_disable,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
static ssize_t sel_read_policyvers(struct file *filp, char __user *buf,
|
||||
@@ -265,6 +268,7 @@ static ssize_t sel_read_policyvers(struct file *filp, char __user *buf,
|
||||
|
||||
static const struct file_operations sel_policyvers_ops = {
|
||||
.read = sel_read_policyvers,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
/* declaration for sel_write_load */
|
||||
@@ -289,6 +293,7 @@ static ssize_t sel_read_mls(struct file *filp, char __user *buf,
|
||||
|
||||
static const struct file_operations sel_mls_ops = {
|
||||
.read = sel_read_mls,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
static ssize_t sel_write_load(struct file *file, const char __user *buf,
|
||||
@@ -356,6 +361,7 @@ out:
|
||||
|
||||
static const struct file_operations sel_load_ops = {
|
||||
.write = sel_write_load,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
static ssize_t sel_write_context(struct file *file, char *buf, size_t size)
|
||||
@@ -437,6 +443,7 @@ out:
|
||||
static const struct file_operations sel_checkreqprot_ops = {
|
||||
.read = sel_read_checkreqprot,
|
||||
.write = sel_write_checkreqprot,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -482,6 +489,7 @@ static const struct file_operations transaction_ops = {
|
||||
.write = selinux_transaction_write,
|
||||
.read = simple_transaction_read,
|
||||
.release = simple_transaction_release,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -883,6 +891,7 @@ out:
|
||||
static const struct file_operations sel_bool_ops = {
|
||||
.read = sel_read_bool,
|
||||
.write = sel_write_bool,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
static ssize_t sel_commit_bools_write(struct file *filep,
|
||||
@@ -935,6 +944,7 @@ out:
|
||||
|
||||
static const struct file_operations sel_commit_bools_ops = {
|
||||
.write = sel_commit_bools_write,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
static void sel_remove_entries(struct dentry *de)
|
||||
@@ -1127,10 +1137,12 @@ out:
|
||||
static const struct file_operations sel_avc_cache_threshold_ops = {
|
||||
.read = sel_read_avc_cache_threshold,
|
||||
.write = sel_write_avc_cache_threshold,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
static const struct file_operations sel_avc_hash_stats_ops = {
|
||||
.read = sel_read_avc_hash_stats,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS
|
||||
@@ -1255,6 +1267,7 @@ static ssize_t sel_read_initcon(struct file *file, char __user *buf,
|
||||
|
||||
static const struct file_operations sel_initcon_ops = {
|
||||
.read = sel_read_initcon,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
static int sel_make_initcon_files(struct dentry *dir)
|
||||
@@ -1330,6 +1343,7 @@ out:
|
||||
|
||||
static const struct file_operations sel_class_ops = {
|
||||
.read = sel_read_class,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
static ssize_t sel_read_perm(struct file *file, char __user *buf,
|
||||
@@ -1354,6 +1368,7 @@ out:
|
||||
|
||||
static const struct file_operations sel_perm_ops = {
|
||||
.read = sel_read_perm,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
static ssize_t sel_read_policycap(struct file *file, char __user *buf,
|
||||
@@ -1372,6 +1387,7 @@ static ssize_t sel_read_policycap(struct file *file, char __user *buf,
|
||||
|
||||
static const struct file_operations sel_policycap_ops = {
|
||||
.read = sel_read_policycap,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
static int sel_make_perm_files(char *objclass, int classvalue,
|
||||
|
||||
+19
-20
@@ -342,20 +342,20 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol,
|
||||
|
||||
if (vers < POLICYDB_VERSION_AVTAB) {
|
||||
rc = next_entry(buf32, fp, sizeof(u32));
|
||||
if (rc < 0) {
|
||||
if (rc) {
|
||||
printk(KERN_ERR "SELinux: avtab: truncated entry\n");
|
||||
return -1;
|
||||
return rc;
|
||||
}
|
||||
items2 = le32_to_cpu(buf32[0]);
|
||||
if (items2 > ARRAY_SIZE(buf32)) {
|
||||
printk(KERN_ERR "SELinux: avtab: entry overflow\n");
|
||||
return -1;
|
||||
return -EINVAL;
|
||||
|
||||
}
|
||||
rc = next_entry(buf32, fp, sizeof(u32)*items2);
|
||||
if (rc < 0) {
|
||||
if (rc) {
|
||||
printk(KERN_ERR "SELinux: avtab: truncated entry\n");
|
||||
return -1;
|
||||
return rc;
|
||||
}
|
||||
items = 0;
|
||||
|
||||
@@ -363,19 +363,19 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol,
|
||||
key.source_type = (u16)val;
|
||||
if (key.source_type != val) {
|
||||
printk(KERN_ERR "SELinux: avtab: truncated source type\n");
|
||||
return -1;
|
||||
return -EINVAL;
|
||||
}
|
||||
val = le32_to_cpu(buf32[items++]);
|
||||
key.target_type = (u16)val;
|
||||
if (key.target_type != val) {
|
||||
printk(KERN_ERR "SELinux: avtab: truncated target type\n");
|
||||
return -1;
|
||||
return -EINVAL;
|
||||
}
|
||||
val = le32_to_cpu(buf32[items++]);
|
||||
key.target_class = (u16)val;
|
||||
if (key.target_class != val) {
|
||||
printk(KERN_ERR "SELinux: avtab: truncated target class\n");
|
||||
return -1;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
val = le32_to_cpu(buf32[items++]);
|
||||
@@ -383,12 +383,12 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol,
|
||||
|
||||
if (!(val & (AVTAB_AV | AVTAB_TYPE))) {
|
||||
printk(KERN_ERR "SELinux: avtab: null entry\n");
|
||||
return -1;
|
||||
return -EINVAL;
|
||||
}
|
||||
if ((val & AVTAB_AV) &&
|
||||
(val & AVTAB_TYPE)) {
|
||||
printk(KERN_ERR "SELinux: avtab: entry has both access vectors and types\n");
|
||||
return -1;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(spec_order); i++) {
|
||||
@@ -403,15 +403,15 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol,
|
||||
|
||||
if (items != items2) {
|
||||
printk(KERN_ERR "SELinux: avtab: entry only had %d items, expected %d\n", items2, items);
|
||||
return -1;
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
rc = next_entry(buf16, fp, sizeof(u16)*4);
|
||||
if (rc < 0) {
|
||||
if (rc) {
|
||||
printk(KERN_ERR "SELinux: avtab: truncated entry\n");
|
||||
return -1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
items = 0;
|
||||
@@ -424,7 +424,7 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol,
|
||||
!policydb_type_isvalid(pol, key.target_type) ||
|
||||
!policydb_class_isvalid(pol, key.target_class)) {
|
||||
printk(KERN_ERR "SELinux: avtab: invalid type or class\n");
|
||||
return -1;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
set = 0;
|
||||
@@ -434,19 +434,19 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol,
|
||||
}
|
||||
if (!set || set > 1) {
|
||||
printk(KERN_ERR "SELinux: avtab: more than one specifier\n");
|
||||
return -1;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rc = next_entry(buf32, fp, sizeof(u32));
|
||||
if (rc < 0) {
|
||||
if (rc) {
|
||||
printk(KERN_ERR "SELinux: avtab: truncated entry\n");
|
||||
return -1;
|
||||
return rc;
|
||||
}
|
||||
datum.data = le32_to_cpu(*buf32);
|
||||
if ((key.specified & AVTAB_TYPE) &&
|
||||
!policydb_type_isvalid(pol, datum.data)) {
|
||||
printk(KERN_ERR "SELinux: avtab: invalid type\n");
|
||||
return -1;
|
||||
return -EINVAL;
|
||||
}
|
||||
return insertf(a, &key, &datum, p);
|
||||
}
|
||||
@@ -487,8 +487,7 @@ int avtab_read(struct avtab *a, void *fp, struct policydb *pol)
|
||||
printk(KERN_ERR "SELinux: avtab: out of memory\n");
|
||||
else if (rc == -EEXIST)
|
||||
printk(KERN_ERR "SELinux: avtab: duplicate entry\n");
|
||||
else
|
||||
rc = -EINVAL;
|
||||
|
||||
goto bad;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,10 +117,14 @@ int evaluate_cond_node(struct policydb *p, struct cond_node *node)
|
||||
|
||||
int cond_policydb_init(struct policydb *p)
|
||||
{
|
||||
int rc;
|
||||
|
||||
p->bool_val_to_struct = NULL;
|
||||
p->cond_list = NULL;
|
||||
if (avtab_init(&p->te_cond_avtab))
|
||||
return -1;
|
||||
|
||||
rc = avtab_init(&p->te_cond_avtab);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -219,34 +223,37 @@ int cond_read_bool(struct policydb *p, struct hashtab *h, void *fp)
|
||||
|
||||
booldatum = kzalloc(sizeof(struct cond_bool_datum), GFP_KERNEL);
|
||||
if (!booldatum)
|
||||
return -1;
|
||||
return -ENOMEM;
|
||||
|
||||
rc = next_entry(buf, fp, sizeof buf);
|
||||
if (rc < 0)
|
||||
if (rc)
|
||||
goto err;
|
||||
|
||||
booldatum->value = le32_to_cpu(buf[0]);
|
||||
booldatum->state = le32_to_cpu(buf[1]);
|
||||
|
||||
rc = -EINVAL;
|
||||
if (!bool_isvalid(booldatum))
|
||||
goto err;
|
||||
|
||||
len = le32_to_cpu(buf[2]);
|
||||
|
||||
rc = -ENOMEM;
|
||||
key = kmalloc(len + 1, GFP_KERNEL);
|
||||
if (!key)
|
||||
goto err;
|
||||
rc = next_entry(key, fp, len);
|
||||
if (rc < 0)
|
||||
if (rc)
|
||||
goto err;
|
||||
key[len] = '\0';
|
||||
if (hashtab_insert(h, key, booldatum))
|
||||
rc = hashtab_insert(h, key, booldatum);
|
||||
if (rc)
|
||||
goto err;
|
||||
|
||||
return 0;
|
||||
err:
|
||||
cond_destroy_bool(key, booldatum, NULL);
|
||||
return -1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct cond_insertf_data {
|
||||
@@ -263,7 +270,7 @@ static int cond_insertf(struct avtab *a, struct avtab_key *k, struct avtab_datum
|
||||
struct cond_av_list *other = data->other, *list, *cur;
|
||||
struct avtab_node *node_ptr;
|
||||
u8 found;
|
||||
|
||||
int rc = -EINVAL;
|
||||
|
||||
/*
|
||||
* For type rules we have to make certain there aren't any
|
||||
@@ -313,12 +320,15 @@ static int cond_insertf(struct avtab *a, struct avtab_key *k, struct avtab_datum
|
||||
node_ptr = avtab_insert_nonunique(&p->te_cond_avtab, k, d);
|
||||
if (!node_ptr) {
|
||||
printk(KERN_ERR "SELinux: could not insert rule.\n");
|
||||
rc = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
list = kzalloc(sizeof(struct cond_av_list), GFP_KERNEL);
|
||||
if (!list)
|
||||
if (!list) {
|
||||
rc = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
list->node = node_ptr;
|
||||
if (!data->head)
|
||||
@@ -331,7 +341,7 @@ static int cond_insertf(struct avtab *a, struct avtab_key *k, struct avtab_datum
|
||||
err:
|
||||
cond_av_list_destroy(data->head);
|
||||
data->head = NULL;
|
||||
return -1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int cond_read_av_list(struct policydb *p, void *fp, struct cond_av_list **ret_list, struct cond_av_list *other)
|
||||
@@ -345,8 +355,8 @@ static int cond_read_av_list(struct policydb *p, void *fp, struct cond_av_list *
|
||||
|
||||
len = 0;
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc < 0)
|
||||
return -1;
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
len = le32_to_cpu(buf[0]);
|
||||
if (len == 0)
|
||||
@@ -361,7 +371,6 @@ static int cond_read_av_list(struct policydb *p, void *fp, struct cond_av_list *
|
||||
&data);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
}
|
||||
|
||||
*ret_list = data.head;
|
||||
@@ -390,24 +399,25 @@ static int cond_read_node(struct policydb *p, struct cond_node *node, void *fp)
|
||||
struct cond_expr *expr = NULL, *last = NULL;
|
||||
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc < 0)
|
||||
return -1;
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
node->cur_state = le32_to_cpu(buf[0]);
|
||||
|
||||
len = 0;
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc < 0)
|
||||
return -1;
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* expr */
|
||||
len = le32_to_cpu(buf[0]);
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
rc = next_entry(buf, fp, sizeof(u32) * 2);
|
||||
if (rc < 0)
|
||||
if (rc)
|
||||
goto err;
|
||||
|
||||
rc = -ENOMEM;
|
||||
expr = kzalloc(sizeof(struct cond_expr), GFP_KERNEL);
|
||||
if (!expr)
|
||||
goto err;
|
||||
@@ -416,6 +426,7 @@ static int cond_read_node(struct policydb *p, struct cond_node *node, void *fp)
|
||||
expr->bool = le32_to_cpu(buf[1]);
|
||||
|
||||
if (!expr_isvalid(p, expr)) {
|
||||
rc = -EINVAL;
|
||||
kfree(expr);
|
||||
goto err;
|
||||
}
|
||||
@@ -427,14 +438,16 @@ static int cond_read_node(struct policydb *p, struct cond_node *node, void *fp)
|
||||
last = expr;
|
||||
}
|
||||
|
||||
if (cond_read_av_list(p, fp, &node->true_list, NULL) != 0)
|
||||
rc = cond_read_av_list(p, fp, &node->true_list, NULL);
|
||||
if (rc)
|
||||
goto err;
|
||||
if (cond_read_av_list(p, fp, &node->false_list, node->true_list) != 0)
|
||||
rc = cond_read_av_list(p, fp, &node->false_list, node->true_list);
|
||||
if (rc)
|
||||
goto err;
|
||||
return 0;
|
||||
err:
|
||||
cond_node_destroy(node);
|
||||
return -1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
int cond_read_list(struct policydb *p, void *fp)
|
||||
@@ -445,8 +458,8 @@ int cond_read_list(struct policydb *p, void *fp)
|
||||
int rc;
|
||||
|
||||
rc = next_entry(buf, fp, sizeof buf);
|
||||
if (rc < 0)
|
||||
return -1;
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
len = le32_to_cpu(buf[0]);
|
||||
|
||||
@@ -455,11 +468,13 @@ int cond_read_list(struct policydb *p, void *fp)
|
||||
goto err;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
rc = -ENOMEM;
|
||||
node = kzalloc(sizeof(struct cond_node), GFP_KERNEL);
|
||||
if (!node)
|
||||
goto err;
|
||||
|
||||
if (cond_read_node(p, node, fp) != 0)
|
||||
rc = cond_read_node(p, node, fp);
|
||||
if (rc)
|
||||
goto err;
|
||||
|
||||
if (i == 0)
|
||||
@@ -472,7 +487,7 @@ int cond_read_list(struct policydb *p, void *fp)
|
||||
err:
|
||||
cond_list_destroy(p->cond_list);
|
||||
p->cond_list = NULL;
|
||||
return -1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Determine whether additional permissions are granted by the conditional
|
||||
|
||||
+370
-288
@@ -31,6 +31,7 @@
|
||||
#include <linux/string.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/audit.h>
|
||||
#include <linux/flex_array.h>
|
||||
#include "security.h"
|
||||
|
||||
#include "policydb.h"
|
||||
@@ -655,6 +656,9 @@ static int range_tr_destroy(void *key, void *datum, void *p)
|
||||
|
||||
static void ocontext_destroy(struct ocontext *c, int i)
|
||||
{
|
||||
if (!c)
|
||||
return;
|
||||
|
||||
context_destroy(&c->context[0]);
|
||||
context_destroy(&c->context[1]);
|
||||
if (i == OCON_ISID || i == OCON_FS ||
|
||||
@@ -736,11 +740,17 @@ void policydb_destroy(struct policydb *p)
|
||||
hashtab_map(p->range_tr, range_tr_destroy, NULL);
|
||||
hashtab_destroy(p->range_tr);
|
||||
|
||||
if (p->type_attr_map) {
|
||||
for (i = 0; i < p->p_types.nprim; i++)
|
||||
ebitmap_destroy(&p->type_attr_map[i]);
|
||||
if (p->type_attr_map_array) {
|
||||
for (i = 0; i < p->p_types.nprim; i++) {
|
||||
struct ebitmap *e;
|
||||
|
||||
e = flex_array_get(p->type_attr_map_array, i);
|
||||
if (!e)
|
||||
continue;
|
||||
ebitmap_destroy(e);
|
||||
}
|
||||
flex_array_free(p->type_attr_map_array);
|
||||
}
|
||||
kfree(p->type_attr_map);
|
||||
ebitmap_destroy(&p->policycaps);
|
||||
ebitmap_destroy(&p->permissive_map);
|
||||
|
||||
@@ -1701,6 +1711,333 @@ u32 string_to_av_perm(struct policydb *p, u16 tclass, const char *name)
|
||||
return 1U << (perdatum->value-1);
|
||||
}
|
||||
|
||||
static int range_read(struct policydb *p, void *fp)
|
||||
{
|
||||
struct range_trans *rt = NULL;
|
||||
struct mls_range *r = NULL;
|
||||
int i, rc;
|
||||
__le32 buf[2];
|
||||
u32 nel;
|
||||
|
||||
if (p->policyvers < POLICYDB_VERSION_MLS)
|
||||
return 0;
|
||||
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
nel = le32_to_cpu(buf[0]);
|
||||
for (i = 0; i < nel; i++) {
|
||||
rc = -ENOMEM;
|
||||
rt = kzalloc(sizeof(*rt), GFP_KERNEL);
|
||||
if (!rt)
|
||||
goto out;
|
||||
|
||||
rc = next_entry(buf, fp, (sizeof(u32) * 2));
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
rt->source_type = le32_to_cpu(buf[0]);
|
||||
rt->target_type = le32_to_cpu(buf[1]);
|
||||
if (p->policyvers >= POLICYDB_VERSION_RANGETRANS) {
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc)
|
||||
goto out;
|
||||
rt->target_class = le32_to_cpu(buf[0]);
|
||||
} else
|
||||
rt->target_class = p->process_class;
|
||||
|
||||
rc = -EINVAL;
|
||||
if (!policydb_type_isvalid(p, rt->source_type) ||
|
||||
!policydb_type_isvalid(p, rt->target_type) ||
|
||||
!policydb_class_isvalid(p, rt->target_class))
|
||||
goto out;
|
||||
|
||||
rc = -ENOMEM;
|
||||
r = kzalloc(sizeof(*r), GFP_KERNEL);
|
||||
if (!r)
|
||||
goto out;
|
||||
|
||||
rc = mls_read_range_helper(r, fp);
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
rc = -EINVAL;
|
||||
if (!mls_range_isvalid(p, r)) {
|
||||
printk(KERN_WARNING "SELinux: rangetrans: invalid range\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = hashtab_insert(p->range_tr, rt, r);
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
rt = NULL;
|
||||
r = NULL;
|
||||
}
|
||||
rangetr_hash_eval(p->range_tr);
|
||||
rc = 0;
|
||||
out:
|
||||
kfree(rt);
|
||||
kfree(r);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int genfs_read(struct policydb *p, void *fp)
|
||||
{
|
||||
int i, j, rc;
|
||||
u32 nel, nel2, len, len2;
|
||||
__le32 buf[1];
|
||||
struct ocontext *l, *c;
|
||||
struct ocontext *newc = NULL;
|
||||
struct genfs *genfs_p, *genfs;
|
||||
struct genfs *newgenfs = NULL;
|
||||
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc)
|
||||
goto out;
|
||||
nel = le32_to_cpu(buf[0]);
|
||||
|
||||
for (i = 0; i < nel; i++) {
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc)
|
||||
goto out;
|
||||
len = le32_to_cpu(buf[0]);
|
||||
|
||||
rc = -ENOMEM;
|
||||
newgenfs = kzalloc(sizeof(*newgenfs), GFP_KERNEL);
|
||||
if (!newgenfs)
|
||||
goto out;
|
||||
|
||||
rc = -ENOMEM;
|
||||
newgenfs->fstype = kmalloc(len + 1, GFP_KERNEL);
|
||||
if (!newgenfs->fstype)
|
||||
goto out;
|
||||
|
||||
rc = next_entry(newgenfs->fstype, fp, len);
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
newgenfs->fstype[len] = 0;
|
||||
|
||||
for (genfs_p = NULL, genfs = p->genfs; genfs;
|
||||
genfs_p = genfs, genfs = genfs->next) {
|
||||
rc = -EINVAL;
|
||||
if (strcmp(newgenfs->fstype, genfs->fstype) == 0) {
|
||||
printk(KERN_ERR "SELinux: dup genfs fstype %s\n",
|
||||
newgenfs->fstype);
|
||||
goto out;
|
||||
}
|
||||
if (strcmp(newgenfs->fstype, genfs->fstype) < 0)
|
||||
break;
|
||||
}
|
||||
newgenfs->next = genfs;
|
||||
if (genfs_p)
|
||||
genfs_p->next = newgenfs;
|
||||
else
|
||||
p->genfs = newgenfs;
|
||||
genfs = newgenfs;
|
||||
newgenfs = NULL;
|
||||
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
nel2 = le32_to_cpu(buf[0]);
|
||||
for (j = 0; j < nel2; j++) {
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc)
|
||||
goto out;
|
||||
len = le32_to_cpu(buf[0]);
|
||||
|
||||
rc = -ENOMEM;
|
||||
newc = kzalloc(sizeof(*newc), GFP_KERNEL);
|
||||
if (!newc)
|
||||
goto out;
|
||||
|
||||
rc = -ENOMEM;
|
||||
newc->u.name = kmalloc(len + 1, GFP_KERNEL);
|
||||
if (!newc->u.name)
|
||||
goto out;
|
||||
|
||||
rc = next_entry(newc->u.name, fp, len);
|
||||
if (rc)
|
||||
goto out;
|
||||
newc->u.name[len] = 0;
|
||||
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
newc->v.sclass = le32_to_cpu(buf[0]);
|
||||
rc = context_read_and_validate(&newc->context[0], p, fp);
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
for (l = NULL, c = genfs->head; c;
|
||||
l = c, c = c->next) {
|
||||
rc = -EINVAL;
|
||||
if (!strcmp(newc->u.name, c->u.name) &&
|
||||
(!c->v.sclass || !newc->v.sclass ||
|
||||
newc->v.sclass == c->v.sclass)) {
|
||||
printk(KERN_ERR "SELinux: dup genfs entry (%s,%s)\n",
|
||||
genfs->fstype, c->u.name);
|
||||
goto out;
|
||||
}
|
||||
len = strlen(newc->u.name);
|
||||
len2 = strlen(c->u.name);
|
||||
if (len > len2)
|
||||
break;
|
||||
}
|
||||
|
||||
newc->next = c;
|
||||
if (l)
|
||||
l->next = newc;
|
||||
else
|
||||
genfs->head = newc;
|
||||
newc = NULL;
|
||||
}
|
||||
}
|
||||
rc = 0;
|
||||
out:
|
||||
if (newgenfs)
|
||||
kfree(newgenfs->fstype);
|
||||
kfree(newgenfs);
|
||||
ocontext_destroy(newc, OCON_FSUSE);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int ocontext_read(struct policydb *p, struct policydb_compat_info *info,
|
||||
void *fp)
|
||||
{
|
||||
int i, j, rc;
|
||||
u32 nel, len;
|
||||
__le32 buf[3];
|
||||
struct ocontext *l, *c;
|
||||
u32 nodebuf[8];
|
||||
|
||||
for (i = 0; i < info->ocon_num; i++) {
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc)
|
||||
goto out;
|
||||
nel = le32_to_cpu(buf[0]);
|
||||
|
||||
l = NULL;
|
||||
for (j = 0; j < nel; j++) {
|
||||
rc = -ENOMEM;
|
||||
c = kzalloc(sizeof(*c), GFP_KERNEL);
|
||||
if (!c)
|
||||
goto out;
|
||||
if (l)
|
||||
l->next = c;
|
||||
else
|
||||
p->ocontexts[i] = c;
|
||||
l = c;
|
||||
|
||||
switch (i) {
|
||||
case OCON_ISID:
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
c->sid[0] = le32_to_cpu(buf[0]);
|
||||
rc = context_read_and_validate(&c->context[0], p, fp);
|
||||
if (rc)
|
||||
goto out;
|
||||
break;
|
||||
case OCON_FS:
|
||||
case OCON_NETIF:
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc)
|
||||
goto out;
|
||||
len = le32_to_cpu(buf[0]);
|
||||
|
||||
rc = -ENOMEM;
|
||||
c->u.name = kmalloc(len + 1, GFP_KERNEL);
|
||||
if (!c->u.name)
|
||||
goto out;
|
||||
|
||||
rc = next_entry(c->u.name, fp, len);
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
c->u.name[len] = 0;
|
||||
rc = context_read_and_validate(&c->context[0], p, fp);
|
||||
if (rc)
|
||||
goto out;
|
||||
rc = context_read_and_validate(&c->context[1], p, fp);
|
||||
if (rc)
|
||||
goto out;
|
||||
break;
|
||||
case OCON_PORT:
|
||||
rc = next_entry(buf, fp, sizeof(u32)*3);
|
||||
if (rc)
|
||||
goto out;
|
||||
c->u.port.protocol = le32_to_cpu(buf[0]);
|
||||
c->u.port.low_port = le32_to_cpu(buf[1]);
|
||||
c->u.port.high_port = le32_to_cpu(buf[2]);
|
||||
rc = context_read_and_validate(&c->context[0], p, fp);
|
||||
if (rc)
|
||||
goto out;
|
||||
break;
|
||||
case OCON_NODE:
|
||||
rc = next_entry(nodebuf, fp, sizeof(u32) * 2);
|
||||
if (rc)
|
||||
goto out;
|
||||
c->u.node.addr = nodebuf[0]; /* network order */
|
||||
c->u.node.mask = nodebuf[1]; /* network order */
|
||||
rc = context_read_and_validate(&c->context[0], p, fp);
|
||||
if (rc)
|
||||
goto out;
|
||||
break;
|
||||
case OCON_FSUSE:
|
||||
rc = next_entry(buf, fp, sizeof(u32)*2);
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
rc = -EINVAL;
|
||||
c->v.behavior = le32_to_cpu(buf[0]);
|
||||
if (c->v.behavior > SECURITY_FS_USE_NONE)
|
||||
goto out;
|
||||
|
||||
rc = -ENOMEM;
|
||||
len = le32_to_cpu(buf[1]);
|
||||
c->u.name = kmalloc(len + 1, GFP_KERNEL);
|
||||
if (!c->u.name)
|
||||
goto out;
|
||||
|
||||
rc = next_entry(c->u.name, fp, len);
|
||||
if (rc)
|
||||
goto out;
|
||||
c->u.name[len] = 0;
|
||||
rc = context_read_and_validate(&c->context[0], p, fp);
|
||||
if (rc)
|
||||
goto out;
|
||||
break;
|
||||
case OCON_NODE6: {
|
||||
int k;
|
||||
|
||||
rc = next_entry(nodebuf, fp, sizeof(u32) * 8);
|
||||
if (rc)
|
||||
goto out;
|
||||
for (k = 0; k < 4; k++)
|
||||
c->u.node6.addr[k] = nodebuf[k];
|
||||
for (k = 0; k < 4; k++)
|
||||
c->u.node6.mask[k] = nodebuf[k+4];
|
||||
rc = context_read_and_validate(&c->context[0], p, fp);
|
||||
if (rc)
|
||||
goto out;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
rc = 0;
|
||||
out:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the configuration data from a policy database binary
|
||||
* representation file into a policy database structure.
|
||||
@@ -1709,16 +2046,12 @@ int policydb_read(struct policydb *p, void *fp)
|
||||
{
|
||||
struct role_allow *ra, *lra;
|
||||
struct role_trans *tr, *ltr;
|
||||
struct ocontext *l, *c, *newc;
|
||||
struct genfs *genfs_p, *genfs, *newgenfs;
|
||||
int i, j, rc;
|
||||
__le32 buf[4];
|
||||
u32 nodebuf[8];
|
||||
u32 len, len2, nprim, nel, nel2;
|
||||
u32 len, nprim, nel;
|
||||
|
||||
char *policydb_str;
|
||||
struct policydb_compat_info *info;
|
||||
struct range_trans *rt;
|
||||
struct mls_range *r;
|
||||
|
||||
rc = policydb_init(p);
|
||||
if (rc)
|
||||
@@ -1919,294 +2252,45 @@ int policydb_read(struct policydb *p, void *fp)
|
||||
if (!p->process_trans_perms)
|
||||
goto bad;
|
||||
|
||||
for (i = 0; i < info->ocon_num; i++) {
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc < 0)
|
||||
goto bad;
|
||||
nel = le32_to_cpu(buf[0]);
|
||||
l = NULL;
|
||||
for (j = 0; j < nel; j++) {
|
||||
c = kzalloc(sizeof(*c), GFP_KERNEL);
|
||||
if (!c) {
|
||||
rc = -ENOMEM;
|
||||
goto bad;
|
||||
}
|
||||
if (l)
|
||||
l->next = c;
|
||||
else
|
||||
p->ocontexts[i] = c;
|
||||
l = c;
|
||||
rc = -EINVAL;
|
||||
switch (i) {
|
||||
case OCON_ISID:
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc < 0)
|
||||
goto bad;
|
||||
c->sid[0] = le32_to_cpu(buf[0]);
|
||||
rc = context_read_and_validate(&c->context[0], p, fp);
|
||||
if (rc)
|
||||
goto bad;
|
||||
break;
|
||||
case OCON_FS:
|
||||
case OCON_NETIF:
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc < 0)
|
||||
goto bad;
|
||||
len = le32_to_cpu(buf[0]);
|
||||
c->u.name = kmalloc(len + 1, GFP_KERNEL);
|
||||
if (!c->u.name) {
|
||||
rc = -ENOMEM;
|
||||
goto bad;
|
||||
}
|
||||
rc = next_entry(c->u.name, fp, len);
|
||||
if (rc < 0)
|
||||
goto bad;
|
||||
c->u.name[len] = 0;
|
||||
rc = context_read_and_validate(&c->context[0], p, fp);
|
||||
if (rc)
|
||||
goto bad;
|
||||
rc = context_read_and_validate(&c->context[1], p, fp);
|
||||
if (rc)
|
||||
goto bad;
|
||||
break;
|
||||
case OCON_PORT:
|
||||
rc = next_entry(buf, fp, sizeof(u32)*3);
|
||||
if (rc < 0)
|
||||
goto bad;
|
||||
c->u.port.protocol = le32_to_cpu(buf[0]);
|
||||
c->u.port.low_port = le32_to_cpu(buf[1]);
|
||||
c->u.port.high_port = le32_to_cpu(buf[2]);
|
||||
rc = context_read_and_validate(&c->context[0], p, fp);
|
||||
if (rc)
|
||||
goto bad;
|
||||
break;
|
||||
case OCON_NODE:
|
||||
rc = next_entry(nodebuf, fp, sizeof(u32) * 2);
|
||||
if (rc < 0)
|
||||
goto bad;
|
||||
c->u.node.addr = nodebuf[0]; /* network order */
|
||||
c->u.node.mask = nodebuf[1]; /* network order */
|
||||
rc = context_read_and_validate(&c->context[0], p, fp);
|
||||
if (rc)
|
||||
goto bad;
|
||||
break;
|
||||
case OCON_FSUSE:
|
||||
rc = next_entry(buf, fp, sizeof(u32)*2);
|
||||
if (rc < 0)
|
||||
goto bad;
|
||||
c->v.behavior = le32_to_cpu(buf[0]);
|
||||
if (c->v.behavior > SECURITY_FS_USE_NONE)
|
||||
goto bad;
|
||||
len = le32_to_cpu(buf[1]);
|
||||
c->u.name = kmalloc(len + 1, GFP_KERNEL);
|
||||
if (!c->u.name) {
|
||||
rc = -ENOMEM;
|
||||
goto bad;
|
||||
}
|
||||
rc = next_entry(c->u.name, fp, len);
|
||||
if (rc < 0)
|
||||
goto bad;
|
||||
c->u.name[len] = 0;
|
||||
rc = context_read_and_validate(&c->context[0], p, fp);
|
||||
if (rc)
|
||||
goto bad;
|
||||
break;
|
||||
case OCON_NODE6: {
|
||||
int k;
|
||||
|
||||
rc = next_entry(nodebuf, fp, sizeof(u32) * 8);
|
||||
if (rc < 0)
|
||||
goto bad;
|
||||
for (k = 0; k < 4; k++)
|
||||
c->u.node6.addr[k] = nodebuf[k];
|
||||
for (k = 0; k < 4; k++)
|
||||
c->u.node6.mask[k] = nodebuf[k+4];
|
||||
if (context_read_and_validate(&c->context[0], p, fp))
|
||||
goto bad;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc < 0)
|
||||
rc = ocontext_read(p, info, fp);
|
||||
if (rc)
|
||||
goto bad;
|
||||
nel = le32_to_cpu(buf[0]);
|
||||
genfs_p = NULL;
|
||||
rc = -EINVAL;
|
||||
for (i = 0; i < nel; i++) {
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc < 0)
|
||||
goto bad;
|
||||
len = le32_to_cpu(buf[0]);
|
||||
newgenfs = kzalloc(sizeof(*newgenfs), GFP_KERNEL);
|
||||
if (!newgenfs) {
|
||||
rc = -ENOMEM;
|
||||
goto bad;
|
||||
}
|
||||
|
||||
newgenfs->fstype = kmalloc(len + 1, GFP_KERNEL);
|
||||
if (!newgenfs->fstype) {
|
||||
rc = -ENOMEM;
|
||||
kfree(newgenfs);
|
||||
goto bad;
|
||||
}
|
||||
rc = next_entry(newgenfs->fstype, fp, len);
|
||||
if (rc < 0) {
|
||||
kfree(newgenfs->fstype);
|
||||
kfree(newgenfs);
|
||||
goto bad;
|
||||
}
|
||||
newgenfs->fstype[len] = 0;
|
||||
for (genfs_p = NULL, genfs = p->genfs; genfs;
|
||||
genfs_p = genfs, genfs = genfs->next) {
|
||||
if (strcmp(newgenfs->fstype, genfs->fstype) == 0) {
|
||||
printk(KERN_ERR "SELinux: dup genfs "
|
||||
"fstype %s\n", newgenfs->fstype);
|
||||
kfree(newgenfs->fstype);
|
||||
kfree(newgenfs);
|
||||
goto bad;
|
||||
}
|
||||
if (strcmp(newgenfs->fstype, genfs->fstype) < 0)
|
||||
break;
|
||||
}
|
||||
newgenfs->next = genfs;
|
||||
if (genfs_p)
|
||||
genfs_p->next = newgenfs;
|
||||
else
|
||||
p->genfs = newgenfs;
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc < 0)
|
||||
goto bad;
|
||||
nel2 = le32_to_cpu(buf[0]);
|
||||
for (j = 0; j < nel2; j++) {
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc < 0)
|
||||
goto bad;
|
||||
len = le32_to_cpu(buf[0]);
|
||||
rc = genfs_read(p, fp);
|
||||
if (rc)
|
||||
goto bad;
|
||||
|
||||
newc = kzalloc(sizeof(*newc), GFP_KERNEL);
|
||||
if (!newc) {
|
||||
rc = -ENOMEM;
|
||||
goto bad;
|
||||
}
|
||||
rc = range_read(p, fp);
|
||||
if (rc)
|
||||
goto bad;
|
||||
|
||||
newc->u.name = kmalloc(len + 1, GFP_KERNEL);
|
||||
if (!newc->u.name) {
|
||||
rc = -ENOMEM;
|
||||
goto bad_newc;
|
||||
}
|
||||
rc = next_entry(newc->u.name, fp, len);
|
||||
if (rc < 0)
|
||||
goto bad_newc;
|
||||
newc->u.name[len] = 0;
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc < 0)
|
||||
goto bad_newc;
|
||||
newc->v.sclass = le32_to_cpu(buf[0]);
|
||||
if (context_read_and_validate(&newc->context[0], p, fp))
|
||||
goto bad_newc;
|
||||
for (l = NULL, c = newgenfs->head; c;
|
||||
l = c, c = c->next) {
|
||||
if (!strcmp(newc->u.name, c->u.name) &&
|
||||
(!c->v.sclass || !newc->v.sclass ||
|
||||
newc->v.sclass == c->v.sclass)) {
|
||||
printk(KERN_ERR "SELinux: dup genfs "
|
||||
"entry (%s,%s)\n",
|
||||
newgenfs->fstype, c->u.name);
|
||||
goto bad_newc;
|
||||
}
|
||||
len = strlen(newc->u.name);
|
||||
len2 = strlen(c->u.name);
|
||||
if (len > len2)
|
||||
break;
|
||||
}
|
||||
rc = -ENOMEM;
|
||||
p->type_attr_map_array = flex_array_alloc(sizeof(struct ebitmap),
|
||||
p->p_types.nprim,
|
||||
GFP_KERNEL | __GFP_ZERO);
|
||||
if (!p->type_attr_map_array)
|
||||
goto bad;
|
||||
|
||||
newc->next = c;
|
||||
if (l)
|
||||
l->next = newc;
|
||||
else
|
||||
newgenfs->head = newc;
|
||||
}
|
||||
}
|
||||
|
||||
if (p->policyvers >= POLICYDB_VERSION_MLS) {
|
||||
int new_rangetr = p->policyvers >= POLICYDB_VERSION_RANGETRANS;
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc < 0)
|
||||
goto bad;
|
||||
nel = le32_to_cpu(buf[0]);
|
||||
for (i = 0; i < nel; i++) {
|
||||
rt = kzalloc(sizeof(*rt), GFP_KERNEL);
|
||||
if (!rt) {
|
||||
rc = -ENOMEM;
|
||||
goto bad;
|
||||
}
|
||||
rc = next_entry(buf, fp, (sizeof(u32) * 2));
|
||||
if (rc < 0) {
|
||||
kfree(rt);
|
||||
goto bad;
|
||||
}
|
||||
rt->source_type = le32_to_cpu(buf[0]);
|
||||
rt->target_type = le32_to_cpu(buf[1]);
|
||||
if (new_rangetr) {
|
||||
rc = next_entry(buf, fp, sizeof(u32));
|
||||
if (rc < 0) {
|
||||
kfree(rt);
|
||||
goto bad;
|
||||
}
|
||||
rt->target_class = le32_to_cpu(buf[0]);
|
||||
} else
|
||||
rt->target_class = p->process_class;
|
||||
if (!policydb_type_isvalid(p, rt->source_type) ||
|
||||
!policydb_type_isvalid(p, rt->target_type) ||
|
||||
!policydb_class_isvalid(p, rt->target_class)) {
|
||||
kfree(rt);
|
||||
rc = -EINVAL;
|
||||
goto bad;
|
||||
}
|
||||
r = kzalloc(sizeof(*r), GFP_KERNEL);
|
||||
if (!r) {
|
||||
kfree(rt);
|
||||
rc = -ENOMEM;
|
||||
goto bad;
|
||||
}
|
||||
rc = mls_read_range_helper(r, fp);
|
||||
if (rc) {
|
||||
kfree(rt);
|
||||
kfree(r);
|
||||
goto bad;
|
||||
}
|
||||
if (!mls_range_isvalid(p, r)) {
|
||||
printk(KERN_WARNING "SELinux: rangetrans: invalid range\n");
|
||||
kfree(rt);
|
||||
kfree(r);
|
||||
goto bad;
|
||||
}
|
||||
rc = hashtab_insert(p->range_tr, rt, r);
|
||||
if (rc) {
|
||||
kfree(rt);
|
||||
kfree(r);
|
||||
goto bad;
|
||||
}
|
||||
}
|
||||
rangetr_hash_eval(p->range_tr);
|
||||
}
|
||||
|
||||
p->type_attr_map = kmalloc(p->p_types.nprim * sizeof(struct ebitmap), GFP_KERNEL);
|
||||
if (!p->type_attr_map)
|
||||
/* preallocate so we don't have to worry about the put ever failing */
|
||||
rc = flex_array_prealloc(p->type_attr_map_array, 0, p->p_types.nprim - 1,
|
||||
GFP_KERNEL | __GFP_ZERO);
|
||||
if (rc)
|
||||
goto bad;
|
||||
|
||||
for (i = 0; i < p->p_types.nprim; i++) {
|
||||
ebitmap_init(&p->type_attr_map[i]);
|
||||
struct ebitmap *e = flex_array_get(p->type_attr_map_array, i);
|
||||
|
||||
BUG_ON(!e);
|
||||
ebitmap_init(e);
|
||||
if (p->policyvers >= POLICYDB_VERSION_AVTAB) {
|
||||
if (ebitmap_read(&p->type_attr_map[i], fp))
|
||||
rc = ebitmap_read(e, fp);
|
||||
if (rc)
|
||||
goto bad;
|
||||
}
|
||||
/* add the type itself as the degenerate case */
|
||||
if (ebitmap_set_bit(&p->type_attr_map[i], i, 1))
|
||||
goto bad;
|
||||
rc = ebitmap_set_bit(e, i, 1);
|
||||
if (rc)
|
||||
goto bad;
|
||||
}
|
||||
|
||||
rc = policydb_bounds_sanity_check(p);
|
||||
@@ -2216,8 +2300,6 @@ int policydb_read(struct policydb *p, void *fp)
|
||||
rc = 0;
|
||||
out:
|
||||
return rc;
|
||||
bad_newc:
|
||||
ocontext_destroy(newc, OCON_FSUSE);
|
||||
bad:
|
||||
if (!rc)
|
||||
rc = -EINVAL;
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#ifndef _SS_POLICYDB_H_
|
||||
#define _SS_POLICYDB_H_
|
||||
|
||||
#include <linux/flex_array.h>
|
||||
|
||||
#include "symtab.h"
|
||||
#include "avtab.h"
|
||||
#include "sidtab.h"
|
||||
@@ -246,7 +248,7 @@ struct policydb {
|
||||
struct hashtab *range_tr;
|
||||
|
||||
/* type -> attribute reverse mapping */
|
||||
struct ebitmap *type_attr_map;
|
||||
struct flex_array *type_attr_map_array;
|
||||
|
||||
struct ebitmap policycaps;
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
#include <linux/audit.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/selinux.h>
|
||||
#include <linux/flex_array.h>
|
||||
#include <net/netlabel.h>
|
||||
|
||||
#include "flask.h"
|
||||
@@ -626,8 +627,10 @@ static void context_struct_compute_av(struct context *scontext,
|
||||
*/
|
||||
avkey.target_class = tclass;
|
||||
avkey.specified = AVTAB_AV;
|
||||
sattr = &policydb.type_attr_map[scontext->type - 1];
|
||||
tattr = &policydb.type_attr_map[tcontext->type - 1];
|
||||
sattr = flex_array_get(policydb.type_attr_map_array, scontext->type - 1);
|
||||
BUG_ON(!sattr);
|
||||
tattr = flex_array_get(policydb.type_attr_map_array, tcontext->type - 1);
|
||||
BUG_ON(!tattr);
|
||||
ebitmap_for_each_positive_bit(sattr, snode, i) {
|
||||
ebitmap_for_each_positive_bit(tattr, tnode, j) {
|
||||
avkey.source_type = i + 1;
|
||||
|
||||
@@ -36,7 +36,7 @@ int symtab_init(struct symtab *s, unsigned int size)
|
||||
{
|
||||
s->table = hashtab_create(symhash, symcmp, size);
|
||||
if (!s->table)
|
||||
return -1;
|
||||
return -ENOMEM;
|
||||
s->nprim = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -123,16 +123,6 @@ struct smack_known {
|
||||
#define SMK_FSHAT "smackfshat="
|
||||
#define SMK_FSROOT "smackfsroot="
|
||||
|
||||
/*
|
||||
* xattr names
|
||||
*/
|
||||
#define XATTR_SMACK_SUFFIX "SMACK64"
|
||||
#define XATTR_SMACK_IPIN "SMACK64IPIN"
|
||||
#define XATTR_SMACK_IPOUT "SMACK64IPOUT"
|
||||
#define XATTR_NAME_SMACK XATTR_SECURITY_PREFIX XATTR_SMACK_SUFFIX
|
||||
#define XATTR_NAME_SMACKIPIN XATTR_SECURITY_PREFIX XATTR_SMACK_IPIN
|
||||
#define XATTR_NAME_SMACKIPOUT XATTR_SECURITY_PREFIX XATTR_SMACK_IPOUT
|
||||
|
||||
#define SMACK_CIPSO_OPTION "-CIPSO"
|
||||
|
||||
/*
|
||||
|
||||
@@ -598,6 +598,8 @@ static int smack_inode_rename(struct inode *old_inode,
|
||||
static int smack_inode_permission(struct inode *inode, int mask)
|
||||
{
|
||||
struct smk_audit_info ad;
|
||||
|
||||
mask &= (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND);
|
||||
/*
|
||||
* No permission to check. Existence test. Yup, it's there.
|
||||
*/
|
||||
@@ -2191,7 +2193,7 @@ static void smack_ipc_getsecid(struct kern_ipc_perm *ipp, u32 *secid)
|
||||
|
||||
/**
|
||||
* smack_d_instantiate - Make sure the blob is correct on an inode
|
||||
* @opt_dentry: unused
|
||||
* @opt_dentry: dentry where inode will be attached
|
||||
* @inode: the object
|
||||
*
|
||||
* Set the inode's security blob if it hasn't been done already.
|
||||
@@ -2310,20 +2312,10 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode)
|
||||
/*
|
||||
* Get the dentry for xattr.
|
||||
*/
|
||||
if (opt_dentry == NULL) {
|
||||
dp = d_find_alias(inode);
|
||||
if (dp == NULL)
|
||||
break;
|
||||
} else {
|
||||
dp = dget(opt_dentry);
|
||||
if (dp == NULL)
|
||||
break;
|
||||
}
|
||||
|
||||
dp = dget(opt_dentry);
|
||||
fetched = smk_fetch(inode, dp);
|
||||
if (fetched != NULL)
|
||||
final = fetched;
|
||||
|
||||
dput(dp);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
obj-y = common.o realpath.o tomoyo.o domain.o file.o gc.o path_group.o
|
||||
obj-y = common.o domain.o file.o gc.o group.o load_policy.o memory.o mount.o realpath.o securityfs_if.o tomoyo.o util.o
|
||||
|
||||
+1361
-1609
File diff suppressed because it is too large
Load Diff
+506
-346
File diff suppressed because it is too large
Load Diff
+309
-572
File diff suppressed because it is too large
Load Diff
+742
-851
File diff suppressed because it is too large
Load Diff
+151
-209
@@ -11,83 +11,75 @@
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
enum tomoyo_gc_id {
|
||||
TOMOYO_ID_PATH_GROUP,
|
||||
TOMOYO_ID_PATH_GROUP_MEMBER,
|
||||
TOMOYO_ID_DOMAIN_INITIALIZER,
|
||||
TOMOYO_ID_DOMAIN_KEEPER,
|
||||
TOMOYO_ID_ALIAS,
|
||||
TOMOYO_ID_GLOBALLY_READABLE,
|
||||
TOMOYO_ID_PATTERN,
|
||||
TOMOYO_ID_NO_REWRITE,
|
||||
TOMOYO_ID_MANAGER,
|
||||
TOMOYO_ID_NAME,
|
||||
TOMOYO_ID_ACL,
|
||||
TOMOYO_ID_DOMAIN
|
||||
};
|
||||
|
||||
struct tomoyo_gc_entry {
|
||||
struct tomoyo_gc {
|
||||
struct list_head list;
|
||||
int type;
|
||||
void *element;
|
||||
struct list_head *element;
|
||||
};
|
||||
static LIST_HEAD(tomoyo_gc_queue);
|
||||
static DEFINE_MUTEX(tomoyo_gc_mutex);
|
||||
|
||||
/* Caller holds tomoyo_policy_lock mutex. */
|
||||
static bool tomoyo_add_to_gc(const int type, void *element)
|
||||
static bool tomoyo_add_to_gc(const int type, struct list_head *element)
|
||||
{
|
||||
struct tomoyo_gc_entry *entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
|
||||
struct tomoyo_gc *entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
|
||||
if (!entry)
|
||||
return false;
|
||||
entry->type = type;
|
||||
entry->element = element;
|
||||
list_add(&entry->list, &tomoyo_gc_queue);
|
||||
list_del_rcu(element);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void tomoyo_del_allow_read
|
||||
(struct tomoyo_globally_readable_file_entry *ptr)
|
||||
static void tomoyo_del_allow_read(struct list_head *element)
|
||||
{
|
||||
struct tomoyo_readable_file *ptr =
|
||||
container_of(element, typeof(*ptr), head.list);
|
||||
tomoyo_put_name(ptr->filename);
|
||||
}
|
||||
|
||||
static void tomoyo_del_file_pattern(struct tomoyo_pattern_entry *ptr)
|
||||
static void tomoyo_del_file_pattern(struct list_head *element)
|
||||
{
|
||||
struct tomoyo_no_pattern *ptr =
|
||||
container_of(element, typeof(*ptr), head.list);
|
||||
tomoyo_put_name(ptr->pattern);
|
||||
}
|
||||
|
||||
static void tomoyo_del_no_rewrite(struct tomoyo_no_rewrite_entry *ptr)
|
||||
static void tomoyo_del_no_rewrite(struct list_head *element)
|
||||
{
|
||||
struct tomoyo_no_rewrite *ptr =
|
||||
container_of(element, typeof(*ptr), head.list);
|
||||
tomoyo_put_name(ptr->pattern);
|
||||
}
|
||||
|
||||
static void tomoyo_del_domain_initializer
|
||||
(struct tomoyo_domain_initializer_entry *ptr)
|
||||
static void tomoyo_del_transition_control(struct list_head *element)
|
||||
{
|
||||
struct tomoyo_transition_control *ptr =
|
||||
container_of(element, typeof(*ptr), head.list);
|
||||
tomoyo_put_name(ptr->domainname);
|
||||
tomoyo_put_name(ptr->program);
|
||||
}
|
||||
|
||||
static void tomoyo_del_domain_keeper(struct tomoyo_domain_keeper_entry *ptr)
|
||||
{
|
||||
tomoyo_put_name(ptr->domainname);
|
||||
tomoyo_put_name(ptr->program);
|
||||
}
|
||||
|
||||
static void tomoyo_del_alias(struct tomoyo_alias_entry *ptr)
|
||||
static void tomoyo_del_aggregator(struct list_head *element)
|
||||
{
|
||||
struct tomoyo_aggregator *ptr =
|
||||
container_of(element, typeof(*ptr), head.list);
|
||||
tomoyo_put_name(ptr->original_name);
|
||||
tomoyo_put_name(ptr->aliased_name);
|
||||
tomoyo_put_name(ptr->aggregated_name);
|
||||
}
|
||||
|
||||
static void tomoyo_del_manager(struct tomoyo_policy_manager_entry *ptr)
|
||||
static void tomoyo_del_manager(struct list_head *element)
|
||||
{
|
||||
struct tomoyo_manager *ptr =
|
||||
container_of(element, typeof(*ptr), head.list);
|
||||
tomoyo_put_name(ptr->manager);
|
||||
}
|
||||
|
||||
static void tomoyo_del_acl(struct tomoyo_acl_info *acl)
|
||||
static void tomoyo_del_acl(struct list_head *element)
|
||||
{
|
||||
struct tomoyo_acl_info *acl =
|
||||
container_of(element, typeof(*acl), list);
|
||||
switch (acl->type) {
|
||||
case TOMOYO_TYPE_PATH_ACL:
|
||||
{
|
||||
@@ -104,14 +96,41 @@ static void tomoyo_del_acl(struct tomoyo_acl_info *acl)
|
||||
tomoyo_put_name_union(&entry->name2);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
printk(KERN_WARNING "Unknown type\n");
|
||||
case TOMOYO_TYPE_PATH_NUMBER_ACL:
|
||||
{
|
||||
struct tomoyo_path_number_acl *entry
|
||||
= container_of(acl, typeof(*entry), head);
|
||||
tomoyo_put_name_union(&entry->name);
|
||||
tomoyo_put_number_union(&entry->number);
|
||||
}
|
||||
break;
|
||||
case TOMOYO_TYPE_MKDEV_ACL:
|
||||
{
|
||||
struct tomoyo_mkdev_acl *entry
|
||||
= container_of(acl, typeof(*entry), head);
|
||||
tomoyo_put_name_union(&entry->name);
|
||||
tomoyo_put_number_union(&entry->mode);
|
||||
tomoyo_put_number_union(&entry->major);
|
||||
tomoyo_put_number_union(&entry->minor);
|
||||
}
|
||||
break;
|
||||
case TOMOYO_TYPE_MOUNT_ACL:
|
||||
{
|
||||
struct tomoyo_mount_acl *entry
|
||||
= container_of(acl, typeof(*entry), head);
|
||||
tomoyo_put_name_union(&entry->dev_name);
|
||||
tomoyo_put_name_union(&entry->dir_name);
|
||||
tomoyo_put_name_union(&entry->fs_type);
|
||||
tomoyo_put_number_union(&entry->flags);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool tomoyo_del_domain(struct tomoyo_domain_info *domain)
|
||||
static bool tomoyo_del_domain(struct list_head *element)
|
||||
{
|
||||
struct tomoyo_domain_info *domain =
|
||||
container_of(element, typeof(*domain), list);
|
||||
struct tomoyo_acl_info *acl;
|
||||
struct tomoyo_acl_info *tmp;
|
||||
/*
|
||||
@@ -139,7 +158,7 @@ static bool tomoyo_del_domain(struct tomoyo_domain_info *domain)
|
||||
if (atomic_read(&domain->users))
|
||||
return false;
|
||||
list_for_each_entry_safe(acl, tmp, &domain->acl_info_list, list) {
|
||||
tomoyo_del_acl(acl);
|
||||
tomoyo_del_acl(&acl->list);
|
||||
tomoyo_memory_free(acl);
|
||||
}
|
||||
tomoyo_put_name(domain->domainname);
|
||||
@@ -147,135 +166,70 @@ static bool tomoyo_del_domain(struct tomoyo_domain_info *domain)
|
||||
}
|
||||
|
||||
|
||||
static void tomoyo_del_name(const struct tomoyo_name_entry *ptr)
|
||||
static void tomoyo_del_name(struct list_head *element)
|
||||
{
|
||||
const struct tomoyo_name *ptr =
|
||||
container_of(element, typeof(*ptr), list);
|
||||
}
|
||||
|
||||
static void tomoyo_del_path_group_member(struct tomoyo_path_group_member
|
||||
*member)
|
||||
static void tomoyo_del_path_group(struct list_head *element)
|
||||
{
|
||||
struct tomoyo_path_group *member =
|
||||
container_of(element, typeof(*member), head.list);
|
||||
tomoyo_put_name(member->member_name);
|
||||
}
|
||||
|
||||
static void tomoyo_del_path_group(struct tomoyo_path_group *group)
|
||||
static void tomoyo_del_group(struct list_head *element)
|
||||
{
|
||||
struct tomoyo_group *group =
|
||||
container_of(element, typeof(*group), list);
|
||||
tomoyo_put_name(group->group_name);
|
||||
}
|
||||
|
||||
static void tomoyo_del_number_group(struct list_head *element)
|
||||
{
|
||||
struct tomoyo_number_group *member =
|
||||
container_of(element, typeof(*member), head.list);
|
||||
}
|
||||
|
||||
static bool tomoyo_collect_member(struct list_head *member_list, int id)
|
||||
{
|
||||
struct tomoyo_acl_head *member;
|
||||
list_for_each_entry(member, member_list, list) {
|
||||
if (!member->is_deleted)
|
||||
continue;
|
||||
if (!tomoyo_add_to_gc(id, &member->list))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool tomoyo_collect_acl(struct tomoyo_domain_info *domain)
|
||||
{
|
||||
struct tomoyo_acl_info *acl;
|
||||
list_for_each_entry(acl, &domain->acl_info_list, list) {
|
||||
if (!acl->is_deleted)
|
||||
continue;
|
||||
if (!tomoyo_add_to_gc(TOMOYO_ID_ACL, &acl->list))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void tomoyo_collect_entry(void)
|
||||
{
|
||||
int i;
|
||||
if (mutex_lock_interruptible(&tomoyo_policy_lock))
|
||||
return;
|
||||
{
|
||||
struct tomoyo_globally_readable_file_entry *ptr;
|
||||
list_for_each_entry_rcu(ptr, &tomoyo_globally_readable_list,
|
||||
list) {
|
||||
if (!ptr->is_deleted)
|
||||
continue;
|
||||
if (tomoyo_add_to_gc(TOMOYO_ID_GLOBALLY_READABLE, ptr))
|
||||
list_del_rcu(&ptr->list);
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
{
|
||||
struct tomoyo_pattern_entry *ptr;
|
||||
list_for_each_entry_rcu(ptr, &tomoyo_pattern_list, list) {
|
||||
if (!ptr->is_deleted)
|
||||
continue;
|
||||
if (tomoyo_add_to_gc(TOMOYO_ID_PATTERN, ptr))
|
||||
list_del_rcu(&ptr->list);
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
{
|
||||
struct tomoyo_no_rewrite_entry *ptr;
|
||||
list_for_each_entry_rcu(ptr, &tomoyo_no_rewrite_list, list) {
|
||||
if (!ptr->is_deleted)
|
||||
continue;
|
||||
if (tomoyo_add_to_gc(TOMOYO_ID_NO_REWRITE, ptr))
|
||||
list_del_rcu(&ptr->list);
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
{
|
||||
struct tomoyo_domain_initializer_entry *ptr;
|
||||
list_for_each_entry_rcu(ptr, &tomoyo_domain_initializer_list,
|
||||
list) {
|
||||
if (!ptr->is_deleted)
|
||||
continue;
|
||||
if (tomoyo_add_to_gc(TOMOYO_ID_DOMAIN_INITIALIZER, ptr))
|
||||
list_del_rcu(&ptr->list);
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
{
|
||||
struct tomoyo_domain_keeper_entry *ptr;
|
||||
list_for_each_entry_rcu(ptr, &tomoyo_domain_keeper_list, list) {
|
||||
if (!ptr->is_deleted)
|
||||
continue;
|
||||
if (tomoyo_add_to_gc(TOMOYO_ID_DOMAIN_KEEPER, ptr))
|
||||
list_del_rcu(&ptr->list);
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
{
|
||||
struct tomoyo_alias_entry *ptr;
|
||||
list_for_each_entry_rcu(ptr, &tomoyo_alias_list, list) {
|
||||
if (!ptr->is_deleted)
|
||||
continue;
|
||||
if (tomoyo_add_to_gc(TOMOYO_ID_ALIAS, ptr))
|
||||
list_del_rcu(&ptr->list);
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
{
|
||||
struct tomoyo_policy_manager_entry *ptr;
|
||||
list_for_each_entry_rcu(ptr, &tomoyo_policy_manager_list,
|
||||
list) {
|
||||
if (!ptr->is_deleted)
|
||||
continue;
|
||||
if (tomoyo_add_to_gc(TOMOYO_ID_MANAGER, ptr))
|
||||
list_del_rcu(&ptr->list);
|
||||
else
|
||||
break;
|
||||
}
|
||||
for (i = 0; i < TOMOYO_MAX_POLICY; i++) {
|
||||
if (!tomoyo_collect_member(&tomoyo_policy_list[i], i))
|
||||
goto unlock;
|
||||
}
|
||||
{
|
||||
struct tomoyo_domain_info *domain;
|
||||
list_for_each_entry_rcu(domain, &tomoyo_domain_list, list) {
|
||||
struct tomoyo_acl_info *acl;
|
||||
list_for_each_entry_rcu(acl, &domain->acl_info_list,
|
||||
list) {
|
||||
switch (acl->type) {
|
||||
case TOMOYO_TYPE_PATH_ACL:
|
||||
if (container_of(acl,
|
||||
struct tomoyo_path_acl,
|
||||
head)->perm ||
|
||||
container_of(acl,
|
||||
struct tomoyo_path_acl,
|
||||
head)->perm_high)
|
||||
continue;
|
||||
break;
|
||||
case TOMOYO_TYPE_PATH2_ACL:
|
||||
if (container_of(acl,
|
||||
struct tomoyo_path2_acl,
|
||||
head)->perm)
|
||||
continue;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
if (tomoyo_add_to_gc(TOMOYO_ID_ACL, acl))
|
||||
list_del_rcu(&acl->list);
|
||||
else
|
||||
break;
|
||||
}
|
||||
if (!tomoyo_collect_acl(domain))
|
||||
goto unlock;
|
||||
if (!domain->is_deleted || atomic_read(&domain->users))
|
||||
continue;
|
||||
/*
|
||||
@@ -283,104 +237,92 @@ static void tomoyo_collect_entry(void)
|
||||
* refer this domain after successful execve().
|
||||
* We recheck domain->users after SRCU synchronization.
|
||||
*/
|
||||
if (tomoyo_add_to_gc(TOMOYO_ID_DOMAIN, domain))
|
||||
list_del_rcu(&domain->list);
|
||||
else
|
||||
break;
|
||||
if (!tomoyo_add_to_gc(TOMOYO_ID_DOMAIN, &domain->list))
|
||||
goto unlock;
|
||||
}
|
||||
}
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < TOMOYO_MAX_HASH; i++) {
|
||||
struct tomoyo_name_entry *ptr;
|
||||
list_for_each_entry_rcu(ptr, &tomoyo_name_list[i],
|
||||
list) {
|
||||
if (atomic_read(&ptr->users))
|
||||
continue;
|
||||
if (tomoyo_add_to_gc(TOMOYO_ID_NAME, ptr))
|
||||
list_del_rcu(&ptr->list);
|
||||
else {
|
||||
i = TOMOYO_MAX_HASH;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < TOMOYO_MAX_HASH; i++) {
|
||||
struct tomoyo_name *ptr;
|
||||
list_for_each_entry_rcu(ptr, &tomoyo_name_list[i], list) {
|
||||
if (atomic_read(&ptr->users))
|
||||
continue;
|
||||
if (!tomoyo_add_to_gc(TOMOYO_ID_NAME, &ptr->list))
|
||||
goto unlock;
|
||||
}
|
||||
}
|
||||
{
|
||||
struct tomoyo_path_group *group;
|
||||
list_for_each_entry_rcu(group, &tomoyo_path_group_list, list) {
|
||||
struct tomoyo_path_group_member *member;
|
||||
list_for_each_entry_rcu(member, &group->member_list,
|
||||
list) {
|
||||
if (!member->is_deleted)
|
||||
continue;
|
||||
if (tomoyo_add_to_gc(TOMOYO_ID_PATH_GROUP_MEMBER,
|
||||
member))
|
||||
list_del_rcu(&member->list);
|
||||
else
|
||||
break;
|
||||
}
|
||||
for (i = 0; i < TOMOYO_MAX_GROUP; i++) {
|
||||
struct list_head *list = &tomoyo_group_list[i];
|
||||
int id;
|
||||
struct tomoyo_group *group;
|
||||
switch (i) {
|
||||
case 0:
|
||||
id = TOMOYO_ID_PATH_GROUP;
|
||||
break;
|
||||
default:
|
||||
id = TOMOYO_ID_NUMBER_GROUP;
|
||||
break;
|
||||
}
|
||||
list_for_each_entry(group, list, list) {
|
||||
if (!tomoyo_collect_member(&group->member_list, id))
|
||||
goto unlock;
|
||||
if (!list_empty(&group->member_list) ||
|
||||
atomic_read(&group->users))
|
||||
continue;
|
||||
if (tomoyo_add_to_gc(TOMOYO_ID_PATH_GROUP, group))
|
||||
list_del_rcu(&group->list);
|
||||
else
|
||||
break;
|
||||
if (!tomoyo_add_to_gc(TOMOYO_ID_GROUP, &group->list))
|
||||
goto unlock;
|
||||
}
|
||||
}
|
||||
unlock:
|
||||
mutex_unlock(&tomoyo_policy_lock);
|
||||
}
|
||||
|
||||
static void tomoyo_kfree_entry(void)
|
||||
{
|
||||
struct tomoyo_gc_entry *p;
|
||||
struct tomoyo_gc_entry *tmp;
|
||||
struct tomoyo_gc *p;
|
||||
struct tomoyo_gc *tmp;
|
||||
|
||||
list_for_each_entry_safe(p, tmp, &tomoyo_gc_queue, list) {
|
||||
struct list_head *element = p->element;
|
||||
switch (p->type) {
|
||||
case TOMOYO_ID_DOMAIN_INITIALIZER:
|
||||
tomoyo_del_domain_initializer(p->element);
|
||||
case TOMOYO_ID_TRANSITION_CONTROL:
|
||||
tomoyo_del_transition_control(element);
|
||||
break;
|
||||
case TOMOYO_ID_DOMAIN_KEEPER:
|
||||
tomoyo_del_domain_keeper(p->element);
|
||||
break;
|
||||
case TOMOYO_ID_ALIAS:
|
||||
tomoyo_del_alias(p->element);
|
||||
case TOMOYO_ID_AGGREGATOR:
|
||||
tomoyo_del_aggregator(element);
|
||||
break;
|
||||
case TOMOYO_ID_GLOBALLY_READABLE:
|
||||
tomoyo_del_allow_read(p->element);
|
||||
tomoyo_del_allow_read(element);
|
||||
break;
|
||||
case TOMOYO_ID_PATTERN:
|
||||
tomoyo_del_file_pattern(p->element);
|
||||
tomoyo_del_file_pattern(element);
|
||||
break;
|
||||
case TOMOYO_ID_NO_REWRITE:
|
||||
tomoyo_del_no_rewrite(p->element);
|
||||
tomoyo_del_no_rewrite(element);
|
||||
break;
|
||||
case TOMOYO_ID_MANAGER:
|
||||
tomoyo_del_manager(p->element);
|
||||
tomoyo_del_manager(element);
|
||||
break;
|
||||
case TOMOYO_ID_NAME:
|
||||
tomoyo_del_name(p->element);
|
||||
tomoyo_del_name(element);
|
||||
break;
|
||||
case TOMOYO_ID_ACL:
|
||||
tomoyo_del_acl(p->element);
|
||||
tomoyo_del_acl(element);
|
||||
break;
|
||||
case TOMOYO_ID_DOMAIN:
|
||||
if (!tomoyo_del_domain(p->element))
|
||||
if (!tomoyo_del_domain(element))
|
||||
continue;
|
||||
break;
|
||||
case TOMOYO_ID_PATH_GROUP_MEMBER:
|
||||
tomoyo_del_path_group_member(p->element);
|
||||
break;
|
||||
case TOMOYO_ID_PATH_GROUP:
|
||||
tomoyo_del_path_group(p->element);
|
||||
tomoyo_del_path_group(element);
|
||||
break;
|
||||
default:
|
||||
printk(KERN_WARNING "Unknown type\n");
|
||||
case TOMOYO_ID_GROUP:
|
||||
tomoyo_del_group(element);
|
||||
break;
|
||||
case TOMOYO_ID_NUMBER_GROUP:
|
||||
tomoyo_del_number_group(element);
|
||||
break;
|
||||
}
|
||||
tomoyo_memory_free(p->element);
|
||||
tomoyo_memory_free(element);
|
||||
list_del(&p->list);
|
||||
kfree(p);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* security/tomoyo/group.c
|
||||
*
|
||||
* Copyright (C) 2005-2010 NTT DATA CORPORATION
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include "common.h"
|
||||
|
||||
static bool tomoyo_same_path_group(const struct tomoyo_acl_head *a,
|
||||
const struct tomoyo_acl_head *b)
|
||||
{
|
||||
return container_of(a, struct tomoyo_path_group, head)->member_name ==
|
||||
container_of(b, struct tomoyo_path_group, head)->member_name;
|
||||
}
|
||||
|
||||
static bool tomoyo_same_number_group(const struct tomoyo_acl_head *a,
|
||||
const struct tomoyo_acl_head *b)
|
||||
{
|
||||
return !memcmp(&container_of(a, struct tomoyo_number_group, head)
|
||||
->number,
|
||||
&container_of(b, struct tomoyo_number_group, head)
|
||||
->number,
|
||||
sizeof(container_of(a, struct tomoyo_number_group, head)
|
||||
->number));
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_write_group - Write "struct tomoyo_path_group"/"struct tomoyo_number_group" list.
|
||||
*
|
||||
* @data: String to parse.
|
||||
* @is_delete: True if it is a delete request.
|
||||
* @type: Type of this group.
|
||||
*
|
||||
* Returns 0 on success, negative value otherwise.
|
||||
*/
|
||||
int tomoyo_write_group(char *data, const bool is_delete, const u8 type)
|
||||
{
|
||||
struct tomoyo_group *group;
|
||||
struct list_head *member;
|
||||
char *w[2];
|
||||
int error = -EINVAL;
|
||||
if (!tomoyo_tokenize(data, w, sizeof(w)) || !w[1][0])
|
||||
return -EINVAL;
|
||||
group = tomoyo_get_group(w[0], type);
|
||||
if (!group)
|
||||
return -ENOMEM;
|
||||
member = &group->member_list;
|
||||
if (type == TOMOYO_PATH_GROUP) {
|
||||
struct tomoyo_path_group e = { };
|
||||
e.member_name = tomoyo_get_name(w[1]);
|
||||
if (!e.member_name) {
|
||||
error = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
error = tomoyo_update_policy(&e.head, sizeof(e), is_delete,
|
||||
member, tomoyo_same_path_group);
|
||||
tomoyo_put_name(e.member_name);
|
||||
} else if (type == TOMOYO_NUMBER_GROUP) {
|
||||
struct tomoyo_number_group e = { };
|
||||
if (w[1][0] == '@'
|
||||
|| !tomoyo_parse_number_union(w[1], &e.number)
|
||||
|| e.number.values[0] > e.number.values[1])
|
||||
goto out;
|
||||
error = tomoyo_update_policy(&e.head, sizeof(e), is_delete,
|
||||
member, tomoyo_same_number_group);
|
||||
/*
|
||||
* tomoyo_put_number_union() is not needed because
|
||||
* w[1][0] != '@'.
|
||||
*/
|
||||
}
|
||||
out:
|
||||
tomoyo_put_group(group);
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_path_matches_group - Check whether the given pathname matches members of the given pathname group.
|
||||
*
|
||||
* @pathname: The name of pathname.
|
||||
* @group: Pointer to "struct tomoyo_path_group".
|
||||
*
|
||||
* Returns matched member's pathname if @pathname matches pathnames in @group,
|
||||
* NULL otherwise.
|
||||
*
|
||||
* Caller holds tomoyo_read_lock().
|
||||
*/
|
||||
const struct tomoyo_path_info *
|
||||
tomoyo_path_matches_group(const struct tomoyo_path_info *pathname,
|
||||
const struct tomoyo_group *group)
|
||||
{
|
||||
struct tomoyo_path_group *member;
|
||||
list_for_each_entry_rcu(member, &group->member_list, head.list) {
|
||||
if (member->head.is_deleted)
|
||||
continue;
|
||||
if (!tomoyo_path_matches_pattern(pathname, member->member_name))
|
||||
continue;
|
||||
return member->member_name;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_number_matches_group - Check whether the given number matches members of the given number group.
|
||||
*
|
||||
* @min: Min number.
|
||||
* @max: Max number.
|
||||
* @group: Pointer to "struct tomoyo_number_group".
|
||||
*
|
||||
* Returns true if @min and @max partially overlaps @group, false otherwise.
|
||||
*
|
||||
* Caller holds tomoyo_read_lock().
|
||||
*/
|
||||
bool tomoyo_number_matches_group(const unsigned long min,
|
||||
const unsigned long max,
|
||||
const struct tomoyo_group *group)
|
||||
{
|
||||
struct tomoyo_number_group *member;
|
||||
bool matched = false;
|
||||
list_for_each_entry_rcu(member, &group->member_list, head.list) {
|
||||
if (member->head.is_deleted)
|
||||
continue;
|
||||
if (min > member->number.values[1] ||
|
||||
max < member->number.values[0])
|
||||
continue;
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* security/tomoyo/load_policy.c
|
||||
*
|
||||
* Policy loader launcher for TOMOYO.
|
||||
*
|
||||
* Copyright (C) 2005-2010 NTT DATA CORPORATION
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
/* path to policy loader */
|
||||
static const char *tomoyo_loader = "/sbin/tomoyo-init";
|
||||
|
||||
/**
|
||||
* tomoyo_policy_loader_exists - Check whether /sbin/tomoyo-init exists.
|
||||
*
|
||||
* Returns true if /sbin/tomoyo-init exists, false otherwise.
|
||||
*/
|
||||
static bool tomoyo_policy_loader_exists(void)
|
||||
{
|
||||
/*
|
||||
* Don't activate MAC if the policy loader doesn't exist.
|
||||
* If the initrd includes /sbin/init but real-root-dev has not
|
||||
* mounted on / yet, activating MAC will block the system since
|
||||
* policies are not loaded yet.
|
||||
* Thus, let do_execve() call this function everytime.
|
||||
*/
|
||||
struct path path;
|
||||
|
||||
if (kern_path(tomoyo_loader, LOOKUP_FOLLOW, &path)) {
|
||||
printk(KERN_INFO "Not activating Mandatory Access Control now "
|
||||
"since %s doesn't exist.\n", tomoyo_loader);
|
||||
return false;
|
||||
}
|
||||
path_put(&path);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_load_policy - Run external policy loader to load policy.
|
||||
*
|
||||
* @filename: The program about to start.
|
||||
*
|
||||
* This function checks whether @filename is /sbin/init , and if so
|
||||
* invoke /sbin/tomoyo-init and wait for the termination of /sbin/tomoyo-init
|
||||
* and then continues invocation of /sbin/init.
|
||||
* /sbin/tomoyo-init reads policy files in /etc/tomoyo/ directory and
|
||||
* writes to /sys/kernel/security/tomoyo/ interfaces.
|
||||
*
|
||||
* Returns nothing.
|
||||
*/
|
||||
void tomoyo_load_policy(const char *filename)
|
||||
{
|
||||
char *argv[2];
|
||||
char *envp[3];
|
||||
|
||||
if (tomoyo_policy_loaded)
|
||||
return;
|
||||
/*
|
||||
* Check filename is /sbin/init or /sbin/tomoyo-start.
|
||||
* /sbin/tomoyo-start is a dummy filename in case where /sbin/init can't
|
||||
* be passed.
|
||||
* You can create /sbin/tomoyo-start by
|
||||
* "ln -s /bin/true /sbin/tomoyo-start".
|
||||
*/
|
||||
if (strcmp(filename, "/sbin/init") &&
|
||||
strcmp(filename, "/sbin/tomoyo-start"))
|
||||
return;
|
||||
if (!tomoyo_policy_loader_exists())
|
||||
return;
|
||||
|
||||
printk(KERN_INFO "Calling %s to load policy. Please wait.\n",
|
||||
tomoyo_loader);
|
||||
argv[0] = (char *) tomoyo_loader;
|
||||
argv[1] = NULL;
|
||||
envp[0] = "HOME=/";
|
||||
envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
|
||||
envp[2] = NULL;
|
||||
call_usermodehelper(argv[0], argv, envp, 1);
|
||||
tomoyo_check_profile();
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* security/tomoyo/memory.c
|
||||
*
|
||||
* Memory management functions for TOMOYO.
|
||||
*
|
||||
* Copyright (C) 2005-2010 NTT DATA CORPORATION
|
||||
*/
|
||||
|
||||
#include <linux/hash.h>
|
||||
#include <linux/slab.h>
|
||||
#include "common.h"
|
||||
|
||||
/**
|
||||
* tomoyo_warn_oom - Print out of memory warning message.
|
||||
*
|
||||
* @function: Function's name.
|
||||
*/
|
||||
void tomoyo_warn_oom(const char *function)
|
||||
{
|
||||
/* Reduce error messages. */
|
||||
static pid_t tomoyo_last_pid;
|
||||
const pid_t pid = current->pid;
|
||||
if (tomoyo_last_pid != pid) {
|
||||
printk(KERN_WARNING "ERROR: Out of memory at %s.\n",
|
||||
function);
|
||||
tomoyo_last_pid = pid;
|
||||
}
|
||||
if (!tomoyo_policy_loaded)
|
||||
panic("MAC Initialization failed.\n");
|
||||
}
|
||||
|
||||
/* Memory allocated for policy. */
|
||||
static atomic_t tomoyo_policy_memory_size;
|
||||
/* Quota for holding policy. */
|
||||
static unsigned int tomoyo_quota_for_policy;
|
||||
|
||||
/**
|
||||
* tomoyo_memory_ok - Check memory quota.
|
||||
*
|
||||
* @ptr: Pointer to allocated memory.
|
||||
*
|
||||
* Returns true on success, false otherwise.
|
||||
*
|
||||
* Returns true if @ptr is not NULL and quota not exceeded, false otherwise.
|
||||
*/
|
||||
bool tomoyo_memory_ok(void *ptr)
|
||||
{
|
||||
size_t s = ptr ? ksize(ptr) : 0;
|
||||
atomic_add(s, &tomoyo_policy_memory_size);
|
||||
if (ptr && (!tomoyo_quota_for_policy ||
|
||||
atomic_read(&tomoyo_policy_memory_size)
|
||||
<= tomoyo_quota_for_policy)) {
|
||||
memset(ptr, 0, s);
|
||||
return true;
|
||||
}
|
||||
atomic_sub(s, &tomoyo_policy_memory_size);
|
||||
tomoyo_warn_oom(__func__);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_commit_ok - Check memory quota.
|
||||
*
|
||||
* @data: Data to copy from.
|
||||
* @size: Size in byte.
|
||||
*
|
||||
* Returns pointer to allocated memory on success, NULL otherwise.
|
||||
* @data is zero-cleared on success.
|
||||
*/
|
||||
void *tomoyo_commit_ok(void *data, const unsigned int size)
|
||||
{
|
||||
void *ptr = kzalloc(size, GFP_NOFS);
|
||||
if (tomoyo_memory_ok(ptr)) {
|
||||
memmove(ptr, data, size);
|
||||
memset(data, 0, size);
|
||||
return ptr;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_memory_free - Free memory for elements.
|
||||
*
|
||||
* @ptr: Pointer to allocated memory.
|
||||
*/
|
||||
void tomoyo_memory_free(void *ptr)
|
||||
{
|
||||
atomic_sub(ksize(ptr), &tomoyo_policy_memory_size);
|
||||
kfree(ptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_get_group - Allocate memory for "struct tomoyo_path_group"/"struct tomoyo_number_group".
|
||||
*
|
||||
* @group_name: The name of address group.
|
||||
* @idx: Index number.
|
||||
*
|
||||
* Returns pointer to "struct tomoyo_group" on success, NULL otherwise.
|
||||
*/
|
||||
struct tomoyo_group *tomoyo_get_group(const char *group_name, const u8 idx)
|
||||
{
|
||||
struct tomoyo_group e = { };
|
||||
struct tomoyo_group *group = NULL;
|
||||
bool found = false;
|
||||
if (!tomoyo_correct_word(group_name) || idx >= TOMOYO_MAX_GROUP)
|
||||
return NULL;
|
||||
e.group_name = tomoyo_get_name(group_name);
|
||||
if (!e.group_name)
|
||||
return NULL;
|
||||
if (mutex_lock_interruptible(&tomoyo_policy_lock))
|
||||
goto out;
|
||||
list_for_each_entry(group, &tomoyo_group_list[idx], list) {
|
||||
if (e.group_name != group->group_name)
|
||||
continue;
|
||||
atomic_inc(&group->users);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (!found) {
|
||||
struct tomoyo_group *entry = tomoyo_commit_ok(&e, sizeof(e));
|
||||
if (entry) {
|
||||
INIT_LIST_HEAD(&entry->member_list);
|
||||
atomic_set(&entry->users, 1);
|
||||
list_add_tail_rcu(&entry->list,
|
||||
&tomoyo_group_list[idx]);
|
||||
group = entry;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&tomoyo_policy_lock);
|
||||
out:
|
||||
tomoyo_put_name(e.group_name);
|
||||
return found ? group : NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* tomoyo_name_list is used for holding string data used by TOMOYO.
|
||||
* Since same string data is likely used for multiple times (e.g.
|
||||
* "/lib/libc-2.5.so"), TOMOYO shares string data in the form of
|
||||
* "const struct tomoyo_path_info *".
|
||||
*/
|
||||
struct list_head tomoyo_name_list[TOMOYO_MAX_HASH];
|
||||
|
||||
/**
|
||||
* tomoyo_get_name - Allocate permanent memory for string data.
|
||||
*
|
||||
* @name: The string to store into the permernent memory.
|
||||
*
|
||||
* Returns pointer to "struct tomoyo_path_info" on success, NULL otherwise.
|
||||
*/
|
||||
const struct tomoyo_path_info *tomoyo_get_name(const char *name)
|
||||
{
|
||||
struct tomoyo_name *ptr;
|
||||
unsigned int hash;
|
||||
int len;
|
||||
int allocated_len;
|
||||
struct list_head *head;
|
||||
|
||||
if (!name)
|
||||
return NULL;
|
||||
len = strlen(name) + 1;
|
||||
hash = full_name_hash((const unsigned char *) name, len - 1);
|
||||
head = &tomoyo_name_list[hash_long(hash, TOMOYO_HASH_BITS)];
|
||||
if (mutex_lock_interruptible(&tomoyo_policy_lock))
|
||||
return NULL;
|
||||
list_for_each_entry(ptr, head, list) {
|
||||
if (hash != ptr->entry.hash || strcmp(name, ptr->entry.name))
|
||||
continue;
|
||||
atomic_inc(&ptr->users);
|
||||
goto out;
|
||||
}
|
||||
ptr = kzalloc(sizeof(*ptr) + len, GFP_NOFS);
|
||||
allocated_len = ptr ? ksize(ptr) : 0;
|
||||
if (!ptr || (tomoyo_quota_for_policy &&
|
||||
atomic_read(&tomoyo_policy_memory_size) + allocated_len
|
||||
> tomoyo_quota_for_policy)) {
|
||||
kfree(ptr);
|
||||
ptr = NULL;
|
||||
tomoyo_warn_oom(__func__);
|
||||
goto out;
|
||||
}
|
||||
atomic_add(allocated_len, &tomoyo_policy_memory_size);
|
||||
ptr->entry.name = ((char *) ptr) + sizeof(*ptr);
|
||||
memmove((char *) ptr->entry.name, name, len);
|
||||
atomic_set(&ptr->users, 1);
|
||||
tomoyo_fill_path_info(&ptr->entry);
|
||||
list_add_tail(&ptr->list, head);
|
||||
out:
|
||||
mutex_unlock(&tomoyo_policy_lock);
|
||||
return ptr ? &ptr->entry : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_mm_init - Initialize mm related code.
|
||||
*/
|
||||
void __init tomoyo_mm_init(void)
|
||||
{
|
||||
int idx;
|
||||
|
||||
for (idx = 0; idx < TOMOYO_MAX_POLICY; idx++)
|
||||
INIT_LIST_HEAD(&tomoyo_policy_list[idx]);
|
||||
for (idx = 0; idx < TOMOYO_MAX_GROUP; idx++)
|
||||
INIT_LIST_HEAD(&tomoyo_group_list[idx]);
|
||||
for (idx = 0; idx < TOMOYO_MAX_HASH; idx++)
|
||||
INIT_LIST_HEAD(&tomoyo_name_list[idx]);
|
||||
INIT_LIST_HEAD(&tomoyo_kernel_domain.acl_info_list);
|
||||
tomoyo_kernel_domain.domainname = tomoyo_get_name(TOMOYO_ROOT_NAME);
|
||||
list_add_tail_rcu(&tomoyo_kernel_domain.list, &tomoyo_domain_list);
|
||||
idx = tomoyo_read_lock();
|
||||
if (tomoyo_find_domain(TOMOYO_ROOT_NAME) != &tomoyo_kernel_domain)
|
||||
panic("Can't register tomoyo_kernel_domain");
|
||||
{
|
||||
/* Load built-in policy. */
|
||||
tomoyo_write_transition_control("/sbin/hotplug", false,
|
||||
TOMOYO_TRANSITION_CONTROL_INITIALIZE);
|
||||
tomoyo_write_transition_control("/sbin/modprobe", false,
|
||||
TOMOYO_TRANSITION_CONTROL_INITIALIZE);
|
||||
}
|
||||
tomoyo_read_unlock(idx);
|
||||
}
|
||||
|
||||
|
||||
/* Memory allocated for query lists. */
|
||||
unsigned int tomoyo_query_memory_size;
|
||||
/* Quota for holding query lists. */
|
||||
unsigned int tomoyo_quota_for_query;
|
||||
|
||||
/**
|
||||
* tomoyo_read_memory_counter - Check for memory usage in bytes.
|
||||
*
|
||||
* @head: Pointer to "struct tomoyo_io_buffer".
|
||||
*
|
||||
* Returns memory usage.
|
||||
*/
|
||||
void tomoyo_read_memory_counter(struct tomoyo_io_buffer *head)
|
||||
{
|
||||
if (!head->r.eof) {
|
||||
const unsigned int policy
|
||||
= atomic_read(&tomoyo_policy_memory_size);
|
||||
const unsigned int query = tomoyo_query_memory_size;
|
||||
char buffer[64];
|
||||
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
if (tomoyo_quota_for_policy)
|
||||
snprintf(buffer, sizeof(buffer) - 1,
|
||||
" (Quota: %10u)",
|
||||
tomoyo_quota_for_policy);
|
||||
else
|
||||
buffer[0] = '\0';
|
||||
tomoyo_io_printf(head, "Policy: %10u%s\n", policy,
|
||||
buffer);
|
||||
if (tomoyo_quota_for_query)
|
||||
snprintf(buffer, sizeof(buffer) - 1,
|
||||
" (Quota: %10u)",
|
||||
tomoyo_quota_for_query);
|
||||
else
|
||||
buffer[0] = '\0';
|
||||
tomoyo_io_printf(head, "Query lists: %10u%s\n", query,
|
||||
buffer);
|
||||
tomoyo_io_printf(head, "Total: %10u\n", policy + query);
|
||||
head->r.eof = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_write_memory_quota - Set memory quota.
|
||||
*
|
||||
* @head: Pointer to "struct tomoyo_io_buffer".
|
||||
*
|
||||
* Returns 0.
|
||||
*/
|
||||
int tomoyo_write_memory_quota(struct tomoyo_io_buffer *head)
|
||||
{
|
||||
char *data = head->write_buf;
|
||||
unsigned int size;
|
||||
|
||||
if (sscanf(data, "Policy: %u", &size) == 1)
|
||||
tomoyo_quota_for_policy = size;
|
||||
else if (sscanf(data, "Query lists: %u", &size) == 1)
|
||||
tomoyo_quota_for_query = size;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* security/tomoyo/mount.c
|
||||
*
|
||||
* Copyright (C) 2005-2010 NTT DATA CORPORATION
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include "common.h"
|
||||
|
||||
/* Keywords for mount restrictions. */
|
||||
|
||||
/* Allow to call 'mount --bind /source_dir /dest_dir' */
|
||||
#define TOMOYO_MOUNT_BIND_KEYWORD "--bind"
|
||||
/* Allow to call 'mount --move /old_dir /new_dir ' */
|
||||
#define TOMOYO_MOUNT_MOVE_KEYWORD "--move"
|
||||
/* Allow to call 'mount -o remount /dir ' */
|
||||
#define TOMOYO_MOUNT_REMOUNT_KEYWORD "--remount"
|
||||
/* Allow to call 'mount --make-unbindable /dir' */
|
||||
#define TOMOYO_MOUNT_MAKE_UNBINDABLE_KEYWORD "--make-unbindable"
|
||||
/* Allow to call 'mount --make-private /dir' */
|
||||
#define TOMOYO_MOUNT_MAKE_PRIVATE_KEYWORD "--make-private"
|
||||
/* Allow to call 'mount --make-slave /dir' */
|
||||
#define TOMOYO_MOUNT_MAKE_SLAVE_KEYWORD "--make-slave"
|
||||
/* Allow to call 'mount --make-shared /dir' */
|
||||
#define TOMOYO_MOUNT_MAKE_SHARED_KEYWORD "--make-shared"
|
||||
|
||||
/**
|
||||
* tomoyo_audit_mount_log - Audit mount log.
|
||||
*
|
||||
* @r: Pointer to "struct tomoyo_request_info".
|
||||
*
|
||||
* Returns 0 on success, negative value otherwise.
|
||||
*/
|
||||
static int tomoyo_audit_mount_log(struct tomoyo_request_info *r)
|
||||
{
|
||||
const char *dev = r->param.mount.dev->name;
|
||||
const char *dir = r->param.mount.dir->name;
|
||||
const char *type = r->param.mount.type->name;
|
||||
const unsigned long flags = r->param.mount.flags;
|
||||
if (r->granted)
|
||||
return 0;
|
||||
if (!strcmp(type, TOMOYO_MOUNT_REMOUNT_KEYWORD))
|
||||
tomoyo_warn_log(r, "mount -o remount %s 0x%lX", dir, flags);
|
||||
else if (!strcmp(type, TOMOYO_MOUNT_BIND_KEYWORD)
|
||||
|| !strcmp(type, TOMOYO_MOUNT_MOVE_KEYWORD))
|
||||
tomoyo_warn_log(r, "mount %s %s %s 0x%lX", type, dev, dir,
|
||||
flags);
|
||||
else if (!strcmp(type, TOMOYO_MOUNT_MAKE_UNBINDABLE_KEYWORD) ||
|
||||
!strcmp(type, TOMOYO_MOUNT_MAKE_PRIVATE_KEYWORD) ||
|
||||
!strcmp(type, TOMOYO_MOUNT_MAKE_SLAVE_KEYWORD) ||
|
||||
!strcmp(type, TOMOYO_MOUNT_MAKE_SHARED_KEYWORD))
|
||||
tomoyo_warn_log(r, "mount %s %s 0x%lX", type, dir, flags);
|
||||
else
|
||||
tomoyo_warn_log(r, "mount -t %s %s %s 0x%lX", type, dev, dir,
|
||||
flags);
|
||||
return tomoyo_supervisor(r,
|
||||
TOMOYO_KEYWORD_ALLOW_MOUNT "%s %s %s 0x%lX\n",
|
||||
tomoyo_pattern(r->param.mount.dev),
|
||||
tomoyo_pattern(r->param.mount.dir), type,
|
||||
flags);
|
||||
}
|
||||
|
||||
static bool tomoyo_check_mount_acl(struct tomoyo_request_info *r,
|
||||
const struct tomoyo_acl_info *ptr)
|
||||
{
|
||||
const struct tomoyo_mount_acl *acl =
|
||||
container_of(ptr, typeof(*acl), head);
|
||||
return tomoyo_compare_number_union(r->param.mount.flags, &acl->flags) &&
|
||||
tomoyo_compare_name_union(r->param.mount.type, &acl->fs_type) &&
|
||||
tomoyo_compare_name_union(r->param.mount.dir, &acl->dir_name) &&
|
||||
(!r->param.mount.need_dev ||
|
||||
tomoyo_compare_name_union(r->param.mount.dev, &acl->dev_name));
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_mount_acl - Check permission for mount() operation.
|
||||
*
|
||||
* @r: Pointer to "struct tomoyo_request_info".
|
||||
* @dev_name: Name of device file.
|
||||
* @dir: Pointer to "struct path".
|
||||
* @type: Name of filesystem type.
|
||||
* @flags: Mount options.
|
||||
*
|
||||
* Returns 0 on success, negative value otherwise.
|
||||
*
|
||||
* Caller holds tomoyo_read_lock().
|
||||
*/
|
||||
static int tomoyo_mount_acl(struct tomoyo_request_info *r, char *dev_name,
|
||||
struct path *dir, char *type, unsigned long flags)
|
||||
{
|
||||
struct path path;
|
||||
struct file_system_type *fstype = NULL;
|
||||
const char *requested_type = NULL;
|
||||
const char *requested_dir_name = NULL;
|
||||
const char *requested_dev_name = NULL;
|
||||
struct tomoyo_path_info rtype;
|
||||
struct tomoyo_path_info rdev;
|
||||
struct tomoyo_path_info rdir;
|
||||
int need_dev = 0;
|
||||
int error = -ENOMEM;
|
||||
|
||||
/* Get fstype. */
|
||||
requested_type = tomoyo_encode(type);
|
||||
if (!requested_type)
|
||||
goto out;
|
||||
rtype.name = requested_type;
|
||||
tomoyo_fill_path_info(&rtype);
|
||||
|
||||
/* Get mount point. */
|
||||
requested_dir_name = tomoyo_realpath_from_path(dir);
|
||||
if (!requested_dir_name) {
|
||||
error = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
rdir.name = requested_dir_name;
|
||||
tomoyo_fill_path_info(&rdir);
|
||||
|
||||
/* Compare fs name. */
|
||||
if (!strcmp(type, TOMOYO_MOUNT_REMOUNT_KEYWORD)) {
|
||||
/* dev_name is ignored. */
|
||||
} else if (!strcmp(type, TOMOYO_MOUNT_MAKE_UNBINDABLE_KEYWORD) ||
|
||||
!strcmp(type, TOMOYO_MOUNT_MAKE_PRIVATE_KEYWORD) ||
|
||||
!strcmp(type, TOMOYO_MOUNT_MAKE_SLAVE_KEYWORD) ||
|
||||
!strcmp(type, TOMOYO_MOUNT_MAKE_SHARED_KEYWORD)) {
|
||||
/* dev_name is ignored. */
|
||||
} else if (!strcmp(type, TOMOYO_MOUNT_BIND_KEYWORD) ||
|
||||
!strcmp(type, TOMOYO_MOUNT_MOVE_KEYWORD)) {
|
||||
need_dev = -1; /* dev_name is a directory */
|
||||
} else {
|
||||
fstype = get_fs_type(type);
|
||||
if (!fstype) {
|
||||
error = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
if (fstype->fs_flags & FS_REQUIRES_DEV)
|
||||
/* dev_name is a block device file. */
|
||||
need_dev = 1;
|
||||
}
|
||||
if (need_dev) {
|
||||
/* Get mount point or device file. */
|
||||
if (kern_path(dev_name, LOOKUP_FOLLOW, &path)) {
|
||||
error = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
requested_dev_name = tomoyo_realpath_from_path(&path);
|
||||
if (!requested_dev_name) {
|
||||
error = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
/* Map dev_name to "<NULL>" if no dev_name given. */
|
||||
if (!dev_name)
|
||||
dev_name = "<NULL>";
|
||||
requested_dev_name = tomoyo_encode(dev_name);
|
||||
if (!requested_dev_name) {
|
||||
error = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
rdev.name = requested_dev_name;
|
||||
tomoyo_fill_path_info(&rdev);
|
||||
r->param_type = TOMOYO_TYPE_MOUNT_ACL;
|
||||
r->param.mount.need_dev = need_dev;
|
||||
r->param.mount.dev = &rdev;
|
||||
r->param.mount.dir = &rdir;
|
||||
r->param.mount.type = &rtype;
|
||||
r->param.mount.flags = flags;
|
||||
do {
|
||||
tomoyo_check_acl(r, tomoyo_check_mount_acl);
|
||||
error = tomoyo_audit_mount_log(r);
|
||||
} while (error == TOMOYO_RETRY_REQUEST);
|
||||
out:
|
||||
kfree(requested_dev_name);
|
||||
kfree(requested_dir_name);
|
||||
if (fstype)
|
||||
put_filesystem(fstype);
|
||||
kfree(requested_type);
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_mount_permission - Check permission for mount() operation.
|
||||
*
|
||||
* @dev_name: Name of device file.
|
||||
* @path: Pointer to "struct path".
|
||||
* @type: Name of filesystem type. May be NULL.
|
||||
* @flags: Mount options.
|
||||
* @data_page: Optional data. May be NULL.
|
||||
*
|
||||
* Returns 0 on success, negative value otherwise.
|
||||
*/
|
||||
int tomoyo_mount_permission(char *dev_name, struct path *path, char *type,
|
||||
unsigned long flags, void *data_page)
|
||||
{
|
||||
struct tomoyo_request_info r;
|
||||
int error;
|
||||
int idx;
|
||||
|
||||
if (tomoyo_init_request_info(&r, NULL, TOMOYO_MAC_FILE_MOUNT)
|
||||
== TOMOYO_CONFIG_DISABLED)
|
||||
return 0;
|
||||
if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
|
||||
flags &= ~MS_MGC_MSK;
|
||||
if (flags & MS_REMOUNT) {
|
||||
type = TOMOYO_MOUNT_REMOUNT_KEYWORD;
|
||||
flags &= ~MS_REMOUNT;
|
||||
}
|
||||
if (flags & MS_MOVE) {
|
||||
type = TOMOYO_MOUNT_MOVE_KEYWORD;
|
||||
flags &= ~MS_MOVE;
|
||||
}
|
||||
if (flags & MS_BIND) {
|
||||
type = TOMOYO_MOUNT_BIND_KEYWORD;
|
||||
flags &= ~MS_BIND;
|
||||
}
|
||||
if (flags & MS_UNBINDABLE) {
|
||||
type = TOMOYO_MOUNT_MAKE_UNBINDABLE_KEYWORD;
|
||||
flags &= ~MS_UNBINDABLE;
|
||||
}
|
||||
if (flags & MS_PRIVATE) {
|
||||
type = TOMOYO_MOUNT_MAKE_PRIVATE_KEYWORD;
|
||||
flags &= ~MS_PRIVATE;
|
||||
}
|
||||
if (flags & MS_SLAVE) {
|
||||
type = TOMOYO_MOUNT_MAKE_SLAVE_KEYWORD;
|
||||
flags &= ~MS_SLAVE;
|
||||
}
|
||||
if (flags & MS_SHARED) {
|
||||
type = TOMOYO_MOUNT_MAKE_SHARED_KEYWORD;
|
||||
flags &= ~MS_SHARED;
|
||||
}
|
||||
if (!type)
|
||||
type = "<NULL>";
|
||||
idx = tomoyo_read_lock();
|
||||
error = tomoyo_mount_acl(&r, dev_name, path, type, flags);
|
||||
tomoyo_read_unlock(idx);
|
||||
return error;
|
||||
}
|
||||
|
||||
static bool tomoyo_same_mount_acl(const struct tomoyo_acl_info *a,
|
||||
const struct tomoyo_acl_info *b)
|
||||
{
|
||||
const struct tomoyo_mount_acl *p1 = container_of(a, typeof(*p1), head);
|
||||
const struct tomoyo_mount_acl *p2 = container_of(b, typeof(*p2), head);
|
||||
return tomoyo_same_acl_head(&p1->head, &p2->head) &&
|
||||
tomoyo_same_name_union(&p1->dev_name, &p2->dev_name) &&
|
||||
tomoyo_same_name_union(&p1->dir_name, &p2->dir_name) &&
|
||||
tomoyo_same_name_union(&p1->fs_type, &p2->fs_type) &&
|
||||
tomoyo_same_number_union(&p1->flags, &p2->flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_write_mount - Write "struct tomoyo_mount_acl" list.
|
||||
*
|
||||
* @data: String to parse.
|
||||
* @domain: Pointer to "struct tomoyo_domain_info".
|
||||
* @is_delete: True if it is a delete request.
|
||||
*
|
||||
* Returns 0 on success, negative value otherwise.
|
||||
*
|
||||
* Caller holds tomoyo_read_lock().
|
||||
*/
|
||||
int tomoyo_write_mount(char *data, struct tomoyo_domain_info *domain,
|
||||
const bool is_delete)
|
||||
{
|
||||
struct tomoyo_mount_acl e = { .head.type = TOMOYO_TYPE_MOUNT_ACL };
|
||||
int error = is_delete ? -ENOENT : -ENOMEM;
|
||||
char *w[4];
|
||||
if (!tomoyo_tokenize(data, w, sizeof(w)) || !w[3][0])
|
||||
return -EINVAL;
|
||||
if (!tomoyo_parse_name_union(w[0], &e.dev_name) ||
|
||||
!tomoyo_parse_name_union(w[1], &e.dir_name) ||
|
||||
!tomoyo_parse_name_union(w[2], &e.fs_type) ||
|
||||
!tomoyo_parse_number_union(w[3], &e.flags))
|
||||
goto out;
|
||||
error = tomoyo_update_domain(&e.head, sizeof(e), is_delete, domain,
|
||||
tomoyo_same_mount_acl, NULL);
|
||||
out:
|
||||
tomoyo_put_name_union(&e.dev_name);
|
||||
tomoyo_put_name_union(&e.dir_name);
|
||||
tomoyo_put_name_union(&e.fs_type);
|
||||
tomoyo_put_number_union(&e.flags);
|
||||
return error;
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
/*
|
||||
* security/tomoyo/path_group.c
|
||||
*
|
||||
* Copyright (C) 2005-2009 NTT DATA CORPORATION
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include "common.h"
|
||||
/* The list for "struct ccs_path_group". */
|
||||
LIST_HEAD(tomoyo_path_group_list);
|
||||
|
||||
/**
|
||||
* tomoyo_get_path_group - Allocate memory for "struct tomoyo_path_group".
|
||||
*
|
||||
* @group_name: The name of pathname group.
|
||||
*
|
||||
* Returns pointer to "struct tomoyo_path_group" on success, NULL otherwise.
|
||||
*/
|
||||
struct tomoyo_path_group *tomoyo_get_path_group(const char *group_name)
|
||||
{
|
||||
struct tomoyo_path_group *entry = NULL;
|
||||
struct tomoyo_path_group *group = NULL;
|
||||
const struct tomoyo_path_info *saved_group_name;
|
||||
int error = -ENOMEM;
|
||||
if (!tomoyo_is_correct_path(group_name, 0, 0, 0) ||
|
||||
!group_name[0])
|
||||
return NULL;
|
||||
saved_group_name = tomoyo_get_name(group_name);
|
||||
if (!saved_group_name)
|
||||
return NULL;
|
||||
entry = kzalloc(sizeof(*entry), GFP_NOFS);
|
||||
if (mutex_lock_interruptible(&tomoyo_policy_lock))
|
||||
goto out;
|
||||
list_for_each_entry_rcu(group, &tomoyo_path_group_list, list) {
|
||||
if (saved_group_name != group->group_name)
|
||||
continue;
|
||||
atomic_inc(&group->users);
|
||||
error = 0;
|
||||
break;
|
||||
}
|
||||
if (error && tomoyo_memory_ok(entry)) {
|
||||
INIT_LIST_HEAD(&entry->member_list);
|
||||
entry->group_name = saved_group_name;
|
||||
saved_group_name = NULL;
|
||||
atomic_set(&entry->users, 1);
|
||||
list_add_tail_rcu(&entry->list, &tomoyo_path_group_list);
|
||||
group = entry;
|
||||
entry = NULL;
|
||||
error = 0;
|
||||
}
|
||||
mutex_unlock(&tomoyo_policy_lock);
|
||||
out:
|
||||
tomoyo_put_name(saved_group_name);
|
||||
kfree(entry);
|
||||
return !error ? group : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_write_path_group_policy - Write "struct tomoyo_path_group" list.
|
||||
*
|
||||
* @data: String to parse.
|
||||
* @is_delete: True if it is a delete request.
|
||||
*
|
||||
* Returns 0 on success, nagative value otherwise.
|
||||
*/
|
||||
int tomoyo_write_path_group_policy(char *data, const bool is_delete)
|
||||
{
|
||||
struct tomoyo_path_group *group;
|
||||
struct tomoyo_path_group_member *member;
|
||||
struct tomoyo_path_group_member e = { };
|
||||
int error = is_delete ? -ENOENT : -ENOMEM;
|
||||
char *w[2];
|
||||
if (!tomoyo_tokenize(data, w, sizeof(w)) || !w[1][0])
|
||||
return -EINVAL;
|
||||
group = tomoyo_get_path_group(w[0]);
|
||||
if (!group)
|
||||
return -ENOMEM;
|
||||
e.member_name = tomoyo_get_name(w[1]);
|
||||
if (!e.member_name)
|
||||
goto out;
|
||||
if (mutex_lock_interruptible(&tomoyo_policy_lock))
|
||||
goto out;
|
||||
list_for_each_entry_rcu(member, &group->member_list, list) {
|
||||
if (member->member_name != e.member_name)
|
||||
continue;
|
||||
member->is_deleted = is_delete;
|
||||
error = 0;
|
||||
break;
|
||||
}
|
||||
if (!is_delete && error) {
|
||||
struct tomoyo_path_group_member *entry =
|
||||
tomoyo_commit_ok(&e, sizeof(e));
|
||||
if (entry) {
|
||||
list_add_tail_rcu(&entry->list, &group->member_list);
|
||||
error = 0;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&tomoyo_policy_lock);
|
||||
out:
|
||||
tomoyo_put_name(e.member_name);
|
||||
tomoyo_put_path_group(group);
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_read_path_group_policy - Read "struct tomoyo_path_group" list.
|
||||
*
|
||||
* @head: Pointer to "struct tomoyo_io_buffer".
|
||||
*
|
||||
* Returns true on success, false otherwise.
|
||||
*
|
||||
* Caller holds tomoyo_read_lock().
|
||||
*/
|
||||
bool tomoyo_read_path_group_policy(struct tomoyo_io_buffer *head)
|
||||
{
|
||||
struct list_head *gpos;
|
||||
struct list_head *mpos;
|
||||
list_for_each_cookie(gpos, head->read_var1, &tomoyo_path_group_list) {
|
||||
struct tomoyo_path_group *group;
|
||||
group = list_entry(gpos, struct tomoyo_path_group, list);
|
||||
list_for_each_cookie(mpos, head->read_var2,
|
||||
&group->member_list) {
|
||||
struct tomoyo_path_group_member *member;
|
||||
member = list_entry(mpos,
|
||||
struct tomoyo_path_group_member,
|
||||
list);
|
||||
if (member->is_deleted)
|
||||
continue;
|
||||
if (!tomoyo_io_printf(head, TOMOYO_KEYWORD_PATH_GROUP
|
||||
"%s %s\n",
|
||||
group->group_name->name,
|
||||
member->member_name->name))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_path_matches_group - Check whether the given pathname matches members of the given pathname group.
|
||||
*
|
||||
* @pathname: The name of pathname.
|
||||
* @group: Pointer to "struct tomoyo_path_group".
|
||||
* @may_use_pattern: True if wild card is permitted.
|
||||
*
|
||||
* Returns true if @pathname matches pathnames in @group, false otherwise.
|
||||
*
|
||||
* Caller holds tomoyo_read_lock().
|
||||
*/
|
||||
bool tomoyo_path_matches_group(const struct tomoyo_path_info *pathname,
|
||||
const struct tomoyo_path_group *group,
|
||||
const bool may_use_pattern)
|
||||
{
|
||||
struct tomoyo_path_group_member *member;
|
||||
bool matched = false;
|
||||
list_for_each_entry_rcu(member, &group->member_list, list) {
|
||||
if (member->is_deleted)
|
||||
continue;
|
||||
if (!member->member_name->is_patterned) {
|
||||
if (tomoyo_pathcmp(pathname, member->member_name))
|
||||
continue;
|
||||
} else if (may_use_pattern) {
|
||||
if (!tomoyo_path_matches_pattern(pathname,
|
||||
member->member_name))
|
||||
continue;
|
||||
} else
|
||||
continue;
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
+120
-318
@@ -1,130 +1,71 @@
|
||||
/*
|
||||
* security/tomoyo/realpath.c
|
||||
*
|
||||
* Get the canonicalized absolute pathnames. The basis for TOMOYO.
|
||||
*
|
||||
* Copyright (C) 2005-2009 NTT DATA CORPORATION
|
||||
*
|
||||
* Version: 2.2.0 2009/04/01
|
||||
* Pathname calculation functions for TOMOYO.
|
||||
*
|
||||
* Copyright (C) 2005-2010 NTT DATA CORPORATION
|
||||
*/
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/mnt_namespace.h>
|
||||
#include <linux/fs_struct.h>
|
||||
#include <linux/hash.h>
|
||||
#include <linux/magic.h>
|
||||
#include <linux/slab.h>
|
||||
#include <net/sock.h>
|
||||
#include "common.h"
|
||||
|
||||
/**
|
||||
* tomoyo_encode: Convert binary string to ascii string.
|
||||
*
|
||||
* @buffer: Buffer for ASCII string.
|
||||
* @buflen: Size of @buffer.
|
||||
* @str: Binary string.
|
||||
* @str: String in binary format.
|
||||
*
|
||||
* Returns 0 on success, -ENOMEM otherwise.
|
||||
* Returns pointer to @str in ascii format on success, NULL otherwise.
|
||||
*
|
||||
* This function uses kzalloc(), so caller must kfree() if this function
|
||||
* didn't return NULL.
|
||||
*/
|
||||
int tomoyo_encode(char *buffer, int buflen, const char *str)
|
||||
char *tomoyo_encode(const char *str)
|
||||
{
|
||||
while (1) {
|
||||
const unsigned char c = *(unsigned char *) str++;
|
||||
int len = 0;
|
||||
const char *p = str;
|
||||
char *cp;
|
||||
char *cp0;
|
||||
|
||||
if (tomoyo_is_valid(c)) {
|
||||
if (--buflen <= 0)
|
||||
break;
|
||||
*buffer++ = (char) c;
|
||||
if (c != '\\')
|
||||
continue;
|
||||
if (--buflen <= 0)
|
||||
break;
|
||||
*buffer++ = (char) c;
|
||||
continue;
|
||||
}
|
||||
if (!c) {
|
||||
if (--buflen <= 0)
|
||||
break;
|
||||
*buffer = '\0';
|
||||
return 0;
|
||||
}
|
||||
buflen -= 4;
|
||||
if (buflen <= 0)
|
||||
break;
|
||||
*buffer++ = '\\';
|
||||
*buffer++ = (c >> 6) + '0';
|
||||
*buffer++ = ((c >> 3) & 7) + '0';
|
||||
*buffer++ = (c & 7) + '0';
|
||||
if (!p)
|
||||
return NULL;
|
||||
while (*p) {
|
||||
const unsigned char c = *p++;
|
||||
if (c == '\\')
|
||||
len += 2;
|
||||
else if (c > ' ' && c < 127)
|
||||
len++;
|
||||
else
|
||||
len += 4;
|
||||
}
|
||||
return -ENOMEM;
|
||||
}
|
||||
len++;
|
||||
/* Reserve space for appending "/". */
|
||||
cp = kzalloc(len + 10, GFP_NOFS);
|
||||
if (!cp)
|
||||
return NULL;
|
||||
cp0 = cp;
|
||||
p = str;
|
||||
while (*p) {
|
||||
const unsigned char c = *p++;
|
||||
|
||||
/**
|
||||
* tomoyo_realpath_from_path2 - Returns realpath(3) of the given dentry but ignores chroot'ed root.
|
||||
*
|
||||
* @path: Pointer to "struct path".
|
||||
* @newname: Pointer to buffer to return value in.
|
||||
* @newname_len: Size of @newname.
|
||||
*
|
||||
* Returns 0 on success, negative value otherwise.
|
||||
*
|
||||
* If dentry is a directory, trailing '/' is appended.
|
||||
* Characters out of 0x20 < c < 0x7F range are converted to
|
||||
* \ooo style octal string.
|
||||
* Character \ is converted to \\ string.
|
||||
*/
|
||||
int tomoyo_realpath_from_path2(struct path *path, char *newname,
|
||||
int newname_len)
|
||||
{
|
||||
int error = -ENOMEM;
|
||||
struct dentry *dentry = path->dentry;
|
||||
char *sp;
|
||||
|
||||
if (!dentry || !path->mnt || !newname || newname_len <= 2048)
|
||||
return -EINVAL;
|
||||
if (dentry->d_op && dentry->d_op->d_dname) {
|
||||
/* For "socket:[\$]" and "pipe:[\$]". */
|
||||
static const int offset = 1536;
|
||||
sp = dentry->d_op->d_dname(dentry, newname + offset,
|
||||
newname_len - offset);
|
||||
} else {
|
||||
struct path ns_root = {.mnt = NULL, .dentry = NULL};
|
||||
|
||||
spin_lock(&dcache_lock);
|
||||
/* go to whatever namespace root we are under */
|
||||
sp = __d_path(path, &ns_root, newname, newname_len);
|
||||
spin_unlock(&dcache_lock);
|
||||
/* Prepend "/proc" prefix if using internal proc vfs mount. */
|
||||
if (!IS_ERR(sp) && (path->mnt->mnt_flags & MNT_INTERNAL) &&
|
||||
(path->mnt->mnt_sb->s_magic == PROC_SUPER_MAGIC)) {
|
||||
sp -= 5;
|
||||
if (sp >= newname)
|
||||
memcpy(sp, "/proc", 5);
|
||||
else
|
||||
sp = ERR_PTR(-ENOMEM);
|
||||
if (c == '\\') {
|
||||
*cp++ = '\\';
|
||||
*cp++ = '\\';
|
||||
} else if (c > ' ' && c < 127) {
|
||||
*cp++ = c;
|
||||
} else {
|
||||
*cp++ = '\\';
|
||||
*cp++ = (c >> 6) + '0';
|
||||
*cp++ = ((c >> 3) & 7) + '0';
|
||||
*cp++ = (c & 7) + '0';
|
||||
}
|
||||
}
|
||||
if (IS_ERR(sp))
|
||||
error = PTR_ERR(sp);
|
||||
else
|
||||
error = tomoyo_encode(newname, sp - newname, sp);
|
||||
/* Append trailing '/' if dentry is a directory. */
|
||||
if (!error && dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)
|
||||
&& *newname) {
|
||||
sp = newname + strlen(newname);
|
||||
if (*(sp - 1) != '/') {
|
||||
if (sp < newname + newname_len - 4) {
|
||||
*sp++ = '/';
|
||||
*sp = '\0';
|
||||
} else {
|
||||
error = -ENOMEM;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (error)
|
||||
printk(KERN_WARNING "tomoyo_realpath: Pathname too long.\n");
|
||||
return error;
|
||||
return cp0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,41 +75,90 @@ int tomoyo_realpath_from_path2(struct path *path, char *newname,
|
||||
*
|
||||
* Returns the realpath of the given @path on success, NULL otherwise.
|
||||
*
|
||||
* If dentry is a directory, trailing '/' is appended.
|
||||
* Characters out of 0x20 < c < 0x7F range are converted to
|
||||
* \ooo style octal string.
|
||||
* Character \ is converted to \\ string.
|
||||
*
|
||||
* These functions use kzalloc(), so the caller must call kfree()
|
||||
* if these functions didn't return NULL.
|
||||
*/
|
||||
char *tomoyo_realpath_from_path(struct path *path)
|
||||
{
|
||||
char *buf = kzalloc(sizeof(struct tomoyo_page_buffer), GFP_NOFS);
|
||||
|
||||
BUILD_BUG_ON(sizeof(struct tomoyo_page_buffer)
|
||||
<= TOMOYO_MAX_PATHNAME_LEN - 1);
|
||||
if (!buf)
|
||||
char *buf = NULL;
|
||||
char *name = NULL;
|
||||
unsigned int buf_len = PAGE_SIZE / 2;
|
||||
struct dentry *dentry = path->dentry;
|
||||
bool is_dir;
|
||||
if (!dentry)
|
||||
return NULL;
|
||||
if (tomoyo_realpath_from_path2(path, buf,
|
||||
TOMOYO_MAX_PATHNAME_LEN - 1) == 0)
|
||||
return buf;
|
||||
kfree(buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_realpath - Get realpath of a pathname.
|
||||
*
|
||||
* @pathname: The pathname to solve.
|
||||
*
|
||||
* Returns the realpath of @pathname on success, NULL otherwise.
|
||||
*/
|
||||
char *tomoyo_realpath(const char *pathname)
|
||||
{
|
||||
struct path path;
|
||||
|
||||
if (pathname && kern_path(pathname, LOOKUP_FOLLOW, &path) == 0) {
|
||||
char *buf = tomoyo_realpath_from_path(&path);
|
||||
path_put(&path);
|
||||
return buf;
|
||||
is_dir = dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode);
|
||||
while (1) {
|
||||
struct path ns_root = { .mnt = NULL, .dentry = NULL };
|
||||
char *pos;
|
||||
buf_len <<= 1;
|
||||
kfree(buf);
|
||||
buf = kmalloc(buf_len, GFP_NOFS);
|
||||
if (!buf)
|
||||
break;
|
||||
/* Get better name for socket. */
|
||||
if (dentry->d_sb && dentry->d_sb->s_magic == SOCKFS_MAGIC) {
|
||||
struct inode *inode = dentry->d_inode;
|
||||
struct socket *sock = inode ? SOCKET_I(inode) : NULL;
|
||||
struct sock *sk = sock ? sock->sk : NULL;
|
||||
if (sk) {
|
||||
snprintf(buf, buf_len - 1, "socket:[family=%u:"
|
||||
"type=%u:protocol=%u]", sk->sk_family,
|
||||
sk->sk_type, sk->sk_protocol);
|
||||
} else {
|
||||
snprintf(buf, buf_len - 1, "socket:[unknown]");
|
||||
}
|
||||
name = tomoyo_encode(buf);
|
||||
break;
|
||||
}
|
||||
/* For "socket:[\$]" and "pipe:[\$]". */
|
||||
if (dentry->d_op && dentry->d_op->d_dname) {
|
||||
pos = dentry->d_op->d_dname(dentry, buf, buf_len - 1);
|
||||
if (IS_ERR(pos))
|
||||
continue;
|
||||
name = tomoyo_encode(pos);
|
||||
break;
|
||||
}
|
||||
/* If we don't have a vfsmount, we can't calculate. */
|
||||
if (!path->mnt)
|
||||
break;
|
||||
spin_lock(&dcache_lock);
|
||||
/* go to whatever namespace root we are under */
|
||||
pos = __d_path(path, &ns_root, buf, buf_len);
|
||||
spin_unlock(&dcache_lock);
|
||||
/* Prepend "/proc" prefix if using internal proc vfs mount. */
|
||||
if (!IS_ERR(pos) && (path->mnt->mnt_flags & MNT_INTERNAL) &&
|
||||
(path->mnt->mnt_sb->s_magic == PROC_SUPER_MAGIC)) {
|
||||
pos -= 5;
|
||||
if (pos >= buf)
|
||||
memcpy(pos, "/proc", 5);
|
||||
else
|
||||
pos = ERR_PTR(-ENOMEM);
|
||||
}
|
||||
if (IS_ERR(pos))
|
||||
continue;
|
||||
name = tomoyo_encode(pos);
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
kfree(buf);
|
||||
if (!name)
|
||||
tomoyo_warn_oom(__func__);
|
||||
else if (is_dir && *name) {
|
||||
/* Append trailing '/' if dentry is a directory. */
|
||||
char *pos = name + strlen(name) - 1;
|
||||
if (*pos != '/')
|
||||
/*
|
||||
* This is OK because tomoyo_encode() reserves space
|
||||
* for appending "/".
|
||||
*/
|
||||
*++pos = '/';
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,191 +179,3 @@ char *tomoyo_realpath_nofollow(const char *pathname)
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Memory allocated for non-string data. */
|
||||
static atomic_t tomoyo_policy_memory_size;
|
||||
/* Quota for holding policy. */
|
||||
static unsigned int tomoyo_quota_for_policy;
|
||||
|
||||
/**
|
||||
* tomoyo_memory_ok - Check memory quota.
|
||||
*
|
||||
* @ptr: Pointer to allocated memory.
|
||||
*
|
||||
* Returns true on success, false otherwise.
|
||||
*
|
||||
* Caller holds tomoyo_policy_lock.
|
||||
* Memory pointed by @ptr will be zeroed on success.
|
||||
*/
|
||||
bool tomoyo_memory_ok(void *ptr)
|
||||
{
|
||||
int allocated_len = ptr ? ksize(ptr) : 0;
|
||||
atomic_add(allocated_len, &tomoyo_policy_memory_size);
|
||||
if (ptr && (!tomoyo_quota_for_policy ||
|
||||
atomic_read(&tomoyo_policy_memory_size)
|
||||
<= tomoyo_quota_for_policy)) {
|
||||
memset(ptr, 0, allocated_len);
|
||||
return true;
|
||||
}
|
||||
printk(KERN_WARNING "ERROR: Out of memory "
|
||||
"for tomoyo_alloc_element().\n");
|
||||
if (!tomoyo_policy_loaded)
|
||||
panic("MAC Initialization failed.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_commit_ok - Check memory quota.
|
||||
*
|
||||
* @data: Data to copy from.
|
||||
* @size: Size in byte.
|
||||
*
|
||||
* Returns pointer to allocated memory on success, NULL otherwise.
|
||||
*/
|
||||
void *tomoyo_commit_ok(void *data, const unsigned int size)
|
||||
{
|
||||
void *ptr = kzalloc(size, GFP_NOFS);
|
||||
if (tomoyo_memory_ok(ptr)) {
|
||||
memmove(ptr, data, size);
|
||||
memset(data, 0, size);
|
||||
return ptr;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_memory_free - Free memory for elements.
|
||||
*
|
||||
* @ptr: Pointer to allocated memory.
|
||||
*/
|
||||
void tomoyo_memory_free(void *ptr)
|
||||
{
|
||||
atomic_sub(ksize(ptr), &tomoyo_policy_memory_size);
|
||||
kfree(ptr);
|
||||
}
|
||||
|
||||
/*
|
||||
* tomoyo_name_list is used for holding string data used by TOMOYO.
|
||||
* Since same string data is likely used for multiple times (e.g.
|
||||
* "/lib/libc-2.5.so"), TOMOYO shares string data in the form of
|
||||
* "const struct tomoyo_path_info *".
|
||||
*/
|
||||
struct list_head tomoyo_name_list[TOMOYO_MAX_HASH];
|
||||
|
||||
/**
|
||||
* tomoyo_get_name - Allocate permanent memory for string data.
|
||||
*
|
||||
* @name: The string to store into the permernent memory.
|
||||
*
|
||||
* Returns pointer to "struct tomoyo_path_info" on success, NULL otherwise.
|
||||
*/
|
||||
const struct tomoyo_path_info *tomoyo_get_name(const char *name)
|
||||
{
|
||||
struct tomoyo_name_entry *ptr;
|
||||
unsigned int hash;
|
||||
int len;
|
||||
int allocated_len;
|
||||
struct list_head *head;
|
||||
|
||||
if (!name)
|
||||
return NULL;
|
||||
len = strlen(name) + 1;
|
||||
hash = full_name_hash((const unsigned char *) name, len - 1);
|
||||
head = &tomoyo_name_list[hash_long(hash, TOMOYO_HASH_BITS)];
|
||||
if (mutex_lock_interruptible(&tomoyo_policy_lock))
|
||||
return NULL;
|
||||
list_for_each_entry(ptr, head, list) {
|
||||
if (hash != ptr->entry.hash || strcmp(name, ptr->entry.name))
|
||||
continue;
|
||||
atomic_inc(&ptr->users);
|
||||
goto out;
|
||||
}
|
||||
ptr = kzalloc(sizeof(*ptr) + len, GFP_NOFS);
|
||||
allocated_len = ptr ? ksize(ptr) : 0;
|
||||
if (!ptr || (tomoyo_quota_for_policy &&
|
||||
atomic_read(&tomoyo_policy_memory_size) + allocated_len
|
||||
> tomoyo_quota_for_policy)) {
|
||||
kfree(ptr);
|
||||
printk(KERN_WARNING "ERROR: Out of memory "
|
||||
"for tomoyo_get_name().\n");
|
||||
if (!tomoyo_policy_loaded)
|
||||
panic("MAC Initialization failed.\n");
|
||||
ptr = NULL;
|
||||
goto out;
|
||||
}
|
||||
atomic_add(allocated_len, &tomoyo_policy_memory_size);
|
||||
ptr->entry.name = ((char *) ptr) + sizeof(*ptr);
|
||||
memmove((char *) ptr->entry.name, name, len);
|
||||
atomic_set(&ptr->users, 1);
|
||||
tomoyo_fill_path_info(&ptr->entry);
|
||||
list_add_tail(&ptr->list, head);
|
||||
out:
|
||||
mutex_unlock(&tomoyo_policy_lock);
|
||||
return ptr ? &ptr->entry : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_realpath_init - Initialize realpath related code.
|
||||
*/
|
||||
void __init tomoyo_realpath_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
BUILD_BUG_ON(TOMOYO_MAX_PATHNAME_LEN > PATH_MAX);
|
||||
for (i = 0; i < TOMOYO_MAX_HASH; i++)
|
||||
INIT_LIST_HEAD(&tomoyo_name_list[i]);
|
||||
INIT_LIST_HEAD(&tomoyo_kernel_domain.acl_info_list);
|
||||
tomoyo_kernel_domain.domainname = tomoyo_get_name(TOMOYO_ROOT_NAME);
|
||||
/*
|
||||
* tomoyo_read_lock() is not needed because this function is
|
||||
* called before the first "delete" request.
|
||||
*/
|
||||
list_add_tail_rcu(&tomoyo_kernel_domain.list, &tomoyo_domain_list);
|
||||
if (tomoyo_find_domain(TOMOYO_ROOT_NAME) != &tomoyo_kernel_domain)
|
||||
panic("Can't register tomoyo_kernel_domain");
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_read_memory_counter - Check for memory usage in bytes.
|
||||
*
|
||||
* @head: Pointer to "struct tomoyo_io_buffer".
|
||||
*
|
||||
* Returns memory usage.
|
||||
*/
|
||||
int tomoyo_read_memory_counter(struct tomoyo_io_buffer *head)
|
||||
{
|
||||
if (!head->read_eof) {
|
||||
const unsigned int policy
|
||||
= atomic_read(&tomoyo_policy_memory_size);
|
||||
char buffer[64];
|
||||
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
if (tomoyo_quota_for_policy)
|
||||
snprintf(buffer, sizeof(buffer) - 1,
|
||||
" (Quota: %10u)",
|
||||
tomoyo_quota_for_policy);
|
||||
else
|
||||
buffer[0] = '\0';
|
||||
tomoyo_io_printf(head, "Policy: %10u%s\n", policy, buffer);
|
||||
tomoyo_io_printf(head, "Total: %10u\n", policy);
|
||||
head->read_eof = true;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_write_memory_quota - Set memory quota.
|
||||
*
|
||||
* @head: Pointer to "struct tomoyo_io_buffer".
|
||||
*
|
||||
* Returns 0.
|
||||
*/
|
||||
int tomoyo_write_memory_quota(struct tomoyo_io_buffer *head)
|
||||
{
|
||||
char *data = head->write_buf;
|
||||
unsigned int size;
|
||||
|
||||
if (sscanf(data, "Policy: %u", &size) == 1)
|
||||
tomoyo_quota_for_policy = size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* security/tomoyo/common.c
|
||||
*
|
||||
* Securityfs interface for TOMOYO.
|
||||
*
|
||||
* Copyright (C) 2005-2010 NTT DATA CORPORATION
|
||||
*/
|
||||
|
||||
#include <linux/security.h>
|
||||
#include "common.h"
|
||||
|
||||
/**
|
||||
* tomoyo_open - open() for /sys/kernel/security/tomoyo/ interface.
|
||||
*
|
||||
* @inode: Pointer to "struct inode".
|
||||
* @file: Pointer to "struct file".
|
||||
*
|
||||
* Returns 0 on success, negative value otherwise.
|
||||
*/
|
||||
static int tomoyo_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
const int key = ((u8 *) file->f_path.dentry->d_inode->i_private)
|
||||
- ((u8 *) NULL);
|
||||
return tomoyo_open_control(key, file);
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_release - close() for /sys/kernel/security/tomoyo/ interface.
|
||||
*
|
||||
* @inode: Pointer to "struct inode".
|
||||
* @file: Pointer to "struct file".
|
||||
*
|
||||
* Returns 0 on success, negative value otherwise.
|
||||
*/
|
||||
static int tomoyo_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
return tomoyo_close_control(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_poll - poll() for /proc/ccs/ interface.
|
||||
*
|
||||
* @file: Pointer to "struct file".
|
||||
* @wait: Pointer to "poll_table".
|
||||
*
|
||||
* Returns 0 on success, negative value otherwise.
|
||||
*/
|
||||
static unsigned int tomoyo_poll(struct file *file, poll_table *wait)
|
||||
{
|
||||
return tomoyo_poll_control(file, wait);
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_read - read() for /sys/kernel/security/tomoyo/ interface.
|
||||
*
|
||||
* @file: Pointer to "struct file".
|
||||
* @buf: Pointer to buffer.
|
||||
* @count: Size of @buf.
|
||||
* @ppos: Unused.
|
||||
*
|
||||
* Returns bytes read on success, negative value otherwise.
|
||||
*/
|
||||
static ssize_t tomoyo_read(struct file *file, char __user *buf, size_t count,
|
||||
loff_t *ppos)
|
||||
{
|
||||
return tomoyo_read_control(file, buf, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_write - write() for /sys/kernel/security/tomoyo/ interface.
|
||||
*
|
||||
* @file: Pointer to "struct file".
|
||||
* @buf: Pointer to buffer.
|
||||
* @count: Size of @buf.
|
||||
* @ppos: Unused.
|
||||
*
|
||||
* Returns @count on success, negative value otherwise.
|
||||
*/
|
||||
static ssize_t tomoyo_write(struct file *file, const char __user *buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
return tomoyo_write_control(file, buf, count);
|
||||
}
|
||||
|
||||
/*
|
||||
* tomoyo_operations is a "struct file_operations" which is used for handling
|
||||
* /sys/kernel/security/tomoyo/ interface.
|
||||
*
|
||||
* Some files under /sys/kernel/security/tomoyo/ directory accept open(O_RDWR).
|
||||
* See tomoyo_io_buffer for internals.
|
||||
*/
|
||||
static const struct file_operations tomoyo_operations = {
|
||||
.open = tomoyo_open,
|
||||
.release = tomoyo_release,
|
||||
.poll = tomoyo_poll,
|
||||
.read = tomoyo_read,
|
||||
.write = tomoyo_write,
|
||||
.llseek = noop_llseek,
|
||||
};
|
||||
|
||||
/**
|
||||
* tomoyo_create_entry - Create interface files under /sys/kernel/security/tomoyo/ directory.
|
||||
*
|
||||
* @name: The name of the interface file.
|
||||
* @mode: The permission of the interface file.
|
||||
* @parent: The parent directory.
|
||||
* @key: Type of interface.
|
||||
*
|
||||
* Returns nothing.
|
||||
*/
|
||||
static void __init tomoyo_create_entry(const char *name, const mode_t mode,
|
||||
struct dentry *parent, const u8 key)
|
||||
{
|
||||
securityfs_create_file(name, mode, parent, ((u8 *) NULL) + key,
|
||||
&tomoyo_operations);
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_initerface_init - Initialize /sys/kernel/security/tomoyo/ interface.
|
||||
*
|
||||
* Returns 0.
|
||||
*/
|
||||
static int __init tomoyo_initerface_init(void)
|
||||
{
|
||||
struct dentry *tomoyo_dir;
|
||||
|
||||
/* Don't create securityfs entries unless registered. */
|
||||
if (current_cred()->security != &tomoyo_kernel_domain)
|
||||
return 0;
|
||||
|
||||
tomoyo_dir = securityfs_create_dir("tomoyo", NULL);
|
||||
tomoyo_create_entry("query", 0600, tomoyo_dir,
|
||||
TOMOYO_QUERY);
|
||||
tomoyo_create_entry("domain_policy", 0600, tomoyo_dir,
|
||||
TOMOYO_DOMAINPOLICY);
|
||||
tomoyo_create_entry("exception_policy", 0600, tomoyo_dir,
|
||||
TOMOYO_EXCEPTIONPOLICY);
|
||||
tomoyo_create_entry("self_domain", 0400, tomoyo_dir,
|
||||
TOMOYO_SELFDOMAIN);
|
||||
tomoyo_create_entry(".domain_status", 0600, tomoyo_dir,
|
||||
TOMOYO_DOMAIN_STATUS);
|
||||
tomoyo_create_entry(".process_status", 0600, tomoyo_dir,
|
||||
TOMOYO_PROCESS_STATUS);
|
||||
tomoyo_create_entry("meminfo", 0600, tomoyo_dir,
|
||||
TOMOYO_MEMINFO);
|
||||
tomoyo_create_entry("profile", 0600, tomoyo_dir,
|
||||
TOMOYO_PROFILE);
|
||||
tomoyo_create_entry("manager", 0600, tomoyo_dir,
|
||||
TOMOYO_MANAGER);
|
||||
tomoyo_create_entry("version", 0400, tomoyo_dir,
|
||||
TOMOYO_VERSION);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fs_initcall(tomoyo_initerface_init);
|
||||
+20
-15
@@ -3,10 +3,7 @@
|
||||
*
|
||||
* LSM hooks for TOMOYO Linux.
|
||||
*
|
||||
* Copyright (C) 2005-2009 NTT DATA CORPORATION
|
||||
*
|
||||
* Version: 2.2.0 2009/04/01
|
||||
*
|
||||
* Copyright (C) 2005-2010 NTT DATA CORPORATION
|
||||
*/
|
||||
|
||||
#include <linux/security.h>
|
||||
@@ -96,8 +93,7 @@ static int tomoyo_bprm_check_security(struct linux_binprm *bprm)
|
||||
return tomoyo_check_open_permission(domain, &bprm->file->f_path, O_RDONLY);
|
||||
}
|
||||
|
||||
static int tomoyo_path_truncate(struct path *path, loff_t length,
|
||||
unsigned int time_attrs)
|
||||
static int tomoyo_path_truncate(struct path *path)
|
||||
{
|
||||
return tomoyo_path_perm(TOMOYO_TYPE_TRUNCATE, path);
|
||||
}
|
||||
@@ -112,7 +108,8 @@ static int tomoyo_path_mkdir(struct path *parent, struct dentry *dentry,
|
||||
int mode)
|
||||
{
|
||||
struct path path = { parent->mnt, dentry };
|
||||
return tomoyo_path_perm(TOMOYO_TYPE_MKDIR, &path);
|
||||
return tomoyo_path_number_perm(TOMOYO_TYPE_MKDIR, &path,
|
||||
mode & S_IALLUGO);
|
||||
}
|
||||
|
||||
static int tomoyo_path_rmdir(struct path *parent, struct dentry *dentry)
|
||||
@@ -133,6 +130,7 @@ static int tomoyo_path_mknod(struct path *parent, struct dentry *dentry,
|
||||
{
|
||||
struct path path = { parent->mnt, dentry };
|
||||
int type = TOMOYO_TYPE_CREATE;
|
||||
const unsigned int perm = mode & S_IALLUGO;
|
||||
|
||||
switch (mode & S_IFMT) {
|
||||
case S_IFCHR:
|
||||
@@ -141,6 +139,12 @@ static int tomoyo_path_mknod(struct path *parent, struct dentry *dentry,
|
||||
case S_IFBLK:
|
||||
type = TOMOYO_TYPE_MKBLOCK;
|
||||
break;
|
||||
default:
|
||||
goto no_dev;
|
||||
}
|
||||
return tomoyo_mkdev_perm(type, &path, perm, dev);
|
||||
no_dev:
|
||||
switch (mode & S_IFMT) {
|
||||
case S_IFIFO:
|
||||
type = TOMOYO_TYPE_MKFIFO;
|
||||
break;
|
||||
@@ -148,7 +152,7 @@ static int tomoyo_path_mknod(struct path *parent, struct dentry *dentry,
|
||||
type = TOMOYO_TYPE_MKSOCK;
|
||||
break;
|
||||
}
|
||||
return tomoyo_path_perm(type, &path);
|
||||
return tomoyo_path_number_perm(type, &path, perm);
|
||||
}
|
||||
|
||||
static int tomoyo_path_link(struct dentry *old_dentry, struct path *new_dir,
|
||||
@@ -173,7 +177,7 @@ static int tomoyo_file_fcntl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
if (cmd == F_SETFL && ((arg ^ file->f_flags) & O_APPEND))
|
||||
return tomoyo_check_rewrite_permission(file);
|
||||
return tomoyo_path_perm(TOMOYO_TYPE_REWRITE, &file->f_path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -189,23 +193,24 @@ static int tomoyo_dentry_open(struct file *f, const struct cred *cred)
|
||||
static int tomoyo_file_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
return tomoyo_path_perm(TOMOYO_TYPE_IOCTL, &file->f_path);
|
||||
return tomoyo_path_number_perm(TOMOYO_TYPE_IOCTL, &file->f_path, cmd);
|
||||
}
|
||||
|
||||
static int tomoyo_path_chmod(struct dentry *dentry, struct vfsmount *mnt,
|
||||
mode_t mode)
|
||||
{
|
||||
struct path path = { mnt, dentry };
|
||||
return tomoyo_path_perm(TOMOYO_TYPE_CHMOD, &path);
|
||||
return tomoyo_path_number_perm(TOMOYO_TYPE_CHMOD, &path,
|
||||
mode & S_IALLUGO);
|
||||
}
|
||||
|
||||
static int tomoyo_path_chown(struct path *path, uid_t uid, gid_t gid)
|
||||
{
|
||||
int error = 0;
|
||||
if (uid != (uid_t) -1)
|
||||
error = tomoyo_path_perm(TOMOYO_TYPE_CHOWN, path);
|
||||
error = tomoyo_path_number_perm(TOMOYO_TYPE_CHOWN, path, uid);
|
||||
if (!error && gid != (gid_t) -1)
|
||||
error = tomoyo_path_perm(TOMOYO_TYPE_CHGRP, path);
|
||||
error = tomoyo_path_number_perm(TOMOYO_TYPE_CHGRP, path, gid);
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -217,7 +222,7 @@ static int tomoyo_path_chroot(struct path *path)
|
||||
static int tomoyo_sb_mount(char *dev_name, struct path *path,
|
||||
char *type, unsigned long flags, void *data)
|
||||
{
|
||||
return tomoyo_path_perm(TOMOYO_TYPE_MOUNT, path);
|
||||
return tomoyo_mount_permission(dev_name, path, type, flags, data);
|
||||
}
|
||||
|
||||
static int tomoyo_sb_umount(struct vfsmount *mnt, int flags)
|
||||
@@ -277,7 +282,7 @@ static int __init tomoyo_init(void)
|
||||
panic("Failure registering TOMOYO Linux");
|
||||
printk(KERN_INFO "TOMOYO Linux initialized\n");
|
||||
cred->security = &tomoyo_kernel_domain;
|
||||
tomoyo_realpath_init();
|
||||
tomoyo_mm_init();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,963 @@
|
||||
/*
|
||||
* security/tomoyo/util.c
|
||||
*
|
||||
* Utility functions for TOMOYO.
|
||||
*
|
||||
* Copyright (C) 2005-2010 NTT DATA CORPORATION
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include "common.h"
|
||||
|
||||
/* Lock for protecting policy. */
|
||||
DEFINE_MUTEX(tomoyo_policy_lock);
|
||||
|
||||
/* Has /sbin/init started? */
|
||||
bool tomoyo_policy_loaded;
|
||||
|
||||
/**
|
||||
* tomoyo_parse_ulong - Parse an "unsigned long" value.
|
||||
*
|
||||
* @result: Pointer to "unsigned long".
|
||||
* @str: Pointer to string to parse.
|
||||
*
|
||||
* Returns value type on success, 0 otherwise.
|
||||
*
|
||||
* The @src is updated to point the first character after the value
|
||||
* on success.
|
||||
*/
|
||||
static u8 tomoyo_parse_ulong(unsigned long *result, char **str)
|
||||
{
|
||||
const char *cp = *str;
|
||||
char *ep;
|
||||
int base = 10;
|
||||
if (*cp == '0') {
|
||||
char c = *(cp + 1);
|
||||
if (c == 'x' || c == 'X') {
|
||||
base = 16;
|
||||
cp += 2;
|
||||
} else if (c >= '0' && c <= '7') {
|
||||
base = 8;
|
||||
cp++;
|
||||
}
|
||||
}
|
||||
*result = simple_strtoul(cp, &ep, base);
|
||||
if (cp == ep)
|
||||
return 0;
|
||||
*str = ep;
|
||||
switch (base) {
|
||||
case 16:
|
||||
return TOMOYO_VALUE_TYPE_HEXADECIMAL;
|
||||
case 8:
|
||||
return TOMOYO_VALUE_TYPE_OCTAL;
|
||||
default:
|
||||
return TOMOYO_VALUE_TYPE_DECIMAL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_print_ulong - Print an "unsigned long" value.
|
||||
*
|
||||
* @buffer: Pointer to buffer.
|
||||
* @buffer_len: Size of @buffer.
|
||||
* @value: An "unsigned long" value.
|
||||
* @type: Type of @value.
|
||||
*
|
||||
* Returns nothing.
|
||||
*/
|
||||
void tomoyo_print_ulong(char *buffer, const int buffer_len,
|
||||
const unsigned long value, const u8 type)
|
||||
{
|
||||
if (type == TOMOYO_VALUE_TYPE_DECIMAL)
|
||||
snprintf(buffer, buffer_len, "%lu", value);
|
||||
else if (type == TOMOYO_VALUE_TYPE_OCTAL)
|
||||
snprintf(buffer, buffer_len, "0%lo", value);
|
||||
else if (type == TOMOYO_VALUE_TYPE_HEXADECIMAL)
|
||||
snprintf(buffer, buffer_len, "0x%lX", value);
|
||||
else
|
||||
snprintf(buffer, buffer_len, "type(%u)", type);
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_parse_name_union - Parse a tomoyo_name_union.
|
||||
*
|
||||
* @filename: Name or name group.
|
||||
* @ptr: Pointer to "struct tomoyo_name_union".
|
||||
*
|
||||
* Returns true on success, false otherwise.
|
||||
*/
|
||||
bool tomoyo_parse_name_union(const char *filename,
|
||||
struct tomoyo_name_union *ptr)
|
||||
{
|
||||
if (!tomoyo_correct_word(filename))
|
||||
return false;
|
||||
if (filename[0] == '@') {
|
||||
ptr->group = tomoyo_get_group(filename + 1, TOMOYO_PATH_GROUP);
|
||||
ptr->is_group = true;
|
||||
return ptr->group != NULL;
|
||||
}
|
||||
ptr->filename = tomoyo_get_name(filename);
|
||||
ptr->is_group = false;
|
||||
return ptr->filename != NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_parse_number_union - Parse a tomoyo_number_union.
|
||||
*
|
||||
* @data: Number or number range or number group.
|
||||
* @ptr: Pointer to "struct tomoyo_number_union".
|
||||
*
|
||||
* Returns true on success, false otherwise.
|
||||
*/
|
||||
bool tomoyo_parse_number_union(char *data, struct tomoyo_number_union *num)
|
||||
{
|
||||
u8 type;
|
||||
unsigned long v;
|
||||
memset(num, 0, sizeof(*num));
|
||||
if (data[0] == '@') {
|
||||
if (!tomoyo_correct_word(data))
|
||||
return false;
|
||||
num->group = tomoyo_get_group(data + 1, TOMOYO_NUMBER_GROUP);
|
||||
num->is_group = true;
|
||||
return num->group != NULL;
|
||||
}
|
||||
type = tomoyo_parse_ulong(&v, &data);
|
||||
if (!type)
|
||||
return false;
|
||||
num->values[0] = v;
|
||||
num->min_type = type;
|
||||
if (!*data) {
|
||||
num->values[1] = v;
|
||||
num->max_type = type;
|
||||
return true;
|
||||
}
|
||||
if (*data++ != '-')
|
||||
return false;
|
||||
type = tomoyo_parse_ulong(&v, &data);
|
||||
if (!type || *data)
|
||||
return false;
|
||||
num->values[1] = v;
|
||||
num->max_type = type;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_byte_range - Check whether the string is a \ooo style octal value.
|
||||
*
|
||||
* @str: Pointer to the string.
|
||||
*
|
||||
* Returns true if @str is a \ooo style octal value, false otherwise.
|
||||
*
|
||||
* TOMOYO uses \ooo style representation for 0x01 - 0x20 and 0x7F - 0xFF.
|
||||
* This function verifies that \ooo is in valid range.
|
||||
*/
|
||||
static inline bool tomoyo_byte_range(const char *str)
|
||||
{
|
||||
return *str >= '0' && *str++ <= '3' &&
|
||||
*str >= '0' && *str++ <= '7' &&
|
||||
*str >= '0' && *str <= '7';
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_alphabet_char - Check whether the character is an alphabet.
|
||||
*
|
||||
* @c: The character to check.
|
||||
*
|
||||
* Returns true if @c is an alphabet character, false otherwise.
|
||||
*/
|
||||
static inline bool tomoyo_alphabet_char(const char c)
|
||||
{
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_make_byte - Make byte value from three octal characters.
|
||||
*
|
||||
* @c1: The first character.
|
||||
* @c2: The second character.
|
||||
* @c3: The third character.
|
||||
*
|
||||
* Returns byte value.
|
||||
*/
|
||||
static inline u8 tomoyo_make_byte(const u8 c1, const u8 c2, const u8 c3)
|
||||
{
|
||||
return ((c1 - '0') << 6) + ((c2 - '0') << 3) + (c3 - '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_str_starts - Check whether the given string starts with the given keyword.
|
||||
*
|
||||
* @src: Pointer to pointer to the string.
|
||||
* @find: Pointer to the keyword.
|
||||
*
|
||||
* Returns true if @src starts with @find, false otherwise.
|
||||
*
|
||||
* The @src is updated to point the first character after the @find
|
||||
* if @src starts with @find.
|
||||
*/
|
||||
bool tomoyo_str_starts(char **src, const char *find)
|
||||
{
|
||||
const int len = strlen(find);
|
||||
char *tmp = *src;
|
||||
|
||||
if (strncmp(tmp, find, len))
|
||||
return false;
|
||||
tmp += len;
|
||||
*src = tmp;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_normalize_line - Format string.
|
||||
*
|
||||
* @buffer: The line to normalize.
|
||||
*
|
||||
* Leading and trailing whitespaces are removed.
|
||||
* Multiple whitespaces are packed into single space.
|
||||
*
|
||||
* Returns nothing.
|
||||
*/
|
||||
void tomoyo_normalize_line(unsigned char *buffer)
|
||||
{
|
||||
unsigned char *sp = buffer;
|
||||
unsigned char *dp = buffer;
|
||||
bool first = true;
|
||||
|
||||
while (tomoyo_invalid(*sp))
|
||||
sp++;
|
||||
while (*sp) {
|
||||
if (!first)
|
||||
*dp++ = ' ';
|
||||
first = false;
|
||||
while (tomoyo_valid(*sp))
|
||||
*dp++ = *sp++;
|
||||
while (tomoyo_invalid(*sp))
|
||||
sp++;
|
||||
}
|
||||
*dp = '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_tokenize - Tokenize string.
|
||||
*
|
||||
* @buffer: The line to tokenize.
|
||||
* @w: Pointer to "char *".
|
||||
* @size: Sizeof @w .
|
||||
*
|
||||
* Returns true on success, false otherwise.
|
||||
*/
|
||||
bool tomoyo_tokenize(char *buffer, char *w[], size_t size)
|
||||
{
|
||||
int count = size / sizeof(char *);
|
||||
int i;
|
||||
for (i = 0; i < count; i++)
|
||||
w[i] = "";
|
||||
for (i = 0; i < count; i++) {
|
||||
char *cp = strchr(buffer, ' ');
|
||||
if (cp)
|
||||
*cp = '\0';
|
||||
w[i] = buffer;
|
||||
if (!cp)
|
||||
break;
|
||||
buffer = cp + 1;
|
||||
}
|
||||
return i < count || !*buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_correct_word2 - Validate a string.
|
||||
*
|
||||
* @string: The string to check. May be non-'\0'-terminated.
|
||||
* @len: Length of @string.
|
||||
*
|
||||
* Check whether the given string follows the naming rules.
|
||||
* Returns true if @string follows the naming rules, false otherwise.
|
||||
*/
|
||||
static bool tomoyo_correct_word2(const char *string, size_t len)
|
||||
{
|
||||
const char *const start = string;
|
||||
bool in_repetition = false;
|
||||
unsigned char c;
|
||||
unsigned char d;
|
||||
unsigned char e;
|
||||
if (!len)
|
||||
goto out;
|
||||
while (len--) {
|
||||
c = *string++;
|
||||
if (c == '\\') {
|
||||
if (!len--)
|
||||
goto out;
|
||||
c = *string++;
|
||||
switch (c) {
|
||||
case '\\': /* "\\" */
|
||||
continue;
|
||||
case '$': /* "\$" */
|
||||
case '+': /* "\+" */
|
||||
case '?': /* "\?" */
|
||||
case '*': /* "\*" */
|
||||
case '@': /* "\@" */
|
||||
case 'x': /* "\x" */
|
||||
case 'X': /* "\X" */
|
||||
case 'a': /* "\a" */
|
||||
case 'A': /* "\A" */
|
||||
case '-': /* "\-" */
|
||||
continue;
|
||||
case '{': /* "/\{" */
|
||||
if (string - 3 < start || *(string - 3) != '/')
|
||||
break;
|
||||
in_repetition = true;
|
||||
continue;
|
||||
case '}': /* "\}/" */
|
||||
if (*string != '/')
|
||||
break;
|
||||
if (!in_repetition)
|
||||
break;
|
||||
in_repetition = false;
|
||||
continue;
|
||||
case '0': /* "\ooo" */
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
if (!len-- || !len--)
|
||||
break;
|
||||
d = *string++;
|
||||
e = *string++;
|
||||
if (d < '0' || d > '7' || e < '0' || e > '7')
|
||||
break;
|
||||
c = tomoyo_make_byte(c, d, e);
|
||||
if (tomoyo_invalid(c))
|
||||
continue; /* pattern is not \000 */
|
||||
}
|
||||
goto out;
|
||||
} else if (in_repetition && c == '/') {
|
||||
goto out;
|
||||
} else if (tomoyo_invalid(c)) {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
if (in_repetition)
|
||||
goto out;
|
||||
return true;
|
||||
out:
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_correct_word - Validate a string.
|
||||
*
|
||||
* @string: The string to check.
|
||||
*
|
||||
* Check whether the given string follows the naming rules.
|
||||
* Returns true if @string follows the naming rules, false otherwise.
|
||||
*/
|
||||
bool tomoyo_correct_word(const char *string)
|
||||
{
|
||||
return tomoyo_correct_word2(string, strlen(string));
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_correct_path - Validate a pathname.
|
||||
*
|
||||
* @filename: The pathname to check.
|
||||
*
|
||||
* Check whether the given pathname follows the naming rules.
|
||||
* Returns true if @filename follows the naming rules, false otherwise.
|
||||
*/
|
||||
bool tomoyo_correct_path(const char *filename)
|
||||
{
|
||||
return *filename == '/' && tomoyo_correct_word(filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_correct_domain - Check whether the given domainname follows the naming rules.
|
||||
*
|
||||
* @domainname: The domainname to check.
|
||||
*
|
||||
* Returns true if @domainname follows the naming rules, false otherwise.
|
||||
*/
|
||||
bool tomoyo_correct_domain(const unsigned char *domainname)
|
||||
{
|
||||
if (!domainname || strncmp(domainname, TOMOYO_ROOT_NAME,
|
||||
TOMOYO_ROOT_NAME_LEN))
|
||||
goto out;
|
||||
domainname += TOMOYO_ROOT_NAME_LEN;
|
||||
if (!*domainname)
|
||||
return true;
|
||||
if (*domainname++ != ' ')
|
||||
goto out;
|
||||
while (1) {
|
||||
const unsigned char *cp = strchr(domainname, ' ');
|
||||
if (!cp)
|
||||
break;
|
||||
if (*domainname != '/' ||
|
||||
!tomoyo_correct_word2(domainname, cp - domainname - 1))
|
||||
goto out;
|
||||
domainname = cp + 1;
|
||||
}
|
||||
return tomoyo_correct_path(domainname);
|
||||
out:
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_domain_def - Check whether the given token can be a domainname.
|
||||
*
|
||||
* @buffer: The token to check.
|
||||
*
|
||||
* Returns true if @buffer possibly be a domainname, false otherwise.
|
||||
*/
|
||||
bool tomoyo_domain_def(const unsigned char *buffer)
|
||||
{
|
||||
return !strncmp(buffer, TOMOYO_ROOT_NAME, TOMOYO_ROOT_NAME_LEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_find_domain - Find a domain by the given name.
|
||||
*
|
||||
* @domainname: The domainname to find.
|
||||
*
|
||||
* Returns pointer to "struct tomoyo_domain_info" if found, NULL otherwise.
|
||||
*
|
||||
* Caller holds tomoyo_read_lock().
|
||||
*/
|
||||
struct tomoyo_domain_info *tomoyo_find_domain(const char *domainname)
|
||||
{
|
||||
struct tomoyo_domain_info *domain;
|
||||
struct tomoyo_path_info name;
|
||||
|
||||
name.name = domainname;
|
||||
tomoyo_fill_path_info(&name);
|
||||
list_for_each_entry_rcu(domain, &tomoyo_domain_list, list) {
|
||||
if (!domain->is_deleted &&
|
||||
!tomoyo_pathcmp(&name, domain->domainname))
|
||||
return domain;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_const_part_length - Evaluate the initial length without a pattern in a token.
|
||||
*
|
||||
* @filename: The string to evaluate.
|
||||
*
|
||||
* Returns the initial length without a pattern in @filename.
|
||||
*/
|
||||
static int tomoyo_const_part_length(const char *filename)
|
||||
{
|
||||
char c;
|
||||
int len = 0;
|
||||
|
||||
if (!filename)
|
||||
return 0;
|
||||
while ((c = *filename++) != '\0') {
|
||||
if (c != '\\') {
|
||||
len++;
|
||||
continue;
|
||||
}
|
||||
c = *filename++;
|
||||
switch (c) {
|
||||
case '\\': /* "\\" */
|
||||
len += 2;
|
||||
continue;
|
||||
case '0': /* "\ooo" */
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
c = *filename++;
|
||||
if (c < '0' || c > '7')
|
||||
break;
|
||||
c = *filename++;
|
||||
if (c < '0' || c > '7')
|
||||
break;
|
||||
len += 4;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_fill_path_info - Fill in "struct tomoyo_path_info" members.
|
||||
*
|
||||
* @ptr: Pointer to "struct tomoyo_path_info" to fill in.
|
||||
*
|
||||
* The caller sets "struct tomoyo_path_info"->name.
|
||||
*/
|
||||
void tomoyo_fill_path_info(struct tomoyo_path_info *ptr)
|
||||
{
|
||||
const char *name = ptr->name;
|
||||
const int len = strlen(name);
|
||||
|
||||
ptr->const_len = tomoyo_const_part_length(name);
|
||||
ptr->is_dir = len && (name[len - 1] == '/');
|
||||
ptr->is_patterned = (ptr->const_len < len);
|
||||
ptr->hash = full_name_hash(name, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_file_matches_pattern2 - Pattern matching without '/' character and "\-" pattern.
|
||||
*
|
||||
* @filename: The start of string to check.
|
||||
* @filename_end: The end of string to check.
|
||||
* @pattern: The start of pattern to compare.
|
||||
* @pattern_end: The end of pattern to compare.
|
||||
*
|
||||
* Returns true if @filename matches @pattern, false otherwise.
|
||||
*/
|
||||
static bool tomoyo_file_matches_pattern2(const char *filename,
|
||||
const char *filename_end,
|
||||
const char *pattern,
|
||||
const char *pattern_end)
|
||||
{
|
||||
while (filename < filename_end && pattern < pattern_end) {
|
||||
char c;
|
||||
if (*pattern != '\\') {
|
||||
if (*filename++ != *pattern++)
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
c = *filename;
|
||||
pattern++;
|
||||
switch (*pattern) {
|
||||
int i;
|
||||
int j;
|
||||
case '?':
|
||||
if (c == '/') {
|
||||
return false;
|
||||
} else if (c == '\\') {
|
||||
if (filename[1] == '\\')
|
||||
filename++;
|
||||
else if (tomoyo_byte_range(filename + 1))
|
||||
filename += 3;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case '\\':
|
||||
if (c != '\\')
|
||||
return false;
|
||||
if (*++filename != '\\')
|
||||
return false;
|
||||
break;
|
||||
case '+':
|
||||
if (!isdigit(c))
|
||||
return false;
|
||||
break;
|
||||
case 'x':
|
||||
if (!isxdigit(c))
|
||||
return false;
|
||||
break;
|
||||
case 'a':
|
||||
if (!tomoyo_alphabet_char(c))
|
||||
return false;
|
||||
break;
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
if (c == '\\' && tomoyo_byte_range(filename + 1)
|
||||
&& strncmp(filename + 1, pattern, 3) == 0) {
|
||||
filename += 3;
|
||||
pattern += 2;
|
||||
break;
|
||||
}
|
||||
return false; /* Not matched. */
|
||||
case '*':
|
||||
case '@':
|
||||
for (i = 0; i <= filename_end - filename; i++) {
|
||||
if (tomoyo_file_matches_pattern2(
|
||||
filename + i, filename_end,
|
||||
pattern + 1, pattern_end))
|
||||
return true;
|
||||
c = filename[i];
|
||||
if (c == '.' && *pattern == '@')
|
||||
break;
|
||||
if (c != '\\')
|
||||
continue;
|
||||
if (filename[i + 1] == '\\')
|
||||
i++;
|
||||
else if (tomoyo_byte_range(filename + i + 1))
|
||||
i += 3;
|
||||
else
|
||||
break; /* Bad pattern. */
|
||||
}
|
||||
return false; /* Not matched. */
|
||||
default:
|
||||
j = 0;
|
||||
c = *pattern;
|
||||
if (c == '$') {
|
||||
while (isdigit(filename[j]))
|
||||
j++;
|
||||
} else if (c == 'X') {
|
||||
while (isxdigit(filename[j]))
|
||||
j++;
|
||||
} else if (c == 'A') {
|
||||
while (tomoyo_alphabet_char(filename[j]))
|
||||
j++;
|
||||
}
|
||||
for (i = 1; i <= j; i++) {
|
||||
if (tomoyo_file_matches_pattern2(
|
||||
filename + i, filename_end,
|
||||
pattern + 1, pattern_end))
|
||||
return true;
|
||||
}
|
||||
return false; /* Not matched or bad pattern. */
|
||||
}
|
||||
filename++;
|
||||
pattern++;
|
||||
}
|
||||
while (*pattern == '\\' &&
|
||||
(*(pattern + 1) == '*' || *(pattern + 1) == '@'))
|
||||
pattern += 2;
|
||||
return filename == filename_end && pattern == pattern_end;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_file_matches_pattern - Pattern matching without '/' character.
|
||||
*
|
||||
* @filename: The start of string to check.
|
||||
* @filename_end: The end of string to check.
|
||||
* @pattern: The start of pattern to compare.
|
||||
* @pattern_end: The end of pattern to compare.
|
||||
*
|
||||
* Returns true if @filename matches @pattern, false otherwise.
|
||||
*/
|
||||
static bool tomoyo_file_matches_pattern(const char *filename,
|
||||
const char *filename_end,
|
||||
const char *pattern,
|
||||
const char *pattern_end)
|
||||
{
|
||||
const char *pattern_start = pattern;
|
||||
bool first = true;
|
||||
bool result;
|
||||
|
||||
while (pattern < pattern_end - 1) {
|
||||
/* Split at "\-" pattern. */
|
||||
if (*pattern++ != '\\' || *pattern++ != '-')
|
||||
continue;
|
||||
result = tomoyo_file_matches_pattern2(filename,
|
||||
filename_end,
|
||||
pattern_start,
|
||||
pattern - 2);
|
||||
if (first)
|
||||
result = !result;
|
||||
if (result)
|
||||
return false;
|
||||
first = false;
|
||||
pattern_start = pattern;
|
||||
}
|
||||
result = tomoyo_file_matches_pattern2(filename, filename_end,
|
||||
pattern_start, pattern_end);
|
||||
return first ? result : !result;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_path_matches_pattern2 - Do pathname pattern matching.
|
||||
*
|
||||
* @f: The start of string to check.
|
||||
* @p: The start of pattern to compare.
|
||||
*
|
||||
* Returns true if @f matches @p, false otherwise.
|
||||
*/
|
||||
static bool tomoyo_path_matches_pattern2(const char *f, const char *p)
|
||||
{
|
||||
const char *f_delimiter;
|
||||
const char *p_delimiter;
|
||||
|
||||
while (*f && *p) {
|
||||
f_delimiter = strchr(f, '/');
|
||||
if (!f_delimiter)
|
||||
f_delimiter = f + strlen(f);
|
||||
p_delimiter = strchr(p, '/');
|
||||
if (!p_delimiter)
|
||||
p_delimiter = p + strlen(p);
|
||||
if (*p == '\\' && *(p + 1) == '{')
|
||||
goto recursive;
|
||||
if (!tomoyo_file_matches_pattern(f, f_delimiter, p,
|
||||
p_delimiter))
|
||||
return false;
|
||||
f = f_delimiter;
|
||||
if (*f)
|
||||
f++;
|
||||
p = p_delimiter;
|
||||
if (*p)
|
||||
p++;
|
||||
}
|
||||
/* Ignore trailing "\*" and "\@" in @pattern. */
|
||||
while (*p == '\\' &&
|
||||
(*(p + 1) == '*' || *(p + 1) == '@'))
|
||||
p += 2;
|
||||
return !*f && !*p;
|
||||
recursive:
|
||||
/*
|
||||
* The "\{" pattern is permitted only after '/' character.
|
||||
* This guarantees that below "*(p - 1)" is safe.
|
||||
* Also, the "\}" pattern is permitted only before '/' character
|
||||
* so that "\{" + "\}" pair will not break the "\-" operator.
|
||||
*/
|
||||
if (*(p - 1) != '/' || p_delimiter <= p + 3 || *p_delimiter != '/' ||
|
||||
*(p_delimiter - 1) != '}' || *(p_delimiter - 2) != '\\')
|
||||
return false; /* Bad pattern. */
|
||||
do {
|
||||
/* Compare current component with pattern. */
|
||||
if (!tomoyo_file_matches_pattern(f, f_delimiter, p + 2,
|
||||
p_delimiter - 2))
|
||||
break;
|
||||
/* Proceed to next component. */
|
||||
f = f_delimiter;
|
||||
if (!*f)
|
||||
break;
|
||||
f++;
|
||||
/* Continue comparison. */
|
||||
if (tomoyo_path_matches_pattern2(f, p_delimiter + 1))
|
||||
return true;
|
||||
f_delimiter = strchr(f, '/');
|
||||
} while (f_delimiter);
|
||||
return false; /* Not matched. */
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_path_matches_pattern - Check whether the given filename matches the given pattern.
|
||||
*
|
||||
* @filename: The filename to check.
|
||||
* @pattern: The pattern to compare.
|
||||
*
|
||||
* Returns true if matches, false otherwise.
|
||||
*
|
||||
* The following patterns are available.
|
||||
* \\ \ itself.
|
||||
* \ooo Octal representation of a byte.
|
||||
* \* Zero or more repetitions of characters other than '/'.
|
||||
* \@ Zero or more repetitions of characters other than '/' or '.'.
|
||||
* \? 1 byte character other than '/'.
|
||||
* \$ One or more repetitions of decimal digits.
|
||||
* \+ 1 decimal digit.
|
||||
* \X One or more repetitions of hexadecimal digits.
|
||||
* \x 1 hexadecimal digit.
|
||||
* \A One or more repetitions of alphabet characters.
|
||||
* \a 1 alphabet character.
|
||||
*
|
||||
* \- Subtraction operator.
|
||||
*
|
||||
* /\{dir\}/ '/' + 'One or more repetitions of dir/' (e.g. /dir/ /dir/dir/
|
||||
* /dir/dir/dir/ ).
|
||||
*/
|
||||
bool tomoyo_path_matches_pattern(const struct tomoyo_path_info *filename,
|
||||
const struct tomoyo_path_info *pattern)
|
||||
{
|
||||
const char *f = filename->name;
|
||||
const char *p = pattern->name;
|
||||
const int len = pattern->const_len;
|
||||
|
||||
/* If @pattern doesn't contain pattern, I can use strcmp(). */
|
||||
if (!pattern->is_patterned)
|
||||
return !tomoyo_pathcmp(filename, pattern);
|
||||
/* Don't compare directory and non-directory. */
|
||||
if (filename->is_dir != pattern->is_dir)
|
||||
return false;
|
||||
/* Compare the initial length without patterns. */
|
||||
if (strncmp(f, p, len))
|
||||
return false;
|
||||
f += len;
|
||||
p += len;
|
||||
return tomoyo_path_matches_pattern2(f, p);
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_get_exe - Get tomoyo_realpath() of current process.
|
||||
*
|
||||
* Returns the tomoyo_realpath() of current process on success, NULL otherwise.
|
||||
*
|
||||
* This function uses kzalloc(), so the caller must call kfree()
|
||||
* if this function didn't return NULL.
|
||||
*/
|
||||
const char *tomoyo_get_exe(void)
|
||||
{
|
||||
struct mm_struct *mm = current->mm;
|
||||
struct vm_area_struct *vma;
|
||||
const char *cp = NULL;
|
||||
|
||||
if (!mm)
|
||||
return NULL;
|
||||
down_read(&mm->mmap_sem);
|
||||
for (vma = mm->mmap; vma; vma = vma->vm_next) {
|
||||
if ((vma->vm_flags & VM_EXECUTABLE) && vma->vm_file) {
|
||||
cp = tomoyo_realpath_from_path(&vma->vm_file->f_path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
up_read(&mm->mmap_sem);
|
||||
return cp;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_get_mode - Get MAC mode.
|
||||
*
|
||||
* @profile: Profile number.
|
||||
* @index: Index number of functionality.
|
||||
*
|
||||
* Returns mode.
|
||||
*/
|
||||
int tomoyo_get_mode(const u8 profile, const u8 index)
|
||||
{
|
||||
u8 mode;
|
||||
const u8 category = TOMOYO_MAC_CATEGORY_FILE;
|
||||
if (!tomoyo_policy_loaded)
|
||||
return TOMOYO_CONFIG_DISABLED;
|
||||
mode = tomoyo_profile(profile)->config[index];
|
||||
if (mode == TOMOYO_CONFIG_USE_DEFAULT)
|
||||
mode = tomoyo_profile(profile)->config[category];
|
||||
if (mode == TOMOYO_CONFIG_USE_DEFAULT)
|
||||
mode = tomoyo_profile(profile)->default_config;
|
||||
return mode & 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_init_request_info - Initialize "struct tomoyo_request_info" members.
|
||||
*
|
||||
* @r: Pointer to "struct tomoyo_request_info" to initialize.
|
||||
* @domain: Pointer to "struct tomoyo_domain_info". NULL for tomoyo_domain().
|
||||
* @index: Index number of functionality.
|
||||
*
|
||||
* Returns mode.
|
||||
*/
|
||||
int tomoyo_init_request_info(struct tomoyo_request_info *r,
|
||||
struct tomoyo_domain_info *domain, const u8 index)
|
||||
{
|
||||
u8 profile;
|
||||
memset(r, 0, sizeof(*r));
|
||||
if (!domain)
|
||||
domain = tomoyo_domain();
|
||||
r->domain = domain;
|
||||
profile = domain->profile;
|
||||
r->profile = profile;
|
||||
r->type = index;
|
||||
r->mode = tomoyo_get_mode(profile, index);
|
||||
return r->mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_last_word - Get last component of a line.
|
||||
*
|
||||
* @line: A line.
|
||||
*
|
||||
* Returns the last word of a line.
|
||||
*/
|
||||
const char *tomoyo_last_word(const char *name)
|
||||
{
|
||||
const char *cp = strrchr(name, ' ');
|
||||
if (cp)
|
||||
return cp + 1;
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_warn_log - Print warning or error message on console.
|
||||
*
|
||||
* @r: Pointer to "struct tomoyo_request_info".
|
||||
* @fmt: The printf()'s format string, followed by parameters.
|
||||
*/
|
||||
void tomoyo_warn_log(struct tomoyo_request_info *r, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
char *buffer;
|
||||
const struct tomoyo_domain_info * const domain = r->domain;
|
||||
const struct tomoyo_profile *profile = tomoyo_profile(domain->profile);
|
||||
switch (r->mode) {
|
||||
case TOMOYO_CONFIG_ENFORCING:
|
||||
if (!profile->enforcing->enforcing_verbose)
|
||||
return;
|
||||
break;
|
||||
case TOMOYO_CONFIG_PERMISSIVE:
|
||||
if (!profile->permissive->permissive_verbose)
|
||||
return;
|
||||
break;
|
||||
case TOMOYO_CONFIG_LEARNING:
|
||||
if (!profile->learning->learning_verbose)
|
||||
return;
|
||||
break;
|
||||
}
|
||||
buffer = kmalloc(4096, GFP_NOFS);
|
||||
if (!buffer)
|
||||
return;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, 4095, fmt, args);
|
||||
va_end(args);
|
||||
buffer[4095] = '\0';
|
||||
printk(KERN_WARNING "%s: Access %s denied for %s\n",
|
||||
r->mode == TOMOYO_CONFIG_ENFORCING ? "ERROR" : "WARNING", buffer,
|
||||
tomoyo_last_word(domain->domainname->name));
|
||||
kfree(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* tomoyo_domain_quota_is_ok - Check for domain's quota.
|
||||
*
|
||||
* @r: Pointer to "struct tomoyo_request_info".
|
||||
*
|
||||
* Returns true if the domain is not exceeded quota, false otherwise.
|
||||
*
|
||||
* Caller holds tomoyo_read_lock().
|
||||
*/
|
||||
bool tomoyo_domain_quota_is_ok(struct tomoyo_request_info *r)
|
||||
{
|
||||
unsigned int count = 0;
|
||||
struct tomoyo_domain_info *domain = r->domain;
|
||||
struct tomoyo_acl_info *ptr;
|
||||
|
||||
if (r->mode != TOMOYO_CONFIG_LEARNING)
|
||||
return false;
|
||||
if (!domain)
|
||||
return true;
|
||||
list_for_each_entry_rcu(ptr, &domain->acl_info_list, list) {
|
||||
if (ptr->is_deleted)
|
||||
continue;
|
||||
switch (ptr->type) {
|
||||
u16 perm;
|
||||
u8 i;
|
||||
case TOMOYO_TYPE_PATH_ACL:
|
||||
perm = container_of(ptr, struct tomoyo_path_acl, head)
|
||||
->perm;
|
||||
for (i = 0; i < TOMOYO_MAX_PATH_OPERATION; i++)
|
||||
if (perm & (1 << i))
|
||||
count++;
|
||||
if (perm & (1 << TOMOYO_TYPE_READ_WRITE))
|
||||
count -= 2;
|
||||
break;
|
||||
case TOMOYO_TYPE_PATH2_ACL:
|
||||
perm = container_of(ptr, struct tomoyo_path2_acl, head)
|
||||
->perm;
|
||||
for (i = 0; i < TOMOYO_MAX_PATH2_OPERATION; i++)
|
||||
if (perm & (1 << i))
|
||||
count++;
|
||||
break;
|
||||
case TOMOYO_TYPE_PATH_NUMBER_ACL:
|
||||
perm = container_of(ptr, struct tomoyo_path_number_acl,
|
||||
head)->perm;
|
||||
for (i = 0; i < TOMOYO_MAX_PATH_NUMBER_OPERATION; i++)
|
||||
if (perm & (1 << i))
|
||||
count++;
|
||||
break;
|
||||
case TOMOYO_TYPE_MKDEV_ACL:
|
||||
perm = container_of(ptr, struct tomoyo_mkdev_acl,
|
||||
head)->perm;
|
||||
for (i = 0; i < TOMOYO_MAX_MKDEV_OPERATION; i++)
|
||||
if (perm & (1 << i))
|
||||
count++;
|
||||
break;
|
||||
default:
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (count < tomoyo_profile(domain->profile)->learning->
|
||||
learning_max_entry)
|
||||
return true;
|
||||
if (!domain->quota_warned) {
|
||||
domain->quota_warned = true;
|
||||
printk(KERN_WARNING "TOMOYO-WARNING: "
|
||||
"Domain '%s' has so many ACLs to hold. "
|
||||
"Stopped learning mode.\n", domain->domainname->name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user