Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/linux-security

Pull security subsystem updates for 3.4 from James Morris:
 "The main addition here is the new Yama security module from Kees Cook,
  which was discussed at the Linux Security Summit last year.  Its
  purpose is to collect miscellaneous DAC security enhancements in one
  place.  This also marks a departure in policy for LSM modules, which
  were previously limited to being standalone access control systems.
  Chromium OS is using Yama, and I believe there are plans for Ubuntu,
  at least.

  This patchset also includes maintenance updates for AppArmor, TOMOYO
  and others."

Fix trivial conflict in <net/sock.h> due to the jumo_label->static_key
rename.

* 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/linux-security: (38 commits)
  AppArmor: Fix location of const qualifier on generated string tables
  TOMOYO: Return error if fails to delete a domain
  AppArmor: add const qualifiers to string arrays
  AppArmor: Add ability to load extended policy
  TOMOYO: Return appropriate value to poll().
  AppArmor: Move path failure information into aa_get_name and rename
  AppArmor: Update dfa matching routines.
  AppArmor: Minor cleanup of d_namespace_path to consolidate error handling
  AppArmor: Retrieve the dentry_path for error reporting when path lookup fails
  AppArmor: Add const qualifiers to generated string tables
  AppArmor: Fix oops in policy unpack auditing
  AppArmor: Fix error returned when a path lookup is disconnected
  KEYS: testing wrong bit for KEY_FLAG_REVOKED
  TOMOYO: Fix mount flags checking order.
  security: fix ima kconfig warning
  AppArmor: Fix the error case for chroot relative path name lookup
  AppArmor: fix mapping of META_READ to audit and quiet flags
  AppArmor: Fix underflow in xindex calculation
  AppArmor: Fix dropping of allowed operations that are force audited
  AppArmor: Add mising end of structure test to caps unpacking
  ...
This commit is contained in:
Linus Torvalds 2012-03-21 13:25:04 -07:00
commit 3556485f15
71 changed files with 1035 additions and 251 deletions

View file

@ -102,6 +102,10 @@ implemented in the module can be called after doing:
If _expiry is non-NULL, the expiry time (TTL) of the result will be
returned also.
The kernel maintains an internal keyring in which it caches looked up keys.
This can be cleared by any process that has the CAP_SYS_ADMIN capability by
the use of KEYCTL_KEYRING_CLEAR on the keyring ID.
===============================
READING DNS KEYS FROM USERSPACE

View file

@ -6,6 +6,8 @@ SELinux.txt
- how to get started with the SELinux security enhancement.
Smack.txt
- documentation on the Smack Linux Security Module.
Yama.txt
- documentation on the Yama Linux Security Module.
apparmor.txt
- documentation on the AppArmor security extension.
credentials.txt

View file

@ -0,0 +1,65 @@
Yama is a Linux Security Module that collects a number of system-wide DAC
security protections that are not handled by the core kernel itself. To
select it at boot time, specify "security=yama" (though this will disable
any other LSM).
Yama is controlled through sysctl in /proc/sys/kernel/yama:
- ptrace_scope
==============================================================
ptrace_scope:
As Linux grows in popularity, it will become a larger target for
malware. One particularly troubling weakness of the Linux process
interfaces is that a single user is able to examine the memory and
running state of any of their processes. For example, if one application
(e.g. Pidgin) was compromised, it would be possible for an attacker to
attach to other running processes (e.g. Firefox, SSH sessions, GPG agent,
etc) to extract additional credentials and continue to expand the scope
of their attack without resorting to user-assisted phishing.
This is not a theoretical problem. SSH session hijacking
(http://www.storm.net.nz/projects/7) and arbitrary code injection
(http://c-skills.blogspot.com/2007/05/injectso.html) attacks already
exist and remain possible if ptrace is allowed to operate as before.
Since ptrace is not commonly used by non-developers and non-admins, system
builders should be allowed the option to disable this debugging system.
For a solution, some applications use prctl(PR_SET_DUMPABLE, ...) to
specifically disallow such ptrace attachment (e.g. ssh-agent), but many
do not. A more general solution is to only allow ptrace directly from a
parent to a child process (i.e. direct "gdb EXE" and "strace EXE" still
work), or with CAP_SYS_PTRACE (i.e. "gdb --pid=PID", and "strace -p PID"
still work as root).
For software that has defined application-specific relationships
between a debugging process and its inferior (crash handlers, etc),
prctl(PR_SET_PTRACER, pid, ...) can be used. An inferior can declare which
other process (and its descendents) are allowed to call PTRACE_ATTACH
against it. Only one such declared debugging process can exists for
each inferior at a time. For example, this is used by KDE, Chromium, and
Firefox's crash handlers, and by Wine for allowing only Wine processes
to ptrace each other. If a process wishes to entirely disable these ptrace
restrictions, it can call prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, ...)
so that any otherwise allowed process (even those in external pid namespaces)
may attach.
The sysctl settings are:
0 - classic ptrace permissions: a process can PTRACE_ATTACH to any other
process running under the same uid, as long as it is dumpable (i.e.
did not transition uids, start privileged, or have called
prctl(PR_SET_DUMPABLE...) already).
1 - restricted ptrace: a process must have a predefined relationship
with the inferior it wants to call PTRACE_ATTACH on. By default,
this relationship is that of only its descendants when the above
classic criteria is also met. To change the relationship, an
inferior can call prctl(PR_SET_PTRACER, debugger, ...) to declare
an allowed debugger PID to call PTRACE_ATTACH on the inferior.
The original children-only logic was based on the restrictions in grsecurity.
==============================================================

View file

@ -554,6 +554,10 @@ The keyctl syscall functions are:
process must have write permission on the keyring, and it must be a
keyring (or else error ENOTDIR will result).
This function can also be used to clear special kernel keyrings if they
are appropriately marked if the user has CAP_SYS_ADMIN capability. The
DNS resolver cache keyring is an example of this.
(*) Link a key into a keyring:

View file

@ -5,7 +5,6 @@
menuconfig TCG_TPM
tristate "TPM Hardware Support"
depends on HAS_IOMEM
depends on EXPERIMENTAL
select SECURITYFS
---help---
If you have a TPM security chip in your system, which

View file

@ -1221,12 +1221,13 @@ ssize_t tpm_read(struct file *file, char __user *buf,
ret_size = atomic_read(&chip->data_pending);
atomic_set(&chip->data_pending, 0);
if (ret_size > 0) { /* relay data */
ssize_t orig_ret_size = ret_size;
if (size < ret_size)
ret_size = size;
mutex_lock(&chip->buffer_mutex);
rc = copy_to_user(buf, chip->data_buffer, ret_size);
memset(chip->data_buffer, 0, ret_size);
memset(chip->data_buffer, 0, orig_ret_size);
if (rc)
ret_size = -EFAULT;

View file

@ -99,6 +99,8 @@ struct tpm_vendor_specific {
wait_queue_head_t int_queue;
};
#define TPM_VID_INTEL 0x8086
struct tpm_chip {
struct device *dev; /* Device stuff */

View file

@ -367,7 +367,12 @@ static int probe_itpm(struct tpm_chip *chip)
0x00, 0x00, 0x00, 0xf1
};
size_t len = sizeof(cmd_getticks);
int rem_itpm = itpm;
bool rem_itpm = itpm;
u16 vendor = ioread16(chip->vendor.iobase + TPM_DID_VID(0));
/* probe only iTPMS */
if (vendor != TPM_VID_INTEL)
return 0;
itpm = 0;
@ -390,9 +395,6 @@ static int probe_itpm(struct tpm_chip *chip)
out:
itpm = rem_itpm;
tpm_tis_ready(chip);
/* some TPMs need a break here otherwise they will not work
* correctly on the immediately subsequent command */
msleep(chip->vendor.timeout_b);
release_locality(chip, chip->vendor.locality, 0);
return rc;
@ -508,7 +510,7 @@ static int tpm_tis_init(struct device *dev, resource_size_t start,
resource_size_t len, unsigned int irq)
{
u32 vendor, intfcaps, intmask;
int rc, i, irq_s, irq_e;
int rc, i, irq_s, irq_e, probe;
struct tpm_chip *chip;
if (!(chip = tpm_register_hardware(dev, &tpm_tis)))
@ -538,11 +540,12 @@ static int tpm_tis_init(struct device *dev, resource_size_t start,
vendor >> 16, ioread8(chip->vendor.iobase + TPM_RID(0)));
if (!itpm) {
itpm = probe_itpm(chip);
if (itpm < 0) {
probe = probe_itpm(chip);
if (probe < 0) {
rc = -ENODEV;
goto out_err;
}
itpm = (probe == 0) ? 0 : 1;
}
if (itpm)

View file

@ -13,6 +13,7 @@
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/cdev.h>
#include <linux/idr.h>
#include <linux/fs.h>
#include <net/net_namespace.h>

View file

@ -23,6 +23,7 @@
#include <linux/crypto.h>
#include <linux/completion.h>
#include <linux/module.h>
#include <linux/idr.h>
#include <asm/unaligned.h>
#include <scsi/scsi_device.h>
#include <scsi/iscsi_proto.h>

View file

@ -21,6 +21,7 @@
#include <linux/string.h>
#include <linux/kthread.h>
#include <linux/crypto.h>
#include <linux/idr.h>
#include <scsi/iscsi_proto.h>
#include <target/target_core_base.h>
#include <target/target_core_fabric.h>

View file

@ -556,6 +556,7 @@ init_cifs_idmap(void)
/* instruct request_key() to use this special keyring as a cache for
* the results it looks up */
set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
cred->thread_keyring = keyring;
cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
root_cred = cred;

View file

@ -36,6 +36,7 @@
#include <linux/inet.h>
#include <linux/in6.h>
#include <linux/slab.h>
#include <linux/idr.h>
#include <net/ipv6.h>
#include <linux/nfs_xdr.h>
#include <linux/sunrpc/bc_xprt.h>

View file

@ -198,6 +198,7 @@ int nfs_idmap_init(void)
if (ret < 0)
goto failed_put_key;
set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
cred->thread_keyring = keyring;
cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
id_resolver_cache = cred;

View file

@ -6,7 +6,9 @@
#include <linux/poll.h>
#include <linux/proc_fs.h>
#include <linux/security.h>
#include <linux/sched.h>
#include <linux/namei.h>
#include <linux/mm.h>
#include "internal.h"
static const struct dentry_operations proc_sys_dentry_operations;

View file

@ -71,6 +71,7 @@
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/security.h>
#include <linux/sched.h>
#include <linux/kmod.h>
#include <linux/namei.h>
#include <linux/capability.h>

View file

@ -32,6 +32,7 @@
#include <linux/backing-dev.h>
#include <linux/rculist_bl.h>
#include <linux/cleancache.h>
#include <linux/fsnotify.h>
#include "internal.h"

View file

@ -155,6 +155,7 @@ struct key {
#define KEY_FLAG_IN_QUOTA 3 /* set if key consumes quota */
#define KEY_FLAG_USER_CONSTRUCT 4 /* set if key is being constructed in userspace */
#define KEY_FLAG_NEGATIVE 5 /* set if key is negative */
#define KEY_FLAG_ROOT_CAN_CLEAR 6 /* set if key can be cleared by root without permission */
/* the description string
* - this is used to match a key against search criteria

View file

@ -114,4 +114,11 @@
# define PR_SET_MM_START_BRK 6
# define PR_SET_MM_BRK 7
/*
* Set specific pid that is allowed to ptrace the current task.
* A value of 0 mean "no process".
*/
#define PR_SET_PTRACER 0x59616d61
# define PR_SET_PTRACER_ANY ((unsigned long)-1)
#endif /* _LINUX_PRCTL_H */

View file

@ -22,22 +22,36 @@
#ifndef __LINUX_SECURITY_H
#define __LINUX_SECURITY_H
#include <linux/fs.h>
#include <linux/fsnotify.h>
#include <linux/binfmts.h>
#include <linux/dcache.h>
#include <linux/signal.h>
#include <linux/resource.h>
#include <linux/sem.h>
#include <linux/shm.h>
#include <linux/mm.h> /* PAGE_ALIGN */
#include <linux/msg.h>
#include <linux/sched.h>
#include <linux/key.h>
#include <linux/xfrm.h>
#include <linux/capability.h>
#include <linux/slab.h>
#include <linux/xattr.h>
#include <net/flow.h>
#include <linux/err.h>
struct linux_binprm;
struct cred;
struct rlimit;
struct siginfo;
struct sem_array;
struct sembuf;
struct kern_ipc_perm;
struct audit_context;
struct super_block;
struct inode;
struct dentry;
struct file;
struct vfsmount;
struct path;
struct qstr;
struct nameidata;
struct iattr;
struct fown_struct;
struct file_operations;
struct shmid_kernel;
struct msg_msg;
struct msg_queue;
struct xattr;
struct xfrm_sec_ctx;
struct mm_struct;
/* Maximum number of letters for an LSM name string */
#define SECURITY_NAME_MAX 10
@ -49,6 +63,7 @@
struct ctl_table;
struct audit_krule;
struct user_namespace;
struct timezone;
/*
* These functions are in security/capability.c and are used
@ -131,18 +146,6 @@ struct request_sock;
#define LSM_UNSAFE_PTRACE_CAP 4
#ifdef CONFIG_MMU
/*
* If a hint addr is less than mmap_min_addr change hint to be as
* low as possible but still greater than mmap_min_addr
*/
static inline unsigned long round_hint_to_min(unsigned long hint)
{
hint &= PAGE_MASK;
if (((void *)hint != NULL) &&
(hint < mmap_min_addr))
return PAGE_ALIGN(mmap_min_addr);
return hint;
}
extern int mmap_min_addr_handler(struct ctl_table *table, int write,
void __user *buffer, size_t *lenp, loff_t *ppos);
#endif
@ -651,6 +654,10 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
* manual page for definitions of the @clone_flags.
* @clone_flags contains the flags indicating what should be shared.
* Return 0 if permission is granted.
* @task_free:
* @task task being freed
* Handle release of task-related resources. (Note that this can be called
* from interrupt context.)
* @cred_alloc_blank:
* @cred points to the credentials.
* @gfp indicates the atomicity of any memory allocations.
@ -1493,6 +1500,7 @@ struct security_operations {
int (*dentry_open) (struct file *file, const struct cred *cred);
int (*task_create) (unsigned long clone_flags);
void (*task_free) (struct task_struct *task);
int (*cred_alloc_blank) (struct cred *cred, gfp_t gfp);
void (*cred_free) (struct cred *cred);
int (*cred_prepare)(struct cred *new, const struct cred *old,
@ -1674,9 +1682,7 @@ int security_quotactl(int cmds, int type, int id, struct super_block *sb);
int security_quota_on(struct dentry *dentry);
int security_syslog(int type);
int security_settime(const struct timespec *ts, const struct timezone *tz);
int security_vm_enough_memory(long pages);
int security_vm_enough_memory_mm(struct mm_struct *mm, long pages);
int security_vm_enough_memory_kern(long pages);
int security_bprm_set_creds(struct linux_binprm *bprm);
int security_bprm_check(struct linux_binprm *bprm);
void security_bprm_committing_creds(struct linux_binprm *bprm);
@ -1752,6 +1758,7 @@ int security_file_send_sigiotask(struct task_struct *tsk,
int security_file_receive(struct file *file);
int security_dentry_open(struct file *file, const struct cred *cred);
int security_task_create(unsigned long clone_flags);
void security_task_free(struct task_struct *task);
int security_cred_alloc_blank(struct cred *cred, gfp_t gfp);
void security_cred_free(struct cred *cred);
int security_prepare_creds(struct cred *new, const struct cred *old, gfp_t gfp);
@ -1896,25 +1903,11 @@ static inline int security_settime(const struct timespec *ts,
return cap_settime(ts, tz);
}
static inline int security_vm_enough_memory(long pages)
{
WARN_ON(current->mm == NULL);
return cap_vm_enough_memory(current->mm, pages);
}
static inline int security_vm_enough_memory_mm(struct mm_struct *mm, long pages)
{
WARN_ON(mm == NULL);
return cap_vm_enough_memory(mm, pages);
}
static inline int security_vm_enough_memory_kern(long pages)
{
/* If current->mm is a kernel thread then we will pass NULL,
for this specific case that is fine */
return cap_vm_enough_memory(current->mm, pages);
}
static inline int security_bprm_set_creds(struct linux_binprm *bprm)
{
return cap_bprm_set_creds(bprm);
@ -2245,6 +2238,9 @@ static inline int security_task_create(unsigned long clone_flags)
return 0;
}
static inline void security_task_free(struct task_struct *task)
{ }
static inline int security_cred_alloc_blank(struct cred *cred, gfp_t gfp)
{
return 0;

View file

@ -56,6 +56,8 @@
#include <linux/memcontrol.h>
#include <linux/res_counter.h>
#include <linux/static_key.h>
#include <linux/aio.h>
#include <linux/sched.h>
#include <linux/filter.h>
#include <linux/rculist_nulls.h>

View file

@ -13,7 +13,9 @@
#include <linux/security.h>
#include <linux/slab.h>
#include <linux/ipc.h>
#include <linux/msg.h>
#include <linux/ipc_namespace.h>
#include <linux/utsname.h>
#include <asm/uaccess.h>
#include "util.h"

View file

@ -16,6 +16,7 @@
#include <linux/keyctl.h>
#include <linux/init_task.h>
#include <linux/security.h>
#include <linux/binfmts.h>
#include <linux/cn_proc.h>
#if 0

View file

@ -52,6 +52,7 @@
#include <linux/hw_breakpoint.h>
#include <linux/oom.h>
#include <linux/writeback.h>
#include <linux/shm.h>
#include <asm/uaccess.h>
#include <asm/unistd.h>

View file

@ -193,6 +193,7 @@ void __put_task_struct(struct task_struct *tsk)
WARN_ON(atomic_read(&tsk->usage));
WARN_ON(tsk == current);
security_task_free(tsk);
exit_creds(tsk);
delayacct_tsk_free(tsk);
put_signal_struct(tsk->signal);
@ -355,7 +356,7 @@ static int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)
charge = 0;
if (mpnt->vm_flags & VM_ACCOUNT) {
unsigned int len = (mpnt->vm_end - mpnt->vm_start) >> PAGE_SHIFT;
if (security_vm_enough_memory(len))
if (security_vm_enough_memory_mm(oldmm, len)) /* sic */
goto fail_nomem;
charge = len;
}

View file

@ -71,6 +71,7 @@
#include <linux/ftrace.h>
#include <linux/slab.h>
#include <linux/init_task.h>
#include <linux/binfmts.h>
#include <asm/tlb.h>
#include <asm/irq_regs.h>

View file

@ -58,6 +58,7 @@
#include <linux/oom.h>
#include <linux/kmod.h>
#include <linux/capability.h>
#include <linux/binfmts.h>
#include <asm/uaccess.h>
#include <asm/processor.h>

View file

@ -935,6 +935,19 @@ void vm_stat_account(struct mm_struct *mm, unsigned long flags,
}
#endif /* CONFIG_PROC_FS */
/*
* If a hint addr is less than mmap_min_addr change hint to be as
* low as possible but still greater than mmap_min_addr
*/
static inline unsigned long round_hint_to_min(unsigned long hint)
{
hint &= PAGE_MASK;
if (((void *)hint != NULL) &&
(hint < mmap_min_addr))
return PAGE_ALIGN(mmap_min_addr);
return hint;
}
/*
* The caller must hold down_write(&current->mm->mmap_sem).
*/
@ -1235,7 +1248,7 @@ munmap_back:
*/
if (accountable_mapping(file, vm_flags)) {
charged = len >> PAGE_SHIFT;
if (security_vm_enough_memory(charged))
if (security_vm_enough_memory_mm(mm, charged))
return -ENOMEM;
vm_flags |= VM_ACCOUNT;
}
@ -2180,7 +2193,7 @@ unsigned long do_brk(unsigned long addr, unsigned long len)
if (mm->map_count > sysctl_max_map_count)
return -ENOMEM;
if (security_vm_enough_memory(len >> PAGE_SHIFT))
if (security_vm_enough_memory_mm(mm, len >> PAGE_SHIFT))
return -ENOMEM;
/* Can we just expand an old private anonymous mapping? */

View file

@ -168,7 +168,7 @@ mprotect_fixup(struct vm_area_struct *vma, struct vm_area_struct **pprev,
if (!(oldflags & (VM_ACCOUNT|VM_WRITE|VM_HUGETLB|
VM_SHARED|VM_NORESERVE))) {
charged = nrpages;
if (security_vm_enough_memory(charged))
if (security_vm_enough_memory_mm(mm, charged))
return -ENOMEM;
newflags |= VM_ACCOUNT;
}

View file

@ -329,7 +329,7 @@ static struct vm_area_struct *vma_to_resize(unsigned long addr,
if (vma->vm_flags & VM_ACCOUNT) {
unsigned long charged = (new_len - old_len) >> PAGE_SHIFT;
if (security_vm_enough_memory(charged))
if (security_vm_enough_memory_mm(mm, charged))
goto Efault;
*p = charged;
}

View file

@ -127,7 +127,7 @@ static inline struct shmem_sb_info *SHMEM_SB(struct super_block *sb)
static inline int shmem_acct_size(unsigned long flags, loff_t size)
{
return (flags & VM_NORESERVE) ?
0 : security_vm_enough_memory_kern(VM_ACCT(size));
0 : security_vm_enough_memory_mm(current->mm, VM_ACCT(size));
}
static inline void shmem_unacct_size(unsigned long flags, loff_t size)
@ -145,7 +145,7 @@ static inline void shmem_unacct_size(unsigned long flags, loff_t size)
static inline int shmem_acct_block(unsigned long flags)
{
return (flags & VM_NORESERVE) ?
security_vm_enough_memory_kern(VM_ACCT(PAGE_CACHE_SIZE)) : 0;
security_vm_enough_memory_mm(current->mm, VM_ACCT(PAGE_CACHE_SIZE)) : 0;
}
static inline void shmem_unacct_blocks(unsigned long flags, long pages)

View file

@ -1563,6 +1563,8 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
BUG_ON(!current->mm);
pathname = getname(specialfile);
err = PTR_ERR(pathname);
if (IS_ERR(pathname))
@ -1590,7 +1592,7 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
spin_unlock(&swap_lock);
goto out_dput;
}
if (!security_vm_enough_memory(p->pages))
if (!security_vm_enough_memory_mm(current->mm, p->pages))
vm_unacct_memory(p->pages);
else {
err = -ENOMEM;

View file

@ -281,6 +281,7 @@ static int __init init_dns_resolver(void)
/* instruct request_key() to use this special keyring as a cache for
* the results it looks up */
set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
cred->thread_keyring = keyring;
cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
dns_resolver_cache = cred;

View file

@ -187,6 +187,7 @@ source security/selinux/Kconfig
source security/smack/Kconfig
source security/tomoyo/Kconfig
source security/apparmor/Kconfig
source security/yama/Kconfig
source security/integrity/Kconfig
@ -196,6 +197,7 @@ choice
default DEFAULT_SECURITY_SMACK if SECURITY_SMACK
default DEFAULT_SECURITY_TOMOYO if SECURITY_TOMOYO
default DEFAULT_SECURITY_APPARMOR if SECURITY_APPARMOR
default DEFAULT_SECURITY_YAMA if SECURITY_YAMA
default DEFAULT_SECURITY_DAC
help
@ -214,6 +216,9 @@ choice
config DEFAULT_SECURITY_APPARMOR
bool "AppArmor" if SECURITY_APPARMOR=y
config DEFAULT_SECURITY_YAMA
bool "Yama" if SECURITY_YAMA=y
config DEFAULT_SECURITY_DAC
bool "Unix Discretionary Access Controls"
@ -225,6 +230,7 @@ config DEFAULT_SECURITY
default "smack" if DEFAULT_SECURITY_SMACK
default "tomoyo" if DEFAULT_SECURITY_TOMOYO
default "apparmor" if DEFAULT_SECURITY_APPARMOR
default "yama" if DEFAULT_SECURITY_YAMA
default "" if DEFAULT_SECURITY_DAC
endmenu

View file

@ -7,6 +7,7 @@ subdir-$(CONFIG_SECURITY_SELINUX) += selinux
subdir-$(CONFIG_SECURITY_SMACK) += smack
subdir-$(CONFIG_SECURITY_TOMOYO) += tomoyo
subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor
subdir-$(CONFIG_SECURITY_YAMA) += yama
# always enable default capabilities
obj-y += commoncap.o
@ -21,6 +22,7 @@ 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_SECURITY_YAMA) += yama/built-in.o
obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o
# Object integrity file lists

View file

@ -15,7 +15,7 @@ clean-files := capability_names.h rlim_names.h
# to
# [1] = "dac_override",
quiet_cmd_make-caps = GEN $@
cmd_make-caps = echo "static const char *capability_names[] = {" > $@ ;\
cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\
sed $< >>$@ -r -n -e '/CAP_FS_MASK/d' \
-e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/[\2] = "\L\1",/p';\
echo "};" >> $@
@ -28,25 +28,38 @@ cmd_make-caps = echo "static const char *capability_names[] = {" > $@ ;\
# [RLIMIT_STACK] = "stack",
#
# and build a second integer table (with the second sed cmd), that maps
# RLIMIT defines to the order defined in asm-generic/resource.h Thi is
# RLIMIT defines to the order defined in asm-generic/resource.h This is
# required by policy load to map policy ordering of RLIMITs to internal
# ordering for architectures that redefine an RLIMIT.
# Transforms lines from
# #define RLIMIT_STACK 3 /* max stack size */
# to
# RLIMIT_STACK,
#
# and build the securityfs entries for the mapping.
# Transforms lines from
# #define RLIMIT_FSIZE 1 /* Maximum filesize */
# #define RLIMIT_STACK 3 /* max stack size */
# to
# #define AA_FS_RLIMIT_MASK "fsize stack"
quiet_cmd_make-rlim = GEN $@
cmd_make-rlim = echo "static const char *rlim_names[] = {" > $@ ;\
cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \
> $@ ;\
sed $< >> $@ -r -n \
-e 's/^\# ?define[ \t]+(RLIMIT_([A-Z0-9_]+)).*/[\1] = "\L\2",/p';\
echo "};" >> $@ ;\
echo "static const int rlim_map[] = {" >> $@ ;\
echo "static const int rlim_map[RLIM_NLIMITS] = {" >> $@ ;\
sed -r -n "s/^\# ?define[ \t]+(RLIMIT_[A-Z0-9_]+).*/\1,/p" $< >> $@ ;\
echo "};" >> $@
echo "};" >> $@ ; \
echo -n '\#define AA_FS_RLIMIT_MASK "' >> $@ ;\
sed -r -n 's/^\# ?define[ \t]+RLIMIT_([A-Z0-9_]+).*/\L\1/p' $< | \
tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@
$(obj)/capability.o : $(obj)/capability_names.h
$(obj)/resource.o : $(obj)/rlim_names.h
$(obj)/capability_names.h : $(srctree)/include/linux/capability.h
$(obj)/capability_names.h : $(srctree)/include/linux/capability.h \
$(src)/Makefile
$(call cmd,make-caps)
$(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h
$(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h \
$(src)/Makefile
$(call cmd,make-rlim)

View file

@ -18,12 +18,14 @@
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include <linux/namei.h>
#include <linux/capability.h>
#include "include/apparmor.h"
#include "include/apparmorfs.h"
#include "include/audit.h"
#include "include/context.h"
#include "include/policy.h"
#include "include/resource.h"
/**
* aa_simple_write_to_buffer - common routine for getting policy from user
@ -142,38 +144,166 @@ static const struct file_operations aa_fs_profile_remove = {
.llseek = default_llseek,
};
static int aa_fs_seq_show(struct seq_file *seq, void *v)
{
struct aa_fs_entry *fs_file = seq->private;
if (!fs_file)
return 0;
switch (fs_file->v_type) {
case AA_FS_TYPE_BOOLEAN:
seq_printf(seq, "%s\n", fs_file->v.boolean ? "yes" : "no");
break;
case AA_FS_TYPE_STRING:
seq_printf(seq, "%s\n", fs_file->v.string);
break;
case AA_FS_TYPE_U64:
seq_printf(seq, "%#08lx\n", fs_file->v.u64);
break;
default:
/* Ignore unpritable entry types. */
break;
}
return 0;
}
static int aa_fs_seq_open(struct inode *inode, struct file *file)
{
return single_open(file, aa_fs_seq_show, inode->i_private);
}
const struct file_operations aa_fs_seq_file_ops = {
.owner = THIS_MODULE,
.open = aa_fs_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
/** Base file system setup **/
static struct dentry *aa_fs_dentry __initdata;
static struct aa_fs_entry aa_fs_entry_file[] = {
AA_FS_FILE_STRING("mask", "create read write exec append mmap_exec " \
"link lock"),
{ }
};
static void __init aafs_remove(const char *name)
static struct aa_fs_entry aa_fs_entry_domain[] = {
AA_FS_FILE_BOOLEAN("change_hat", 1),
AA_FS_FILE_BOOLEAN("change_hatv", 1),
AA_FS_FILE_BOOLEAN("change_onexec", 1),
AA_FS_FILE_BOOLEAN("change_profile", 1),
{ }
};
static struct aa_fs_entry aa_fs_entry_features[] = {
AA_FS_DIR("domain", aa_fs_entry_domain),
AA_FS_DIR("file", aa_fs_entry_file),
AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK),
AA_FS_DIR("rlimit", aa_fs_entry_rlimit),
{ }
};
static struct aa_fs_entry aa_fs_entry_apparmor[] = {
AA_FS_FILE_FOPS(".load", 0640, &aa_fs_profile_load),
AA_FS_FILE_FOPS(".replace", 0640, &aa_fs_profile_replace),
AA_FS_FILE_FOPS(".remove", 0640, &aa_fs_profile_remove),
AA_FS_DIR("features", aa_fs_entry_features),
{ }
};
static struct aa_fs_entry aa_fs_entry =
AA_FS_DIR("apparmor", aa_fs_entry_apparmor);
/**
* aafs_create_file - create a file entry in the apparmor securityfs
* @fs_file: aa_fs_entry to build an entry for (NOT NULL)
* @parent: the parent dentry in the securityfs
*
* Use aafs_remove_file to remove entries created with this fn.
*/
static int __init aafs_create_file(struct aa_fs_entry *fs_file,
struct dentry *parent)
{
struct dentry *dentry;
int error = 0;
dentry = lookup_one_len(name, aa_fs_dentry, strlen(name));
if (!IS_ERR(dentry)) {
securityfs_remove(dentry);
dput(dentry);
fs_file->dentry = securityfs_create_file(fs_file->name,
S_IFREG | fs_file->mode,
parent, fs_file,
fs_file->file_ops);
if (IS_ERR(fs_file->dentry)) {
error = PTR_ERR(fs_file->dentry);
fs_file->dentry = NULL;
}
return error;
}
/**
* 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)
* aafs_create_dir - recursively create a directory entry in the securityfs
* @fs_dir: aa_fs_entry (and all child entries) to build (NOT NULL)
* @parent: the parent dentry in the securityfs
*
* Used aafs_remove to remove entries created with this fn.
* Use aafs_remove_dir to remove entries created with this fn.
*/
static int __init aafs_create(const char *name, umode_t mask,
const struct file_operations *fops)
static int __init aafs_create_dir(struct aa_fs_entry *fs_dir,
struct dentry *parent)
{
struct dentry *dentry;
int error;
struct aa_fs_entry *fs_file;
dentry = securityfs_create_file(name, S_IFREG | mask, aa_fs_dentry,
NULL, fops);
fs_dir->dentry = securityfs_create_dir(fs_dir->name, parent);
if (IS_ERR(fs_dir->dentry)) {
error = PTR_ERR(fs_dir->dentry);
fs_dir->dentry = NULL;
goto failed;
}
return IS_ERR(dentry) ? PTR_ERR(dentry) : 0;
for (fs_file = fs_dir->v.files; fs_file->name; ++fs_file) {
if (fs_file->v_type == AA_FS_TYPE_DIR)
error = aafs_create_dir(fs_file, fs_dir->dentry);
else
error = aafs_create_file(fs_file, fs_dir->dentry);
if (error)
goto failed;
}
return 0;
failed:
return error;
}
/**
* aafs_remove_file - drop a single file entry in the apparmor securityfs
* @fs_file: aa_fs_entry to detach from the securityfs (NOT NULL)
*/
static void __init aafs_remove_file(struct aa_fs_entry *fs_file)
{
if (!fs_file->dentry)
return;
securityfs_remove(fs_file->dentry);
fs_file->dentry = NULL;
}
/**
* aafs_remove_dir - recursively drop a directory entry from the securityfs
* @fs_dir: aa_fs_entry (and all child entries) to detach (NOT NULL)
*/
static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir)
{
struct aa_fs_entry *fs_file;
for (fs_file = fs_dir->v.files; fs_file->name; ++fs_file) {
if (fs_file->v_type == AA_FS_TYPE_DIR)
aafs_remove_dir(fs_file);
else
aafs_remove_file(fs_file);
}
aafs_remove_file(fs_dir);
}
/**
@ -183,14 +313,7 @@ static int __init aafs_create(const char *name, umode_t mask,
*/
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;
}
aafs_remove_dir(&aa_fs_entry);
}
/**
@ -207,25 +330,13 @@ static int __init aa_create_aafs(void)
if (!apparmor_initialized)
return 0;
if (aa_fs_dentry) {
if (aa_fs_entry.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);
/* Populate fs tree. */
error = aafs_create_dir(&aa_fs_entry, NULL);
if (error)
goto error;

View file

@ -19,7 +19,7 @@
#include "include/audit.h"
#include "include/policy.h"
const char *op_table[] = {
const char *const op_table[] = {
"null",
"sysctl",
@ -73,7 +73,7 @@ const char *op_table[] = {
"profile_remove"
};
const char *audit_mode_names[] = {
const char *const audit_mode_names[] = {
"normal",
"quiet_denied",
"quiet",
@ -81,7 +81,7 @@ const char *audit_mode_names[] = {
"all"
};
static char *aa_audit_type[] = {
static const char *const aa_audit_type[] = {
"AUDIT",
"ALLOWED",
"DENIED",
@ -89,6 +89,7 @@ static char *aa_audit_type[] = {
"STATUS",
"ERROR",
"KILLED"
"AUTO"
};
/*

View file

@ -372,13 +372,12 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
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);
error = aa_path_name(&bprm->file->f_path, profile->path_flags, &buffer,
&name, &info);
if (error) {
if (profile->flags &
(PFLAG_IX_ON_NAME_ERROR | PFLAG_UNCONFINED))
error = 0;
info = "Exec failed name resolution";
name = bprm->filename;
goto audit;
}

View file

@ -173,8 +173,6 @@ static u32 map_old_perms(u32 old)
if (old & 0x40) /* AA_EXEC_MMAP */
new |= AA_EXEC_MMAP;
new |= AA_MAY_META_READ;
return new;
}
@ -212,6 +210,7 @@ static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state,
perms.quiet = map_old_perms(dfa_other_quiet(dfa, state));
perms.xindex = dfa_other_xindex(dfa, state);
}
perms.allow |= AA_MAY_META_READ;
/* change_profile wasn't determined by ownership in old mapping */
if (ACCEPT_TABLE(dfa)[state] & 0x80000000)
@ -279,22 +278,16 @@ int aa_path_perm(int op, struct aa_profile *profile, struct path *path,
int error;
flags |= profile->path_flags | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0);
error = aa_get_name(path, flags, &buffer, &name);
error = aa_path_name(path, flags, &buffer, &name, &info);
if (error) {
if (error == -ENOENT && is_deleted(path->dentry)) {
/* Access to open files that are deleted are
* give a pass (implicit delegation)
*/
error = 0;
info = NULL;
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);
@ -365,12 +358,14 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
lperms = nullperms;
/* buffer freed below, lname is pointer in buffer */
error = aa_get_name(&link, profile->path_flags, &buffer, &lname);
error = aa_path_name(&link, profile->path_flags, &buffer, &lname,
&info);
if (error)
goto audit;
/* buffer2 freed below, tname is pointer in buffer2 */
error = aa_get_name(&target, profile->path_flags, &buffer2, &tname);
error = aa_path_name(&target, profile->path_flags, &buffer2, &tname,
&info);
if (error)
goto audit;

View file

@ -19,6 +19,19 @@
#include "match.h"
/*
* Class of mediation types in the AppArmor policy db
*/
#define AA_CLASS_ENTRY 0
#define AA_CLASS_UNKNOWN 1
#define AA_CLASS_FILE 2
#define AA_CLASS_CAP 3
#define AA_CLASS_NET 4
#define AA_CLASS_RLIMITS 5
#define AA_CLASS_DOMAIN 6
#define AA_CLASS_LAST AA_CLASS_DOMAIN
/* Control parameters settable through module/boot flags */
extern enum audit_mode aa_g_audit;
extern bool aa_g_audit_header;
@ -81,7 +94,7 @@ 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);
return aa_dfa_next(dfa, start, 0);
}
static inline bool mediated_filesystem(struct inode *inode)

View file

@ -15,6 +15,50 @@
#ifndef __AA_APPARMORFS_H
#define __AA_APPARMORFS_H
enum aa_fs_type {
AA_FS_TYPE_BOOLEAN,
AA_FS_TYPE_STRING,
AA_FS_TYPE_U64,
AA_FS_TYPE_FOPS,
AA_FS_TYPE_DIR,
};
struct aa_fs_entry;
struct aa_fs_entry {
const char *name;
struct dentry *dentry;
umode_t mode;
enum aa_fs_type v_type;
union {
bool boolean;
char *string;
unsigned long u64;
struct aa_fs_entry *files;
} v;
const struct file_operations *file_ops;
};
extern const struct file_operations aa_fs_seq_file_ops;
#define AA_FS_FILE_BOOLEAN(_name, _value) \
{ .name = (_name), .mode = 0444, \
.v_type = AA_FS_TYPE_BOOLEAN, .v.boolean = (_value), \
.file_ops = &aa_fs_seq_file_ops }
#define AA_FS_FILE_STRING(_name, _value) \
{ .name = (_name), .mode = 0444, \
.v_type = AA_FS_TYPE_STRING, .v.string = (_value), \
.file_ops = &aa_fs_seq_file_ops }
#define AA_FS_FILE_U64(_name, _value) \
{ .name = (_name), .mode = 0444, \
.v_type = AA_FS_TYPE_U64, .v.u64 = (_value), \
.file_ops = &aa_fs_seq_file_ops }
#define AA_FS_FILE_FOPS(_name, _mode, _fops) \
{ .name = (_name), .v_type = AA_FS_TYPE_FOPS, \
.mode = (_mode), .file_ops = (_fops) }
#define AA_FS_DIR(_name, _value) \
{ .name = (_name), .v_type = AA_FS_TYPE_DIR, .v.files = (_value) }
extern void __init aa_destroy_aafs(void);
#endif /* __AA_APPARMORFS_H */

View file

@ -25,11 +25,9 @@
struct aa_profile;
extern const char *audit_mode_names[];
extern const char *const 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 */
@ -45,10 +43,11 @@ enum audit_type {
AUDIT_APPARMOR_HINT,
AUDIT_APPARMOR_STATUS,
AUDIT_APPARMOR_ERROR,
AUDIT_APPARMOR_KILL
AUDIT_APPARMOR_KILL,
AUDIT_APPARMOR_AUTO
};
extern const char *op_table[];
extern const char *const op_table[];
enum aa_ops {
OP_NULL,

View file

@ -117,7 +117,7 @@ static inline u16 dfa_map_xindex(u16 mask)
index |= AA_X_NAME;
} else if (old_index == 3) {
index |= AA_X_NAME | AA_X_CHILD;
} else {
} else if (old_index) {
index |= AA_X_TABLE;
index |= old_index - 4;
}

View file

@ -116,6 +116,9 @@ 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);
unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state,
const char c);
void aa_dfa_free_kref(struct kref *kref);
/**

View file

@ -26,6 +26,7 @@ enum path_flags {
PATH_MEDIATE_DELETED = 0x10000, /* mediate deleted paths */
};
int aa_get_name(struct path *path, int flags, char **buffer, const char **name);
int aa_path_name(struct path *path, int flags, char **buffer,
const char **name, const char **info);
#endif /* __AA_PATH_H */

View file

@ -29,7 +29,7 @@
#include "file.h"
#include "resource.h"
extern const char *profile_mode_names[];
extern const char *const profile_mode_names[];
#define APPARMOR_NAMES_MAX_INDEX 3
#define COMPLAIN_MODE(_profile) \
@ -129,6 +129,17 @@ struct aa_namespace {
struct list_head sub_ns;
};
/* struct aa_policydb - match engine for a policy
* dfa: dfa pattern match
* start: set of start states for the different classes of data
*/
struct aa_policydb {
/* Generic policy DFA specific rule types will be subsections of it */
struct aa_dfa *dfa;
unsigned int start[AA_CLASS_LAST + 1];
};
/* struct aa_profile - basic confinement data
* @base - base components of the profile (name, refcount, lists, lock ...)
* @parent: parent of profile
@ -143,6 +154,7 @@ struct aa_namespace {
* @flags: flags controlling profile behavior
* @path_flags: flags controlling path generation behavior
* @size: the memory consumed by this profiles rules
* @policy: general match rules governing policy
* @file: The set of rules governing basic file access and domain transitions
* @caps: capabilities for the profile
* @rlimits: rlimits for the profile
@ -179,6 +191,7 @@ struct aa_profile {
u32 path_flags;
int size;
struct aa_policydb policy;
struct aa_file_rules file;
struct aa_caps caps;
struct aa_rlimit rlimits;

View file

@ -18,6 +18,8 @@
#include <linux/resource.h>
#include <linux/sched.h>
#include "apparmorfs.h"
struct aa_profile;
/* struct aa_rlimit - rlimit settings for the profile
@ -32,6 +34,8 @@ struct aa_rlimit {
struct rlimit limits[RLIM_NLIMITS];
};
extern struct aa_fs_entry aa_fs_entry_rlimit[];
int aa_map_resource(int resource);
int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *,
unsigned int resource, struct rlimit *new_rlim);

View file

@ -335,12 +335,12 @@ unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
}
/**
* aa_dfa_next_state - traverse @dfa to find state @str stops at
* aa_dfa_match - 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
* aa_dfa_match 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.
*
@ -349,5 +349,79 @@ unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
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));
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 */
while (*str) {
pos = base[state] + equiv[(u8) *str++];
if (check[pos] == state)
state = next[pos];
else
state = def[state];
}
} else {
/* default is direct to next state */
while (*str) {
pos = base[state] + (u8) *str++;
if (check[pos] == state)
state = next[pos];
else
state = def[state];
}
}
return state;
}
/**
* aa_dfa_next - step one character to the next state in the dfa
* @dfa: the dfa to tranverse (NOT NULL)
* @state: the state to start in
* @c: the input character to transition on
*
* aa_dfa_match will step through the dfa by one input character @c
*
* Returns: state reach after input @c
*/
unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state,
const char c)
{
u16 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
u16 *next = NEXT_TABLE(dfa);
u16 *check = CHECK_TABLE(dfa);
unsigned int pos;
/* 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 */
pos = base[state] + equiv[(u8) c];
if (check[pos] == state)
state = next[pos];
else
state = def[state];
} else {
/* default is direct to next state */
pos = base[state] + (u8) c;
if (check[pos] == state)
state = next[pos];
else
state = def[state];
}
return state;
}

View file

@ -83,31 +83,29 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
struct path root;
get_fs_root(current->fs, &root);
res = __d_path(path, &root, buf, buflen);
if (res && !IS_ERR(res)) {
/* everything's fine */
*name = res;
path_put(&root);
goto ok;
}
path_put(&root);
connected = 0;
} else {
res = d_absolute_path(path, buf, buflen);
if (!our_mnt(path->mnt))
connected = 0;
}
res = d_absolute_path(path, buf, buflen);
*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 (!our_mnt(path->mnt))
if (!res || IS_ERR(res)) {
connected = 0;
res = dentry_path_raw(path->dentry, buf, buflen);
if (IS_ERR(res)) {
error = PTR_ERR(res);
*name = buf;
goto out;
};
} else if (!our_mnt(path->mnt))
connected = 0;
ok:
*name = res;
/* Handle two cases:
* 1. A deleted dentry && profile is not allowing mediation of deleted
* 2. On some filesystems, newly allocated dentries appear to the
@ -138,7 +136,7 @@ ok:
/* disconnected path, don't return pathname starting
* with '/'
*/
error = -ESTALE;
error = -EACCES;
if (*res == '/')
*name = res + 1;
}
@ -159,7 +157,7 @@ out:
* Returns: %0 else error on failure
*/
static int get_name_to_buffer(struct path *path, int flags, char *buffer,
int size, char **name)
int size, char **name, const char **info)
{
int adjust = (flags & PATH_IS_DIR) ? 1 : 0;
int error = d_namespace_path(path, buffer, size - adjust, name, flags);
@ -171,15 +169,27 @@ static int get_name_to_buffer(struct path *path, int flags, char *buffer,
*/
strcpy(&buffer[size - 2], "/");
if (info && error) {
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";
}
return error;
}
/**
* aa_get_name - compute the pathname of a file
* aa_path_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)
* @info: Returns - information on why the path lookup failed (MAYBE 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
@ -192,7 +202,8 @@ static int get_name_to_buffer(struct path *path, int flags, char *buffer,
*
* Returns: %0 else error code if could retrieve name
*/
int aa_get_name(struct path *path, int flags, char **buffer, const char **name)
int aa_path_name(struct path *path, int flags, char **buffer, const char **name,
const char **info)
{
char *buf, *str = NULL;
int size = 256;
@ -206,7 +217,7 @@ int aa_get_name(struct path *path, int flags, char **buffer, const char **name)
if (!buf)
return -ENOMEM;
error = get_name_to_buffer(path, flags, buf, size, &str);
error = get_name_to_buffer(path, flags, buf, size, &str, info);
if (error != -ENAMETOOLONG)
break;
@ -214,6 +225,7 @@ int aa_get_name(struct path *path, int flags, char **buffer, const char **name)
size <<= 1;
if (size > aa_g_path_max)
return -ENAMETOOLONG;
*info = NULL;
}
*buffer = buf;
*name = str;

View file

@ -93,7 +93,7 @@
/* root profile namespace */
struct aa_namespace *root_ns;
const char *profile_mode_names[] = {
const char *const profile_mode_names[] = {
"enforce",
"complain",
"kill",
@ -749,6 +749,7 @@ static void free_profile(struct aa_profile *profile)
aa_free_sid(profile->sid);
aa_put_dfa(profile->xmatch);
aa_put_dfa(profile->policy.dfa);
aa_put_profile(profile->replacedby);

View file

@ -84,7 +84,7 @@ static void audit_cb(struct audit_buffer *ab, void *va)
* @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)
* @e: buffer position info
* @error: error code
*
* Returns: %0 or error
@ -95,7 +95,8 @@ static int audit_iface(struct aa_profile *new, const char *name,
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;
if (e)
sa.aad.iface.pos = e->pos - e->start;
sa.aad.iface.target = new;
sa.aad.name = name;
sa.aad.info = info;
@ -468,7 +469,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
{
struct aa_profile *profile = NULL;
const char *name = NULL;
int error = -EPROTO;
int i, error = -EPROTO;
kernel_cap_t tmpcap;
u32 tmp;
@ -554,11 +555,35 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
goto fail;
if (!unpack_u32(e, &(profile->caps.extended.cap[1]), NULL))
goto fail;
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
goto fail;
}
if (!unpack_rlimits(e, profile))
goto fail;
if (unpack_nameX(e, AA_STRUCT, "policydb")) {
/* generic policy dfa - optional and may be NULL */
profile->policy.dfa = unpack_dfa(e);
if (IS_ERR(profile->policy.dfa)) {
error = PTR_ERR(profile->policy.dfa);
profile->policy.dfa = NULL;
goto fail;
}
if (!unpack_u32(e, &profile->policy.start[0], "start"))
/* default start state */
profile->policy.start[0] = DFA_START;
/* setup class index */
for (i = AA_CLASS_FILE; i <= AA_CLASS_LAST; i++) {
profile->policy.start[i] =
aa_dfa_next(profile->policy.dfa,
profile->policy.start[0],
i);
}
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
goto fail;
}
/* get file rules */
profile->file.dfa = unpack_dfa(e);
if (IS_ERR(profile->file.dfa)) {

View file

@ -23,6 +23,11 @@
*/
#include "rlim_names.h"
struct aa_fs_entry aa_fs_entry_rlimit[] = {
AA_FS_FILE_STRING("mask", AA_FS_RLIMIT_MASK),
{ }
};
/* audit callback for resource specific fields */
static void audit_cb(struct audit_buffer *ab, void *va)
{

View file

@ -358,6 +358,10 @@ static int cap_task_create(unsigned long clone_flags)
return 0;
}
static void cap_task_free(struct task_struct *task)
{
}
static int cap_cred_alloc_blank(struct cred *cred, gfp_t gfp)
{
return 0;
@ -954,6 +958,7 @@ void __init security_fixup_ops(struct security_operations *ops)
set_to_cap_if_null(ops, file_receive);
set_to_cap_if_null(ops, dentry_open);
set_to_cap_if_null(ops, task_create);
set_to_cap_if_null(ops, task_free);
set_to_cap_if_null(ops, cred_alloc_blank);
set_to_cap_if_null(ops, cred_free);
set_to_cap_if_null(ops, cred_prepare);

View file

@ -28,6 +28,7 @@
#include <linux/prctl.h>
#include <linux/securebits.h>
#include <linux/user_namespace.h>
#include <linux/binfmts.h>
/*
* If a non-root user executes a setuid-root binary in

View file

@ -9,8 +9,8 @@ config IMA
select CRYPTO_HMAC
select CRYPTO_MD5
select CRYPTO_SHA1
select TCG_TPM if !S390 && !UML
select TCG_TIS if TCG_TPM
select TCG_TPM if HAS_IOMEM && !UML
select TCG_TIS if TCG_TPM && X86
help
The Trusted Computing Group(TCG) runtime Integrity
Measurement Architecture(IMA) maintains a list of hash

View file

@ -61,6 +61,6 @@ void integrity_audit_msg(int audit_msgno, struct inode *inode,
audit_log_untrustedstring(ab, inode->i_sb->s_id);
audit_log_format(ab, " ino=%lu", inode->i_ino);
}
audit_log_format(ab, " res=%d", !result ? 0 : 1);
audit_log_format(ab, " res=%d", !result);
audit_log_end(ab);
}

View file

@ -62,6 +62,7 @@ static struct ima_measure_rule_entry default_rules[] = {
{.action = DONT_MEASURE,.fsmagic = SYSFS_MAGIC,.flags = IMA_FSMAGIC},
{.action = DONT_MEASURE,.fsmagic = DEBUGFS_MAGIC,.flags = IMA_FSMAGIC},
{.action = DONT_MEASURE,.fsmagic = TMPFS_MAGIC,.flags = IMA_FSMAGIC},
{.action = DONT_MEASURE,.fsmagic = RAMFS_MAGIC,.flags = IMA_FSMAGIC},
{.action = DONT_MEASURE,.fsmagic = SECURITYFS_MAGIC,.flags = IMA_FSMAGIC},
{.action = DONT_MEASURE,.fsmagic = SELINUX_MAGIC,.flags = IMA_FSMAGIC},
{.action = MEASURE,.func = FILE_MMAP,.mask = MAY_EXEC,
@ -417,7 +418,7 @@ static int ima_parse_rule(char *rule, struct ima_measure_rule_entry *entry)
if (!result && (entry->action == UNKNOWN))
result = -EINVAL;
audit_log_format(ab, "res=%d", !!result);
audit_log_format(ab, "res=%d", !result);
audit_log_end(ab);
return result;
}

View file

@ -388,11 +388,24 @@ long keyctl_keyring_clear(key_serial_t ringid)
keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_WRITE);
if (IS_ERR(keyring_ref)) {
ret = PTR_ERR(keyring_ref);
/* Root is permitted to invalidate certain special keyrings */
if (capable(CAP_SYS_ADMIN)) {
keyring_ref = lookup_user_key(ringid, 0, 0);
if (IS_ERR(keyring_ref))
goto error;
if (test_bit(KEY_FLAG_ROOT_CAN_CLEAR,
&key_ref_to_ptr(keyring_ref)->flags))
goto clear;
goto error_put;
}
goto error;
}
clear:
ret = keyring_clear(key_ref_to_ptr(keyring_ref));
error_put:
key_ref_put(keyring_ref);
error:
return ret;

View file

@ -657,7 +657,8 @@ try_again:
goto error;
down_read(&cred->request_key_auth->sem);
if (cred->request_key_auth->flags & KEY_FLAG_REVOKED) {
if (test_bit(KEY_FLAG_REVOKED,
&cred->request_key_auth->flags)) {
key_ref = ERR_PTR(-EKEYREVOKED);
key = NULL;
} else {

View file

@ -19,6 +19,8 @@
#include <linux/integrity.h>
#include <linux/ima.h>
#include <linux/evm.h>
#include <linux/fsnotify.h>
#include <net/flow.h>
#define MAX_LSM_EVM_XATTR 2
@ -187,25 +189,11 @@ int security_settime(const struct timespec *ts, const struct timezone *tz)
return security_ops->settime(ts, tz);
}
int security_vm_enough_memory(long pages)
{
WARN_ON(current->mm == NULL);
return security_ops->vm_enough_memory(current->mm, pages);
}
int security_vm_enough_memory_mm(struct mm_struct *mm, long pages)
{
WARN_ON(mm == NULL);
return security_ops->vm_enough_memory(mm, pages);
}
int security_vm_enough_memory_kern(long pages)
{
/* If current->mm is a kernel thread then we will pass NULL,
for this specific case that is fine */
return security_ops->vm_enough_memory(current->mm, pages);
}
int security_bprm_set_creds(struct linux_binprm *bprm)
{
return security_ops->bprm_set_creds(bprm);
@ -729,6 +717,11 @@ int security_task_create(unsigned long clone_flags)
return security_ops->task_create(clone_flags);
}
void security_task_free(struct task_struct *task)
{
security_ops->task_free(task);
}
int security_cred_alloc_blank(struct cred *cred, gfp_t gfp)
{
return security_ops->cred_alloc_blank(cred, gfp);

View file

@ -81,6 +81,8 @@
#include <linux/syslog.h>
#include <linux/user_namespace.h>
#include <linux/export.h>
#include <linux/msg.h>
#include <linux/shm.h>
#include "avc.h"
#include "objsec.h"

View file

@ -36,6 +36,9 @@
#include <linux/magic.h>
#include <linux/dcache.h>
#include <linux/personality.h>
#include <linux/msg.h>
#include <linux/shm.h>
#include <linux/binfmts.h>
#include "smack.h"
#define task_security(task) (task_cred_xxx((task), security))

View file

@ -446,11 +446,11 @@ void tomoyo_read_log(struct tomoyo_io_buffer *head)
* tomoyo_poll_log - Wait for an audit log.
*
* @file: Pointer to "struct file".
* @wait: Pointer to "poll_table".
* @wait: Pointer to "poll_table". Maybe NULL.
*
* Returns POLLIN | POLLRDNORM when ready to read an audit log.
*/
int tomoyo_poll_log(struct file *file, poll_table *wait)
unsigned int tomoyo_poll_log(struct file *file, poll_table *wait)
{
if (tomoyo_log_count)
return POLLIN | POLLRDNORM;

View file

@ -1069,7 +1069,7 @@ static int tomoyo_write_task(struct tomoyo_acl_param *param)
*
* @domainname: The name of domain.
*
* Returns 0.
* Returns 0 on success, negative value otherwise.
*
* Caller holds tomoyo_read_lock().
*/
@ -1081,7 +1081,7 @@ static int tomoyo_delete_domain(char *domainname)
name.name = domainname;
tomoyo_fill_path_info(&name);
if (mutex_lock_interruptible(&tomoyo_policy_lock))
return 0;
return -EINTR;
/* Is there an active domain? */
list_for_each_entry_rcu(domain, &tomoyo_domain_list, list) {
/* Never delete tomoyo_kernel_domain */
@ -1164,15 +1164,16 @@ static int tomoyo_write_domain(struct tomoyo_io_buffer *head)
bool is_select = !is_delete && tomoyo_str_starts(&data, "select ");
unsigned int profile;
if (*data == '<') {
int ret = 0;
domain = NULL;
if (is_delete)
tomoyo_delete_domain(data);
ret = tomoyo_delete_domain(data);
else if (is_select)
domain = tomoyo_find_domain(data);
else
domain = tomoyo_assign_domain(data, false);
head->w.domain = domain;
return 0;
return ret;
}
if (!domain)
return -EINVAL;
@ -2111,7 +2112,7 @@ static struct tomoyo_domain_info *tomoyo_find_domain_by_qid
struct tomoyo_domain_info *domain = NULL;
spin_lock(&tomoyo_query_list_lock);
list_for_each_entry(ptr, &tomoyo_query_list, list) {
if (ptr->serial != serial || ptr->answer)
if (ptr->serial != serial)
continue;
domain = ptr->domain;
break;
@ -2130,28 +2131,13 @@ static struct tomoyo_domain_info *tomoyo_find_domain_by_qid
*
* Waits for access requests which violated policy in enforcing mode.
*/
static int tomoyo_poll_query(struct file *file, poll_table *wait)
static unsigned int tomoyo_poll_query(struct file *file, poll_table *wait)
{
struct list_head *tmp;
bool found = false;
u8 i;
for (i = 0; i < 2; i++) {
spin_lock(&tomoyo_query_list_lock);
list_for_each(tmp, &tomoyo_query_list) {
struct tomoyo_query *ptr =
list_entry(tmp, typeof(*ptr), list);
if (ptr->answer)
continue;
found = true;
break;
}
spin_unlock(&tomoyo_query_list_lock);
if (found)
return POLLIN | POLLRDNORM;
if (i)
break;
poll_wait(file, &tomoyo_query_wait, wait);
}
if (!list_empty(&tomoyo_query_list))
return POLLIN | POLLRDNORM;
poll_wait(file, &tomoyo_query_wait, wait);
if (!list_empty(&tomoyo_query_list))
return POLLIN | POLLRDNORM;
return 0;
}
@ -2175,8 +2161,6 @@ static void tomoyo_read_query(struct tomoyo_io_buffer *head)
spin_lock(&tomoyo_query_list_lock);
list_for_each(tmp, &tomoyo_query_list) {
struct tomoyo_query *ptr = list_entry(tmp, typeof(*ptr), list);
if (ptr->answer)
continue;
if (pos++ != head->r.query_index)
continue;
len = ptr->query_len;
@ -2194,8 +2178,6 @@ static void tomoyo_read_query(struct tomoyo_io_buffer *head)
spin_lock(&tomoyo_query_list_lock);
list_for_each(tmp, &tomoyo_query_list) {
struct tomoyo_query *ptr = list_entry(tmp, typeof(*ptr), list);
if (ptr->answer)
continue;
if (pos++ != head->r.query_index)
continue;
/*
@ -2243,8 +2225,10 @@ static int tomoyo_write_answer(struct tomoyo_io_buffer *head)
struct tomoyo_query *ptr = list_entry(tmp, typeof(*ptr), list);
if (ptr->serial != serial)
continue;
if (!ptr->answer)
ptr->answer = answer;
ptr->answer = answer;
/* Remove from tomoyo_query_list. */
if (ptr->answer)
list_del_init(&ptr->list);
break;
}
spin_unlock(&tomoyo_query_list_lock);
@ -2477,18 +2461,17 @@ int tomoyo_open_control(const u8 type, struct file *file)
* tomoyo_poll_control - poll() for /sys/kernel/security/tomoyo/ interface.
*
* @file: Pointer to "struct file".
* @wait: Pointer to "poll_table".
* @wait: Pointer to "poll_table". Maybe NULL.
*
* Waits for read readiness.
* /sys/kernel/security/tomoyo/query is handled by /usr/sbin/tomoyo-queryd and
* /sys/kernel/security/tomoyo/audit is handled by /usr/sbin/tomoyo-auditd.
* Returns POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM if ready to read/write,
* POLLOUT | POLLWRNORM otherwise.
*/
int tomoyo_poll_control(struct file *file, poll_table *wait)
unsigned int tomoyo_poll_control(struct file *file, poll_table *wait)
{
struct tomoyo_io_buffer *head = file->private_data;
if (!head->poll)
return -ENOSYS;
return head->poll(file, wait);
if (head->poll)
return head->poll(file, wait) | POLLOUT | POLLWRNORM;
return POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM;
}
/**

View file

@ -788,7 +788,7 @@ struct tomoyo_acl_param {
struct tomoyo_io_buffer {
void (*read) (struct tomoyo_io_buffer *);
int (*write) (struct tomoyo_io_buffer *);
int (*poll) (struct file *file, poll_table *wait);
unsigned int (*poll) (struct file *file, poll_table *wait);
/* Exclusive lock for this structure. */
struct mutex io_sem;
char __user *read_user_buf;
@ -981,8 +981,8 @@ int tomoyo_path_number_perm(const u8 operation, struct path *path,
unsigned long number);
int tomoyo_path_perm(const u8 operation, struct path *path,
const char *target);
int tomoyo_poll_control(struct file *file, poll_table *wait);
int tomoyo_poll_log(struct file *file, poll_table *wait);
unsigned int tomoyo_poll_control(struct file *file, poll_table *wait);
unsigned int tomoyo_poll_log(struct file *file, poll_table *wait);
int tomoyo_socket_bind_permission(struct socket *sock, struct sockaddr *addr,
int addr_len);
int tomoyo_socket_connect_permission(struct socket *sock,

View file

@ -199,30 +199,32 @@ int tomoyo_mount_permission(char *dev_name, struct path *path,
if (flags & MS_REMOUNT) {
type = tomoyo_mounts[TOMOYO_MOUNT_REMOUNT];
flags &= ~MS_REMOUNT;
}
if (flags & MS_MOVE) {
type = tomoyo_mounts[TOMOYO_MOUNT_MOVE];
flags &= ~MS_MOVE;
}
if (flags & MS_BIND) {
} else if (flags & MS_BIND) {
type = tomoyo_mounts[TOMOYO_MOUNT_BIND];
flags &= ~MS_BIND;
}
if (flags & MS_UNBINDABLE) {
type = tomoyo_mounts[TOMOYO_MOUNT_MAKE_UNBINDABLE];
flags &= ~MS_UNBINDABLE;
}
if (flags & MS_PRIVATE) {
type = tomoyo_mounts[TOMOYO_MOUNT_MAKE_PRIVATE];
flags &= ~MS_PRIVATE;
}
if (flags & MS_SLAVE) {
type = tomoyo_mounts[TOMOYO_MOUNT_MAKE_SLAVE];
flags &= ~MS_SLAVE;
}
if (flags & MS_SHARED) {
} else if (flags & MS_SHARED) {
if (flags & (MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
return -EINVAL;
type = tomoyo_mounts[TOMOYO_MOUNT_MAKE_SHARED];
flags &= ~MS_SHARED;
} else if (flags & MS_PRIVATE) {
if (flags & (MS_SHARED | MS_SLAVE | MS_UNBINDABLE))
return -EINVAL;
type = tomoyo_mounts[TOMOYO_MOUNT_MAKE_PRIVATE];
flags &= ~MS_PRIVATE;
} else if (flags & MS_SLAVE) {
if (flags & (MS_SHARED | MS_PRIVATE | MS_UNBINDABLE))
return -EINVAL;
type = tomoyo_mounts[TOMOYO_MOUNT_MAKE_SLAVE];
flags &= ~MS_SLAVE;
} else if (flags & MS_UNBINDABLE) {
if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE))
return -EINVAL;
type = tomoyo_mounts[TOMOYO_MOUNT_MAKE_UNBINDABLE];
flags &= ~MS_UNBINDABLE;
} else if (flags & MS_MOVE) {
type = tomoyo_mounts[TOMOYO_MOUNT_MOVE];
flags &= ~MS_MOVE;
}
if (!type)
type = "<NULL>";

View file

@ -157,9 +157,10 @@ static int tomoyo_release(struct inode *inode, struct file *file)
* tomoyo_poll - poll() for /sys/kernel/security/tomoyo/ interface.
*
* @file: Pointer to "struct file".
* @wait: Pointer to "poll_table".
* @wait: Pointer to "poll_table". Maybe NULL.
*
* Returns 0 on success, negative value otherwise.
* Returns POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM if ready to read/write,
* POLLOUT | POLLWRNORM otherwise.
*/
static unsigned int tomoyo_poll(struct file *file, poll_table *wait)
{

13
security/yama/Kconfig Normal file
View file

@ -0,0 +1,13 @@
config SECURITY_YAMA
bool "Yama support"
depends on SECURITY
select SECURITYFS
select SECURITY_PATH
default n
help
This selects Yama, which extends DAC support with additional
system-wide security settings beyond regular Linux discretionary
access controls. Currently available is ptrace scope restriction.
Further information can be found in Documentation/security/Yama.txt.
If you are unsure how to answer this question, answer N.

3
security/yama/Makefile Normal file
View file

@ -0,0 +1,3 @@
obj-$(CONFIG_SECURITY_YAMA) := yama.o
yama-y := yama_lsm.o

323
security/yama/yama_lsm.c Normal file
View file

@ -0,0 +1,323 @@
/*
* Yama Linux Security Module
*
* Author: Kees Cook <keescook@chromium.org>
*
* Copyright (C) 2010 Canonical, Ltd.
* Copyright (C) 2011 The Chromium OS Authors.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2, as
* published by the Free Software Foundation.
*
*/
#include <linux/security.h>
#include <linux/sysctl.h>
#include <linux/ptrace.h>
#include <linux/prctl.h>
#include <linux/ratelimit.h>
static int ptrace_scope = 1;
/* describe a ptrace relationship for potential exception */
struct ptrace_relation {
struct task_struct *tracer;
struct task_struct *tracee;
struct list_head node;
};
static LIST_HEAD(ptracer_relations);
static DEFINE_SPINLOCK(ptracer_relations_lock);
/**
* yama_ptracer_add - add/replace an exception for this tracer/tracee pair
* @tracer: the task_struct of the process doing the ptrace
* @tracee: the task_struct of the process to be ptraced
*
* Each tracee can have, at most, one tracer registered. Each time this
* is called, the prior registered tracer will be replaced for the tracee.
*
* Returns 0 if relationship was added, -ve on error.
*/
static int yama_ptracer_add(struct task_struct *tracer,
struct task_struct *tracee)
{
int rc = 0;
struct ptrace_relation *added;
struct ptrace_relation *entry, *relation = NULL;
added = kmalloc(sizeof(*added), GFP_KERNEL);
if (!added)
return -ENOMEM;
spin_lock_bh(&ptracer_relations_lock);
list_for_each_entry(entry, &ptracer_relations, node)
if (entry->tracee == tracee) {
relation = entry;
break;
}
if (!relation) {
relation = added;
relation->tracee = tracee;
list_add(&relation->node, &ptracer_relations);
}
relation->tracer = tracer;
spin_unlock_bh(&ptracer_relations_lock);
if (added != relation)
kfree(added);
return rc;
}
/**
* yama_ptracer_del - remove exceptions related to the given tasks
* @tracer: remove any relation where tracer task matches
* @tracee: remove any relation where tracee task matches
*/
static void yama_ptracer_del(struct task_struct *tracer,
struct task_struct *tracee)
{
struct ptrace_relation *relation, *safe;
spin_lock_bh(&ptracer_relations_lock);
list_for_each_entry_safe(relation, safe, &ptracer_relations, node)
if (relation->tracee == tracee ||
(tracer && relation->tracer == tracer)) {
list_del(&relation->node);
kfree(relation);
}
spin_unlock_bh(&ptracer_relations_lock);
}
/**
* yama_task_free - check for task_pid to remove from exception list
* @task: task being removed
*/
static void yama_task_free(struct task_struct *task)
{
yama_ptracer_del(task, task);
}
/**
* yama_task_prctl - check for Yama-specific prctl operations
* @option: operation
* @arg2: argument
* @arg3: argument
* @arg4: argument
* @arg5: argument
*
* Return 0 on success, -ve on error. -ENOSYS is returned when Yama
* does not handle the given option.
*/
static int yama_task_prctl(int option, unsigned long arg2, unsigned long arg3,
unsigned long arg4, unsigned long arg5)
{
int rc;
struct task_struct *myself = current;
rc = cap_task_prctl(option, arg2, arg3, arg4, arg5);
if (rc != -ENOSYS)
return rc;
switch (option) {
case PR_SET_PTRACER:
/* Since a thread can call prctl(), find the group leader
* before calling _add() or _del() on it, since we want
* process-level granularity of control. The tracer group
* leader checking is handled later when walking the ancestry
* at the time of PTRACE_ATTACH check.
*/
rcu_read_lock();
if (!thread_group_leader(myself))
myself = rcu_dereference(myself->group_leader);
get_task_struct(myself);
rcu_read_unlock();
if (arg2 == 0) {
yama_ptracer_del(NULL, myself);
rc = 0;
} else if (arg2 == PR_SET_PTRACER_ANY) {
rc = yama_ptracer_add(NULL, myself);
} else {
struct task_struct *tracer;
rcu_read_lock();
tracer = find_task_by_vpid(arg2);
if (tracer)
get_task_struct(tracer);
else
rc = -EINVAL;
rcu_read_unlock();
if (tracer) {
rc = yama_ptracer_add(tracer, myself);
put_task_struct(tracer);
}
}
put_task_struct(myself);
break;
}
return rc;
}
/**
* task_is_descendant - walk up a process family tree looking for a match
* @parent: the process to compare against while walking up from child
* @child: the process to start from while looking upwards for parent
*
* Returns 1 if child is a descendant of parent, 0 if not.
*/
static int task_is_descendant(struct task_struct *parent,
struct task_struct *child)
{
int rc = 0;
struct task_struct *walker = child;
if (!parent || !child)
return 0;
rcu_read_lock();
if (!thread_group_leader(parent))
parent = rcu_dereference(parent->group_leader);
while (walker->pid > 0) {
if (!thread_group_leader(walker))
walker = rcu_dereference(walker->group_leader);
if (walker == parent) {
rc = 1;
break;
}
walker = rcu_dereference(walker->real_parent);
}
rcu_read_unlock();
return rc;
}
/**
* ptracer_exception_found - tracer registered as exception for this tracee
* @tracer: the task_struct of the process attempting ptrace
* @tracee: the task_struct of the process to be ptraced
*
* Returns 1 if tracer has is ptracer exception ancestor for tracee.
*/
static int ptracer_exception_found(struct task_struct *tracer,
struct task_struct *tracee)
{
int rc = 0;
struct ptrace_relation *relation;
struct task_struct *parent = NULL;
bool found = false;
spin_lock_bh(&ptracer_relations_lock);
rcu_read_lock();
if (!thread_group_leader(tracee))
tracee = rcu_dereference(tracee->group_leader);
list_for_each_entry(relation, &ptracer_relations, node)
if (relation->tracee == tracee) {
parent = relation->tracer;
found = true;
break;
}
if (found && (parent == NULL || task_is_descendant(parent, tracer)))
rc = 1;
rcu_read_unlock();
spin_unlock_bh(&ptracer_relations_lock);
return rc;
}
/**
* yama_ptrace_access_check - validate PTRACE_ATTACH calls
* @child: task that current task is attempting to ptrace
* @mode: ptrace attach mode
*
* Returns 0 if following the ptrace is allowed, -ve on error.
*/
static int yama_ptrace_access_check(struct task_struct *child,
unsigned int mode)
{
int rc;
/* If standard caps disallows it, so does Yama. We should
* only tighten restrictions further.
*/
rc = cap_ptrace_access_check(child, mode);
if (rc)
return rc;
/* require ptrace target be a child of ptracer on attach */
if (mode == PTRACE_MODE_ATTACH &&
ptrace_scope &&
!task_is_descendant(current, child) &&
!ptracer_exception_found(current, child) &&
!capable(CAP_SYS_PTRACE))
rc = -EPERM;
if (rc) {
char name[sizeof(current->comm)];
printk_ratelimited(KERN_NOTICE "ptrace of non-child"
" pid %d was attempted by: %s (pid %d)\n",
child->pid,
get_task_comm(name, current),
current->pid);
}
return rc;
}
static struct security_operations yama_ops = {
.name = "yama",
.ptrace_access_check = yama_ptrace_access_check,
.task_prctl = yama_task_prctl,
.task_free = yama_task_free,
};
#ifdef CONFIG_SYSCTL
static int zero;
static int one = 1;
struct ctl_path yama_sysctl_path[] = {
{ .procname = "kernel", },
{ .procname = "yama", },
{ }
};
static struct ctl_table yama_sysctl_table[] = {
{
.procname = "ptrace_scope",
.data = &ptrace_scope,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = &zero,
.extra2 = &one,
},
{ }
};
#endif /* CONFIG_SYSCTL */
static __init int yama_init(void)
{
if (!security_module_enable(&yama_ops))
return 0;
printk(KERN_INFO "Yama: becoming mindful.\n");
if (register_security(&yama_ops))
panic("Yama: kernel registration failed.\n");
#ifdef CONFIG_SYSCTL
if (!register_sysctl_paths(yama_sysctl_path, yama_sysctl_table))
panic("Yama: sysctl registration failed.\n");
#endif
return 0;
}
security_initcall(yama_init);