diff options
Diffstat (limited to 'security')
143 files changed, 60616 insertions, 0 deletions
diff --git a/security/Kconfig b/security/Kconfig new file mode 100644 index 00000000..e0f08b52 --- /dev/null +++ b/security/Kconfig @@ -0,0 +1,229 @@ +# +# Security configuration +# + +menu "Security options" + +config KEYS + bool "Enable access key retention support" + help + This option provides support for retaining authentication tokens and + access keys in the kernel. + + It also includes provision of methods by which such keys might be + associated with a process so that network filesystems, encryption + support and the like can find them. + + Furthermore, a special type of key is available that acts as keyring: + a searchable sequence of keys. Each process is equipped with access + to five standard keyrings: UID-specific, GID-specific, session, + process and thread. + + If you are unsure as to whether this is required, answer N. + +config TRUSTED_KEYS + tristate "TRUSTED KEYS" + depends on KEYS && TCG_TPM + select CRYPTO + select CRYPTO_HMAC + select CRYPTO_SHA1 + help + This option provides support for creating, sealing, and unsealing + keys in the kernel. Trusted keys are random number symmetric keys, + generated and RSA-sealed by the TPM. The TPM only unseals the keys, + if the boot PCRs and other criteria match. Userspace will only ever + see encrypted blobs. + + If you are unsure as to whether this is required, answer N. + +config ENCRYPTED_KEYS + tristate "ENCRYPTED KEYS" + depends on KEYS && TRUSTED_KEYS + select CRYPTO_AES + select CRYPTO_CBC + select CRYPTO_SHA256 + select CRYPTO_RNG + help + This option provides support for create/encrypting/decrypting keys + in the kernel. Encrypted keys are kernel generated random numbers, + which are encrypted/decrypted with a 'master' symmetric key. The + 'master' key can be either a trusted-key or user-key type. + Userspace only ever sees/stores encrypted blobs. + + If you are unsure as to whether this is required, answer N. + +config KEYS_DEBUG_PROC_KEYS + bool "Enable the /proc/keys file by which keys may be viewed" + depends on KEYS + help + This option turns on support for the /proc/keys file - through which + can be listed all the keys on the system that are viewable by the + reading process. + + The only keys included in the list are those that grant View + permission to the reading process whether or not it possesses them. + Note that LSM security checks are still performed, and may further + filter out keys that the current process is not authorised to view. + + Only key attributes are listed here; key payloads are not included in + the resulting table. + + If you are unsure as to whether this is required, answer N. + +config SECURITY_DMESG_RESTRICT + bool "Restrict unprivileged access to the kernel syslog" + default n + help + This enforces restrictions on unprivileged users reading the kernel + syslog via dmesg(8). + + If this option is not selected, no restrictions will be enforced + unless the dmesg_restrict sysctl is explicitly set to (1). + + If you are unsure how to answer this question, answer N. + +config SECURITY + bool "Enable different security models" + depends on SYSFS + help + This allows you to choose different security modules to be + configured into your kernel. + + If this option is not selected, the default Linux security + model will be used. + + If you are unsure how to answer this question, answer N. + +config SECURITYFS + bool "Enable the securityfs filesystem" + help + This will build the securityfs filesystem. It is currently used by + the TPM bios character driver and IMA, an integrity provider. It is + not used by SELinux or SMACK. + + If you are unsure how to answer this question, answer N. + +config SECURITY_NETWORK + bool "Socket and Networking Security Hooks" + depends on SECURITY + help + This enables the socket and networking security hooks. + If enabled, a security module can use these hooks to + implement socket and networking access controls. + If you are unsure how to answer this question, answer N. + +config SECURITY_NETWORK_XFRM + bool "XFRM (IPSec) Networking Security Hooks" + depends on XFRM && SECURITY_NETWORK + help + This enables the XFRM (IPSec) networking security hooks. + If enabled, a security module can use these hooks to + implement per-packet access controls based on labels + derived from IPSec policy. Non-IPSec communications are + designated as unlabelled, and only sockets authorized + to communicate unlabelled data can send without using + IPSec. + If you are unsure how to answer this question, answer N. + +config SECURITY_PATH + bool "Security hooks for pathname based access control" + depends on SECURITY + help + This enables the security hooks for pathname based access control. + If enabled, a security module can use these hooks to + implement pathname based access controls. + If you are unsure how to answer this question, answer N. + +config INTEL_TXT + bool "Enable Intel(R) Trusted Execution Technology (Intel(R) TXT)" + depends on HAVE_INTEL_TXT + help + This option enables support for booting the kernel with the + Trusted Boot (tboot) module. This will utilize + Intel(R) Trusted Execution Technology to perform a measured launch + of the kernel. If the system does not support Intel(R) TXT, this + will have no effect. + + Intel TXT will provide higher assurance of system configuration and + initial state as well as data reset protection. This is used to + create a robust initial kernel measurement and verification, which + helps to ensure that kernel security mechanisms are functioning + correctly. This level of protection requires a root of trust outside + of the kernel itself. + + Intel TXT also helps solve real end user concerns about having + confidence that their hardware is running the VMM or kernel that + it was configured with, especially since they may be responsible for + providing such assurances to VMs and services running on it. + + See <http://www.intel.com/technology/security/> for more information + about Intel(R) TXT. + See <http://tboot.sourceforge.net> for more information about tboot. + See Documentation/intel_txt.txt for a description of how to enable + Intel TXT support in a kernel boot. + + If you are unsure as to whether this is required, answer N. + +config LSM_MMAP_MIN_ADDR + int "Low address space for LSM to protect from user allocation" + depends on SECURITY && SECURITY_SELINUX + default 32768 if ARM + default 65536 + help + This is the portion of low virtual memory which should be protected + from userspace allocation. Keeping a user from writing to low pages + can help reduce the impact of kernel NULL pointer bugs. + + For most ia64, ppc64 and x86 users with lots of address space + a value of 65536 is reasonable and should cause no problems. + On arm and other archs it should not be higher than 32768. + Programs which use vm86 functionality or have some need to map + this low address space will need the permission specific to the + systems running LSM. + +source security/selinux/Kconfig +source security/smack/Kconfig +source security/tomoyo/Kconfig +source security/apparmor/Kconfig + +source security/integrity/ima/Kconfig + +choice + prompt "Default security module" + 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 + Select the security module that will be used by default if the + kernel parameter security= is not specified. + + config DEFAULT_SECURITY_SELINUX + bool "SELinux" if SECURITY_SELINUX=y + + config DEFAULT_SECURITY_SMACK + bool "Simplified Mandatory Access Control" if SECURITY_SMACK=y + + 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" + +endchoice + +config DEFAULT_SECURITY + string + 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 + diff --git a/security/Makefile b/security/Makefile new file mode 100644 index 00000000..8bb0fe9e --- /dev/null +++ b/security/Makefile @@ -0,0 +1,28 @@ +# +# Makefile for the kernel security code +# + +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 +obj-$(CONFIG_MMU) += min_addr.o + +# Object file lists +obj-$(CONFIG_SECURITY) += security.o capability.o +obj-$(CONFIG_SECURITYFS) += inode.o +# Must precede capability.o in order to stack properly. +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 +subdir-$(CONFIG_IMA) += integrity/ima +obj-$(CONFIG_IMA) += integrity/ima/built-in.o diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore new file mode 100644 index 00000000..4d995aea --- /dev/null +++ b/security/apparmor/.gitignore @@ -0,0 +1,6 @@ +# +# Generated include files +# +af_names.h +capability_names.h +rlim_names.h diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig new file mode 100644 index 00000000..9b9013b2 --- /dev/null +++ b/security/apparmor/Kconfig @@ -0,0 +1,31 @@ +config SECURITY_APPARMOR + bool "AppArmor support" + depends on SECURITY && NET + 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. diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile new file mode 100644 index 00000000..2dafe50a --- /dev/null +++ b/security/apparmor/Makefile @@ -0,0 +1,52 @@ +# 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 rlim_names.h + + +# Build a lower case string table of capability names +# Transforms lines from +# #define CAP_DAC_OVERRIDE 1 +# to +# [1] = "dac_override", +quiet_cmd_make-caps = GEN $@ +cmd_make-caps = echo "static const char *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 "};" >> $@ + + +# Build a lower case string table of rlimit names. +# Transforms lines from +# #define RLIMIT_STACK 3 /* max stack size */ +# to +# [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 +# 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, +quiet_cmd_make-rlim = GEN $@ +cmd_make-rlim = echo "static const char *rlim_names[] = {" > $@ ;\ + sed $< >> $@ -r -n \ + -e 's/^\# ?define[ \t]+(RLIMIT_([A-Z0-9_]+)).*/[\1] = "\L\2",/p';\ + echo "};" >> $@ ;\ + echo "static const int rlim_map[] = {" >> $@ ;\ + sed -r -n "s/^\# ?define[ \t]+(RLIMIT_[A-Z0-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)/rlim_names.h : $(srctree)/include/asm-generic/resource.h + $(call cmd,make-rlim) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c new file mode 100644 index 00000000..08482929 --- /dev/null +++ b/security/apparmor/apparmorfs.c @@ -0,0 +1,244 @@ +/* + * 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 (REQUIRES: @alloc_size >= @copy_size) + * @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; + + BUG_ON(copy_size > alloc_size); + + 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, + .llseek = default_llseek, +}; + +/* .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, + .llseek = default_llseek, +}; + +/* .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, + .llseek = default_llseek, +}; + +/** 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); diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c new file mode 100644 index 00000000..96502b22 --- /dev/null +++ b/security/apparmor/audit.c @@ -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; +} diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c new file mode 100644 index 00000000..9982c48d --- /dev/null +++ b/security/apparmor/capability.c @@ -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); +} diff --git a/security/apparmor/context.c b/security/apparmor/context.c new file mode 100644 index 00000000..8a9b5027 --- /dev/null +++ b/security/apparmor/context.c @@ -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; +} diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c new file mode 100644 index 00000000..78adc430 --- /dev/null +++ b/security/apparmor/domain.c @@ -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); + } + + /* not ptraced */ + if (!tracer || unconfined(tracerp)) + goto out; + + error = aa_may_ptrace(tracer, tracerp, to_profile, PTRACE_MODE_ATTACH); + +out: + rcu_read_unlock(); + 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; +} diff --git a/security/apparmor/file.c b/security/apparmor/file.c new file mode 100644 index 00000000..7312db74 --- /dev/null +++ b/security/apparmor/file.c @@ -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); +} diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h new file mode 100644 index 00000000..38ccaea0 --- /dev/null +++ b/security/apparmor/include/apparmor.h @@ -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 */ diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h new file mode 100644 index 00000000..cb1e93a1 --- /dev/null +++ b/security/apparmor/include/apparmorfs.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 */ diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h new file mode 100644 index 00000000..1951786d --- /dev/null +++ b/security/apparmor/include/audit.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 */ diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h new file mode 100644 index 00000000..c24d2959 --- /dev/null +++ b/security/apparmor/include/capability.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 */ diff --git a/security/apparmor/include/context.h b/security/apparmor/include/context.h new file mode 100644 index 00000000..a9cbee4d --- /dev/null +++ b/security/apparmor/include/context.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 */ diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h new file mode 100644 index 00000000..de04464f --- /dev/null +++ b/security/apparmor/include/domain.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 */ diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h new file mode 100644 index 00000000..ab8c6d87 --- /dev/null +++ b/security/apparmor/include/file.h @@ -0,0 +1,216 @@ +/* + * 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 "domain.h" +#include "match.h" + +struct aa_profile; +struct path; + +/* + * 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 */ diff --git a/security/apparmor/include/ipc.h b/security/apparmor/include/ipc.h new file mode 100644 index 00000000..aeda0fbc --- /dev/null +++ b/security/apparmor/include/ipc.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 */ diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h new file mode 100644 index 00000000..a4a86399 --- /dev/null +++ b/security/apparmor/include/match.h @@ -0,0 +1,133 @@ +/* + * 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/kref.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 instead 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 */ diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h new file mode 100644 index 00000000..27b327a7 --- /dev/null +++ b/security/apparmor/include/path.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 */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h new file mode 100644 index 00000000..aeda5cf5 --- /dev/null +++ b/security/apparmor/include/policy.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 */ diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h new file mode 100644 index 00000000..a2dcccac --- /dev/null +++ b/security/apparmor/include/policy_unpack.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 */ diff --git a/security/apparmor/include/procattr.h b/security/apparmor/include/procattr.h new file mode 100644 index 00000000..544aa6b7 --- /dev/null +++ b/security/apparmor/include/procattr.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 */ diff --git a/security/apparmor/include/resource.h b/security/apparmor/include/resource.h new file mode 100644 index 00000000..02baec73 --- /dev/null +++ b/security/apparmor/include/resource.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, struct task_struct *, + 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 */ diff --git a/security/apparmor/include/sid.h b/security/apparmor/include/sid.h new file mode 100644 index 00000000..020db35c --- /dev/null +++ b/security/apparmor/include/sid.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 */ diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c new file mode 100644 index 00000000..649fad88 --- /dev/null +++ b/security/apparmor/ipc.c @@ -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; +} diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c new file mode 100644 index 00000000..b82e383b --- /dev/null +++ b/security/apparmor/lib.c @@ -0,0 +1,134 @@ +/* + * 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/mm.h> +#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], ':'); + *ns_name = skip_spaces(&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; + } + 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); +} diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c new file mode 100644 index 00000000..37832026 --- /dev/null +++ b/security/apparmor/lsm.c @@ -0,0 +1,952 @@ +/* + * 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 <linux/user_namespace.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) && !COMPLAIN_MODE(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, + struct user_namespace *ns, int cap, int audit) +{ + struct aa_profile *profile; + /* cap_capable returns 0 on success, else -EPERM */ + int error = cap_capable(task, cred, ns, 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, + __aa_current_profile(), 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(struct task_struct *task, + 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, task, 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, const struct kernel_param *kp); +static int param_get_aabool(char *buffer, const struct kernel_param *kp); +#define param_check_aabool(name, p) __param_check(name, p, int) +static struct kernel_param_ops param_ops_aabool = { + .set = param_set_aabool, + .get = param_get_aabool +}; + +static int param_set_aauint(const char *val, const struct kernel_param *kp); +static int param_get_aauint(char *buffer, const struct kernel_param *kp); +#define param_check_aauint(name, p) __param_check(name, p, int) +static struct kernel_param_ops param_ops_aauint = { + .set = param_set_aauint, + .get = param_get_aauint +}; + +static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp); +static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp); +#define param_check_aalockpolicy(name, p) __param_check(name, p, int) +static struct kernel_param_ops param_ops_aalockpolicy = { + .set = param_set_aalockpolicy, + .get = param_get_aalockpolicy +}; + +static int param_set_audit(const char *val, struct kernel_param *kp); +static int param_get_audit(char *buffer, struct kernel_param *kp); + +static int param_set_mode(const char *val, struct kernel_param *kp); +static int param_get_mode(char *buffer, struct kernel_param *kp); + +/* 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, const 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, const 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, const struct kernel_param *kp) +{ + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + return param_set_bool(val, kp); +} + +static int param_get_aabool(char *buffer, const 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, const struct kernel_param *kp) +{ + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + return param_set_uint(val, kp); +} + +static int param_get_aauint(char *buffer, const 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 set_init_cxt_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; + +set_init_cxt_out: + aa_free_task_context(current->real_cred->security); + +register_security_out: + aa_free_root_ns(); + +alloc_out: + aa_destroy_aafs(); + + apparmor_enabled = 0; + return error; +} + +security_initcall(apparmor_init); diff --git a/security/apparmor/match.c b/security/apparmor/match.c new file mode 100644 index 00000000..94de6b49 --- /dev/null +++ b/security/apparmor/match.c @@ -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/security/apparmor.txt + * Assumes the dfa @blob stream has been aligned on a 8 byte boundary + * + * 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)); +} diff --git a/security/apparmor/path.c b/security/apparmor/path.c new file mode 100644 index 00000000..b566eba4 --- /dev/null +++ b/security/apparmor/path.c @@ -0,0 +1,223 @@ +/* + * 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) +{ + char *res; + int error = 0; + int connected = 1; + + if (path->mnt->mnt_flags & MNT_INTERNAL) { + /* it's not mounted anywhere */ + res = dentry_path(path->dentry, buf, buflen); + *name = res; + if (IS_ERR(res)) { + *name = buf; + return PTR_ERR(res); + } + if (path->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 + */ + return prepend(name, *name - buf, "/proc", 5); + } + return 0; + } + + /* resolve paths relative to chroot?*/ + if (flags & PATH_CHROOT_REL) { + 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; + } + + 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)) + connected = 0; + +ok: + /* Handle two cases: + * 1. A deleted dentry && profile is not allowing mediation of deleted + * 2. On some filesystems, newly allocated dentries appear to the + * security_path hooks as a deleted dentry except without an inode + * allocated. + */ + if (d_unlinked(path->dentry) && path->dentry->d_inode && + !(flags & PATH_MEDIATE_DELETED)) { + error = -ENOENT; + goto out; + } + + /* If the path is not connected to the expected root, + * 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) { + if (!(flags & PATH_CONNECT_PATH) && + !(((flags & CHROOT_NSCONNECT) == CHROOT_NSCONNECT) && + our_mnt(path->mnt))) { + /* disconnected path, don't return pathname starting + * with '/' + */ + error = -ESTALE; + if (*res == '/') + *name = res + 1; + } + } + +out: + 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; +} diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c new file mode 100644 index 00000000..4f0eadee --- /dev/null +++ b/security/apparmor/policy.c @@ -0,0 +1,1186 @@ +/* + * AppArmor security module + * + * This file contains AppArmor policy manipulation 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. + * + * + * AppArmor policy is based around profiles, which contain the rules a + * task is confined by. Every task in the system has a profile attached + * to it determined either by matching "unconfined" tasks against the + * visible set of profiles or by following a profiles attachment rules. + * + * Each profile exists in a profile namespace which is a container of + * visible profiles. Each namespace contains a special "unconfined" profile, + * which doesn't enforce any confinement on a task beyond DAC. + * + * Namespace and profile names can be written together in either + * of two syntaxes. + * :namespace:profile - used by kernel interfaces for easy detection + * namespace://profile - used by policy + * + * Profile names can not start with : or @ or ^ and may not contain \0 + * + * Reserved profile names + * unconfined - special automatically generated unconfined profile + * inherit - special name to indicate profile inheritance + * null-XXXX-YYYY - special automatically generated learning profiles + * + * Namespace names may not start with / or @ and may not contain \0 or : + * Reserved namespace names + * user-XXXX - user defined profiles + * + * a // in a profile or namespace name indicates a hierarchical name with the + * name before the // being the parent and the name after the child. + * + * Profile and namespace hierarchies serve two different but similar purposes. + * The namespace contains the set of visible profiles that are considered + * for attachment. The hierarchy of namespaces allows for virtualizing + * the namespace so that for example a chroot can have its own set of profiles + * which may define some local user namespaces. + * The profile hierarchy severs two distinct purposes, + * - it allows for sub profiles or hats, which allows an application to run + * subprograms under its own profile with different restriction than it + * self, and not have it use the system profile. + * eg. if a mail program starts an editor, the policy might make the + * restrictions tighter on the editor tighter than the mail program, + * and definitely different than general editor restrictions + * - it allows for binary hierarchy of profiles, so that execution history + * is preserved. This feature isn't exploited by AppArmor reference policy + * but is allowed. NOTE: this is currently suboptimal because profile + * aliasing is not currently implemented so that a profile for each + * level must be defined. + * eg. /bin/bash///bin/ls as a name would indicate /bin/ls was started + * from /bin/bash + * + * A profile or namespace name that can contain one or more // separators + * is referred to as an hname (hierarchical). + * eg. /bin/bash//bin/ls + * + * An fqname is a name that may contain both namespace and profile hnames. + * eg. :ns:/bin/bash//bin/ls + * + * NOTES: + * - locking of profile lists is currently fairly coarse. All profile + * lists within a namespace use the namespace lock. + * FIXME: move profile lists to using rcu_lists + */ + +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/string.h> + +#include "include/apparmor.h" +#include "include/capability.h" +#include "include/context.h" +#include "include/file.h" +#include "include/ipc.h" +#include "include/match.h" +#include "include/path.h" +#include "include/policy.h" +#include "include/policy_unpack.h" +#include "include/resource.h" +#include "include/sid.h" + + +/* root profile namespace */ +struct aa_namespace *root_ns; + +const char *profile_mode_names[] = { + "enforce", + "complain", + "kill", +}; + +/** + * hname_tail - find the last component of an hname + * @name: hname to find the base profile name component of (NOT NULL) + * + * Returns: the tail (base profile name) name component of an hname + */ +static const char *hname_tail(const char *hname) +{ + char *split; + hname = strim((char *)hname); + for (split = strstr(hname, "//"); split; split = strstr(hname, "//")) + hname = split + 2; + + return hname; +} + +/** + * policy_init - initialize a policy structure + * @policy: policy to initialize (NOT NULL) + * @prefix: prefix name if any is required. (MAYBE NULL) + * @name: name of the policy, init will make a copy of it (NOT NULL) + * + * Note: this fn creates a copy of strings passed in + * + * Returns: true if policy init successful + */ +static bool policy_init(struct aa_policy *policy, const char *prefix, + const char *name) +{ + /* freed by policy_free */ + if (prefix) { + policy->hname = kmalloc(strlen(prefix) + strlen(name) + 3, + GFP_KERNEL); + if (policy->hname) + sprintf(policy->hname, "%s//%s", prefix, name); + } else + policy->hname = kstrdup(name, GFP_KERNEL); + if (!policy->hname) + return 0; + /* base.name is a substring of fqname */ + policy->name = (char *)hname_tail(policy->hname); + INIT_LIST_HEAD(&policy->list); + INIT_LIST_HEAD(&policy->profiles); + kref_init(&policy->count); + + return 1; +} + +/** + * policy_destroy - free the elements referenced by @policy + * @policy: policy that is to have its elements freed (NOT NULL) + */ +static void policy_destroy(struct aa_policy *policy) +{ + /* still contains profiles -- invalid */ + if (!list_empty(&policy->profiles)) { + AA_ERROR("%s: internal error, " + "policy '%s' still contains profiles\n", + __func__, policy->name); + BUG(); + } + if (!list_empty(&policy->list)) { + AA_ERROR("%s: internal error, policy '%s' still on list\n", + __func__, policy->name); + BUG(); + } + + /* don't free name as its a subset of hname */ + kzfree(policy->hname); +} + +/** + * __policy_find - find a policy by @name on a policy list + * @head: list to search (NOT NULL) + * @name: name to search for (NOT NULL) + * + * Requires: correct locks for the @head list be held + * + * Returns: unrefcounted policy that match @name or NULL if not found + */ +static struct aa_policy *__policy_find(struct list_head *head, const char *name) +{ + struct aa_policy *policy; + + list_for_each_entry(policy, head, list) { + if (!strcmp(policy->name, name)) + return policy; + } + return NULL; +} + +/** + * __policy_strn_find - find a policy that's name matches @len chars of @str + * @head: list to search (NOT NULL) + * @str: string to search for (NOT NULL) + * @len: length of match required + * + * Requires: correct locks for the @head list be held + * + * Returns: unrefcounted policy that match @str or NULL if not found + * + * if @len == strlen(@strlen) then this is equiv to __policy_find + * other wise it allows searching for policy by a partial match of name + */ +static struct aa_policy *__policy_strn_find(struct list_head *head, + const char *str, int len) +{ + struct aa_policy *policy; + + list_for_each_entry(policy, head, list) { + if (aa_strneq(policy->name, str, len)) + return policy; + } + + return NULL; +} + +/* + * Routines for AppArmor namespaces + */ + +static const char *hidden_ns_name = "---"; +/** + * aa_ns_visible - test if @view is visible from @curr + * @curr: namespace to treat as the parent (NOT NULL) + * @view: namespace to test if visible from @curr (NOT NULL) + * + * Returns: true if @view is visible from @curr else false + */ +bool aa_ns_visible(struct aa_namespace *curr, struct aa_namespace *view) +{ + if (curr == view) + return true; + + for ( ; view; view = view->parent) { + if (view->parent == curr) + return true; + } + return false; +} + +/** + * aa_na_name - Find the ns name to display for @view from @curr + * @curr - current namespace (NOT NULL) + * @view - namespace attempting to view (NOT NULL) + * + * Returns: name of @view visible from @curr + */ +const char *aa_ns_name(struct aa_namespace *curr, struct aa_namespace *view) +{ + /* if view == curr then the namespace name isn't displayed */ + if (curr == view) + return ""; + + if (aa_ns_visible(curr, view)) { + /* at this point if a ns is visible it is in a view ns + * thus the curr ns.hname is a prefix of its name. + * Only output the virtualized portion of the name + * Add + 2 to skip over // separating curr hname prefix + * from the visible tail of the views hname + */ + return view->base.hname + strlen(curr->base.hname) + 2; + } else + return hidden_ns_name; +} + +/** + * alloc_namespace - allocate, initialize and return a new namespace + * @prefix: parent namespace name (MAYBE NULL) + * @name: a preallocated name (NOT NULL) + * + * Returns: refcounted namespace or NULL on failure. + */ +static struct aa_namespace *alloc_namespace(const char *prefix, + const char *name) +{ + struct aa_namespace *ns; + + ns = kzalloc(sizeof(*ns), GFP_KERNEL); + AA_DEBUG("%s(%p)\n", __func__, ns); + if (!ns) + return NULL; + if (!policy_init(&ns->base, prefix, name)) + goto fail_ns; + + INIT_LIST_HEAD(&ns->sub_ns); + rwlock_init(&ns->lock); + + /* released by free_namespace */ + ns->unconfined = aa_alloc_profile("unconfined"); + if (!ns->unconfined) + goto fail_unconfined; + + ns->unconfined->sid = aa_alloc_sid(); + ns->unconfined->flags = PFLAG_UNCONFINED | PFLAG_IX_ON_NAME_ERROR | + PFLAG_IMMUTABLE; + + /* + * released by free_namespace, however __remove_namespace breaks + * the cyclic references (ns->unconfined, and unconfined->ns) and + * replaces with refs to parent namespace unconfined + */ + ns->unconfined->ns = aa_get_namespace(ns); + + return ns; + +fail_unconfined: + kzfree(ns->base.hname); +fail_ns: + kzfree(ns); + return NULL; +} + +/** + * free_namespace - free a profile namespace + * @ns: the namespace to free (MAYBE NULL) + * + * Requires: All references to the namespace must have been put, if the + * namespace was referenced by a profile confining a task, + */ +static void free_namespace(struct aa_namespace *ns) +{ + if (!ns) + return; + + policy_destroy(&ns->base); + aa_put_namespace(ns->parent); + + if (ns->unconfined && ns->unconfined->ns == ns) + ns->unconfined->ns = NULL; + + aa_put_profile(ns->unconfined); + kzfree(ns); +} + +/** + * aa_free_namespace_kref - free aa_namespace by kref (see aa_put_namespace) + * @kr: kref callback for freeing of a namespace (NOT NULL) + */ +void aa_free_namespace_kref(struct kref *kref) +{ + free_namespace(container_of(kref, struct aa_namespace, base.count)); +} + +/** + * __aa_find_namespace - find a namespace on a list by @name + * @head: list to search for namespace on (NOT NULL) + * @name: name of namespace to look for (NOT NULL) + * + * Returns: unrefcounted namespace + * + * Requires: ns lock be held + */ +static struct aa_namespace *__aa_find_namespace(struct list_head *head, + const char *name) +{ + return (struct aa_namespace *)__policy_find(head, name); +} + +/** + * aa_find_namespace - look up a profile namespace on the namespace list + * @root: namespace to search in (NOT NULL) + * @name: name of namespace to find (NOT NULL) + * + * Returns: a refcounted namespace on the list, or NULL if no namespace + * called @name exists. + * + * refcount released by caller + */ +struct aa_namespace *aa_find_namespace(struct aa_namespace *root, + const char *name) +{ + struct aa_namespace *ns = NULL; + + read_lock(&root->lock); + ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name)); + read_unlock(&root->lock); + + return ns; +} + +/** + * aa_prepare_namespace - find an existing or create a new namespace of @name + * @name: the namespace to find or add (MAYBE NULL) + * + * Returns: refcounted namespace or NULL if failed to create one + */ +static struct aa_namespace *aa_prepare_namespace(const char *name) +{ + struct aa_namespace *ns, *root; + + root = aa_current_profile()->ns; + + write_lock(&root->lock); + + /* if name isn't specified the profile is loaded to the current ns */ + if (!name) { + /* released by caller */ + ns = aa_get_namespace(root); + goto out; + } + + /* try and find the specified ns and if it doesn't exist create it */ + /* released by caller */ + ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name)); + if (!ns) { + /* namespace not found */ + struct aa_namespace *new_ns; + write_unlock(&root->lock); + new_ns = alloc_namespace(root->base.hname, name); + if (!new_ns) + return NULL; + write_lock(&root->lock); + /* test for race when new_ns was allocated */ + ns = __aa_find_namespace(&root->sub_ns, name); + if (!ns) { + /* add parent ref */ + new_ns->parent = aa_get_namespace(root); + + list_add(&new_ns->base.list, &root->sub_ns); + /* add list ref */ + ns = aa_get_namespace(new_ns); + } else { + /* raced so free the new one */ + free_namespace(new_ns); + /* get reference on namespace */ + aa_get_namespace(ns); + } + } +out: + write_unlock(&root->lock); + + /* return ref */ + return ns; +} + +/** + * __list_add_profile - add a profile to a list + * @list: list to add it to (NOT NULL) + * @profile: the profile to add (NOT NULL) + * + * refcount @profile, should be put by __list_remove_profile + * + * Requires: namespace lock be held, or list not be shared + */ +static void __list_add_profile(struct list_head *list, + struct aa_profile *profile) +{ + list_add(&profile->base.list, list); + /* get list reference */ + aa_get_profile(profile); +} + +/** + * __list_remove_profile - remove a profile from the list it is on + * @profile: the profile to remove (NOT NULL) + * + * remove a profile from the list, warning generally removal should + * be done with __replace_profile as most profile removals are + * replacements to the unconfined profile. + * + * put @profile list refcount + * + * Requires: namespace lock be held, or list not have been live + */ +static void __list_remove_profile(struct aa_profile *profile) +{ + list_del_init(&profile->base.list); + if (!(profile->flags & PFLAG_NO_LIST_REF)) + /* release list reference */ + aa_put_profile(profile); +} + +/** + * __replace_profile - replace @old with @new on a list + * @old: profile to be replaced (NOT NULL) + * @new: profile to replace @old with (NOT NULL) + * + * Will duplicate and refcount elements that @new inherits from @old + * and will inherit @old children. + * + * refcount @new for list, put @old list refcount + * + * Requires: namespace list lock be held, or list not be shared + */ +static void __replace_profile(struct aa_profile *old, struct aa_profile *new) +{ + struct aa_policy *policy; + struct aa_profile *child, *tmp; + + if (old->parent) + policy = &old->parent->base; + else + policy = &old->ns->base; + + /* released when @new is freed */ + new->parent = aa_get_profile(old->parent); + new->ns = aa_get_namespace(old->ns); + new->sid = old->sid; + __list_add_profile(&policy->profiles, new); + /* inherit children */ + list_for_each_entry_safe(child, tmp, &old->base.profiles, base.list) { + aa_put_profile(child->parent); + child->parent = aa_get_profile(new); + /* list refcount transferred to @new*/ + list_move(&child->base.list, &new->base.profiles); + } + + /* released by free_profile */ + old->replacedby = aa_get_profile(new); + __list_remove_profile(old); +} + +static void __profile_list_release(struct list_head *head); + +/** + * __remove_profile - remove old profile, and children + * @profile: profile to be replaced (NOT NULL) + * + * Requires: namespace list lock be held, or list not be shared + */ +static void __remove_profile(struct aa_profile *profile) +{ + /* release any children lists first */ + __profile_list_release(&profile->base.profiles); + /* released by free_profile */ + profile->replacedby = aa_get_profile(profile->ns->unconfined); + __list_remove_profile(profile); +} + +/** + * __profile_list_release - remove all profiles on the list and put refs + * @head: list of profiles (NOT NULL) + * + * Requires: namespace lock be held + */ +static void __profile_list_release(struct list_head *head) +{ + struct aa_profile *profile, *tmp; + list_for_each_entry_safe(profile, tmp, head, base.list) + __remove_profile(profile); +} + +static void __ns_list_release(struct list_head *head); + +/** + * destroy_namespace - remove everything contained by @ns + * @ns: namespace to have it contents removed (NOT NULL) + */ +static void destroy_namespace(struct aa_namespace *ns) +{ + if (!ns) + return; + + write_lock(&ns->lock); + /* release all profiles in this namespace */ + __profile_list_release(&ns->base.profiles); + + /* release all sub namespaces */ + __ns_list_release(&ns->sub_ns); + + write_unlock(&ns->lock); +} + +/** + * __remove_namespace - remove a namespace and all its children + * @ns: namespace to be removed (NOT NULL) + * + * Requires: ns->parent->lock be held and ns removed from parent. + */ +static void __remove_namespace(struct aa_namespace *ns) +{ + struct aa_profile *unconfined = ns->unconfined; + + /* remove ns from namespace list */ + list_del_init(&ns->base.list); + + /* + * break the ns, unconfined profile cyclic reference and forward + * all new unconfined profiles requests to the parent namespace + * This will result in all confined tasks that have a profile + * being removed, inheriting the parent->unconfined profile. + */ + if (ns->parent) + ns->unconfined = aa_get_profile(ns->parent->unconfined); + + destroy_namespace(ns); + + /* release original ns->unconfined ref */ + aa_put_profile(unconfined); + /* release ns->base.list ref, from removal above */ + aa_put_namespace(ns); +} + +/** + * __ns_list_release - remove all profile namespaces on the list put refs + * @head: list of profile namespaces (NOT NULL) + * + * Requires: namespace lock be held + */ +static void __ns_list_release(struct list_head *head) +{ + struct aa_namespace *ns, *tmp; + list_for_each_entry_safe(ns, tmp, head, base.list) + __remove_namespace(ns); + +} + +/** + * aa_alloc_root_ns - allocate the root profile namespace + * + * Returns: %0 on success else error + * + */ +int __init aa_alloc_root_ns(void) +{ + /* released by aa_free_root_ns - used as list ref*/ + root_ns = alloc_namespace(NULL, "root"); + if (!root_ns) + return -ENOMEM; + + return 0; +} + + /** + * aa_free_root_ns - free the root profile namespace + */ +void __init aa_free_root_ns(void) + { + struct aa_namespace *ns = root_ns; + root_ns = NULL; + + destroy_namespace(ns); + aa_put_namespace(ns); +} + +/** + * aa_alloc_profile - allocate, initialize and return a new profile + * @hname: name of the profile (NOT NULL) + * + * Returns: refcount profile or NULL on failure + */ +struct aa_profile *aa_alloc_profile(const char *hname) +{ + struct aa_profile *profile; + + /* freed by free_profile - usually through aa_put_profile */ + profile = kzalloc(sizeof(*profile), GFP_KERNEL); + if (!profile) + return NULL; + + if (!policy_init(&profile->base, NULL, hname)) { + kzfree(profile); + return NULL; + } + + /* refcount released by caller */ + return profile; +} + +/** + * aa_new_null_profile - create a new null-X learning profile + * @parent: profile that caused this profile to be created (NOT NULL) + * @hat: true if the null- learning profile is a hat + * + * Create a null- complain mode profile used in learning mode. The name of + * the profile is unique and follows the format of parent//null-sid. + * + * null profiles are added to the profile list but the list does not + * hold a count on them so that they are automatically released when + * not in use. + * + * Returns: new refcounted profile else NULL on failure + */ +struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat) +{ + struct aa_profile *profile = NULL; + char *name; + u32 sid = aa_alloc_sid(); + + /* freed below */ + name = kmalloc(strlen(parent->base.hname) + 2 + 7 + 8, GFP_KERNEL); + if (!name) + goto fail; + sprintf(name, "%s//null-%x", parent->base.hname, sid); + + profile = aa_alloc_profile(name); + kfree(name); + if (!profile) + goto fail; + + profile->sid = sid; + profile->mode = APPARMOR_COMPLAIN; + profile->flags = PFLAG_NULL; + if (hat) + profile->flags |= PFLAG_HAT; + + /* released on free_profile */ + profile->parent = aa_get_profile(parent); + profile->ns = aa_get_namespace(parent->ns); + + write_lock(&profile->ns->lock); + __list_add_profile(&parent->base.profiles, profile); + write_unlock(&profile->ns->lock); + + /* refcount released by caller */ + return profile; + +fail: + aa_free_sid(sid); + return NULL; +} + +/** + * free_profile - free a profile + * @profile: the profile to free (MAYBE NULL) + * + * Free a profile, its hats and null_profile. All references to the profile, + * its hats and null_profile must have been put. + * + * If the profile was referenced from a task context, free_profile() will + * be called from an rcu callback routine, so we must not sleep here. + */ +static void free_profile(struct aa_profile *profile) +{ + AA_DEBUG("%s(%p)\n", __func__, profile); + + if (!profile) + return; + + if (!list_empty(&profile->base.list)) { + AA_ERROR("%s: internal error, " + "profile '%s' still on ns list\n", + __func__, profile->base.name); + BUG(); + } + + /* free children profiles */ + policy_destroy(&profile->base); + aa_put_profile(profile->parent); + + aa_put_namespace(profile->ns); + kzfree(profile->rename); + + aa_free_file_rules(&profile->file); + aa_free_cap_rules(&profile->caps); + aa_free_rlimit_rules(&profile->rlimits); + + aa_free_sid(profile->sid); + aa_put_dfa(profile->xmatch); + + aa_put_profile(profile->replacedby); + + kzfree(profile); +} + +/** + * aa_free_profile_kref - free aa_profile by kref (called by aa_put_profile) + * @kr: kref callback for freeing of a profile (NOT NULL) + */ +void aa_free_profile_kref(struct kref *kref) +{ + struct aa_profile *p = container_of(kref, struct aa_profile, + base.count); + + free_profile(p); +} + +/* TODO: profile accounting - setup in remove */ + +/** + * __find_child - find a profile on @head list with a name matching @name + * @head: list to search (NOT NULL) + * @name: name of profile (NOT NULL) + * + * Requires: ns lock protecting list be held + * + * Returns: unrefcounted profile ptr, or NULL if not found + */ +static struct aa_profile *__find_child(struct list_head *head, const char *name) +{ + return (struct aa_profile *)__policy_find(head, name); +} + +/** + * __strn_find_child - find a profile on @head list using substring of @name + * @head: list to search (NOT NULL) + * @name: name of profile (NOT NULL) + * @len: length of @name substring to match + * + * Requires: ns lock protecting list be held + * + * Returns: unrefcounted profile ptr, or NULL if not found + */ +static struct aa_profile *__strn_find_child(struct list_head *head, + const char *name, int len) +{ + return (struct aa_profile *)__policy_strn_find(head, name, len); +} + +/** + * aa_find_child - find a profile by @name in @parent + * @parent: profile to search (NOT NULL) + * @name: profile name to search for (NOT NULL) + * + * Returns: a refcounted profile or NULL if not found + */ +struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name) +{ + struct aa_profile *profile; + + read_lock(&parent->ns->lock); + profile = aa_get_profile(__find_child(&parent->base.profiles, name)); + read_unlock(&parent->ns->lock); + + /* refcount released by caller */ + return profile; +} + +/** + * __lookup_parent - lookup the parent of a profile of name @hname + * @ns: namespace to lookup profile in (NOT NULL) + * @hname: hierarchical profile name to find parent of (NOT NULL) + * + * Lookups up the parent of a fully qualified profile name, the profile + * that matches hname does not need to exist, in general this + * is used to load a new profile. + * + * Requires: ns->lock be held + * + * Returns: unrefcounted policy or NULL if not found + */ +static struct aa_policy *__lookup_parent(struct aa_namespace *ns, + const char *hname) +{ + struct aa_policy *policy; + struct aa_profile *profile = NULL; + char *split; + + policy = &ns->base; + + for (split = strstr(hname, "//"); split;) { + profile = __strn_find_child(&policy->profiles, hname, + split - hname); + if (!profile) + return NULL; + policy = &profile->base; + hname = split + 2; + split = strstr(hname, "//"); + } + if (!profile) + return &ns->base; + return &profile->base; +} + +/** + * __lookup_profile - lookup the profile matching @hname + * @base: base list to start looking up profile name from (NOT NULL) + * @hname: hierarchical profile name (NOT NULL) + * + * Requires: ns->lock be held + * + * Returns: unrefcounted profile pointer or NULL if not found + * + * Do a relative name lookup, recursing through profile tree. + */ +static struct aa_profile *__lookup_profile(struct aa_policy *base, + const char *hname) +{ + struct aa_profile *profile = NULL; + char *split; + + for (split = strstr(hname, "//"); split;) { + profile = __strn_find_child(&base->profiles, hname, + split - hname); + if (!profile) + return NULL; + + base = &profile->base; + hname = split + 2; + split = strstr(hname, "//"); + } + + profile = __find_child(&base->profiles, hname); + + return profile; +} + +/** + * aa_lookup_profile - find a profile by its full or partial name + * @ns: the namespace to start from (NOT NULL) + * @hname: name to do lookup on. Does not contain namespace prefix (NOT NULL) + * + * Returns: refcounted profile or NULL if not found + */ +struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *hname) +{ + struct aa_profile *profile; + + read_lock(&ns->lock); + profile = aa_get_profile(__lookup_profile(&ns->base, hname)); + read_unlock(&ns->lock); + + /* refcount released by caller */ + return profile; +} + +/** + * replacement_allowed - test to see if replacement is allowed + * @profile: profile to test if it can be replaced (MAYBE NULL) + * @noreplace: true if replacement shouldn't be allowed but addition is okay + * @info: Returns - info about why replacement failed (NOT NULL) + * + * Returns: %0 if replacement allowed else error code + */ +static int replacement_allowed(struct aa_profile *profile, int noreplace, + const char **info) +{ + if (profile) { + if (profile->flags & PFLAG_IMMUTABLE) { + *info = "cannot replace immutible profile"; + return -EPERM; + } else if (noreplace) { + *info = "profile already exists"; + return -EEXIST; + } + } + return 0; +} + +/** + * __add_new_profile - simple wrapper around __list_add_profile + * @ns: namespace that profile is being added to (NOT NULL) + * @policy: the policy container to add the profile to (NOT NULL) + * @profile: profile to add (NOT NULL) + * + * add a profile to a list and do other required basic allocations + */ +static void __add_new_profile(struct aa_namespace *ns, struct aa_policy *policy, + struct aa_profile *profile) +{ + if (policy != &ns->base) + /* released on profile replacement or free_profile */ + profile->parent = aa_get_profile((struct aa_profile *) policy); + __list_add_profile(&policy->profiles, profile); + /* released on free_profile */ + profile->sid = aa_alloc_sid(); + profile->ns = aa_get_namespace(ns); +} + +/** + * aa_audit_policy - Do auditing of policy changes + * @op: policy operation being performed + * @gfp: memory allocation flags + * @name: name of profile being manipulated (NOT NULL) + * @info: any extra information to be audited (MAYBE NULL) + * @error: error code + * + * Returns: the error to be returned after audit is done + */ +static int audit_policy(int op, gfp_t gfp, const char *name, const char *info, + int error) +{ + struct common_audit_data sa; + COMMON_AUDIT_DATA_INIT(&sa, NONE); + sa.aad.op = op; + sa.aad.name = name; + sa.aad.info = info; + sa.aad.error = error; + + return aa_audit(AUDIT_APPARMOR_STATUS, __aa_current_profile(), gfp, + &sa, NULL); +} + +/** + * aa_may_manage_policy - can the current task manage policy + * @op: the policy manipulation operation being done + * + * Returns: true if the task is allowed to manipulate policy + */ +bool aa_may_manage_policy(int op) +{ + /* check if loading policy is locked out */ + if (aa_g_lock_policy) { + audit_policy(op, GFP_KERNEL, NULL, "policy_locked", -EACCES); + return 0; + } + + if (!capable(CAP_MAC_ADMIN)) { + audit_policy(op, GFP_KERNEL, NULL, "not policy admin", -EACCES); + return 0; + } + + return 1; +} + +/** + * aa_replace_profiles - replace profile(s) on the profile list + * @udata: serialized data stream (NOT NULL) + * @size: size of the serialized data stream + * @noreplace: true if only doing addition, no replacement allowed + * + * unpack and replace a profile on the profile list and uses of that profile + * by any aa_task_cxt. If the profile does not exist on the profile list + * it is added. + * + * Returns: size of data consumed else error code on failure. + */ +ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) +{ + struct aa_policy *policy; + struct aa_profile *old_profile = NULL, *new_profile = NULL; + struct aa_profile *rename_profile = NULL; + struct aa_namespace *ns = NULL; + const char *ns_name, *name = NULL, *info = NULL; + int op = OP_PROF_REPL; + ssize_t error; + + /* released below */ + new_profile = aa_unpack(udata, size, &ns_name); + if (IS_ERR(new_profile)) { + error = PTR_ERR(new_profile); + new_profile = NULL; + goto fail; + } + + /* released below */ + ns = aa_prepare_namespace(ns_name); + if (!ns) { + info = "failed to prepare namespace"; + error = -ENOMEM; + name = ns_name; + goto fail; + } + + name = new_profile->base.hname; + + write_lock(&ns->lock); + /* no ref on policy only use inside lock */ + policy = __lookup_parent(ns, new_profile->base.hname); + + if (!policy) { + info = "parent does not exist"; + error = -ENOENT; + goto audit; + } + + old_profile = __find_child(&policy->profiles, new_profile->base.name); + /* released below */ + aa_get_profile(old_profile); + + if (new_profile->rename) { + rename_profile = __lookup_profile(&ns->base, + new_profile->rename); + /* released below */ + aa_get_profile(rename_profile); + + if (!rename_profile) { + info = "profile to rename does not exist"; + name = new_profile->rename; + error = -ENOENT; + goto audit; + } + } + + error = replacement_allowed(old_profile, noreplace, &info); + if (error) + goto audit; + + error = replacement_allowed(rename_profile, noreplace, &info); + if (error) + goto audit; + +audit: + if (!old_profile && !rename_profile) + op = OP_PROF_LOAD; + + error = audit_policy(op, GFP_ATOMIC, name, info, error); + + if (!error) { + if (rename_profile) + __replace_profile(rename_profile, new_profile); + if (old_profile) { + /* when there are both rename and old profiles + * inherit old profiles sid + */ + if (rename_profile) + aa_free_sid(new_profile->sid); + __replace_profile(old_profile, new_profile); + } + if (!(old_profile || rename_profile)) + __add_new_profile(ns, policy, new_profile); + } + write_unlock(&ns->lock); + +out: + aa_put_namespace(ns); + aa_put_profile(rename_profile); + aa_put_profile(old_profile); + aa_put_profile(new_profile); + if (error) + return error; + return size; + +fail: + error = audit_policy(op, GFP_KERNEL, name, info, error); + goto out; +} + +/** + * aa_remove_profiles - remove profile(s) from the system + * @fqname: name of the profile or namespace to remove (NOT NULL) + * @size: size of the name + * + * Remove a profile or sub namespace from the current namespace, so that + * they can not be found anymore and mark them as replaced by unconfined + * + * NOTE: removing confinement does not restore rlimits to preconfinemnet values + * + * Returns: size of data consume else error code if fails + */ +ssize_t aa_remove_profiles(char *fqname, size_t size) +{ + struct aa_namespace *root, *ns = NULL; + struct aa_profile *profile = NULL; + const char *name = fqname, *info = NULL; + ssize_t error = 0; + + if (*fqname == 0) { + info = "no profile specified"; + error = -ENOENT; + goto fail; + } + + root = aa_current_profile()->ns; + + if (fqname[0] == ':') { + char *ns_name; + name = aa_split_fqname(fqname, &ns_name); + if (ns_name) { + /* released below */ + ns = aa_find_namespace(root, ns_name); + if (!ns) { + info = "namespace does not exist"; + error = -ENOENT; + goto fail; + } + } + } else + /* released below */ + ns = aa_get_namespace(root); + + if (!name) { + /* remove namespace - can only happen if fqname[0] == ':' */ + write_lock(&ns->parent->lock); + __remove_namespace(ns); + write_unlock(&ns->parent->lock); + } else { + /* remove profile */ + write_lock(&ns->lock); + profile = aa_get_profile(__lookup_profile(&ns->base, name)); + if (!profile) { + error = -ENOENT; + info = "profile does not exist"; + goto fail_ns_lock; + } + name = profile->base.hname; + __remove_profile(profile); + write_unlock(&ns->lock); + } + + /* don't fail removal if audit fails */ + (void) audit_policy(OP_PROF_RM, GFP_KERNEL, name, info, error); + aa_put_namespace(ns); + aa_put_profile(profile); + return size; + +fail_ns_lock: + write_unlock(&ns->lock); + aa_put_namespace(ns); + +fail: + (void) audit_policy(OP_PROF_RM, GFP_KERNEL, name, info, error); + return error; +} diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c new file mode 100644 index 00000000..d6d9a57b --- /dev/null +++ b/security/apparmor/policy_unpack.c @@ -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/security/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 successfully 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; +} diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c new file mode 100644 index 00000000..04a2cf8d --- /dev/null +++ b/security/apparmor/procattr.c @@ -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; +} diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c new file mode 100644 index 00000000..a4136c10 --- /dev/null +++ b/security/apparmor/resource.c @@ -0,0 +1,138 @@ +/* + * 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) + * @task - task the resource is being set on + * @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, struct task_struct *task, + unsigned int resource, struct rlimit *new_rlim) +{ + int error = 0; + + /* TODO: extend resource control to handle other (non current) + * processes. AppArmor rules currently have the implicit assumption + * that the task is setting the resource of the current process + */ + if ((task != current->group_leader) || + (profile->rlimits.mask & (1 << resource) && + new_rlim->rlim_max > profile->rlimits.limits[resource].rlim_max)) + error = -EACCES; + + return audit_resource(profile, resource, new_rlim->rlim_max, 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); + } +} diff --git a/security/apparmor/sid.c b/security/apparmor/sid.c new file mode 100644 index 00000000..f0b34f76 --- /dev/null +++ b/security/apparmor/sid.c @@ -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 */ +} diff --git a/security/capability.c b/security/capability.c new file mode 100644 index 00000000..bbb51156 --- /dev/null +++ b/security/capability.c @@ -0,0 +1,1071 @@ +/* + * Capabilities Linux Security Module + * + * This is the default security module in case no other module is loaded. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include <linux/security.h> + +static int cap_syslog(int type) +{ + return 0; +} + +static int cap_quotactl(int cmds, int type, int id, struct super_block *sb) +{ + return 0; +} + +static int cap_quota_on(struct dentry *dentry) +{ + return 0; +} + +static int cap_bprm_check_security(struct linux_binprm *bprm) +{ + return 0; +} + +static void cap_bprm_committing_creds(struct linux_binprm *bprm) +{ +} + +static void cap_bprm_committed_creds(struct linux_binprm *bprm) +{ +} + +static int cap_sb_alloc_security(struct super_block *sb) +{ + return 0; +} + +static void cap_sb_free_security(struct super_block *sb) +{ +} + +static int cap_sb_copy_data(char *orig, char *copy) +{ + return 0; +} + +static int cap_sb_remount(struct super_block *sb, void *data) +{ + return 0; +} + +static int cap_sb_kern_mount(struct super_block *sb, int flags, void *data) +{ + return 0; +} + +static int cap_sb_show_options(struct seq_file *m, struct super_block *sb) +{ + return 0; +} + +static int cap_sb_statfs(struct dentry *dentry) +{ + return 0; +} + +static int cap_sb_mount(char *dev_name, struct path *path, char *type, + unsigned long flags, void *data) +{ + return 0; +} + +static int cap_sb_umount(struct vfsmount *mnt, int flags) +{ + return 0; +} + +static int cap_sb_pivotroot(struct path *old_path, struct path *new_path) +{ + return 0; +} + +static int cap_sb_set_mnt_opts(struct super_block *sb, + struct security_mnt_opts *opts) +{ + if (unlikely(opts->num_mnt_opts)) + return -EOPNOTSUPP; + return 0; +} + +static void cap_sb_clone_mnt_opts(const struct super_block *oldsb, + struct super_block *newsb) +{ +} + +static int cap_sb_parse_opts_str(char *options, struct security_mnt_opts *opts) +{ + return 0; +} + +static int cap_inode_alloc_security(struct inode *inode) +{ + return 0; +} + +static void cap_inode_free_security(struct inode *inode) +{ +} + +static int cap_inode_init_security(struct inode *inode, struct inode *dir, + const struct qstr *qstr, char **name, + void **value, size_t *len) +{ + return -EOPNOTSUPP; +} + +static int cap_inode_create(struct inode *inode, struct dentry *dentry, + int mask) +{ + return 0; +} + +static int cap_inode_link(struct dentry *old_dentry, struct inode *inode, + struct dentry *new_dentry) +{ + return 0; +} + +static int cap_inode_unlink(struct inode *inode, struct dentry *dentry) +{ + return 0; +} + +static int cap_inode_symlink(struct inode *inode, struct dentry *dentry, + const char *name) +{ + return 0; +} + +static int cap_inode_mkdir(struct inode *inode, struct dentry *dentry, + int mask) +{ + return 0; +} + +static int cap_inode_rmdir(struct inode *inode, struct dentry *dentry) +{ + return 0; +} + +static int cap_inode_mknod(struct inode *inode, struct dentry *dentry, + int mode, dev_t dev) +{ + return 0; +} + +static int cap_inode_rename(struct inode *old_inode, struct dentry *old_dentry, + struct inode *new_inode, struct dentry *new_dentry) +{ + return 0; +} + +static int cap_inode_readlink(struct dentry *dentry) +{ + return 0; +} + +static int cap_inode_follow_link(struct dentry *dentry, + struct nameidata *nameidata) +{ + return 0; +} + +static int cap_inode_permission(struct inode *inode, int mask, unsigned flags) +{ + return 0; +} + +static int cap_inode_setattr(struct dentry *dentry, struct iattr *iattr) +{ + return 0; +} + +static int cap_inode_getattr(struct vfsmount *mnt, struct dentry *dentry) +{ + return 0; +} + +static void cap_inode_post_setxattr(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags) +{ +} + +static int cap_inode_getxattr(struct dentry *dentry, const char *name) +{ + return 0; +} + +static int cap_inode_listxattr(struct dentry *dentry) +{ + return 0; +} + +static int cap_inode_getsecurity(const struct inode *inode, const char *name, + void **buffer, bool alloc) +{ + return -EOPNOTSUPP; +} + +static int cap_inode_setsecurity(struct inode *inode, const char *name, + const void *value, size_t size, int flags) +{ + return -EOPNOTSUPP; +} + +static int cap_inode_listsecurity(struct inode *inode, char *buffer, + size_t buffer_size) +{ + return 0; +} + +static void cap_inode_getsecid(const struct inode *inode, u32 *secid) +{ + *secid = 0; +} + +#ifdef CONFIG_SECURITY_PATH +static int cap_path_mknod(struct path *dir, struct dentry *dentry, int mode, + unsigned int dev) +{ + return 0; +} + +static int cap_path_mkdir(struct path *dir, struct dentry *dentry, int mode) +{ + return 0; +} + +static int cap_path_rmdir(struct path *dir, struct dentry *dentry) +{ + return 0; +} + +static int cap_path_unlink(struct path *dir, struct dentry *dentry) +{ + return 0; +} + +static int cap_path_symlink(struct path *dir, struct dentry *dentry, + const char *old_name) +{ + return 0; +} + +static int cap_path_link(struct dentry *old_dentry, struct path *new_dir, + struct dentry *new_dentry) +{ + return 0; +} + +static int cap_path_rename(struct path *old_path, struct dentry *old_dentry, + struct path *new_path, struct dentry *new_dentry) +{ + return 0; +} + +static int cap_path_truncate(struct path *path) +{ + return 0; +} + +static int cap_path_chmod(struct dentry *dentry, struct vfsmount *mnt, + mode_t mode) +{ + return 0; +} + +static int cap_path_chown(struct path *path, uid_t uid, gid_t gid) +{ + return 0; +} + +static int cap_path_chroot(struct path *root) +{ + return 0; +} +#endif + +static int cap_file_permission(struct file *file, int mask) +{ + return 0; +} + +static int cap_file_alloc_security(struct file *file) +{ + return 0; +} + +static void cap_file_free_security(struct file *file) +{ +} + +static int cap_file_ioctl(struct file *file, unsigned int command, + unsigned long arg) +{ + return 0; +} + +static int cap_file_mprotect(struct vm_area_struct *vma, unsigned long reqprot, + unsigned long prot) +{ + return 0; +} + +static int cap_file_lock(struct file *file, unsigned int cmd) +{ + return 0; +} + +static int cap_file_fcntl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return 0; +} + +static int cap_file_set_fowner(struct file *file) +{ + return 0; +} + +static int cap_file_send_sigiotask(struct task_struct *tsk, + struct fown_struct *fown, int sig) +{ + return 0; +} + +static int cap_file_receive(struct file *file) +{ + return 0; +} + +static int cap_dentry_open(struct file *file, const struct cred *cred) +{ + return 0; +} + +static int cap_task_create(unsigned long clone_flags) +{ + return 0; +} + +static int cap_cred_alloc_blank(struct cred *cred, gfp_t gfp) +{ + return 0; +} + +static void cap_cred_free(struct cred *cred) +{ +} + +static int cap_cred_prepare(struct cred *new, const struct cred *old, gfp_t gfp) +{ + return 0; +} + +static void cap_cred_transfer(struct cred *new, const struct cred *old) +{ +} + +static int cap_kernel_act_as(struct cred *new, u32 secid) +{ + return 0; +} + +static int cap_kernel_create_files_as(struct cred *new, struct inode *inode) +{ + return 0; +} + +static int cap_kernel_module_request(char *kmod_name) +{ + return 0; +} + +static int cap_task_setpgid(struct task_struct *p, pid_t pgid) +{ + return 0; +} + +static int cap_task_getpgid(struct task_struct *p) +{ + return 0; +} + +static int cap_task_getsid(struct task_struct *p) +{ + return 0; +} + +static void cap_task_getsecid(struct task_struct *p, u32 *secid) +{ + *secid = 0; +} + +static int cap_task_getioprio(struct task_struct *p) +{ + return 0; +} + +static int cap_task_setrlimit(struct task_struct *p, unsigned int resource, + struct rlimit *new_rlim) +{ + return 0; +} + +static int cap_task_getscheduler(struct task_struct *p) +{ + return 0; +} + +static int cap_task_movememory(struct task_struct *p) +{ + return 0; +} + +static int cap_task_wait(struct task_struct *p) +{ + return 0; +} + +static int cap_task_kill(struct task_struct *p, struct siginfo *info, + int sig, u32 secid) +{ + return 0; +} + +static void cap_task_to_inode(struct task_struct *p, struct inode *inode) +{ +} + +static int cap_ipc_permission(struct kern_ipc_perm *ipcp, short flag) +{ + return 0; +} + +static void cap_ipc_getsecid(struct kern_ipc_perm *ipcp, u32 *secid) +{ + *secid = 0; +} + +static int cap_msg_msg_alloc_security(struct msg_msg *msg) +{ + return 0; +} + +static void cap_msg_msg_free_security(struct msg_msg *msg) +{ +} + +static int cap_msg_queue_alloc_security(struct msg_queue *msq) +{ + return 0; +} + +static void cap_msg_queue_free_security(struct msg_queue *msq) +{ +} + +static int cap_msg_queue_associate(struct msg_queue *msq, int msqflg) +{ + return 0; +} + +static int cap_msg_queue_msgctl(struct msg_queue *msq, int cmd) +{ + return 0; +} + +static int cap_msg_queue_msgsnd(struct msg_queue *msq, struct msg_msg *msg, + int msgflg) +{ + return 0; +} + +static int cap_msg_queue_msgrcv(struct msg_queue *msq, struct msg_msg *msg, + struct task_struct *target, long type, int mode) +{ + return 0; +} + +static int cap_shm_alloc_security(struct shmid_kernel *shp) +{ + return 0; +} + +static void cap_shm_free_security(struct shmid_kernel *shp) +{ +} + +static int cap_shm_associate(struct shmid_kernel *shp, int shmflg) +{ + return 0; +} + +static int cap_shm_shmctl(struct shmid_kernel *shp, int cmd) +{ + return 0; +} + +static int cap_shm_shmat(struct shmid_kernel *shp, char __user *shmaddr, + int shmflg) +{ + return 0; +} + +static int cap_sem_alloc_security(struct sem_array *sma) +{ + return 0; +} + +static void cap_sem_free_security(struct sem_array *sma) +{ +} + +static int cap_sem_associate(struct sem_array *sma, int semflg) +{ + return 0; +} + +static int cap_sem_semctl(struct sem_array *sma, int cmd) +{ + return 0; +} + +static int cap_sem_semop(struct sem_array *sma, struct sembuf *sops, + unsigned nsops, int alter) +{ + return 0; +} + +#ifdef CONFIG_SECURITY_NETWORK +static int cap_unix_stream_connect(struct sock *sock, struct sock *other, + struct sock *newsk) +{ + return 0; +} + +static int cap_unix_may_send(struct socket *sock, struct socket *other) +{ + return 0; +} + +static int cap_socket_create(int family, int type, int protocol, int kern) +{ + return 0; +} + +static int cap_socket_post_create(struct socket *sock, int family, int type, + int protocol, int kern) +{ + return 0; +} + +static int cap_socket_bind(struct socket *sock, struct sockaddr *address, + int addrlen) +{ + return 0; +} + +static int cap_socket_connect(struct socket *sock, struct sockaddr *address, + int addrlen) +{ + return 0; +} + +static int cap_socket_listen(struct socket *sock, int backlog) +{ + return 0; +} + +static int cap_socket_accept(struct socket *sock, struct socket *newsock) +{ + return 0; +} + +static int cap_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size) +{ + return 0; +} + +static int cap_socket_recvmsg(struct socket *sock, struct msghdr *msg, + int size, int flags) +{ + return 0; +} + +static int cap_socket_getsockname(struct socket *sock) +{ + return 0; +} + +static int cap_socket_getpeername(struct socket *sock) +{ + return 0; +} + +static int cap_socket_setsockopt(struct socket *sock, int level, int optname) +{ + return 0; +} + +static int cap_socket_getsockopt(struct socket *sock, int level, int optname) +{ + return 0; +} + +static int cap_socket_shutdown(struct socket *sock, int how) +{ + return 0; +} + +static int cap_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) +{ + return 0; +} + +static int cap_socket_getpeersec_stream(struct socket *sock, + char __user *optval, + int __user *optlen, unsigned len) +{ + return -ENOPROTOOPT; +} + +static int cap_socket_getpeersec_dgram(struct socket *sock, + struct sk_buff *skb, u32 *secid) +{ + return -ENOPROTOOPT; +} + +static int cap_sk_alloc_security(struct sock *sk, int family, gfp_t priority) +{ + return 0; +} + +static void cap_sk_free_security(struct sock *sk) +{ +} + +static void cap_sk_clone_security(const struct sock *sk, struct sock *newsk) +{ +} + +static void cap_sk_getsecid(struct sock *sk, u32 *secid) +{ +} + +static void cap_sock_graft(struct sock *sk, struct socket *parent) +{ +} + +static int cap_inet_conn_request(struct sock *sk, struct sk_buff *skb, + struct request_sock *req) +{ + return 0; +} + +static void cap_inet_csk_clone(struct sock *newsk, + const struct request_sock *req) +{ +} + +static void cap_inet_conn_established(struct sock *sk, struct sk_buff *skb) +{ +} + +static int cap_secmark_relabel_packet(u32 secid) +{ + return 0; +} + +static void cap_secmark_refcount_inc(void) +{ +} + +static void cap_secmark_refcount_dec(void) +{ +} + +static void cap_req_classify_flow(const struct request_sock *req, + struct flowi *fl) +{ +} + +static int cap_tun_dev_create(void) +{ + return 0; +} + +static void cap_tun_dev_post_create(struct sock *sk) +{ +} + +static int cap_tun_dev_attach(struct sock *sk) +{ + return 0; +} +#endif /* CONFIG_SECURITY_NETWORK */ + +#ifdef CONFIG_SECURITY_NETWORK_XFRM +static int cap_xfrm_policy_alloc_security(struct xfrm_sec_ctx **ctxp, + struct xfrm_user_sec_ctx *sec_ctx) +{ + return 0; +} + +static int cap_xfrm_policy_clone_security(struct xfrm_sec_ctx *old_ctx, + struct xfrm_sec_ctx **new_ctxp) +{ + return 0; +} + +static void cap_xfrm_policy_free_security(struct xfrm_sec_ctx *ctx) +{ +} + +static int cap_xfrm_policy_delete_security(struct xfrm_sec_ctx *ctx) +{ + return 0; +} + +static int cap_xfrm_state_alloc_security(struct xfrm_state *x, + struct xfrm_user_sec_ctx *sec_ctx, + u32 secid) +{ + return 0; +} + +static void cap_xfrm_state_free_security(struct xfrm_state *x) +{ +} + +static int cap_xfrm_state_delete_security(struct xfrm_state *x) +{ + return 0; +} + +static int cap_xfrm_policy_lookup(struct xfrm_sec_ctx *ctx, u32 sk_sid, u8 dir) +{ + return 0; +} + +static int cap_xfrm_state_pol_flow_match(struct xfrm_state *x, + struct xfrm_policy *xp, + const struct flowi *fl) +{ + return 1; +} + +static int cap_xfrm_decode_session(struct sk_buff *skb, u32 *fl, int ckall) +{ + return 0; +} + +#endif /* CONFIG_SECURITY_NETWORK_XFRM */ +static void cap_d_instantiate(struct dentry *dentry, struct inode *inode) +{ +} + +static int cap_getprocattr(struct task_struct *p, char *name, char **value) +{ + return -EINVAL; +} + +static int cap_setprocattr(struct task_struct *p, char *name, void *value, + size_t size) +{ + return -EINVAL; +} + +static int cap_secid_to_secctx(u32 secid, char **secdata, u32 *seclen) +{ + return -EOPNOTSUPP; +} + +static int cap_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid) +{ + *secid = 0; + return 0; +} + +static void cap_release_secctx(char *secdata, u32 seclen) +{ +} + +static int cap_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen) +{ + return 0; +} + +static int cap_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen) +{ + return 0; +} + +static int cap_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen) +{ + return 0; +} +#ifdef CONFIG_KEYS +static int cap_key_alloc(struct key *key, const struct cred *cred, + unsigned long flags) +{ + return 0; +} + +static void cap_key_free(struct key *key) +{ +} + +static int cap_key_permission(key_ref_t key_ref, const struct cred *cred, + key_perm_t perm) +{ + return 0; +} + +static int cap_key_getsecurity(struct key *key, char **_buffer) +{ + *_buffer = NULL; + return 0; +} + +#endif /* CONFIG_KEYS */ + +#ifdef CONFIG_AUDIT +static int cap_audit_rule_init(u32 field, u32 op, char *rulestr, void **lsmrule) +{ + return 0; +} + +static int cap_audit_rule_known(struct audit_krule *krule) +{ + return 0; +} + +static int cap_audit_rule_match(u32 secid, u32 field, u32 op, void *lsmrule, + struct audit_context *actx) +{ + return 0; +} + +static void cap_audit_rule_free(void *lsmrule) +{ +} +#endif /* CONFIG_AUDIT */ + +#define set_to_cap_if_null(ops, function) \ + do { \ + if (!ops->function) { \ + ops->function = cap_##function; \ + pr_debug("Had to override the " #function \ + " security operation with the default.\n");\ + } \ + } while (0) + +void __init security_fixup_ops(struct security_operations *ops) +{ + set_to_cap_if_null(ops, ptrace_access_check); + set_to_cap_if_null(ops, ptrace_traceme); + set_to_cap_if_null(ops, capget); + set_to_cap_if_null(ops, capset); + set_to_cap_if_null(ops, capable); + set_to_cap_if_null(ops, quotactl); + set_to_cap_if_null(ops, quota_on); + set_to_cap_if_null(ops, syslog); + set_to_cap_if_null(ops, settime); + set_to_cap_if_null(ops, vm_enough_memory); + set_to_cap_if_null(ops, bprm_set_creds); + set_to_cap_if_null(ops, bprm_committing_creds); + set_to_cap_if_null(ops, bprm_committed_creds); + set_to_cap_if_null(ops, bprm_check_security); + set_to_cap_if_null(ops, bprm_secureexec); + set_to_cap_if_null(ops, sb_alloc_security); + set_to_cap_if_null(ops, sb_free_security); + set_to_cap_if_null(ops, sb_copy_data); + set_to_cap_if_null(ops, sb_remount); + set_to_cap_if_null(ops, sb_kern_mount); + set_to_cap_if_null(ops, sb_show_options); + set_to_cap_if_null(ops, sb_statfs); + set_to_cap_if_null(ops, sb_mount); + set_to_cap_if_null(ops, sb_umount); + set_to_cap_if_null(ops, sb_pivotroot); + set_to_cap_if_null(ops, sb_set_mnt_opts); + set_to_cap_if_null(ops, sb_clone_mnt_opts); + set_to_cap_if_null(ops, sb_parse_opts_str); + set_to_cap_if_null(ops, inode_alloc_security); + set_to_cap_if_null(ops, inode_free_security); + set_to_cap_if_null(ops, inode_init_security); + set_to_cap_if_null(ops, inode_create); + set_to_cap_if_null(ops, inode_link); + set_to_cap_if_null(ops, inode_unlink); + set_to_cap_if_null(ops, inode_symlink); + set_to_cap_if_null(ops, inode_mkdir); + set_to_cap_if_null(ops, inode_rmdir); + set_to_cap_if_null(ops, inode_mknod); + set_to_cap_if_null(ops, inode_rename); + set_to_cap_if_null(ops, inode_readlink); + set_to_cap_if_null(ops, inode_follow_link); + set_to_cap_if_null(ops, inode_permission); + set_to_cap_if_null(ops, inode_setattr); + set_to_cap_if_null(ops, inode_getattr); + set_to_cap_if_null(ops, inode_setxattr); + set_to_cap_if_null(ops, inode_post_setxattr); + set_to_cap_if_null(ops, inode_getxattr); + set_to_cap_if_null(ops, inode_listxattr); + set_to_cap_if_null(ops, inode_removexattr); + set_to_cap_if_null(ops, inode_need_killpriv); + set_to_cap_if_null(ops, inode_killpriv); + set_to_cap_if_null(ops, inode_getsecurity); + set_to_cap_if_null(ops, inode_setsecurity); + set_to_cap_if_null(ops, inode_listsecurity); + set_to_cap_if_null(ops, inode_getsecid); +#ifdef CONFIG_SECURITY_PATH + set_to_cap_if_null(ops, path_mknod); + set_to_cap_if_null(ops, path_mkdir); + set_to_cap_if_null(ops, path_rmdir); + set_to_cap_if_null(ops, path_unlink); + set_to_cap_if_null(ops, path_symlink); + set_to_cap_if_null(ops, path_link); + set_to_cap_if_null(ops, path_rename); + set_to_cap_if_null(ops, path_truncate); + set_to_cap_if_null(ops, path_chmod); + set_to_cap_if_null(ops, path_chown); + set_to_cap_if_null(ops, path_chroot); +#endif + set_to_cap_if_null(ops, file_permission); + set_to_cap_if_null(ops, file_alloc_security); + set_to_cap_if_null(ops, file_free_security); + set_to_cap_if_null(ops, file_ioctl); + set_to_cap_if_null(ops, file_mmap); + set_to_cap_if_null(ops, file_mprotect); + set_to_cap_if_null(ops, file_lock); + set_to_cap_if_null(ops, file_fcntl); + set_to_cap_if_null(ops, file_set_fowner); + set_to_cap_if_null(ops, file_send_sigiotask); + 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, cred_alloc_blank); + set_to_cap_if_null(ops, cred_free); + set_to_cap_if_null(ops, cred_prepare); + set_to_cap_if_null(ops, cred_transfer); + set_to_cap_if_null(ops, kernel_act_as); + set_to_cap_if_null(ops, kernel_create_files_as); + set_to_cap_if_null(ops, kernel_module_request); + set_to_cap_if_null(ops, task_fix_setuid); + set_to_cap_if_null(ops, task_setpgid); + set_to_cap_if_null(ops, task_getpgid); + set_to_cap_if_null(ops, task_getsid); + set_to_cap_if_null(ops, task_getsecid); + set_to_cap_if_null(ops, task_setnice); + set_to_cap_if_null(ops, task_setioprio); + set_to_cap_if_null(ops, task_getioprio); + set_to_cap_if_null(ops, task_setrlimit); + set_to_cap_if_null(ops, task_setscheduler); + set_to_cap_if_null(ops, task_getscheduler); + set_to_cap_if_null(ops, task_movememory); + set_to_cap_if_null(ops, task_wait); + set_to_cap_if_null(ops, task_kill); + set_to_cap_if_null(ops, task_prctl); + set_to_cap_if_null(ops, task_to_inode); + set_to_cap_if_null(ops, ipc_permission); + set_to_cap_if_null(ops, ipc_getsecid); + set_to_cap_if_null(ops, msg_msg_alloc_security); + set_to_cap_if_null(ops, msg_msg_free_security); + set_to_cap_if_null(ops, msg_queue_alloc_security); + set_to_cap_if_null(ops, msg_queue_free_security); + set_to_cap_if_null(ops, msg_queue_associate); + set_to_cap_if_null(ops, msg_queue_msgctl); + set_to_cap_if_null(ops, msg_queue_msgsnd); + set_to_cap_if_null(ops, msg_queue_msgrcv); + set_to_cap_if_null(ops, shm_alloc_security); + set_to_cap_if_null(ops, shm_free_security); + set_to_cap_if_null(ops, shm_associate); + set_to_cap_if_null(ops, shm_shmctl); + set_to_cap_if_null(ops, shm_shmat); + set_to_cap_if_null(ops, sem_alloc_security); + set_to_cap_if_null(ops, sem_free_security); + set_to_cap_if_null(ops, sem_associate); + set_to_cap_if_null(ops, sem_semctl); + set_to_cap_if_null(ops, sem_semop); + set_to_cap_if_null(ops, netlink_send); + set_to_cap_if_null(ops, netlink_recv); + set_to_cap_if_null(ops, d_instantiate); + set_to_cap_if_null(ops, getprocattr); + set_to_cap_if_null(ops, setprocattr); + set_to_cap_if_null(ops, secid_to_secctx); + set_to_cap_if_null(ops, secctx_to_secid); + set_to_cap_if_null(ops, release_secctx); + set_to_cap_if_null(ops, inode_notifysecctx); + set_to_cap_if_null(ops, inode_setsecctx); + set_to_cap_if_null(ops, inode_getsecctx); +#ifdef CONFIG_SECURITY_NETWORK + set_to_cap_if_null(ops, unix_stream_connect); + set_to_cap_if_null(ops, unix_may_send); + set_to_cap_if_null(ops, socket_create); + set_to_cap_if_null(ops, socket_post_create); + set_to_cap_if_null(ops, socket_bind); + set_to_cap_if_null(ops, socket_connect); + set_to_cap_if_null(ops, socket_listen); + set_to_cap_if_null(ops, socket_accept); + set_to_cap_if_null(ops, socket_sendmsg); + set_to_cap_if_null(ops, socket_recvmsg); + set_to_cap_if_null(ops, socket_getsockname); + set_to_cap_if_null(ops, socket_getpeername); + set_to_cap_if_null(ops, socket_setsockopt); + set_to_cap_if_null(ops, socket_getsockopt); + set_to_cap_if_null(ops, socket_shutdown); + set_to_cap_if_null(ops, socket_sock_rcv_skb); + set_to_cap_if_null(ops, socket_getpeersec_stream); + set_to_cap_if_null(ops, socket_getpeersec_dgram); + set_to_cap_if_null(ops, sk_alloc_security); + set_to_cap_if_null(ops, sk_free_security); + set_to_cap_if_null(ops, sk_clone_security); + set_to_cap_if_null(ops, sk_getsecid); + set_to_cap_if_null(ops, sock_graft); + set_to_cap_if_null(ops, inet_conn_request); + set_to_cap_if_null(ops, inet_csk_clone); + set_to_cap_if_null(ops, inet_conn_established); + set_to_cap_if_null(ops, secmark_relabel_packet); + set_to_cap_if_null(ops, secmark_refcount_inc); + set_to_cap_if_null(ops, secmark_refcount_dec); + set_to_cap_if_null(ops, req_classify_flow); + set_to_cap_if_null(ops, tun_dev_create); + set_to_cap_if_null(ops, tun_dev_post_create); + set_to_cap_if_null(ops, tun_dev_attach); +#endif /* CONFIG_SECURITY_NETWORK */ +#ifdef CONFIG_SECURITY_NETWORK_XFRM + set_to_cap_if_null(ops, xfrm_policy_alloc_security); + set_to_cap_if_null(ops, xfrm_policy_clone_security); + set_to_cap_if_null(ops, xfrm_policy_free_security); + set_to_cap_if_null(ops, xfrm_policy_delete_security); + set_to_cap_if_null(ops, xfrm_state_alloc_security); + set_to_cap_if_null(ops, xfrm_state_free_security); + set_to_cap_if_null(ops, xfrm_state_delete_security); + set_to_cap_if_null(ops, xfrm_policy_lookup); + set_to_cap_if_null(ops, xfrm_state_pol_flow_match); + set_to_cap_if_null(ops, xfrm_decode_session); +#endif /* CONFIG_SECURITY_NETWORK_XFRM */ +#ifdef CONFIG_KEYS + set_to_cap_if_null(ops, key_alloc); + set_to_cap_if_null(ops, key_free); + set_to_cap_if_null(ops, key_permission); + set_to_cap_if_null(ops, key_getsecurity); +#endif /* CONFIG_KEYS */ +#ifdef CONFIG_AUDIT + set_to_cap_if_null(ops, audit_rule_init); + set_to_cap_if_null(ops, audit_rule_known); + set_to_cap_if_null(ops, audit_rule_match); + set_to_cap_if_null(ops, audit_rule_free); +#endif +} diff --git a/security/commoncap.c b/security/commoncap.c new file mode 100644 index 00000000..ccfe568b --- /dev/null +++ b/security/commoncap.c @@ -0,0 +1,988 @@ +/* Common capabilities, needed by capability.o. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include <linux/capability.h> +#include <linux/audit.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/security.h> +#include <linux/file.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/pagemap.h> +#include <linux/swap.h> +#include <linux/skbuff.h> +#include <linux/netlink.h> +#include <linux/ptrace.h> +#include <linux/xattr.h> +#include <linux/hugetlb.h> +#include <linux/mount.h> +#include <linux/sched.h> +#include <linux/prctl.h> +#include <linux/securebits.h> +#include <linux/user_namespace.h> +#include <linux/personality.h> + +#ifdef CONFIG_ANDROID_PARANOID_NETWORK +#include <linux/android_aid.h> +#endif + +/* + * If a non-root user executes a setuid-root binary in + * !secure(SECURE_NOROOT) mode, then we raise capabilities. + * However if fE is also set, then the intent is for only + * the file capabilities to be applied, and the setuid-root + * bit is left on either to change the uid (plausible) or + * to get full privilege on a kernel without file capabilities + * support. So in that case we do not raise capabilities. + * + * Warn if that happens, once per boot. + */ +static void warn_setuid_and_fcaps_mixed(const char *fname) +{ + static int warned; + if (!warned) { + printk(KERN_INFO "warning: `%s' has both setuid-root and" + " effective capabilities. Therefore not raising all" + " capabilities.\n", fname); + warned = 1; + } +} + +int cap_netlink_send(struct sock *sk, struct sk_buff *skb) +{ + return 0; +} + +int cap_netlink_recv(struct sk_buff *skb, int cap) +{ + if (!cap_raised(current_cap(), cap)) + return -EPERM; + return 0; +} +EXPORT_SYMBOL(cap_netlink_recv); + +/** + * cap_capable - Determine whether a task has a particular effective capability + * @tsk: The task to query + * @cred: The credentials to use + * @ns: The user namespace in which we need the capability + * @cap: The capability to check for + * @audit: Whether to write an audit message or not + * + * Determine whether the nominated task has the specified capability amongst + * its effective set, returning 0 if it does, -ve if it does not. + * + * NOTE WELL: cap_has_capability() cannot be used like the kernel's capable() + * and has_capability() functions. That is, it has the reverse semantics: + * cap_has_capability() returns 0 when a task has a capability, but the + * kernel's capable() and has_capability() returns 1 for this case. + */ +int cap_capable(struct task_struct *tsk, const struct cred *cred, + struct user_namespace *targ_ns, int cap, int audit) +{ + if (cap == CAP_NET_RAW && in_egroup_p(AID_NET_RAW)) + return 0; + if (cap == CAP_NET_ADMIN && in_egroup_p(AID_NET_ADMIN)) + return 0; + + for (;;) { + /* The creator of the user namespace has all caps. */ + if (targ_ns != &init_user_ns && targ_ns->creator == cred->user) + return 0; + + /* Do we have the necessary capabilities? */ + if (targ_ns == cred->user->user_ns) + return cap_raised(cred->cap_effective, cap) ? 0 : -EPERM; + + /* Have we tried all of the parent namespaces? */ + if (targ_ns == &init_user_ns) + return -EPERM; + + /* + *If you have a capability in a parent user ns, then you have + * it over all children user namespaces as well. + */ + targ_ns = targ_ns->creator->user_ns; + } + + /* We never get here */ +} + +/** + * cap_settime - Determine whether the current process may set the system clock + * @ts: The time to set + * @tz: The timezone to set + * + * Determine whether the current process may set the system clock and timezone + * information, returning 0 if permission granted, -ve if denied. + */ +int cap_settime(const struct timespec *ts, const struct timezone *tz) +{ + if (!capable(CAP_SYS_TIME)) + return -EPERM; + return 0; +} + +/** + * cap_ptrace_access_check - Determine whether the current process may access + * another + * @child: The process to be accessed + * @mode: The mode of attachment. + * + * If we are in the same or an ancestor user_ns and have all the target + * task's capabilities, then ptrace access is allowed. + * If we have the ptrace capability to the target user_ns, then ptrace + * access is allowed. + * Else denied. + * + * Determine whether a process may access another, returning 0 if permission + * granted, -ve if denied. + */ +int cap_ptrace_access_check(struct task_struct *child, unsigned int mode) +{ + int ret = 0; + const struct cred *cred, *child_cred; + + rcu_read_lock(); + cred = current_cred(); + child_cred = __task_cred(child); + if (cred->user->user_ns == child_cred->user->user_ns && + cap_issubset(child_cred->cap_permitted, cred->cap_permitted)) + goto out; + if (ns_capable(child_cred->user->user_ns, CAP_SYS_PTRACE)) + goto out; + ret = -EPERM; +out: + rcu_read_unlock(); + return ret; +} + +/** + * cap_ptrace_traceme - Determine whether another process may trace the current + * @parent: The task proposed to be the tracer + * + * If parent is in the same or an ancestor user_ns and has all current's + * capabilities, then ptrace access is allowed. + * If parent has the ptrace capability to current's user_ns, then ptrace + * access is allowed. + * Else denied. + * + * Determine whether the nominated task is permitted to trace the current + * process, returning 0 if permission is granted, -ve if denied. + */ +int cap_ptrace_traceme(struct task_struct *parent) +{ + int ret = 0; + const struct cred *cred, *child_cred; + + rcu_read_lock(); + cred = __task_cred(parent); + child_cred = current_cred(); + if (cred->user->user_ns == child_cred->user->user_ns && + cap_issubset(child_cred->cap_permitted, cred->cap_permitted)) + goto out; + if (has_ns_capability(parent, child_cred->user->user_ns, CAP_SYS_PTRACE)) + goto out; + ret = -EPERM; +out: + rcu_read_unlock(); + return ret; +} + +/** + * cap_capget - Retrieve a task's capability sets + * @target: The task from which to retrieve the capability sets + * @effective: The place to record the effective set + * @inheritable: The place to record the inheritable set + * @permitted: The place to record the permitted set + * + * This function retrieves the capabilities of the nominated task and returns + * them to the caller. + */ +int cap_capget(struct task_struct *target, kernel_cap_t *effective, + kernel_cap_t *inheritable, kernel_cap_t *permitted) +{ + const struct cred *cred; + + /* Derived from kernel/capability.c:sys_capget. */ + rcu_read_lock(); + cred = __task_cred(target); + *effective = cred->cap_effective; + *inheritable = cred->cap_inheritable; + *permitted = cred->cap_permitted; + rcu_read_unlock(); + return 0; +} + +/* + * Determine whether the inheritable capabilities are limited to the old + * permitted set. Returns 1 if they are limited, 0 if they are not. + */ +static inline int cap_inh_is_capped(void) +{ + + /* they are so limited unless the current task has the CAP_SETPCAP + * capability + */ + if (cap_capable(current, current_cred(), + current_cred()->user->user_ns, CAP_SETPCAP, + SECURITY_CAP_AUDIT) == 0) + return 0; + return 1; +} + +/** + * cap_capset - Validate and apply proposed changes to current's capabilities + * @new: The proposed new credentials; alterations should be made here + * @old: The current task's current credentials + * @effective: A pointer to the proposed new effective capabilities set + * @inheritable: A pointer to the proposed new inheritable capabilities set + * @permitted: A pointer to the proposed new permitted capabilities set + * + * This function validates and applies a proposed mass change to the current + * process's capability sets. The changes are made to the proposed new + * credentials, and assuming no error, will be committed by the caller of LSM. + */ +int cap_capset(struct cred *new, + const struct cred *old, + const kernel_cap_t *effective, + const kernel_cap_t *inheritable, + const kernel_cap_t *permitted) +{ + if (cap_inh_is_capped() && + !cap_issubset(*inheritable, + cap_combine(old->cap_inheritable, + old->cap_permitted))) + /* incapable of using this inheritable set */ + return -EPERM; + + if (!cap_issubset(*inheritable, + cap_combine(old->cap_inheritable, + old->cap_bset))) + /* no new pI capabilities outside bounding set */ + return -EPERM; + + /* verify restrictions on target's new Permitted set */ + if (!cap_issubset(*permitted, old->cap_permitted)) + return -EPERM; + + /* verify the _new_Effective_ is a subset of the _new_Permitted_ */ + if (!cap_issubset(*effective, *permitted)) + return -EPERM; + + new->cap_effective = *effective; + new->cap_inheritable = *inheritable; + new->cap_permitted = *permitted; + return 0; +} + +/* + * Clear proposed capability sets for execve(). + */ +static inline void bprm_clear_caps(struct linux_binprm *bprm) +{ + cap_clear(bprm->cred->cap_permitted); + bprm->cap_effective = false; +} + +/** + * cap_inode_need_killpriv - Determine if inode change affects privileges + * @dentry: The inode/dentry in being changed with change marked ATTR_KILL_PRIV + * + * Determine if an inode having a change applied that's marked ATTR_KILL_PRIV + * affects the security markings on that inode, and if it is, should + * inode_killpriv() be invoked or the change rejected? + * + * Returns 0 if granted; +ve if granted, but inode_killpriv() is required; and + * -ve to deny the change. + */ +int cap_inode_need_killpriv(struct dentry *dentry) +{ + struct inode *inode = dentry->d_inode; + int error; + + if (!inode->i_op->getxattr) + return 0; + + error = inode->i_op->getxattr(dentry, XATTR_NAME_CAPS, NULL, 0); + if (error <= 0) + return 0; + return 1; +} + +/** + * cap_inode_killpriv - Erase the security markings on an inode + * @dentry: The inode/dentry to alter + * + * Erase the privilege-enhancing security markings on an inode. + * + * Returns 0 if successful, -ve on error. + */ +int cap_inode_killpriv(struct dentry *dentry) +{ + struct inode *inode = dentry->d_inode; + + if (!inode->i_op->removexattr) + return 0; + + return inode->i_op->removexattr(dentry, XATTR_NAME_CAPS); +} + +/* + * Calculate the new process capability sets from the capability sets attached + * to a file. + */ +static inline int bprm_caps_from_vfs_caps(struct cpu_vfs_cap_data *caps, + struct linux_binprm *bprm, + bool *effective) +{ + struct cred *new = bprm->cred; + unsigned i; + int ret = 0; + + if (caps->magic_etc & VFS_CAP_FLAGS_EFFECTIVE) + *effective = true; + + CAP_FOR_EACH_U32(i) { + __u32 permitted = caps->permitted.cap[i]; + __u32 inheritable = caps->inheritable.cap[i]; + + /* + * pP' = (X & fP) | (pI & fI) + */ + new->cap_permitted.cap[i] = + (new->cap_bset.cap[i] & permitted) | + (new->cap_inheritable.cap[i] & inheritable); + + if (permitted & ~new->cap_permitted.cap[i]) + /* insufficient to execute correctly */ + ret = -EPERM; + } + + /* + * For legacy apps, with no internal support for recognizing they + * do not have enough capabilities, we return an error if they are + * missing some "forced" (aka file-permitted) capabilities. + */ + return *effective ? ret : 0; +} + +/* + * Extract the on-exec-apply capability sets for an executable file. + */ +int get_vfs_caps_from_disk(const struct dentry *dentry, struct cpu_vfs_cap_data *cpu_caps) +{ + struct inode *inode = dentry->d_inode; + __u32 magic_etc; + unsigned tocopy, i; + int size; + struct vfs_cap_data caps; + + memset(cpu_caps, 0, sizeof(struct cpu_vfs_cap_data)); + + if (!inode || !inode->i_op->getxattr) + return -ENODATA; + + size = inode->i_op->getxattr((struct dentry *)dentry, XATTR_NAME_CAPS, &caps, + XATTR_CAPS_SZ); + if (size == -ENODATA || size == -EOPNOTSUPP) + /* no data, that's ok */ + return -ENODATA; + if (size < 0) + return size; + + if (size < sizeof(magic_etc)) + return -EINVAL; + + cpu_caps->magic_etc = magic_etc = le32_to_cpu(caps.magic_etc); + + switch (magic_etc & VFS_CAP_REVISION_MASK) { + case VFS_CAP_REVISION_1: + if (size != XATTR_CAPS_SZ_1) + return -EINVAL; + tocopy = VFS_CAP_U32_1; + break; + case VFS_CAP_REVISION_2: + if (size != XATTR_CAPS_SZ_2) + return -EINVAL; + tocopy = VFS_CAP_U32_2; + break; + default: + return -EINVAL; + } + + CAP_FOR_EACH_U32(i) { + if (i >= tocopy) + break; + cpu_caps->permitted.cap[i] = le32_to_cpu(caps.data[i].permitted); + cpu_caps->inheritable.cap[i] = le32_to_cpu(caps.data[i].inheritable); + } + + return 0; +} + +/* + * Attempt to get the on-exec apply capability sets for an executable file from + * its xattrs and, if present, apply them to the proposed credentials being + * constructed by execve(). + */ +static int get_file_caps(struct linux_binprm *bprm, bool *effective) +{ + struct dentry *dentry; + int rc = 0; + struct cpu_vfs_cap_data vcaps; + + bprm_clear_caps(bprm); + + if (!file_caps_enabled) + return 0; + + if (bprm->file->f_vfsmnt->mnt_flags & MNT_NOSUID) + return 0; + + dentry = dget(bprm->file->f_dentry); + + rc = get_vfs_caps_from_disk(dentry, &vcaps); + if (rc < 0) { + if (rc == -EINVAL) + printk(KERN_NOTICE "%s: get_vfs_caps_from_disk returned %d for %s\n", + __func__, rc, bprm->filename); + else if (rc == -ENODATA) + rc = 0; + goto out; + } + + rc = bprm_caps_from_vfs_caps(&vcaps, bprm, effective); + if (rc == -EINVAL) + printk(KERN_NOTICE "%s: cap_from_disk returned %d for %s\n", + __func__, rc, bprm->filename); + +out: + dput(dentry); + if (rc) + bprm_clear_caps(bprm); + + return rc; +} + +/** + * cap_bprm_set_creds - Set up the proposed credentials for execve(). + * @bprm: The execution parameters, including the proposed creds + * + * Set up the proposed credentials for a new execution context being + * constructed by execve(). The proposed creds in @bprm->cred is altered, + * which won't take effect immediately. Returns 0 if successful, -ve on error. + */ +int cap_bprm_set_creds(struct linux_binprm *bprm) +{ + const struct cred *old = current_cred(); + struct cred *new = bprm->cred; + bool effective; + int ret; + + effective = false; + ret = get_file_caps(bprm, &effective); + if (ret < 0) + return ret; + + if (!issecure(SECURE_NOROOT)) { + /* + * If the legacy file capability is set, then don't set privs + * for a setuid root binary run by a non-root user. Do set it + * for a root user just to cause least surprise to an admin. + */ + if (effective && new->uid != 0 && new->euid == 0) { + warn_setuid_and_fcaps_mixed(bprm->filename); + goto skip; + } + /* + * To support inheritance of root-permissions and suid-root + * executables under compatibility mode, we override the + * capability sets for the file. + * + * If only the real uid is 0, we do not set the effective bit. + */ + if (new->euid == 0 || new->uid == 0) { + /* pP' = (cap_bset & ~0) | (pI & ~0) */ + new->cap_permitted = cap_combine(old->cap_bset, + old->cap_inheritable); + } + if (new->euid == 0) + effective = true; + } +skip: + + /* if we have fs caps, clear dangerous personality flags */ + if (!cap_issubset(new->cap_permitted, old->cap_permitted)) + bprm->per_clear |= PER_CLEAR_ON_SETID; + + + /* Don't let someone trace a set[ug]id/setpcap binary with the revised + * credentials unless they have the appropriate permit + */ + if ((new->euid != old->uid || + new->egid != old->gid || + !cap_issubset(new->cap_permitted, old->cap_permitted)) && + bprm->unsafe & ~LSM_UNSAFE_PTRACE_CAP) { + /* downgrade; they get no more than they had, and maybe less */ + if (!capable(CAP_SETUID)) { + new->euid = new->uid; + new->egid = new->gid; + } + new->cap_permitted = cap_intersect(new->cap_permitted, + old->cap_permitted); + } + + new->suid = new->fsuid = new->euid; + new->sgid = new->fsgid = new->egid; + + if (effective) + new->cap_effective = new->cap_permitted; + else + cap_clear(new->cap_effective); + bprm->cap_effective = effective; + + /* + * Audit candidate if current->cap_effective is set + * + * We do not bother to audit if 3 things are true: + * 1) cap_effective has all caps + * 2) we are root + * 3) root is supposed to have all caps (SECURE_NOROOT) + * Since this is just a normal root execing a process. + * + * Number 1 above might fail if you don't have a full bset, but I think + * that is interesting information to audit. + */ + if (!cap_isclear(new->cap_effective)) { + if (!cap_issubset(CAP_FULL_SET, new->cap_effective) || + new->euid != 0 || new->uid != 0 || + issecure(SECURE_NOROOT)) { + ret = audit_log_bprm_fcaps(bprm, new, old); + if (ret < 0) + return ret; + } + } + + new->securebits &= ~issecure_mask(SECURE_KEEP_CAPS); + return 0; +} + +/** + * cap_bprm_secureexec - Determine whether a secure execution is required + * @bprm: The execution parameters + * + * Determine whether a secure execution is required, return 1 if it is, and 0 + * if it is not. + * + * The credentials have been committed by this point, and so are no longer + * available through @bprm->cred. + */ +int cap_bprm_secureexec(struct linux_binprm *bprm) +{ + const struct cred *cred = current_cred(); + + if (cred->uid != 0) { + if (bprm->cap_effective) + return 1; + if (!cap_isclear(cred->cap_permitted)) + return 1; + } + + return (cred->euid != cred->uid || + cred->egid != cred->gid); +} + +/** + * cap_inode_setxattr - Determine whether an xattr may be altered + * @dentry: The inode/dentry being altered + * @name: The name of the xattr to be changed + * @value: The value that the xattr will be changed to + * @size: The size of value + * @flags: The replacement flag + * + * Determine whether an xattr may be altered or set on an inode, returning 0 if + * permission is granted, -ve if denied. + * + * This is used to make sure security xattrs don't get updated or set by those + * who aren't privileged to do so. + */ +int cap_inode_setxattr(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags) +{ + if (!strcmp(name, XATTR_NAME_CAPS)) { + if (!capable(CAP_SETFCAP)) + return -EPERM; + return 0; + } + + if (!strncmp(name, XATTR_SECURITY_PREFIX, + sizeof(XATTR_SECURITY_PREFIX) - 1) && + !capable(CAP_SYS_ADMIN)) + return -EPERM; + return 0; +} + +/** + * cap_inode_removexattr - Determine whether an xattr may be removed + * @dentry: The inode/dentry being altered + * @name: The name of the xattr to be changed + * + * Determine whether an xattr may be removed from an inode, returning 0 if + * permission is granted, -ve if denied. + * + * This is used to make sure security xattrs don't get removed by those who + * aren't privileged to remove them. + */ +int cap_inode_removexattr(struct dentry *dentry, const char *name) +{ + if (!strcmp(name, XATTR_NAME_CAPS)) { + if (!capable(CAP_SETFCAP)) + return -EPERM; + return 0; + } + + if (!strncmp(name, XATTR_SECURITY_PREFIX, + sizeof(XATTR_SECURITY_PREFIX) - 1) && + !capable(CAP_SYS_ADMIN)) + return -EPERM; + return 0; +} + +/* + * cap_emulate_setxuid() fixes the effective / permitted capabilities of + * a process after a call to setuid, setreuid, or setresuid. + * + * 1) When set*uiding _from_ one of {r,e,s}uid == 0 _to_ all of + * {r,e,s}uid != 0, the permitted and effective capabilities are + * cleared. + * + * 2) When set*uiding _from_ euid == 0 _to_ euid != 0, the effective + * capabilities of the process are cleared. + * + * 3) When set*uiding _from_ euid != 0 _to_ euid == 0, the effective + * capabilities are set to the permitted capabilities. + * + * fsuid is handled elsewhere. fsuid == 0 and {r,e,s}uid!= 0 should + * never happen. + * + * -astor + * + * cevans - New behaviour, Oct '99 + * A process may, via prctl(), elect to keep its capabilities when it + * calls setuid() and switches away from uid==0. Both permitted and + * effective sets will be retained. + * Without this change, it was impossible for a daemon to drop only some + * of its privilege. The call to setuid(!=0) would drop all privileges! + * Keeping uid 0 is not an option because uid 0 owns too many vital + * files.. + * Thanks to Olaf Kirch and Peter Benie for spotting this. + */ +static inline void cap_emulate_setxuid(struct cred *new, const struct cred *old) +{ + if ((old->uid == 0 || old->euid == 0 || old->suid == 0) && + (new->uid != 0 && new->euid != 0 && new->suid != 0) && + !issecure(SECURE_KEEP_CAPS)) { + cap_clear(new->cap_permitted); + cap_clear(new->cap_effective); + } + if (old->euid == 0 && new->euid != 0) + cap_clear(new->cap_effective); + if (old->euid != 0 && new->euid == 0) + new->cap_effective = new->cap_permitted; +} + +/** + * cap_task_fix_setuid - Fix up the results of setuid() call + * @new: The proposed credentials + * @old: The current task's current credentials + * @flags: Indications of what has changed + * + * Fix up the results of setuid() call before the credential changes are + * actually applied, returning 0 to grant the changes, -ve to deny them. + */ +int cap_task_fix_setuid(struct cred *new, const struct cred *old, int flags) +{ + switch (flags) { + case LSM_SETID_RE: + case LSM_SETID_ID: + case LSM_SETID_RES: + /* juggle the capabilities to follow [RES]UID changes unless + * otherwise suppressed */ + if (!issecure(SECURE_NO_SETUID_FIXUP)) + cap_emulate_setxuid(new, old); + break; + + case LSM_SETID_FS: + /* juggle the capabilties to follow FSUID changes, unless + * otherwise suppressed + * + * FIXME - is fsuser used for all CAP_FS_MASK capabilities? + * if not, we might be a bit too harsh here. + */ + if (!issecure(SECURE_NO_SETUID_FIXUP)) { + if (old->fsuid == 0 && new->fsuid != 0) + new->cap_effective = + cap_drop_fs_set(new->cap_effective); + + if (old->fsuid != 0 && new->fsuid == 0) + new->cap_effective = + cap_raise_fs_set(new->cap_effective, + new->cap_permitted); + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* + * Rationale: code calling task_setscheduler, task_setioprio, and + * task_setnice, assumes that + * . if capable(cap_sys_nice), then those actions should be allowed + * . if not capable(cap_sys_nice), but acting on your own processes, + * then those actions should be allowed + * This is insufficient now since you can call code without suid, but + * yet with increased caps. + * So we check for increased caps on the target process. + */ +static int cap_safe_nice(struct task_struct *p) +{ + int is_subset; + + rcu_read_lock(); + is_subset = cap_issubset(__task_cred(p)->cap_permitted, + current_cred()->cap_permitted); + rcu_read_unlock(); + + if (!is_subset && !capable(CAP_SYS_NICE)) + return -EPERM; + return 0; +} + +/** + * cap_task_setscheduler - Detemine if scheduler policy change is permitted + * @p: The task to affect + * + * Detemine if the requested scheduler policy change is permitted for the + * specified task, returning 0 if permission is granted, -ve if denied. + */ +int cap_task_setscheduler(struct task_struct *p) +{ + return cap_safe_nice(p); +} + +/** + * cap_task_ioprio - Detemine if I/O priority change is permitted + * @p: The task to affect + * @ioprio: The I/O priority to set + * + * Detemine if the requested I/O priority change is permitted for the specified + * task, returning 0 if permission is granted, -ve if denied. + */ +int cap_task_setioprio(struct task_struct *p, int ioprio) +{ + return cap_safe_nice(p); +} + +/** + * cap_task_ioprio - Detemine if task priority change is permitted + * @p: The task to affect + * @nice: The nice value to set + * + * Detemine if the requested task priority change is permitted for the + * specified task, returning 0 if permission is granted, -ve if denied. + */ +int cap_task_setnice(struct task_struct *p, int nice) +{ + return cap_safe_nice(p); +} + +/* + * Implement PR_CAPBSET_DROP. Attempt to remove the specified capability from + * the current task's bounding set. Returns 0 on success, -ve on error. + */ +static long cap_prctl_drop(struct cred *new, unsigned long cap) +{ + if (!capable(CAP_SETPCAP)) + return -EPERM; + if (!cap_valid(cap)) + return -EINVAL; + + cap_lower(new->cap_bset, cap); + return 0; +} + +/** + * cap_task_prctl - Implement process control functions for this security module + * @option: The process control function requested + * @arg2, @arg3, @arg4, @arg5: The argument data for this function + * + * Allow process control functions (sys_prctl()) to alter capabilities; may + * also deny access to other functions not otherwise implemented here. + * + * Returns 0 or +ve on success, -ENOSYS if this function is not implemented + * here, other -ve on error. If -ENOSYS is returned, sys_prctl() and other LSM + * modules will consider performing the function. + */ +int cap_task_prctl(int option, unsigned long arg2, unsigned long arg3, + unsigned long arg4, unsigned long arg5) +{ + struct cred *new; + long error = 0; + + new = prepare_creds(); + if (!new) + return -ENOMEM; + + switch (option) { + case PR_CAPBSET_READ: + error = -EINVAL; + if (!cap_valid(arg2)) + goto error; + error = !!cap_raised(new->cap_bset, arg2); + goto no_change; + + case PR_CAPBSET_DROP: + error = cap_prctl_drop(new, arg2); + if (error < 0) + goto error; + goto changed; + + /* + * The next four prctl's remain to assist with transitioning a + * system from legacy UID=0 based privilege (when filesystem + * capabilities are not in use) to a system using filesystem + * capabilities only - as the POSIX.1e draft intended. + * + * Note: + * + * PR_SET_SECUREBITS = + * issecure_mask(SECURE_KEEP_CAPS_LOCKED) + * | issecure_mask(SECURE_NOROOT) + * | issecure_mask(SECURE_NOROOT_LOCKED) + * | issecure_mask(SECURE_NO_SETUID_FIXUP) + * | issecure_mask(SECURE_NO_SETUID_FIXUP_LOCKED) + * + * will ensure that the current process and all of its + * children will be locked into a pure + * capability-based-privilege environment. + */ + case PR_SET_SECUREBITS: + error = -EPERM; + if ((((new->securebits & SECURE_ALL_LOCKS) >> 1) + & (new->securebits ^ arg2)) /*[1]*/ + || ((new->securebits & SECURE_ALL_LOCKS & ~arg2)) /*[2]*/ + || (arg2 & ~(SECURE_ALL_LOCKS | SECURE_ALL_BITS)) /*[3]*/ + || (cap_capable(current, current_cred(), + current_cred()->user->user_ns, CAP_SETPCAP, + SECURITY_CAP_AUDIT) != 0) /*[4]*/ + /* + * [1] no changing of bits that are locked + * [2] no unlocking of locks + * [3] no setting of unsupported bits + * [4] doing anything requires privilege (go read about + * the "sendmail capabilities bug") + */ + ) + /* cannot change a locked bit */ + goto error; + new->securebits = arg2; + goto changed; + + case PR_GET_SECUREBITS: + error = new->securebits; + goto no_change; + + case PR_GET_KEEPCAPS: + if (issecure(SECURE_KEEP_CAPS)) + error = 1; + goto no_change; + + case PR_SET_KEEPCAPS: + error = -EINVAL; + if (arg2 > 1) /* Note, we rely on arg2 being unsigned here */ + goto error; + error = -EPERM; + if (issecure(SECURE_KEEP_CAPS_LOCKED)) + goto error; + if (arg2) + new->securebits |= issecure_mask(SECURE_KEEP_CAPS); + else + new->securebits &= ~issecure_mask(SECURE_KEEP_CAPS); + goto changed; + + default: + /* No functionality available - continue with default */ + error = -ENOSYS; + goto error; + } + + /* Functionality provided */ +changed: + return commit_creds(new); + +no_change: +error: + abort_creds(new); + return error; +} + +/** + * cap_vm_enough_memory - Determine whether a new virtual mapping is permitted + * @mm: The VM space in which the new mapping is to be made + * @pages: The size of the mapping + * + * Determine whether the allocation of a new virtual mapping by the current + * task is permitted, returning 0 if permission is granted, -ve if not. + */ +int cap_vm_enough_memory(struct mm_struct *mm, long pages) +{ + int cap_sys_admin = 0; + + if (cap_capable(current, current_cred(), &init_user_ns, CAP_SYS_ADMIN, + SECURITY_CAP_NOAUDIT) == 0) + cap_sys_admin = 1; + return __vm_enough_memory(mm, pages, cap_sys_admin); +} + +/* + * cap_file_mmap - check if able to map given addr + * @file: unused + * @reqprot: unused + * @prot: unused + * @flags: unused + * @addr: address attempting to be mapped + * @addr_only: unused + * + * If the process is attempting to map memory below dac_mmap_min_addr they need + * CAP_SYS_RAWIO. The other parameters to this function are unused by the + * capability security module. Returns 0 if this mapping should be allowed + * -EPERM if not. + */ +int cap_file_mmap(struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags, + unsigned long addr, unsigned long addr_only) +{ + int ret = 0; + + if (addr < dac_mmap_min_addr) { + ret = cap_capable(current, current_cred(), &init_user_ns, CAP_SYS_RAWIO, + SECURITY_CAP_AUDIT); + /* set PF_SUPERPRIV if it turns out we allow the low mmap */ + if (ret == 0) + current->flags |= PF_SUPERPRIV; + } + return ret; +} diff --git a/security/device_cgroup.c b/security/device_cgroup.c new file mode 100644 index 00000000..1be68269 --- /dev/null +++ b/security/device_cgroup.c @@ -0,0 +1,546 @@ +/* + * device_cgroup.c - device cgroup subsystem + * + * Copyright 2007 IBM Corp + */ + +#include <linux/device_cgroup.h> +#include <linux/cgroup.h> +#include <linux/ctype.h> +#include <linux/list.h> +#include <linux/uaccess.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/rcupdate.h> +#include <linux/mutex.h> + +#define ACC_MKNOD 1 +#define ACC_READ 2 +#define ACC_WRITE 4 +#define ACC_MASK (ACC_MKNOD | ACC_READ | ACC_WRITE) + +#define DEV_BLOCK 1 +#define DEV_CHAR 2 +#define DEV_ALL 4 /* this represents all devices */ + +static DEFINE_MUTEX(devcgroup_mutex); + +/* + * whitelist locking rules: + * hold devcgroup_mutex for update/read. + * hold rcu_read_lock() for read. + */ + +struct dev_whitelist_item { + u32 major, minor; + short type; + short access; + struct list_head list; + struct rcu_head rcu; +}; + +struct dev_cgroup { + struct cgroup_subsys_state css; + struct list_head whitelist; +}; + +static inline struct dev_cgroup *css_to_devcgroup(struct cgroup_subsys_state *s) +{ + return container_of(s, struct dev_cgroup, css); +} + +static inline struct dev_cgroup *cgroup_to_devcgroup(struct cgroup *cgroup) +{ + return css_to_devcgroup(cgroup_subsys_state(cgroup, devices_subsys_id)); +} + +static inline struct dev_cgroup *task_devcgroup(struct task_struct *task) +{ + return css_to_devcgroup(task_subsys_state(task, devices_subsys_id)); +} + +struct cgroup_subsys devices_subsys; + +static int devcgroup_can_attach(struct cgroup_subsys *ss, + struct cgroup *new_cgroup, struct task_struct *task) +{ + if (current != task && !capable(CAP_SYS_ADMIN)) + return -EPERM; + + return 0; +} + +/* + * called under devcgroup_mutex + */ +static int dev_whitelist_copy(struct list_head *dest, struct list_head *orig) +{ + struct dev_whitelist_item *wh, *tmp, *new; + + list_for_each_entry(wh, orig, list) { + new = kmemdup(wh, sizeof(*wh), GFP_KERNEL); + if (!new) + goto free_and_exit; + list_add_tail(&new->list, dest); + } + + return 0; + +free_and_exit: + list_for_each_entry_safe(wh, tmp, dest, list) { + list_del(&wh->list); + kfree(wh); + } + return -ENOMEM; +} + +/* Stupid prototype - don't bother combining existing entries */ +/* + * called under devcgroup_mutex + */ +static int dev_whitelist_add(struct dev_cgroup *dev_cgroup, + struct dev_whitelist_item *wh) +{ + struct dev_whitelist_item *whcopy, *walk; + + whcopy = kmemdup(wh, sizeof(*wh), GFP_KERNEL); + if (!whcopy) + return -ENOMEM; + + list_for_each_entry(walk, &dev_cgroup->whitelist, list) { + if (walk->type != wh->type) + continue; + if (walk->major != wh->major) + continue; + if (walk->minor != wh->minor) + continue; + + walk->access |= wh->access; + kfree(whcopy); + whcopy = NULL; + } + + if (whcopy != NULL) + list_add_tail_rcu(&whcopy->list, &dev_cgroup->whitelist); + return 0; +} + +static void whitelist_item_free(struct rcu_head *rcu) +{ + struct dev_whitelist_item *item; + + item = container_of(rcu, struct dev_whitelist_item, rcu); + kfree(item); +} + +/* + * called under devcgroup_mutex + */ +static void dev_whitelist_rm(struct dev_cgroup *dev_cgroup, + struct dev_whitelist_item *wh) +{ + struct dev_whitelist_item *walk, *tmp; + + list_for_each_entry_safe(walk, tmp, &dev_cgroup->whitelist, list) { + if (walk->type == DEV_ALL) + goto remove; + if (walk->type != wh->type) + continue; + if (walk->major != ~0 && walk->major != wh->major) + continue; + if (walk->minor != ~0 && walk->minor != wh->minor) + continue; + +remove: + walk->access &= ~wh->access; + if (!walk->access) { + list_del_rcu(&walk->list); + call_rcu(&walk->rcu, whitelist_item_free); + } + } +} + +/* + * called from kernel/cgroup.c with cgroup_lock() held. + */ +static struct cgroup_subsys_state *devcgroup_create(struct cgroup_subsys *ss, + struct cgroup *cgroup) +{ + struct dev_cgroup *dev_cgroup, *parent_dev_cgroup; + struct cgroup *parent_cgroup; + int ret; + + dev_cgroup = kzalloc(sizeof(*dev_cgroup), GFP_KERNEL); + if (!dev_cgroup) + return ERR_PTR(-ENOMEM); + INIT_LIST_HEAD(&dev_cgroup->whitelist); + parent_cgroup = cgroup->parent; + + if (parent_cgroup == NULL) { + struct dev_whitelist_item *wh; + wh = kmalloc(sizeof(*wh), GFP_KERNEL); + if (!wh) { + kfree(dev_cgroup); + return ERR_PTR(-ENOMEM); + } + wh->minor = wh->major = ~0; + wh->type = DEV_ALL; + wh->access = ACC_MASK; + list_add(&wh->list, &dev_cgroup->whitelist); + } else { + parent_dev_cgroup = cgroup_to_devcgroup(parent_cgroup); + mutex_lock(&devcgroup_mutex); + ret = dev_whitelist_copy(&dev_cgroup->whitelist, + &parent_dev_cgroup->whitelist); + mutex_unlock(&devcgroup_mutex); + if (ret) { + kfree(dev_cgroup); + return ERR_PTR(ret); + } + } + + return &dev_cgroup->css; +} + +static void devcgroup_destroy(struct cgroup_subsys *ss, + struct cgroup *cgroup) +{ + struct dev_cgroup *dev_cgroup; + struct dev_whitelist_item *wh, *tmp; + + dev_cgroup = cgroup_to_devcgroup(cgroup); + list_for_each_entry_safe(wh, tmp, &dev_cgroup->whitelist, list) { + list_del(&wh->list); + kfree(wh); + } + kfree(dev_cgroup); +} + +#define DEVCG_ALLOW 1 +#define DEVCG_DENY 2 +#define DEVCG_LIST 3 + +#define MAJMINLEN 13 +#define ACCLEN 4 + +static void set_access(char *acc, short access) +{ + int idx = 0; + memset(acc, 0, ACCLEN); + if (access & ACC_READ) + acc[idx++] = 'r'; + if (access & ACC_WRITE) + acc[idx++] = 'w'; + if (access & ACC_MKNOD) + acc[idx++] = 'm'; +} + +static char type_to_char(short type) +{ + if (type == DEV_ALL) + return 'a'; + if (type == DEV_CHAR) + return 'c'; + if (type == DEV_BLOCK) + return 'b'; + return 'X'; +} + +static void set_majmin(char *str, unsigned m) +{ + if (m == ~0) + strcpy(str, "*"); + else + sprintf(str, "%u", m); +} + +static int devcgroup_seq_read(struct cgroup *cgroup, struct cftype *cft, + struct seq_file *m) +{ + struct dev_cgroup *devcgroup = cgroup_to_devcgroup(cgroup); + struct dev_whitelist_item *wh; + char maj[MAJMINLEN], min[MAJMINLEN], acc[ACCLEN]; + + rcu_read_lock(); + list_for_each_entry_rcu(wh, &devcgroup->whitelist, list) { + set_access(acc, wh->access); + set_majmin(maj, wh->major); + set_majmin(min, wh->minor); + seq_printf(m, "%c %s:%s %s\n", type_to_char(wh->type), + maj, min, acc); + } + rcu_read_unlock(); + + return 0; +} + +/* + * may_access_whitelist: + * does the access granted to dev_cgroup c contain the access + * requested in whitelist item refwh. + * return 1 if yes, 0 if no. + * call with devcgroup_mutex held + */ +static int may_access_whitelist(struct dev_cgroup *c, + struct dev_whitelist_item *refwh) +{ + struct dev_whitelist_item *whitem; + + list_for_each_entry(whitem, &c->whitelist, list) { + if (whitem->type & DEV_ALL) + return 1; + if ((refwh->type & DEV_BLOCK) && !(whitem->type & DEV_BLOCK)) + continue; + if ((refwh->type & DEV_CHAR) && !(whitem->type & DEV_CHAR)) + continue; + if (whitem->major != ~0 && whitem->major != refwh->major) + continue; + if (whitem->minor != ~0 && whitem->minor != refwh->minor) + continue; + if (refwh->access & (~whitem->access)) + continue; + return 1; + } + return 0; +} + +/* + * parent_has_perm: + * when adding a new allow rule to a device whitelist, the rule + * must be allowed in the parent device + */ +static int parent_has_perm(struct dev_cgroup *childcg, + struct dev_whitelist_item *wh) +{ + struct cgroup *pcg = childcg->css.cgroup->parent; + struct dev_cgroup *parent; + + if (!pcg) + return 1; + parent = cgroup_to_devcgroup(pcg); + return may_access_whitelist(parent, wh); +} + +/* + * Modify the whitelist using allow/deny rules. + * CAP_SYS_ADMIN is needed for this. It's at least separate from CAP_MKNOD + * so we can give a container CAP_MKNOD to let it create devices but not + * modify the whitelist. + * It seems likely we'll want to add a CAP_CONTAINER capability to allow + * us to also grant CAP_SYS_ADMIN to containers without giving away the + * device whitelist controls, but for now we'll stick with CAP_SYS_ADMIN + * + * Taking rules away is always allowed (given CAP_SYS_ADMIN). Granting + * new access is only allowed if you're in the top-level cgroup, or your + * parent cgroup has the access you're asking for. + */ +static int devcgroup_update_access(struct dev_cgroup *devcgroup, + int filetype, const char *buffer) +{ + const char *b; + char *endp; + int count; + struct dev_whitelist_item wh; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + memset(&wh, 0, sizeof(wh)); + b = buffer; + + switch (*b) { + case 'a': + wh.type = DEV_ALL; + wh.access = ACC_MASK; + wh.major = ~0; + wh.minor = ~0; + goto handle; + case 'b': + wh.type = DEV_BLOCK; + break; + case 'c': + wh.type = DEV_CHAR; + break; + default: + return -EINVAL; + } + b++; + if (!isspace(*b)) + return -EINVAL; + b++; + if (*b == '*') { + wh.major = ~0; + b++; + } else if (isdigit(*b)) { + wh.major = simple_strtoul(b, &endp, 10); + b = endp; + } else { + return -EINVAL; + } + if (*b != ':') + return -EINVAL; + b++; + + /* read minor */ + if (*b == '*') { + wh.minor = ~0; + b++; + } else if (isdigit(*b)) { + wh.minor = simple_strtoul(b, &endp, 10); + b = endp; + } else { + return -EINVAL; + } + if (!isspace(*b)) + return -EINVAL; + for (b++, count = 0; count < 3; count++, b++) { + switch (*b) { + case 'r': + wh.access |= ACC_READ; + break; + case 'w': + wh.access |= ACC_WRITE; + break; + case 'm': + wh.access |= ACC_MKNOD; + break; + case '\n': + case '\0': + count = 3; + break; + default: + return -EINVAL; + } + } + +handle: + switch (filetype) { + case DEVCG_ALLOW: + if (!parent_has_perm(devcgroup, &wh)) + return -EPERM; + return dev_whitelist_add(devcgroup, &wh); + case DEVCG_DENY: + dev_whitelist_rm(devcgroup, &wh); + break; + default: + return -EINVAL; + } + return 0; +} + +static int devcgroup_access_write(struct cgroup *cgrp, struct cftype *cft, + const char *buffer) +{ + int retval; + + mutex_lock(&devcgroup_mutex); + retval = devcgroup_update_access(cgroup_to_devcgroup(cgrp), + cft->private, buffer); + mutex_unlock(&devcgroup_mutex); + return retval; +} + +static struct cftype dev_cgroup_files[] = { + { + .name = "allow", + .write_string = devcgroup_access_write, + .private = DEVCG_ALLOW, + }, + { + .name = "deny", + .write_string = devcgroup_access_write, + .private = DEVCG_DENY, + }, + { + .name = "list", + .read_seq_string = devcgroup_seq_read, + .private = DEVCG_LIST, + }, +}; + +static int devcgroup_populate(struct cgroup_subsys *ss, + struct cgroup *cgroup) +{ + return cgroup_add_files(cgroup, ss, dev_cgroup_files, + ARRAY_SIZE(dev_cgroup_files)); +} + +struct cgroup_subsys devices_subsys = { + .name = "devices", + .can_attach = devcgroup_can_attach, + .create = devcgroup_create, + .destroy = devcgroup_destroy, + .populate = devcgroup_populate, + .subsys_id = devices_subsys_id, +}; + +int __devcgroup_inode_permission(struct inode *inode, int mask) +{ + struct dev_cgroup *dev_cgroup; + struct dev_whitelist_item *wh; + + rcu_read_lock(); + + dev_cgroup = task_devcgroup(current); + + list_for_each_entry_rcu(wh, &dev_cgroup->whitelist, list) { + if (wh->type & DEV_ALL) + goto found; + if ((wh->type & DEV_BLOCK) && !S_ISBLK(inode->i_mode)) + continue; + if ((wh->type & DEV_CHAR) && !S_ISCHR(inode->i_mode)) + continue; + if (wh->major != ~0 && wh->major != imajor(inode)) + continue; + if (wh->minor != ~0 && wh->minor != iminor(inode)) + continue; + + if ((mask & MAY_WRITE) && !(wh->access & ACC_WRITE)) + continue; + if ((mask & MAY_READ) && !(wh->access & ACC_READ)) + continue; +found: + rcu_read_unlock(); + return 0; + } + + rcu_read_unlock(); + + return -EPERM; +} + +int devcgroup_inode_mknod(int mode, dev_t dev) +{ + struct dev_cgroup *dev_cgroup; + struct dev_whitelist_item *wh; + + if (!S_ISBLK(mode) && !S_ISCHR(mode)) + return 0; + + rcu_read_lock(); + + dev_cgroup = task_devcgroup(current); + + list_for_each_entry_rcu(wh, &dev_cgroup->whitelist, list) { + if (wh->type & DEV_ALL) + goto found; + if ((wh->type & DEV_BLOCK) && !S_ISBLK(mode)) + continue; + if ((wh->type & DEV_CHAR) && !S_ISCHR(mode)) + continue; + if (wh->major != ~0 && wh->major != MAJOR(dev)) + continue; + if (wh->minor != ~0 && wh->minor != MINOR(dev)) + continue; + + if (!(wh->access & ACC_MKNOD)) + continue; +found: + rcu_read_unlock(); + return 0; + } + + rcu_read_unlock(); + + return -EPERM; +} diff --git a/security/inode.c b/security/inode.c new file mode 100644 index 00000000..c4df2fbe --- /dev/null +++ b/security/inode.c @@ -0,0 +1,327 @@ +/* + * inode.c - securityfs + * + * Copyright (C) 2005 Greg Kroah-Hartman <gregkh@suse.de> + * + * 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. + * + * Based on fs/debugfs/inode.c which had the following copyright notice: + * Copyright (C) 2004 Greg Kroah-Hartman <greg@kroah.com> + * Copyright (C) 2004 IBM Inc. + */ + +/* #define DEBUG */ +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/mount.h> +#include <linux/pagemap.h> +#include <linux/init.h> +#include <linux/namei.h> +#include <linux/security.h> +#include <linux/magic.h> + +static struct vfsmount *mount; +static int mount_count; + +/* + * TODO: + * I think I can get rid of these default_file_ops, but not quite sure... + */ +static ssize_t default_read_file(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + return 0; +} + +static ssize_t default_write_file(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + return count; +} + +static int default_open(struct inode *inode, struct file *file) +{ + if (inode->i_private) + file->private_data = inode->i_private; + + return 0; +} + +static const struct file_operations default_file_ops = { + .read = default_read_file, + .write = default_write_file, + .open = default_open, + .llseek = noop_llseek, +}; + +static struct inode *get_inode(struct super_block *sb, int mode, dev_t dev) +{ + struct inode *inode = new_inode(sb); + + if (inode) { + inode->i_ino = get_next_ino(); + inode->i_mode = mode; + inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; + switch (mode & S_IFMT) { + default: + init_special_inode(inode, mode, dev); + break; + case S_IFREG: + inode->i_fop = &default_file_ops; + break; + case S_IFDIR: + inode->i_op = &simple_dir_inode_operations; + inode->i_fop = &simple_dir_operations; + + /* directory inodes start off with i_nlink == 2 (for "." entry) */ + inc_nlink(inode); + break; + } + } + return inode; +} + +/* SMP-safe */ +static int mknod(struct inode *dir, struct dentry *dentry, + int mode, dev_t dev) +{ + struct inode *inode; + int error = -ENOMEM; + + if (dentry->d_inode) + return -EEXIST; + + inode = get_inode(dir->i_sb, mode, dev); + if (inode) { + d_instantiate(dentry, inode); + dget(dentry); + error = 0; + } + return error; +} + +static int mkdir(struct inode *dir, struct dentry *dentry, int mode) +{ + int res; + + mode = (mode & (S_IRWXUGO | S_ISVTX)) | S_IFDIR; + res = mknod(dir, dentry, mode, 0); + if (!res) + inc_nlink(dir); + return res; +} + +static int create(struct inode *dir, struct dentry *dentry, int mode) +{ + mode = (mode & S_IALLUGO) | S_IFREG; + return mknod(dir, dentry, mode, 0); +} + +static inline int positive(struct dentry *dentry) +{ + return dentry->d_inode && !d_unhashed(dentry); +} + +static int fill_super(struct super_block *sb, void *data, int silent) +{ + static struct tree_descr files[] = {{""}}; + + return simple_fill_super(sb, SECURITYFS_MAGIC, files); +} + +static struct dentry *get_sb(struct file_system_type *fs_type, + int flags, const char *dev_name, + void *data) +{ + return mount_single(fs_type, flags, data, fill_super); +} + +static struct file_system_type fs_type = { + .owner = THIS_MODULE, + .name = "securityfs", + .mount = get_sb, + .kill_sb = kill_litter_super, +}; + +static int create_by_name(const char *name, mode_t mode, + struct dentry *parent, + struct dentry **dentry) +{ + int error = 0; + + *dentry = NULL; + + /* If the parent is not specified, we create it in the root. + * We need the root dentry to do this, which is in the super + * block. A pointer to that is in the struct vfsmount that we + * have around. + */ + if (!parent) + parent = mount->mnt_sb->s_root; + + mutex_lock(&parent->d_inode->i_mutex); + *dentry = lookup_one_len(name, parent, strlen(name)); + if (!IS_ERR(*dentry)) { + if ((mode & S_IFMT) == S_IFDIR) + 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); + + return error; +} + +/** + * securityfs_create_file - create a file in the securityfs filesystem + * + * @name: a pointer to a string containing the name of the file to create. + * @mode: the permission that the file should have + * @parent: a pointer to the parent dentry for this file. This should be a + * directory dentry if set. If this parameter is %NULL, then the + * file will be created in the root of the securityfs filesystem. + * @data: a pointer to something that the caller will want to get to later + * on. The inode.i_private pointer will point to this value on + * the open() call. + * @fops: a pointer to a struct file_operations that should be used for + * this file. + * + * This is the basic "create a file" function for securityfs. It allows for a + * wide range of flexibility in creating a file, or a directory (if you + * want to create a directory, the securityfs_create_dir() function is + * recommended to be used instead). + * + * This function returns a pointer to a dentry if it succeeds. This + * pointer must be passed to the securityfs_remove() function when the file is + * to be removed (no automatic cleanup happens if your module is unloaded, + * you are responsible here). If an error occurs, the function will return + * the erorr value (via ERR_PTR). + * + * If securityfs is not enabled in the kernel, the value %-ENODEV is + * returned. + */ +struct dentry *securityfs_create_file(const char *name, mode_t mode, + struct dentry *parent, void *data, + const struct file_operations *fops) +{ + struct dentry *dentry = NULL; + int error; + + pr_debug("securityfs: creating file '%s'\n",name); + + error = simple_pin_fs(&fs_type, &mount, &mount_count); + if (error) { + dentry = ERR_PTR(error); + goto exit; + } + + error = create_by_name(name, mode, parent, &dentry); + if (error) { + dentry = ERR_PTR(error); + simple_release_fs(&mount, &mount_count); + goto exit; + } + + if (dentry->d_inode) { + if (fops) + dentry->d_inode->i_fop = fops; + if (data) + dentry->d_inode->i_private = data; + } +exit: + return dentry; +} +EXPORT_SYMBOL_GPL(securityfs_create_file); + +/** + * securityfs_create_dir - create a directory in the securityfs filesystem + * + * @name: a pointer to a string containing the name of the directory to + * create. + * @parent: a pointer to the parent dentry for this file. This should be a + * directory dentry if set. If this parameter is %NULL, then the + * directory will be created in the root of the securityfs filesystem. + * + * This function creates a directory in securityfs with the given @name. + * + * This function returns a pointer to a dentry if it succeeds. This + * pointer must be passed to the securityfs_remove() function when the file is + * to be removed (no automatic cleanup happens if your module is unloaded, + * you are responsible here). If an error occurs, %NULL will be returned. + * + * If securityfs is not enabled in the kernel, the value %-ENODEV is + * returned. It is not wise to check for this value, but rather, check for + * %NULL or !%NULL instead as to eliminate the need for #ifdef in the calling + * code. + */ +struct dentry *securityfs_create_dir(const char *name, struct dentry *parent) +{ + return securityfs_create_file(name, + S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO, + parent, NULL, NULL); +} +EXPORT_SYMBOL_GPL(securityfs_create_dir); + +/** + * securityfs_remove - removes a file or directory from the securityfs filesystem + * + * @dentry: a pointer to a the dentry of the file or directory to be removed. + * + * This function removes a file or directory in securityfs that was previously + * created with a call to another securityfs function (like + * securityfs_create_file() or variants thereof.) + * + * This function is required to be called in order for the file to be + * removed. No automatic cleanup of files will happen when a module is + * removed; you are responsible here. + */ +void securityfs_remove(struct dentry *dentry) +{ + struct dentry *parent; + + if (!dentry || IS_ERR(dentry)) + return; + + parent = dentry->d_parent; + if (!parent || !parent->d_inode) + return; + + mutex_lock(&parent->d_inode->i_mutex); + if (positive(dentry)) { + if (dentry->d_inode) { + if (S_ISDIR(dentry->d_inode->i_mode)) + simple_rmdir(parent->d_inode, dentry); + else + simple_unlink(parent->d_inode, dentry); + dput(dentry); + } + } + mutex_unlock(&parent->d_inode->i_mutex); + simple_release_fs(&mount, &mount_count); +} +EXPORT_SYMBOL_GPL(securityfs_remove); + +static struct kobject *security_kobj; + +static int __init securityfs_init(void) +{ + int retval; + + security_kobj = kobject_create_and_add("security", kernel_kobj); + if (!security_kobj) + return -EINVAL; + + retval = register_filesystem(&fs_type); + if (retval) + kobject_put(security_kobj); + return retval; +} + +core_initcall(securityfs_init); +MODULE_LICENSE("GPL"); + diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig new file mode 100644 index 00000000..b6ecfd4d --- /dev/null +++ b/security/integrity/ima/Kconfig @@ -0,0 +1,55 @@ +# IBM Integrity Measurement Architecture +# +config IMA + bool "Integrity Measurement Architecture(IMA)" + depends on SECURITY + select SECURITYFS + select CRYPTO + select CRYPTO_HMAC + select CRYPTO_MD5 + select CRYPTO_SHA1 + select TCG_TPM if !S390 + select TCG_TIS if TCG_TPM + help + The Trusted Computing Group(TCG) runtime Integrity + Measurement Architecture(IMA) maintains a list of hash + values of executables and other sensitive system files, + as they are read or executed. If an attacker manages + to change the contents of an important system file + being measured, we can tell. + + If your system has a TPM chip, then IMA also maintains + an aggregate integrity value over this list inside the + TPM hardware, so that the TPM can prove to a third party + whether or not critical system files have been modified. + Read <http://www.usenix.org/events/sec04/tech/sailer.html> + to learn more about IMA. + If unsure, say N. + +config IMA_MEASURE_PCR_IDX + int + depends on IMA + range 8 14 + default 10 + help + IMA_MEASURE_PCR_IDX determines the TPM PCR register index + that IMA uses to maintain the integrity aggregate of the + measurement list. If unsure, use the default 10. + +config IMA_AUDIT + bool + depends on IMA + default y + help + This option adds a kernel parameter 'ima_audit', which + allows informational auditing messages to be enabled + at boot. If this option is selected, informational integrity + auditing messages can be enabled with 'ima_audit=1' on + the kernel command line. + +config IMA_LSM_RULES + bool + depends on IMA && AUDIT && (SECURITY_SELINUX || SECURITY_SMACK) + default y + help + Disabling this option will disregard LSM based policy rules. diff --git a/security/integrity/ima/Makefile b/security/integrity/ima/Makefile new file mode 100644 index 00000000..787c4cb9 --- /dev/null +++ b/security/integrity/ima/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for building Trusted Computing Group's(TCG) runtime Integrity +# Measurement Architecture(IMA). +# + +obj-$(CONFIG_IMA) += ima.o + +ima-y := ima_fs.o ima_queue.o ima_init.o ima_main.o ima_crypto.o ima_api.o \ + ima_policy.o ima_iint.o ima_audit.o diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h new file mode 100644 index 00000000..08408bd7 --- /dev/null +++ b/security/integrity/ima/ima.h @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * + * Authors: + * Reiner Sailer <sailer@watson.ibm.com> + * Mimi Zohar <zohar@us.ibm.com> + * + * 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. + * + * File: ima.h + * internal Integrity Measurement Architecture (IMA) definitions + */ + +#ifndef __LINUX_IMA_H +#define __LINUX_IMA_H + +#include <linux/types.h> +#include <linux/crypto.h> +#include <linux/security.h> +#include <linux/hash.h> +#include <linux/tpm.h> +#include <linux/audit.h> + +enum ima_show_type { IMA_SHOW_BINARY, IMA_SHOW_ASCII }; +enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8 }; + +/* digest size for IMA, fits SHA1 or MD5 */ +#define IMA_DIGEST_SIZE 20 +#define IMA_EVENT_NAME_LEN_MAX 255 + +#define IMA_HASH_BITS 9 +#define IMA_MEASURE_HTABLE_SIZE (1 << IMA_HASH_BITS) + +/* set during initialization */ +extern int iint_initialized; +extern int ima_initialized; +extern int ima_used_chip; +extern char *ima_hash; + +/* IMA inode template definition */ +struct ima_template_data { + u8 digest[IMA_DIGEST_SIZE]; /* sha1/md5 measurement hash */ + char file_name[IMA_EVENT_NAME_LEN_MAX + 1]; /* name + \0 */ +}; + +struct ima_template_entry { + u8 digest[IMA_DIGEST_SIZE]; /* sha1 or md5 measurement hash */ + const char *template_name; + int template_len; + struct ima_template_data template; +}; + +struct ima_queue_entry { + struct hlist_node hnext; /* place in hash collision list */ + struct list_head later; /* place in ima_measurements list */ + struct ima_template_entry *entry; +}; +extern struct list_head ima_measurements; /* list of all measurements */ + +/* declarations */ +void integrity_audit_msg(int audit_msgno, struct inode *inode, + const unsigned char *fname, const char *op, + const char *cause, int result, int info); + +/* Internal IMA function definitions */ +int ima_init(void); +void ima_cleanup(void); +int ima_fs_init(void); +void ima_fs_cleanup(void); +int ima_inode_alloc(struct inode *inode); +int ima_add_template_entry(struct ima_template_entry *entry, int violation, + const char *op, struct inode *inode); +int ima_calc_hash(struct file *file, char *digest); +int ima_calc_template_hash(int template_len, void *template, char *digest); +int ima_calc_boot_aggregate(char *digest); +void ima_add_violation(struct inode *inode, const unsigned char *filename, + const char *op, const char *cause); + +/* + * used to protect h_table and sha_table + */ +extern spinlock_t ima_queue_lock; + +struct ima_h_table { + atomic_long_t len; /* number of stored measurements in the list */ + atomic_long_t violations; + struct hlist_head queue[IMA_MEASURE_HTABLE_SIZE]; +}; +extern struct ima_h_table ima_htable; + +static inline unsigned long ima_hash_key(u8 *digest) +{ + return hash_long(*digest, IMA_HASH_BITS); +} + +/* iint cache flags */ +#define IMA_MEASURED 0x01 + +/* integrity data associated with an inode */ +struct ima_iint_cache { + struct rb_node rb_node; /* rooted in ima_iint_tree */ + struct inode *inode; /* back pointer to inode in question */ + u64 version; /* track inode changes */ + unsigned char flags; + u8 digest[IMA_DIGEST_SIZE]; + struct mutex mutex; /* protects: version, flags, digest */ +}; + +/* LIM API function definitions */ +int ima_must_measure(struct inode *inode, int mask, int function); +int ima_collect_measurement(struct ima_iint_cache *iint, struct file *file); +void ima_store_measurement(struct ima_iint_cache *iint, struct file *file, + const unsigned char *filename); +int ima_store_template(struct ima_template_entry *entry, int violation, + struct inode *inode); +void ima_template_show(struct seq_file *m, void *e, + enum ima_show_type show); + +/* rbtree tree calls to lookup, insert, delete + * integrity data associated with an inode. + */ +struct ima_iint_cache *ima_iint_insert(struct inode *inode); +struct ima_iint_cache *ima_iint_find(struct inode *inode); + +/* IMA policy related functions */ +enum ima_hooks { FILE_CHECK = 1, FILE_MMAP, BPRM_CHECK }; + +int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask); +void ima_init_policy(void); +void ima_update_policy(void); +ssize_t ima_parse_add_rule(char *); +void ima_delete_rules(void); + +/* LSM based policy rules require audit */ +#ifdef CONFIG_IMA_LSM_RULES + +#define security_filter_rule_init security_audit_rule_init +#define security_filter_rule_match security_audit_rule_match + +#else + +static inline int security_filter_rule_init(u32 field, u32 op, char *rulestr, + void **lsmrule) +{ + return -EINVAL; +} + +static inline int security_filter_rule_match(u32 secid, u32 field, u32 op, + void *lsmrule, + struct audit_context *actx) +{ + return -EINVAL; +} +#endif /* CONFIG_IMA_LSM_RULES */ +#endif diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c new file mode 100644 index 00000000..53356055 --- /dev/null +++ b/security/integrity/ima/ima_api.c @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2008 IBM Corporation + * + * Author: Mimi Zohar <zohar@us.ibm.com> + * + * 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. + * + * File: ima_api.c + * Implements must_measure, collect_measurement, store_measurement, + * and store_template. + */ +#include <linux/module.h> +#include <linux/slab.h> + +#include "ima.h" +static const char *IMA_TEMPLATE_NAME = "ima"; + +/* + * ima_store_template - store ima template measurements + * + * Calculate the hash of a template entry, add the template entry + * to an ordered list of measurement entries maintained inside the kernel, + * and also update the aggregate integrity value (maintained inside the + * configured TPM PCR) over the hashes of the current list of measurement + * entries. + * + * Applications retrieve the current kernel-held measurement list through + * the securityfs entries in /sys/kernel/security/ima. The signed aggregate + * TPM PCR (called quote) can be retrieved using a TPM user space library + * and is used to validate the measurement list. + * + * Returns 0 on success, error code otherwise + */ +int ima_store_template(struct ima_template_entry *entry, + int violation, struct inode *inode) +{ + const char *op = "add_template_measure"; + const char *audit_cause = "hashing_error"; + int result; + + memset(entry->digest, 0, sizeof(entry->digest)); + entry->template_name = IMA_TEMPLATE_NAME; + entry->template_len = sizeof(entry->template); + + if (!violation) { + result = ima_calc_template_hash(entry->template_len, + &entry->template, + entry->digest); + if (result < 0) { + integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, + entry->template_name, op, + audit_cause, result, 0); + return result; + } + } + result = ima_add_template_entry(entry, violation, op, inode); + return result; +} + +/* + * ima_add_violation - add violation to measurement list. + * + * Violations are flagged in the measurement list with zero hash values. + * By extending the PCR with 0xFF's instead of with zeroes, the PCR + * value is invalidated. + */ +void ima_add_violation(struct inode *inode, const unsigned char *filename, + const char *op, const char *cause) +{ + struct ima_template_entry *entry; + int violation = 1; + int result; + + /* can overflow, only indicator */ + atomic_long_inc(&ima_htable.violations); + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) { + result = -ENOMEM; + goto err_out; + } + memset(&entry->template, 0, sizeof(entry->template)); + strncpy(entry->template.file_name, filename, IMA_EVENT_NAME_LEN_MAX); + result = ima_store_template(entry, violation, inode); + if (result < 0) + kfree(entry); +err_out: + integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename, + op, cause, result, 0); +} + +/** + * ima_must_measure - measure decision based on policy. + * @inode: pointer to inode to measure + * @mask: contains the permission mask (MAY_READ, MAY_WRITE, MAY_EXECUTE) + * @function: calling function (FILE_CHECK, BPRM_CHECK, FILE_MMAP) + * + * The policy is defined in terms of keypairs: + * subj=, obj=, type=, func=, mask=, fsmagic= + * subj,obj, and type: are LSM specific. + * func: FILE_CHECK | BPRM_CHECK | FILE_MMAP + * mask: contains the permission mask + * fsmagic: hex value + * + * Return 0 to measure. For matching a DONT_MEASURE policy, no policy, + * or other error, return an error code. +*/ +int ima_must_measure(struct inode *inode, int mask, int function) +{ + int must_measure; + + must_measure = ima_match_policy(inode, function, mask); + return must_measure ? 0 : -EACCES; +} + +/* + * ima_collect_measurement - collect file measurement + * + * Calculate the file hash, if it doesn't already exist, + * storing the measurement and i_version in the iint. + * + * Must be called with iint->mutex held. + * + * Return 0 on success, error code otherwise + */ +int ima_collect_measurement(struct ima_iint_cache *iint, struct file *file) +{ + int result = -EEXIST; + + if (!(iint->flags & IMA_MEASURED)) { + u64 i_version = file->f_dentry->d_inode->i_version; + + memset(iint->digest, 0, IMA_DIGEST_SIZE); + result = ima_calc_hash(file, iint->digest); + if (!result) + iint->version = i_version; + } + return result; +} + +/* + * ima_store_measurement - store file measurement + * + * Create an "ima" template and then store the template by calling + * ima_store_template. + * + * We only get here if the inode has not already been measured, + * but the measurement could already exist: + * - multiple copies of the same file on either the same or + * different filesystems. + * - the inode was previously flushed as well as the iint info, + * containing the hashing info. + * + * Must be called with iint->mutex held. + */ +void ima_store_measurement(struct ima_iint_cache *iint, struct file *file, + const unsigned char *filename) +{ + const char *op = "add_template_measure"; + const char *audit_cause = "ENOMEM"; + int result = -ENOMEM; + struct inode *inode = file->f_dentry->d_inode; + struct ima_template_entry *entry; + int violation = 0; + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) { + integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename, + op, audit_cause, result, 0); + return; + } + memset(&entry->template, 0, sizeof(entry->template)); + memcpy(entry->template.digest, iint->digest, IMA_DIGEST_SIZE); + strncpy(entry->template.file_name, filename, IMA_EVENT_NAME_LEN_MAX); + + result = ima_store_template(entry, violation, inode); + if (!result || result == -EEXIST) + iint->flags |= IMA_MEASURED; + if (result < 0) + kfree(entry); +} diff --git a/security/integrity/ima/ima_audit.c b/security/integrity/ima/ima_audit.c new file mode 100644 index 00000000..c5c5a72c --- /dev/null +++ b/security/integrity/ima/ima_audit.c @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2008 IBM Corporation + * Author: Mimi Zohar <zohar@us.ibm.com> + * + * 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. + * + * File: integrity_audit.c + * Audit calls for the integrity subsystem + */ + +#include <linux/fs.h> +#include <linux/gfp.h> +#include <linux/audit.h> +#include "ima.h" + +static int ima_audit; + +#ifdef CONFIG_IMA_AUDIT + +/* ima_audit_setup - enable informational auditing messages */ +static int __init ima_audit_setup(char *str) +{ + unsigned long audit; + + if (!strict_strtoul(str, 0, &audit)) + ima_audit = audit ? 1 : 0; + return 1; +} +__setup("ima_audit=", ima_audit_setup); +#endif + +void integrity_audit_msg(int audit_msgno, struct inode *inode, + const unsigned char *fname, const char *op, + const char *cause, int result, int audit_info) +{ + struct audit_buffer *ab; + + if (!ima_audit && audit_info == 1) /* Skip informational messages */ + return; + + ab = audit_log_start(current->audit_context, GFP_KERNEL, audit_msgno); + audit_log_format(ab, "pid=%d uid=%u auid=%u ses=%u", + current->pid, current_cred()->uid, + audit_get_loginuid(current), + audit_get_sessionid(current)); + audit_log_task_context(ab); + audit_log_format(ab, " op="); + audit_log_string(ab, op); + audit_log_format(ab, " cause="); + audit_log_string(ab, cause); + audit_log_format(ab, " comm="); + audit_log_untrustedstring(ab, current->comm); + if (fname) { + audit_log_format(ab, " name="); + audit_log_untrustedstring(ab, fname); + } + if (inode) + audit_log_format(ab, " dev=%s ino=%lu", + inode->i_sb->s_id, inode->i_ino); + audit_log_format(ab, " res=%d", !result ? 0 : 1); + audit_log_end(ab); +} diff --git a/security/integrity/ima/ima_crypto.c b/security/integrity/ima/ima_crypto.c new file mode 100644 index 00000000..9b3ade74 --- /dev/null +++ b/security/integrity/ima/ima_crypto.c @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * + * Authors: + * Mimi Zohar <zohar@us.ibm.com> + * Kylene Hall <kjhall@us.ibm.com> + * + * 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. + * + * File: ima_crypto.c + * Calculates md5/sha1 file hash, template hash, boot-aggreate hash + */ + +#include <linux/kernel.h> +#include <linux/file.h> +#include <linux/crypto.h> +#include <linux/scatterlist.h> +#include <linux/err.h> +#include <linux/slab.h> +#include "ima.h" + +static int init_desc(struct hash_desc *desc) +{ + int rc; + + desc->tfm = crypto_alloc_hash(ima_hash, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(desc->tfm)) { + pr_info("IMA: failed to load %s transform: %ld\n", + ima_hash, PTR_ERR(desc->tfm)); + rc = PTR_ERR(desc->tfm); + return rc; + } + desc->flags = 0; + rc = crypto_hash_init(desc); + if (rc) + crypto_free_hash(desc->tfm); + return rc; +} + +/* + * Calculate the MD5/SHA1 file digest + */ +int ima_calc_hash(struct file *file, char *digest) +{ + struct hash_desc desc; + struct scatterlist sg[1]; + loff_t i_size, offset = 0; + char *rbuf; + int rc; + + rc = init_desc(&desc); + if (rc != 0) + return rc; + + rbuf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!rbuf) { + rc = -ENOMEM; + goto out; + } + i_size = i_size_read(file->f_dentry->d_inode); + while (offset < i_size) { + int rbuf_len; + + rbuf_len = kernel_read(file, offset, rbuf, PAGE_SIZE); + if (rbuf_len < 0) { + rc = rbuf_len; + break; + } + if (rbuf_len == 0) + break; + offset += rbuf_len; + sg_init_one(sg, rbuf, rbuf_len); + + rc = crypto_hash_update(&desc, sg, rbuf_len); + if (rc) + break; + } + kfree(rbuf); + if (!rc) + rc = crypto_hash_final(&desc, digest); +out: + crypto_free_hash(desc.tfm); + return rc; +} + +/* + * Calculate the hash of a given template + */ +int ima_calc_template_hash(int template_len, void *template, char *digest) +{ + struct hash_desc desc; + struct scatterlist sg[1]; + int rc; + + rc = init_desc(&desc); + if (rc != 0) + return rc; + + sg_init_one(sg, template, template_len); + rc = crypto_hash_update(&desc, sg, template_len); + if (!rc) + rc = crypto_hash_final(&desc, digest); + crypto_free_hash(desc.tfm); + return rc; +} + +static void __init ima_pcrread(int idx, u8 *pcr) +{ + if (!ima_used_chip) + return; + + if (tpm_pcr_read(TPM_ANY_NUM, idx, pcr) != 0) + pr_err("IMA: Error Communicating to TPM chip\n"); +} + +/* + * Calculate the boot aggregate hash + */ +int __init ima_calc_boot_aggregate(char *digest) +{ + struct hash_desc desc; + struct scatterlist sg; + u8 pcr_i[IMA_DIGEST_SIZE]; + int rc, i; + + rc = init_desc(&desc); + if (rc != 0) + return rc; + + /* cumulative sha1 over tpm registers 0-7 */ + for (i = TPM_PCR0; i < TPM_PCR8; i++) { + ima_pcrread(i, pcr_i); + /* now accumulate with current aggregate */ + sg_init_one(&sg, pcr_i, IMA_DIGEST_SIZE); + rc = crypto_hash_update(&desc, &sg, IMA_DIGEST_SIZE); + } + if (!rc) + crypto_hash_final(&desc, digest); + crypto_free_hash(desc.tfm); + return rc; +} diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c new file mode 100644 index 00000000..ef21b96a --- /dev/null +++ b/security/integrity/ima/ima_fs.c @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * + * Authors: + * Kylene Hall <kjhall@us.ibm.com> + * Reiner Sailer <sailer@us.ibm.com> + * Mimi Zohar <zohar@us.ibm.com> + * + * 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. + * + * File: ima_fs.c + * implemenents security file system for reporting + * current measurement list and IMA statistics + */ +#include <linux/fcntl.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/seq_file.h> +#include <linux/rculist.h> +#include <linux/rcupdate.h> +#include <linux/parser.h> + +#include "ima.h" + +static int valid_policy = 1; +#define TMPBUFLEN 12 +static ssize_t ima_show_htable_value(char __user *buf, size_t count, + loff_t *ppos, atomic_long_t *val) +{ + char tmpbuf[TMPBUFLEN]; + ssize_t len; + + len = scnprintf(tmpbuf, TMPBUFLEN, "%li\n", atomic_long_read(val)); + return simple_read_from_buffer(buf, count, ppos, tmpbuf, len); +} + +static ssize_t ima_show_htable_violations(struct file *filp, + char __user *buf, + size_t count, loff_t *ppos) +{ + return ima_show_htable_value(buf, count, ppos, &ima_htable.violations); +} + +static const struct file_operations ima_htable_violations_ops = { + .read = ima_show_htable_violations, + .llseek = generic_file_llseek, +}; + +static ssize_t ima_show_measurements_count(struct file *filp, + char __user *buf, + size_t count, loff_t *ppos) +{ + return ima_show_htable_value(buf, count, ppos, &ima_htable.len); + +} + +static const struct file_operations ima_measurements_count_ops = { + .read = ima_show_measurements_count, + .llseek = generic_file_llseek, +}; + +/* returns pointer to hlist_node */ +static void *ima_measurements_start(struct seq_file *m, loff_t *pos) +{ + loff_t l = *pos; + struct ima_queue_entry *qe; + + /* we need a lock since pos could point beyond last element */ + rcu_read_lock(); + list_for_each_entry_rcu(qe, &ima_measurements, later) { + if (!l--) { + rcu_read_unlock(); + return qe; + } + } + rcu_read_unlock(); + return NULL; +} + +static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos) +{ + struct ima_queue_entry *qe = v; + + /* lock protects when reading beyond last element + * against concurrent list-extension + */ + rcu_read_lock(); + qe = list_entry_rcu(qe->later.next, + struct ima_queue_entry, later); + rcu_read_unlock(); + (*pos)++; + + return (&qe->later == &ima_measurements) ? NULL : qe; +} + +static void ima_measurements_stop(struct seq_file *m, void *v) +{ +} + +static void ima_putc(struct seq_file *m, void *data, int datalen) +{ + while (datalen--) + seq_putc(m, *(char *)data++); +} + +/* print format: + * 32bit-le=pcr# + * char[20]=template digest + * 32bit-le=template name size + * char[n]=template name + * eventdata[n]=template specific data + */ +static int ima_measurements_show(struct seq_file *m, void *v) +{ + /* the list never shrinks, so we don't need a lock here */ + struct ima_queue_entry *qe = v; + struct ima_template_entry *e; + int namelen; + u32 pcr = CONFIG_IMA_MEASURE_PCR_IDX; + + /* get entry */ + e = qe->entry; + if (e == NULL) + return -1; + + /* + * 1st: PCRIndex + * PCR used is always the same (config option) in + * little-endian format + */ + ima_putc(m, &pcr, sizeof pcr); + + /* 2nd: template digest */ + ima_putc(m, e->digest, IMA_DIGEST_SIZE); + + /* 3rd: template name size */ + namelen = strlen(e->template_name); + ima_putc(m, &namelen, sizeof namelen); + + /* 4th: template name */ + ima_putc(m, (void *)e->template_name, namelen); + + /* 5th: template specific data */ + ima_template_show(m, (struct ima_template_data *)&e->template, + IMA_SHOW_BINARY); + return 0; +} + +static const struct seq_operations ima_measurments_seqops = { + .start = ima_measurements_start, + .next = ima_measurements_next, + .stop = ima_measurements_stop, + .show = ima_measurements_show +}; + +static int ima_measurements_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &ima_measurments_seqops); +} + +static const struct file_operations ima_measurements_ops = { + .open = ima_measurements_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static void ima_print_digest(struct seq_file *m, u8 *digest) +{ + int i; + + for (i = 0; i < IMA_DIGEST_SIZE; i++) + seq_printf(m, "%02x", *(digest + i)); +} + +void ima_template_show(struct seq_file *m, void *e, enum ima_show_type show) +{ + struct ima_template_data *entry = e; + int namelen; + + switch (show) { + case IMA_SHOW_ASCII: + ima_print_digest(m, entry->digest); + seq_printf(m, " %s\n", entry->file_name); + break; + case IMA_SHOW_BINARY: + ima_putc(m, entry->digest, IMA_DIGEST_SIZE); + + namelen = strlen(entry->file_name); + ima_putc(m, &namelen, sizeof namelen); + ima_putc(m, entry->file_name, namelen); + default: + break; + } +} + +/* print in ascii */ +static int ima_ascii_measurements_show(struct seq_file *m, void *v) +{ + /* the list never shrinks, so we don't need a lock here */ + struct ima_queue_entry *qe = v; + struct ima_template_entry *e; + + /* get entry */ + e = qe->entry; + if (e == NULL) + return -1; + + /* 1st: PCR used (config option) */ + seq_printf(m, "%2d ", CONFIG_IMA_MEASURE_PCR_IDX); + + /* 2nd: SHA1 template hash */ + ima_print_digest(m, e->digest); + + /* 3th: template name */ + seq_printf(m, " %s ", e->template_name); + + /* 4th: template specific data */ + ima_template_show(m, (struct ima_template_data *)&e->template, + IMA_SHOW_ASCII); + return 0; +} + +static const struct seq_operations ima_ascii_measurements_seqops = { + .start = ima_measurements_start, + .next = ima_measurements_next, + .stop = ima_measurements_stop, + .show = ima_ascii_measurements_show +}; + +static int ima_ascii_measurements_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &ima_ascii_measurements_seqops); +} + +static const struct file_operations ima_ascii_measurements_ops = { + .open = ima_ascii_measurements_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static ssize_t ima_write_policy(struct file *file, const char __user *buf, + size_t datalen, loff_t *ppos) +{ + char *data = NULL; + ssize_t result; + + if (datalen >= PAGE_SIZE) + datalen = PAGE_SIZE - 1; + + /* No partial writes. */ + result = -EINVAL; + if (*ppos != 0) + goto out; + + result = -ENOMEM; + data = kmalloc(datalen + 1, GFP_KERNEL); + if (!data) + goto out; + + *(data + datalen) = '\0'; + + result = -EFAULT; + if (copy_from_user(data, buf, datalen)) + goto out; + + result = ima_parse_add_rule(data); +out: + if (result < 0) + valid_policy = 0; + kfree(data); + return result; +} + +static struct dentry *ima_dir; +static struct dentry *binary_runtime_measurements; +static struct dentry *ascii_runtime_measurements; +static struct dentry *runtime_measurements_count; +static struct dentry *violations; +static struct dentry *ima_policy; + +static atomic_t policy_opencount = ATOMIC_INIT(1); +/* + * ima_open_policy: sequentialize access to the policy file + */ +int ima_open_policy(struct inode * inode, struct file * filp) +{ + /* No point in being allowed to open it if you aren't going to write */ + if (!(filp->f_flags & O_WRONLY)) + return -EACCES; + if (atomic_dec_and_test(&policy_opencount)) + return 0; + return -EBUSY; +} + +/* + * ima_release_policy - start using the new measure policy rules. + * + * Initially, ima_measure points to the default policy rules, now + * point to the new policy rules, and remove the securityfs policy file, + * assuming a valid policy. + */ +static int ima_release_policy(struct inode *inode, struct file *file) +{ + if (!valid_policy) { + ima_delete_rules(); + valid_policy = 1; + atomic_set(&policy_opencount, 1); + return 0; + } + ima_update_policy(); + securityfs_remove(ima_policy); + ima_policy = NULL; + return 0; +} + +static const struct file_operations ima_measure_policy_ops = { + .open = ima_open_policy, + .write = ima_write_policy, + .release = ima_release_policy, + .llseek = generic_file_llseek, +}; + +int __init ima_fs_init(void) +{ + ima_dir = securityfs_create_dir("ima", NULL); + if (IS_ERR(ima_dir)) + return -1; + + binary_runtime_measurements = + securityfs_create_file("binary_runtime_measurements", + S_IRUSR | S_IRGRP, ima_dir, NULL, + &ima_measurements_ops); + if (IS_ERR(binary_runtime_measurements)) + goto out; + + ascii_runtime_measurements = + securityfs_create_file("ascii_runtime_measurements", + S_IRUSR | S_IRGRP, ima_dir, NULL, + &ima_ascii_measurements_ops); + if (IS_ERR(ascii_runtime_measurements)) + goto out; + + runtime_measurements_count = + securityfs_create_file("runtime_measurements_count", + S_IRUSR | S_IRGRP, ima_dir, NULL, + &ima_measurements_count_ops); + if (IS_ERR(runtime_measurements_count)) + goto out; + + violations = + securityfs_create_file("violations", S_IRUSR | S_IRGRP, + ima_dir, NULL, &ima_htable_violations_ops); + if (IS_ERR(violations)) + goto out; + + ima_policy = securityfs_create_file("policy", + S_IWUSR, + ima_dir, NULL, + &ima_measure_policy_ops); + if (IS_ERR(ima_policy)) + goto out; + + return 0; +out: + securityfs_remove(runtime_measurements_count); + securityfs_remove(ascii_runtime_measurements); + securityfs_remove(binary_runtime_measurements); + securityfs_remove(ima_dir); + securityfs_remove(ima_policy); + return -1; +} + +void __exit ima_fs_cleanup(void) +{ + securityfs_remove(violations); + securityfs_remove(runtime_measurements_count); + securityfs_remove(ascii_runtime_measurements); + securityfs_remove(binary_runtime_measurements); + securityfs_remove(ima_dir); + securityfs_remove(ima_policy); +} diff --git a/security/integrity/ima/ima_iint.c b/security/integrity/ima/ima_iint.c new file mode 100644 index 00000000..4ae73040 --- /dev/null +++ b/security/integrity/ima/ima_iint.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2008 IBM Corporation + * + * Authors: + * Mimi Zohar <zohar@us.ibm.com> + * + * 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. + * + * File: ima_iint.c + * - implements the IMA hooks: ima_inode_alloc, ima_inode_free + * - cache integrity information associated with an inode + * using a rbtree tree. + */ +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/rbtree.h> +#include "ima.h" + +static struct rb_root ima_iint_tree = RB_ROOT; +static DEFINE_SPINLOCK(ima_iint_lock); +static struct kmem_cache *iint_cache __read_mostly; + +int iint_initialized = 0; + +/* + * __ima_iint_find - return the iint associated with an inode + */ +static struct ima_iint_cache *__ima_iint_find(struct inode *inode) +{ + struct ima_iint_cache *iint; + struct rb_node *n = ima_iint_tree.rb_node; + + assert_spin_locked(&ima_iint_lock); + + while (n) { + iint = rb_entry(n, struct ima_iint_cache, rb_node); + + if (inode < iint->inode) + n = n->rb_left; + else if (inode > iint->inode) + n = n->rb_right; + else + break; + } + if (!n) + return NULL; + + return iint; +} + +/* + * ima_iint_find - return the iint associated with an inode + */ +struct ima_iint_cache *ima_iint_find(struct inode *inode) +{ + struct ima_iint_cache *iint; + + if (!IS_IMA(inode)) + return NULL; + + spin_lock(&ima_iint_lock); + iint = __ima_iint_find(inode); + spin_unlock(&ima_iint_lock); + + return iint; +} + +static void iint_free(struct ima_iint_cache *iint) +{ + iint->version = 0; + iint->flags = 0UL; + kmem_cache_free(iint_cache, iint); +} + +/** + * ima_inode_alloc - allocate an iint associated with an inode + * @inode: pointer to the inode + */ +int ima_inode_alloc(struct inode *inode) +{ + struct rb_node **p; + struct rb_node *new_node, *parent = NULL; + struct ima_iint_cache *new_iint, *test_iint; + int rc; + + new_iint = kmem_cache_alloc(iint_cache, GFP_NOFS); + if (!new_iint) + return -ENOMEM; + + new_iint->inode = inode; + new_node = &new_iint->rb_node; + + mutex_lock(&inode->i_mutex); /* i_flags */ + spin_lock(&ima_iint_lock); + + p = &ima_iint_tree.rb_node; + while (*p) { + parent = *p; + test_iint = rb_entry(parent, struct ima_iint_cache, rb_node); + + rc = -EEXIST; + if (inode < test_iint->inode) + p = &(*p)->rb_left; + else if (inode > test_iint->inode) + p = &(*p)->rb_right; + else + goto out_err; + } + + inode->i_flags |= S_IMA; + rb_link_node(new_node, parent, p); + rb_insert_color(new_node, &ima_iint_tree); + + spin_unlock(&ima_iint_lock); + mutex_unlock(&inode->i_mutex); /* i_flags */ + + return 0; +out_err: + spin_unlock(&ima_iint_lock); + mutex_unlock(&inode->i_mutex); /* i_flags */ + iint_free(new_iint); + + return rc; +} + +/** + * ima_inode_free - called on security_inode_free + * @inode: pointer to the inode + * + * Free the integrity information(iint) associated with an inode. + */ +void ima_inode_free(struct inode *inode) +{ + struct ima_iint_cache *iint; + + if (!IS_IMA(inode)) + return; + + spin_lock(&ima_iint_lock); + iint = __ima_iint_find(inode); + rb_erase(&iint->rb_node, &ima_iint_tree); + spin_unlock(&ima_iint_lock); + + iint_free(iint); +} + +static void init_once(void *foo) +{ + struct ima_iint_cache *iint = foo; + + memset(iint, 0, sizeof *iint); + iint->version = 0; + iint->flags = 0UL; + mutex_init(&iint->mutex); +} + +static int __init ima_iintcache_init(void) +{ + iint_cache = + kmem_cache_create("iint_cache", sizeof(struct ima_iint_cache), 0, + SLAB_PANIC, init_once); + iint_initialized = 1; + return 0; +} +security_initcall(ima_iintcache_init); diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c new file mode 100644 index 00000000..17f1f060 --- /dev/null +++ b/security/integrity/ima/ima_init.c @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * + * Authors: + * Reiner Sailer <sailer@watson.ibm.com> + * Leendert van Doorn <leendert@watson.ibm.com> + * Mimi Zohar <zohar@us.ibm.com> + * + * 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. + * + * File: ima_init.c + * initialization and cleanup functions + */ +#include <linux/module.h> +#include <linux/scatterlist.h> +#include <linux/slab.h> +#include <linux/err.h> +#include "ima.h" + +/* name for boot aggregate entry */ +static const char *boot_aggregate_name = "boot_aggregate"; +int ima_used_chip; + +/* Add the boot aggregate to the IMA measurement list and extend + * the PCR register. + * + * Calculate the boot aggregate, a SHA1 over tpm registers 0-7, + * assuming a TPM chip exists, and zeroes if the TPM chip does not + * exist. Add the boot aggregate measurement to the measurement + * list and extend the PCR register. + * + * If a tpm chip does not exist, indicate the core root of trust is + * not hardware based by invalidating the aggregate PCR value. + * (The aggregate PCR value is invalidated by adding one value to + * the measurement list and extending the aggregate PCR value with + * a different value.) Violations add a zero entry to the measurement + * list and extend the aggregate PCR value with ff...ff's. + */ +static void __init ima_add_boot_aggregate(void) +{ + struct ima_template_entry *entry; + const char *op = "add_boot_aggregate"; + const char *audit_cause = "ENOMEM"; + int result = -ENOMEM; + int violation = 1; + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + goto err_out; + + memset(&entry->template, 0, sizeof(entry->template)); + strncpy(entry->template.file_name, boot_aggregate_name, + IMA_EVENT_NAME_LEN_MAX); + if (ima_used_chip) { + violation = 0; + result = ima_calc_boot_aggregate(entry->template.digest); + if (result < 0) { + audit_cause = "hashing_error"; + kfree(entry); + goto err_out; + } + } + result = ima_store_template(entry, violation, NULL); + if (result < 0) + kfree(entry); + return; +err_out: + integrity_audit_msg(AUDIT_INTEGRITY_PCR, NULL, boot_aggregate_name, op, + audit_cause, result, 0); +} + +int __init ima_init(void) +{ + u8 pcr_i[IMA_DIGEST_SIZE]; + int rc; + + ima_used_chip = 0; + rc = tpm_pcr_read(TPM_ANY_NUM, 0, pcr_i); + if (rc == 0) + ima_used_chip = 1; + + if (!ima_used_chip) + pr_info("IMA: No TPM chip found, activating TPM-bypass!\n"); + + ima_add_boot_aggregate(); /* boot aggregate must be first entry */ + ima_init_policy(); + + return ima_fs_init(); +} + +void __exit ima_cleanup(void) +{ + ima_fs_cleanup(); +} diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c new file mode 100644 index 00000000..39d66dc2 --- /dev/null +++ b/security/integrity/ima/ima_main.c @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * + * Authors: + * Reiner Sailer <sailer@watson.ibm.com> + * Serge Hallyn <serue@us.ibm.com> + * Kylene Hall <kylene@us.ibm.com> + * Mimi Zohar <zohar@us.ibm.com> + * + * 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. + * + * File: ima_main.c + * implements the IMA hooks: ima_bprm_check, ima_file_mmap, + * and ima_file_check. + */ +#include <linux/module.h> +#include <linux/file.h> +#include <linux/binfmts.h> +#include <linux/mount.h> +#include <linux/mman.h> +#include <linux/slab.h> + +#include "ima.h" + +int ima_initialized; + +char *ima_hash = "sha1"; +static int __init hash_setup(char *str) +{ + if (strncmp(str, "md5", 3) == 0) + ima_hash = "md5"; + return 1; +} +__setup("ima_hash=", hash_setup); + +/* + * ima_rdwr_violation_check + * + * Only invalidate the PCR for measured files: + * - Opening a file for write when already open for read, + * results in a time of measure, time of use (ToMToU) error. + * - Opening a file for read when already open for write, + * could result in a file measurement error. + * + */ +static void ima_rdwr_violation_check(struct file *file) +{ + struct dentry *dentry = file->f_path.dentry; + struct inode *inode = dentry->d_inode; + fmode_t mode = file->f_mode; + int rc; + bool send_tomtou = false, send_writers = false; + + if (!S_ISREG(inode->i_mode) || !ima_initialized) + return; + + mutex_lock(&inode->i_mutex); /* file metadata: permissions, xattr */ + + if (mode & FMODE_WRITE) { + if (atomic_read(&inode->i_readcount) && IS_IMA(inode)) + send_tomtou = true; + goto out; + } + + rc = ima_must_measure(inode, MAY_READ, FILE_CHECK); + if (rc < 0) + goto out; + + if (atomic_read(&inode->i_writecount) > 0) + send_writers = true; +out: + mutex_unlock(&inode->i_mutex); + + if (send_tomtou) + ima_add_violation(inode, dentry->d_name.name, "invalid_pcr", + "ToMToU"); + if (send_writers) + ima_add_violation(inode, dentry->d_name.name, "invalid_pcr", + "open_writers"); +} + +static void ima_check_last_writer(struct ima_iint_cache *iint, + struct inode *inode, + struct file *file) +{ + mode_t mode = file->f_mode; + + mutex_lock(&iint->mutex); + if (mode & FMODE_WRITE && + atomic_read(&inode->i_writecount) == 1 && + iint->version != inode->i_version) + iint->flags &= ~IMA_MEASURED; + mutex_unlock(&iint->mutex); +} + +/** + * ima_file_free - called on __fput() + * @file: pointer to file structure being freed + * + * Flag files that changed, based on i_version + */ +void ima_file_free(struct file *file) +{ + struct inode *inode = file->f_dentry->d_inode; + struct ima_iint_cache *iint; + + if (!iint_initialized || !S_ISREG(inode->i_mode)) + return; + + iint = ima_iint_find(inode); + if (!iint) + return; + + ima_check_last_writer(iint, inode, file); +} + +static int process_measurement(struct file *file, const unsigned char *filename, + int mask, int function) +{ + struct inode *inode = file->f_dentry->d_inode; + struct ima_iint_cache *iint; + int rc = 0; + + if (!ima_initialized || !S_ISREG(inode->i_mode)) + return 0; + + rc = ima_must_measure(inode, mask, function); + if (rc != 0) + return rc; +retry: + iint = ima_iint_find(inode); + if (!iint) { + rc = ima_inode_alloc(inode); + if (!rc || rc == -EEXIST) + goto retry; + return rc; + } + + mutex_lock(&iint->mutex); + + rc = iint->flags & IMA_MEASURED ? 1 : 0; + if (rc != 0) + goto out; + + rc = ima_collect_measurement(iint, file); + if (!rc) + ima_store_measurement(iint, file, filename); +out: + mutex_unlock(&iint->mutex); + return rc; +} + +/** + * ima_file_mmap - based on policy, collect/store measurement. + * @file: pointer to the file to be measured (May be NULL) + * @prot: contains the protection that will be applied by the kernel. + * + * Measure files being mmapped executable based on the ima_must_measure() + * policy decision. + * + * Return 0 on success, an error code on failure. + * (Based on the results of appraise_measurement().) + */ +int ima_file_mmap(struct file *file, unsigned long prot) +{ + int rc; + + if (!file) + return 0; + if (prot & PROT_EXEC) + rc = process_measurement(file, file->f_dentry->d_name.name, + MAY_EXEC, FILE_MMAP); + return 0; +} + +/** + * ima_bprm_check - based on policy, collect/store measurement. + * @bprm: contains the linux_binprm structure + * + * The OS protects against an executable file, already open for write, + * from being executed in deny_write_access() and an executable file, + * already open for execute, from being modified in get_write_access(). + * So we can be certain that what we verify and measure here is actually + * what is being executed. + * + * Return 0 on success, an error code on failure. + * (Based on the results of appraise_measurement().) + */ +int ima_bprm_check(struct linux_binprm *bprm) +{ + int rc; + + rc = process_measurement(bprm->file, bprm->filename, + MAY_EXEC, BPRM_CHECK); + return 0; +} + +/** + * ima_path_check - based on policy, collect/store measurement. + * @file: pointer to the file to be measured + * @mask: contains MAY_READ, MAY_WRITE or MAY_EXECUTE + * + * Measure files based on the ima_must_measure() policy decision. + * + * Always return 0 and audit dentry_open failures. + * (Return code will be based upon measurement appraisal.) + */ +int ima_file_check(struct file *file, int mask) +{ + int rc; + + ima_rdwr_violation_check(file); + rc = process_measurement(file, file->f_dentry->d_name.name, + mask & (MAY_READ | MAY_WRITE | MAY_EXEC), + FILE_CHECK); + return 0; +} +EXPORT_SYMBOL_GPL(ima_file_check); + +static int __init init_ima(void) +{ + int error; + + error = ima_init(); + ima_initialized = 1; + return error; +} + +static void __exit cleanup_ima(void) +{ + ima_cleanup(); +} + +late_initcall(init_ima); /* Start IMA after the TPM is available */ + +MODULE_DESCRIPTION("Integrity Measurement Architecture"); +MODULE_LICENSE("GPL"); diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c new file mode 100644 index 00000000..d661afbe --- /dev/null +++ b/security/integrity/ima/ima_policy.c @@ -0,0 +1,491 @@ +/* + * Copyright (C) 2008 IBM Corporation + * Author: Mimi Zohar <zohar@us.ibm.com> + * + * 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. + * + * ima_policy.c + * - initialize default measure policy rules + * + */ +#include <linux/module.h> +#include <linux/list.h> +#include <linux/security.h> +#include <linux/magic.h> +#include <linux/parser.h> +#include <linux/slab.h> + +#include "ima.h" + +/* flags definitions */ +#define IMA_FUNC 0x0001 +#define IMA_MASK 0x0002 +#define IMA_FSMAGIC 0x0004 +#define IMA_UID 0x0008 + +enum ima_action { UNKNOWN = -1, DONT_MEASURE = 0, MEASURE }; + +#define MAX_LSM_RULES 6 +enum lsm_rule_types { LSM_OBJ_USER, LSM_OBJ_ROLE, LSM_OBJ_TYPE, + LSM_SUBJ_USER, LSM_SUBJ_ROLE, LSM_SUBJ_TYPE +}; + +struct ima_measure_rule_entry { + struct list_head list; + enum ima_action action; + unsigned int flags; + enum ima_hooks func; + int mask; + unsigned long fsmagic; + uid_t uid; + struct { + void *rule; /* LSM file metadata specific */ + int type; /* audit type */ + } lsm[MAX_LSM_RULES]; +}; + +/* + * Without LSM specific knowledge, the default policy can only be + * written in terms of .action, .func, .mask, .fsmagic, and .uid + */ + +/* + * The minimum rule set to allow for full TCB coverage. Measures all files + * opened or mmap for exec and everything read by root. Dangerous because + * normal users can easily run the machine out of memory simply building + * and running executables. + */ +static struct ima_measure_rule_entry default_rules[] = { + {.action = DONT_MEASURE,.fsmagic = PROC_SUPER_MAGIC,.flags = IMA_FSMAGIC}, + {.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 = SECURITYFS_MAGIC,.flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE,.fsmagic = SELINUX_MAGIC,.flags = IMA_FSMAGIC}, + {.action = MEASURE,.func = FILE_MMAP,.mask = MAY_EXEC, + .flags = IMA_FUNC | IMA_MASK}, + {.action = MEASURE,.func = BPRM_CHECK,.mask = MAY_EXEC, + .flags = IMA_FUNC | IMA_MASK}, + {.action = MEASURE,.func = FILE_CHECK,.mask = MAY_READ,.uid = 0, + .flags = IMA_FUNC | IMA_MASK | IMA_UID}, +}; + +static LIST_HEAD(measure_default_rules); +static LIST_HEAD(measure_policy_rules); +static struct list_head *ima_measure; + +static DEFINE_MUTEX(ima_measure_mutex); + +static bool ima_use_tcb __initdata; +static int __init default_policy_setup(char *str) +{ + ima_use_tcb = 1; + return 1; +} +__setup("ima_tcb", default_policy_setup); + +/** + * ima_match_rules - determine whether an inode matches the measure rule. + * @rule: a pointer to a rule + * @inode: a pointer to an inode + * @func: LIM hook identifier + * @mask: requested action (MAY_READ | MAY_WRITE | MAY_APPEND | MAY_EXEC) + * + * Returns true on rule match, false on failure. + */ +static bool ima_match_rules(struct ima_measure_rule_entry *rule, + struct inode *inode, enum ima_hooks func, int mask) +{ + struct task_struct *tsk = current; + int i; + + if ((rule->flags & IMA_FUNC) && rule->func != func) + return false; + if ((rule->flags & IMA_MASK) && rule->mask != mask) + return false; + if ((rule->flags & IMA_FSMAGIC) + && rule->fsmagic != inode->i_sb->s_magic) + return false; + if ((rule->flags & IMA_UID) && rule->uid != tsk->cred->uid) + return false; + for (i = 0; i < MAX_LSM_RULES; i++) { + int rc = 0; + u32 osid, sid; + + if (!rule->lsm[i].rule) + continue; + + switch (i) { + case LSM_OBJ_USER: + case LSM_OBJ_ROLE: + case LSM_OBJ_TYPE: + security_inode_getsecid(inode, &osid); + rc = security_filter_rule_match(osid, + rule->lsm[i].type, + Audit_equal, + rule->lsm[i].rule, + NULL); + break; + case LSM_SUBJ_USER: + case LSM_SUBJ_ROLE: + case LSM_SUBJ_TYPE: + security_task_getsecid(tsk, &sid); + rc = security_filter_rule_match(sid, + rule->lsm[i].type, + Audit_equal, + rule->lsm[i].rule, + NULL); + default: + break; + } + if (!rc) + return false; + } + return true; +} + +/** + * ima_match_policy - decision based on LSM and other conditions + * @inode: pointer to an inode for which the policy decision is being made + * @func: IMA hook identifier + * @mask: requested action (MAY_READ | MAY_WRITE | MAY_APPEND | MAY_EXEC) + * + * Measure decision based on func/mask/fsmagic and LSM(subj/obj/type) + * conditions. + * + * (There is no need for locking when walking the policy list, + * as elements in the list are never deleted, nor does the list + * change.) + */ +int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask) +{ + struct ima_measure_rule_entry *entry; + + list_for_each_entry(entry, ima_measure, list) { + bool rc; + + rc = ima_match_rules(entry, inode, func, mask); + if (rc) + return entry->action; + } + return 0; +} + +/** + * ima_init_policy - initialize the default measure rules. + * + * ima_measure points to either the measure_default_rules or the + * the new measure_policy_rules. + */ +void __init ima_init_policy(void) +{ + int i, entries; + + /* if !ima_use_tcb set entries = 0 so we load NO default rules */ + if (ima_use_tcb) + entries = ARRAY_SIZE(default_rules); + else + entries = 0; + + for (i = 0; i < entries; i++) + list_add_tail(&default_rules[i].list, &measure_default_rules); + ima_measure = &measure_default_rules; +} + +/** + * ima_update_policy - update default_rules with new measure rules + * + * Called on file .release to update the default rules with a complete new + * policy. Once updated, the policy is locked, no additional rules can be + * added to the policy. + */ +void ima_update_policy(void) +{ + const char *op = "policy_update"; + const char *cause = "already exists"; + int result = 1; + int audit_info = 0; + + if (ima_measure == &measure_default_rules) { + ima_measure = &measure_policy_rules; + cause = "complete"; + result = 0; + } + integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, + NULL, op, cause, result, audit_info); +} + +enum { + Opt_err = -1, + Opt_measure = 1, Opt_dont_measure, + Opt_obj_user, Opt_obj_role, Opt_obj_type, + Opt_subj_user, Opt_subj_role, Opt_subj_type, + Opt_func, Opt_mask, Opt_fsmagic, Opt_uid +}; + +static match_table_t policy_tokens = { + {Opt_measure, "measure"}, + {Opt_dont_measure, "dont_measure"}, + {Opt_obj_user, "obj_user=%s"}, + {Opt_obj_role, "obj_role=%s"}, + {Opt_obj_type, "obj_type=%s"}, + {Opt_subj_user, "subj_user=%s"}, + {Opt_subj_role, "subj_role=%s"}, + {Opt_subj_type, "subj_type=%s"}, + {Opt_func, "func=%s"}, + {Opt_mask, "mask=%s"}, + {Opt_fsmagic, "fsmagic=%s"}, + {Opt_uid, "uid=%s"}, + {Opt_err, NULL} +}; + +static int ima_lsm_rule_init(struct ima_measure_rule_entry *entry, + char *args, int lsm_rule, int audit_type) +{ + int result; + + if (entry->lsm[lsm_rule].rule) + return -EINVAL; + + entry->lsm[lsm_rule].type = audit_type; + result = security_filter_rule_init(entry->lsm[lsm_rule].type, + Audit_equal, args, + &entry->lsm[lsm_rule].rule); + if (!entry->lsm[lsm_rule].rule) + return -EINVAL; + return result; +} + +static void ima_log_string(struct audit_buffer *ab, char *key, char *value) +{ + audit_log_format(ab, "%s=", key); + audit_log_untrustedstring(ab, value); + audit_log_format(ab, " "); +} + +static int ima_parse_rule(char *rule, struct ima_measure_rule_entry *entry) +{ + struct audit_buffer *ab; + char *p; + int result = 0; + + ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_INTEGRITY_RULE); + + entry->uid = -1; + entry->action = UNKNOWN; + while ((p = strsep(&rule, " \t")) != NULL) { + substring_t args[MAX_OPT_ARGS]; + int token; + unsigned long lnum; + + if (result < 0) + break; + if ((*p == '\0') || (*p == ' ') || (*p == '\t')) + continue; + token = match_token(p, policy_tokens, args); + switch (token) { + case Opt_measure: + ima_log_string(ab, "action", "measure"); + + if (entry->action != UNKNOWN) + result = -EINVAL; + + entry->action = MEASURE; + break; + case Opt_dont_measure: + ima_log_string(ab, "action", "dont_measure"); + + if (entry->action != UNKNOWN) + result = -EINVAL; + + entry->action = DONT_MEASURE; + break; + case Opt_func: + ima_log_string(ab, "func", args[0].from); + + if (entry->func) + result = -EINVAL; + + if (strcmp(args[0].from, "FILE_CHECK") == 0) + entry->func = FILE_CHECK; + /* PATH_CHECK is for backwards compat */ + else if (strcmp(args[0].from, "PATH_CHECK") == 0) + entry->func = FILE_CHECK; + else if (strcmp(args[0].from, "FILE_MMAP") == 0) + entry->func = FILE_MMAP; + else if (strcmp(args[0].from, "BPRM_CHECK") == 0) + entry->func = BPRM_CHECK; + else + result = -EINVAL; + if (!result) + entry->flags |= IMA_FUNC; + break; + case Opt_mask: + ima_log_string(ab, "mask", args[0].from); + + if (entry->mask) + result = -EINVAL; + + if ((strcmp(args[0].from, "MAY_EXEC")) == 0) + entry->mask = MAY_EXEC; + else if (strcmp(args[0].from, "MAY_WRITE") == 0) + entry->mask = MAY_WRITE; + else if (strcmp(args[0].from, "MAY_READ") == 0) + entry->mask = MAY_READ; + else if (strcmp(args[0].from, "MAY_APPEND") == 0) + entry->mask = MAY_APPEND; + else + result = -EINVAL; + if (!result) + entry->flags |= IMA_MASK; + break; + case Opt_fsmagic: + ima_log_string(ab, "fsmagic", args[0].from); + + if (entry->fsmagic) { + result = -EINVAL; + break; + } + + result = strict_strtoul(args[0].from, 16, + &entry->fsmagic); + if (!result) + entry->flags |= IMA_FSMAGIC; + break; + case Opt_uid: + ima_log_string(ab, "uid", args[0].from); + + if (entry->uid != -1) { + result = -EINVAL; + break; + } + + result = strict_strtoul(args[0].from, 10, &lnum); + if (!result) { + entry->uid = (uid_t) lnum; + if (entry->uid != lnum) + result = -EINVAL; + else + entry->flags |= IMA_UID; + } + break; + case Opt_obj_user: + ima_log_string(ab, "obj_user", args[0].from); + result = ima_lsm_rule_init(entry, args[0].from, + LSM_OBJ_USER, + AUDIT_OBJ_USER); + break; + case Opt_obj_role: + ima_log_string(ab, "obj_role", args[0].from); + result = ima_lsm_rule_init(entry, args[0].from, + LSM_OBJ_ROLE, + AUDIT_OBJ_ROLE); + break; + case Opt_obj_type: + ima_log_string(ab, "obj_type", args[0].from); + result = ima_lsm_rule_init(entry, args[0].from, + LSM_OBJ_TYPE, + AUDIT_OBJ_TYPE); + break; + case Opt_subj_user: + ima_log_string(ab, "subj_user", args[0].from); + result = ima_lsm_rule_init(entry, args[0].from, + LSM_SUBJ_USER, + AUDIT_SUBJ_USER); + break; + case Opt_subj_role: + ima_log_string(ab, "subj_role", args[0].from); + result = ima_lsm_rule_init(entry, args[0].from, + LSM_SUBJ_ROLE, + AUDIT_SUBJ_ROLE); + break; + case Opt_subj_type: + ima_log_string(ab, "subj_type", args[0].from); + result = ima_lsm_rule_init(entry, args[0].from, + LSM_SUBJ_TYPE, + AUDIT_SUBJ_TYPE); + break; + case Opt_err: + ima_log_string(ab, "UNKNOWN", p); + result = -EINVAL; + break; + } + } + if (!result && (entry->action == UNKNOWN)) + result = -EINVAL; + + audit_log_format(ab, "res=%d", !!result); + audit_log_end(ab); + return result; +} + +/** + * ima_parse_add_rule - add a rule to measure_policy_rules + * @rule - ima measurement policy rule + * + * Uses a mutex to protect the policy list from multiple concurrent writers. + * Returns the length of the rule parsed, an error code on failure + */ +ssize_t ima_parse_add_rule(char *rule) +{ + const char *op = "update_policy"; + char *p; + struct ima_measure_rule_entry *entry; + ssize_t result, len; + int audit_info = 0; + + /* Prevent installed policy from changing */ + if (ima_measure != &measure_default_rules) { + integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, + NULL, op, "already exists", + -EACCES, audit_info); + return -EACCES; + } + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) { + integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, + NULL, op, "-ENOMEM", -ENOMEM, audit_info); + return -ENOMEM; + } + + INIT_LIST_HEAD(&entry->list); + + p = strsep(&rule, "\n"); + len = strlen(p) + 1; + + if (*p == '#') { + kfree(entry); + return len; + } + + result = ima_parse_rule(p, entry); + if (result) { + kfree(entry); + integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, + NULL, op, "invalid policy", result, + audit_info); + return result; + } + + mutex_lock(&ima_measure_mutex); + list_add_tail(&entry->list, &measure_policy_rules); + mutex_unlock(&ima_measure_mutex); + + return len; +} + +/* ima_delete_rules called to cleanup invalid policy */ +void ima_delete_rules(void) +{ + struct ima_measure_rule_entry *entry, *tmp; + + mutex_lock(&ima_measure_mutex); + list_for_each_entry_safe(entry, tmp, &measure_policy_rules, list) { + list_del(&entry->list); + kfree(entry); + } + mutex_unlock(&ima_measure_mutex); +} diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c new file mode 100644 index 00000000..55a6271b --- /dev/null +++ b/security/integrity/ima/ima_queue.c @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * + * Authors: + * Serge Hallyn <serue@us.ibm.com> + * Reiner Sailer <sailer@watson.ibm.com> + * Mimi Zohar <zohar@us.ibm.com> + * + * 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. + * + * File: ima_queue.c + * Implements queues that store template measurements and + * maintains aggregate over the stored measurements + * in the pre-configured TPM PCR (if available). + * The measurement list is append-only. No entry is + * ever removed or changed during the boot-cycle. + */ +#include <linux/module.h> +#include <linux/rculist.h> +#include <linux/slab.h> +#include "ima.h" + +#define AUDIT_CAUSE_LEN_MAX 32 + +LIST_HEAD(ima_measurements); /* list of all measurements */ + +/* key: inode (before secure-hashing a file) */ +struct ima_h_table ima_htable = { + .len = ATOMIC_LONG_INIT(0), + .violations = ATOMIC_LONG_INIT(0), + .queue[0 ... IMA_MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT +}; + +/* mutex protects atomicity of extending measurement list + * and extending the TPM PCR aggregate. Since tpm_extend can take + * long (and the tpm driver uses a mutex), we can't use the spinlock. + */ +static DEFINE_MUTEX(ima_extend_list_mutex); + +/* lookup up the digest value in the hash table, and return the entry */ +static struct ima_queue_entry *ima_lookup_digest_entry(u8 *digest_value) +{ + struct ima_queue_entry *qe, *ret = NULL; + unsigned int key; + struct hlist_node *pos; + int rc; + + key = ima_hash_key(digest_value); + rcu_read_lock(); + hlist_for_each_entry_rcu(qe, pos, &ima_htable.queue[key], hnext) { + rc = memcmp(qe->entry->digest, digest_value, IMA_DIGEST_SIZE); + if (rc == 0) { + ret = qe; + break; + } + } + rcu_read_unlock(); + return ret; +} + +/* ima_add_template_entry helper function: + * - Add template entry to measurement list and hash table. + * + * (Called with ima_extend_list_mutex held.) + */ +static int ima_add_digest_entry(struct ima_template_entry *entry) +{ + struct ima_queue_entry *qe; + unsigned int key; + + qe = kmalloc(sizeof(*qe), GFP_KERNEL); + if (qe == NULL) { + pr_err("IMA: OUT OF MEMORY ERROR creating queue entry.\n"); + return -ENOMEM; + } + qe->entry = entry; + + INIT_LIST_HEAD(&qe->later); + list_add_tail_rcu(&qe->later, &ima_measurements); + + atomic_long_inc(&ima_htable.len); + key = ima_hash_key(entry->digest); + hlist_add_head_rcu(&qe->hnext, &ima_htable.queue[key]); + return 0; +} + +static int ima_pcr_extend(const u8 *hash) +{ + int result = 0; + + if (!ima_used_chip) + return result; + + result = tpm_pcr_extend(TPM_ANY_NUM, CONFIG_IMA_MEASURE_PCR_IDX, hash); + if (result != 0) + pr_err("IMA: Error Communicating to TPM chip, result: %d\n", + result); + return result; +} + +/* Add template entry to the measurement list and hash table, + * and extend the pcr. + */ +int ima_add_template_entry(struct ima_template_entry *entry, int violation, + const char *op, struct inode *inode) +{ + u8 digest[IMA_DIGEST_SIZE]; + const char *audit_cause = "hash_added"; + char tpm_audit_cause[AUDIT_CAUSE_LEN_MAX]; + int audit_info = 1; + int result = 0, tpmresult = 0; + + mutex_lock(&ima_extend_list_mutex); + if (!violation) { + memcpy(digest, entry->digest, sizeof digest); + if (ima_lookup_digest_entry(digest)) { + audit_cause = "hash_exists"; + result = -EEXIST; + goto out; + } + } + + result = ima_add_digest_entry(entry); + if (result < 0) { + audit_cause = "ENOMEM"; + audit_info = 0; + goto out; + } + + if (violation) /* invalidate pcr */ + memset(digest, 0xff, sizeof digest); + + tpmresult = ima_pcr_extend(digest); + if (tpmresult != 0) { + snprintf(tpm_audit_cause, AUDIT_CAUSE_LEN_MAX, "TPM_error(%d)", + tpmresult); + audit_cause = tpm_audit_cause; + audit_info = 0; + } +out: + mutex_unlock(&ima_extend_list_mutex); + integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, + entry->template.file_name, + op, audit_cause, result, audit_info); + return result; +} diff --git a/security/keys/Makefile b/security/keys/Makefile new file mode 100644 index 00000000..1bf090a8 --- /dev/null +++ b/security/keys/Makefile @@ -0,0 +1,20 @@ +# +# Makefile for key management +# + +obj-y := \ + gc.o \ + key.o \ + keyring.o \ + keyctl.o \ + permission.o \ + process_keys.o \ + request_key.o \ + request_key_auth.o \ + user_defined.o + +obj-$(CONFIG_TRUSTED_KEYS) += trusted.o +obj-$(CONFIG_ENCRYPTED_KEYS) += encrypted.o +obj-$(CONFIG_KEYS_COMPAT) += compat.o +obj-$(CONFIG_PROC_FS) += proc.o +obj-$(CONFIG_SYSCTL) += sysctl.o diff --git a/security/keys/compat.c b/security/keys/compat.c new file mode 100644 index 00000000..338b510e --- /dev/null +++ b/security/keys/compat.c @@ -0,0 +1,141 @@ +/* 32-bit compatibility syscall for 64-bit systems + * + * Copyright (C) 2004-5 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/syscalls.h> +#include <linux/keyctl.h> +#include <linux/compat.h> +#include <linux/slab.h> +#include "internal.h" + +/* + * Instantiate a key with the specified compatibility multipart payload and + * link the key into the destination keyring if one is given. + * + * The caller must have the appropriate instantiation permit set for this to + * work (see keyctl_assume_authority). No other permissions are required. + * + * If successful, 0 will be returned. + */ +long compat_keyctl_instantiate_key_iov( + key_serial_t id, + const struct compat_iovec __user *_payload_iov, + unsigned ioc, + key_serial_t ringid) +{ + struct iovec iovstack[UIO_FASTIOV], *iov = iovstack; + long ret; + + if (_payload_iov == 0 || ioc == 0) + goto no_payload; + + ret = compat_rw_copy_check_uvector(WRITE, _payload_iov, ioc, + ARRAY_SIZE(iovstack), + iovstack, &iov); + if (ret < 0) + return ret; + if (ret == 0) + goto no_payload_free; + + ret = keyctl_instantiate_key_common(id, iov, ioc, ret, ringid); + + if (iov != iovstack) + kfree(iov); + return ret; + +no_payload_free: + if (iov != iovstack) + kfree(iov); +no_payload: + return keyctl_instantiate_key_common(id, NULL, 0, 0, ringid); +} + +/* + * The key control system call, 32-bit compatibility version for 64-bit archs + * + * This should only be called if the 64-bit arch uses weird pointers in 32-bit + * mode or doesn't guarantee that the top 32-bits of the argument registers on + * taking a 32-bit syscall are zero. If you can, you should call sys_keyctl() + * directly. + */ +asmlinkage long compat_sys_keyctl(u32 option, + u32 arg2, u32 arg3, u32 arg4, u32 arg5) +{ + switch (option) { + case KEYCTL_GET_KEYRING_ID: + return keyctl_get_keyring_ID(arg2, arg3); + + case KEYCTL_JOIN_SESSION_KEYRING: + return keyctl_join_session_keyring(compat_ptr(arg2)); + + case KEYCTL_UPDATE: + return keyctl_update_key(arg2, compat_ptr(arg3), arg4); + + case KEYCTL_REVOKE: + return keyctl_revoke_key(arg2); + + case KEYCTL_DESCRIBE: + return keyctl_describe_key(arg2, compat_ptr(arg3), arg4); + + case KEYCTL_CLEAR: + return keyctl_keyring_clear(arg2); + + case KEYCTL_LINK: + return keyctl_keyring_link(arg2, arg3); + + case KEYCTL_UNLINK: + return keyctl_keyring_unlink(arg2, arg3); + + case KEYCTL_SEARCH: + return keyctl_keyring_search(arg2, compat_ptr(arg3), + compat_ptr(arg4), arg5); + + case KEYCTL_READ: + return keyctl_read_key(arg2, compat_ptr(arg3), arg4); + + case KEYCTL_CHOWN: + return keyctl_chown_key(arg2, arg3, arg4); + + case KEYCTL_SETPERM: + return keyctl_setperm_key(arg2, arg3); + + case KEYCTL_INSTANTIATE: + return keyctl_instantiate_key(arg2, compat_ptr(arg3), arg4, + arg5); + + case KEYCTL_NEGATE: + return keyctl_negate_key(arg2, arg3, arg4); + + case KEYCTL_SET_REQKEY_KEYRING: + return keyctl_set_reqkey_keyring(arg2); + + case KEYCTL_SET_TIMEOUT: + return keyctl_set_timeout(arg2, arg3); + + case KEYCTL_ASSUME_AUTHORITY: + return keyctl_assume_authority(arg2); + + case KEYCTL_GET_SECURITY: + return keyctl_get_security(arg2, compat_ptr(arg3), arg4); + + case KEYCTL_SESSION_TO_PARENT: + return keyctl_session_to_parent(); + + case KEYCTL_REJECT: + return keyctl_reject_key(arg2, arg3, arg4, arg5); + + case KEYCTL_INSTANTIATE_IOV: + return compat_keyctl_instantiate_key_iov( + arg2, compat_ptr(arg3), arg4, arg5); + + default: + return -EOPNOTSUPP; + } +} diff --git a/security/keys/encrypted.c b/security/keys/encrypted.c new file mode 100644 index 00000000..b1cba5bf --- /dev/null +++ b/security/keys/encrypted.c @@ -0,0 +1,902 @@ +/* + * Copyright (C) 2010 IBM Corporation + * + * Author: + * Mimi Zohar <zohar@us.ibm.com> + * + * 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. + * + * See Documentation/security/keys-trusted-encrypted.txt + */ + +#include <linux/uaccess.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/parser.h> +#include <linux/string.h> +#include <linux/err.h> +#include <keys/user-type.h> +#include <keys/trusted-type.h> +#include <keys/encrypted-type.h> +#include <linux/key-type.h> +#include <linux/random.h> +#include <linux/rcupdate.h> +#include <linux/scatterlist.h> +#include <linux/crypto.h> +#include <crypto/hash.h> +#include <crypto/sha.h> +#include <crypto/aes.h> + +#include "encrypted.h" + +static const char KEY_TRUSTED_PREFIX[] = "trusted:"; +static const char KEY_USER_PREFIX[] = "user:"; +static const char hash_alg[] = "sha256"; +static const char hmac_alg[] = "hmac(sha256)"; +static const char blkcipher_alg[] = "cbc(aes)"; +static unsigned int ivsize; +static int blksize; + +#define KEY_TRUSTED_PREFIX_LEN (sizeof (KEY_TRUSTED_PREFIX) - 1) +#define KEY_USER_PREFIX_LEN (sizeof (KEY_USER_PREFIX) - 1) +#define HASH_SIZE SHA256_DIGEST_SIZE +#define MAX_DATA_SIZE 4096 +#define MIN_DATA_SIZE 20 + +struct sdesc { + struct shash_desc shash; + char ctx[]; +}; + +static struct crypto_shash *hashalg; +static struct crypto_shash *hmacalg; + +enum { + Opt_err = -1, Opt_new, Opt_load, Opt_update +}; + +static const match_table_t key_tokens = { + {Opt_new, "new"}, + {Opt_load, "load"}, + {Opt_update, "update"}, + {Opt_err, NULL} +}; + +static int aes_get_sizes(void) +{ + struct crypto_blkcipher *tfm; + + tfm = crypto_alloc_blkcipher(blkcipher_alg, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm)) { + pr_err("encrypted_key: failed to alloc_cipher (%ld)\n", + PTR_ERR(tfm)); + return PTR_ERR(tfm); + } + ivsize = crypto_blkcipher_ivsize(tfm); + blksize = crypto_blkcipher_blocksize(tfm); + crypto_free_blkcipher(tfm); + return 0; +} + +/* + * valid_master_desc - verify the 'key-type:desc' of a new/updated master-key + * + * key-type:= "trusted:" | "encrypted:" + * desc:= master-key description + * + * Verify that 'key-type' is valid and that 'desc' exists. On key update, + * only the master key description is permitted to change, not the key-type. + * The key-type remains constant. + * + * On success returns 0, otherwise -EINVAL. + */ +static int valid_master_desc(const char *new_desc, const char *orig_desc) +{ + if (!memcmp(new_desc, KEY_TRUSTED_PREFIX, KEY_TRUSTED_PREFIX_LEN)) { + if (strlen(new_desc) == KEY_TRUSTED_PREFIX_LEN) + goto out; + if (orig_desc) + if (memcmp(new_desc, orig_desc, KEY_TRUSTED_PREFIX_LEN)) + goto out; + } else if (!memcmp(new_desc, KEY_USER_PREFIX, KEY_USER_PREFIX_LEN)) { + if (strlen(new_desc) == KEY_USER_PREFIX_LEN) + goto out; + if (orig_desc) + if (memcmp(new_desc, orig_desc, KEY_USER_PREFIX_LEN)) + goto out; + } else + goto out; + return 0; +out: + return -EINVAL; +} + +/* + * datablob_parse - parse the keyctl data + * + * datablob format: + * new <master-key name> <decrypted data length> + * load <master-key name> <decrypted data length> <encrypted iv + data> + * update <new-master-key name> + * + * Tokenizes a copy of the keyctl data, returning a pointer to each token, + * which is null terminated. + * + * On success returns 0, otherwise -EINVAL. + */ +static int datablob_parse(char *datablob, char **master_desc, + char **decrypted_datalen, char **hex_encoded_iv) +{ + substring_t args[MAX_OPT_ARGS]; + int ret = -EINVAL; + int key_cmd; + char *p; + + p = strsep(&datablob, " \t"); + if (!p) + return ret; + key_cmd = match_token(p, key_tokens, args); + + *master_desc = strsep(&datablob, " \t"); + if (!*master_desc) + goto out; + + if (valid_master_desc(*master_desc, NULL) < 0) + goto out; + + if (decrypted_datalen) { + *decrypted_datalen = strsep(&datablob, " \t"); + if (!*decrypted_datalen) + goto out; + } + + switch (key_cmd) { + case Opt_new: + if (!decrypted_datalen) + break; + ret = 0; + break; + case Opt_load: + if (!decrypted_datalen) + break; + *hex_encoded_iv = strsep(&datablob, " \t"); + if (!*hex_encoded_iv) + break; + ret = 0; + break; + case Opt_update: + if (decrypted_datalen) + break; + ret = 0; + break; + case Opt_err: + break; + } +out: + return ret; +} + +/* + * datablob_format - format as an ascii string, before copying to userspace + */ +static char *datablob_format(struct encrypted_key_payload *epayload, + size_t asciiblob_len) +{ + char *ascii_buf, *bufp; + u8 *iv = epayload->iv; + int len; + int i; + + ascii_buf = kmalloc(asciiblob_len + 1, GFP_KERNEL); + if (!ascii_buf) + goto out; + + ascii_buf[asciiblob_len] = '\0'; + + /* copy datablob master_desc and datalen strings */ + len = sprintf(ascii_buf, "%s %s ", epayload->master_desc, + epayload->datalen); + + /* convert the hex encoded iv, encrypted-data and HMAC to ascii */ + bufp = &ascii_buf[len]; + for (i = 0; i < (asciiblob_len - len) / 2; i++) + bufp = pack_hex_byte(bufp, iv[i]); +out: + return ascii_buf; +} + +/* + * request_trusted_key - request the trusted key + * + * Trusted keys are sealed to PCRs and other metadata. Although userspace + * manages both trusted/encrypted key-types, like the encrypted key type + * data, trusted key type data is not visible decrypted from userspace. + */ +static struct key *request_trusted_key(const char *trusted_desc, + u8 **master_key, size_t *master_keylen) +{ + struct trusted_key_payload *tpayload; + struct key *tkey; + + tkey = request_key(&key_type_trusted, trusted_desc, NULL); + if (IS_ERR(tkey)) + goto error; + + down_read(&tkey->sem); + tpayload = rcu_dereference(tkey->payload.data); + *master_key = tpayload->key; + *master_keylen = tpayload->key_len; +error: + return tkey; +} + +/* + * request_user_key - request the user key + * + * Use a user provided key to encrypt/decrypt an encrypted-key. + */ +static struct key *request_user_key(const char *master_desc, u8 **master_key, + size_t *master_keylen) +{ + struct user_key_payload *upayload; + struct key *ukey; + + ukey = request_key(&key_type_user, master_desc, NULL); + if (IS_ERR(ukey)) + goto error; + + down_read(&ukey->sem); + upayload = rcu_dereference(ukey->payload.data); + *master_key = upayload->data; + *master_keylen = upayload->datalen; +error: + return ukey; +} + +static struct sdesc *alloc_sdesc(struct crypto_shash *alg) +{ + struct sdesc *sdesc; + int size; + + size = sizeof(struct shash_desc) + crypto_shash_descsize(alg); + sdesc = kmalloc(size, GFP_KERNEL); + if (!sdesc) + return ERR_PTR(-ENOMEM); + sdesc->shash.tfm = alg; + sdesc->shash.flags = 0x0; + return sdesc; +} + +static int calc_hmac(u8 *digest, const u8 *key, unsigned int keylen, + const u8 *buf, unsigned int buflen) +{ + struct sdesc *sdesc; + int ret; + + sdesc = alloc_sdesc(hmacalg); + if (IS_ERR(sdesc)) { + pr_info("encrypted_key: can't alloc %s\n", hmac_alg); + return PTR_ERR(sdesc); + } + + ret = crypto_shash_setkey(hmacalg, key, keylen); + if (!ret) + ret = crypto_shash_digest(&sdesc->shash, buf, buflen, digest); + kfree(sdesc); + return ret; +} + +static int calc_hash(u8 *digest, const u8 *buf, unsigned int buflen) +{ + struct sdesc *sdesc; + int ret; + + sdesc = alloc_sdesc(hashalg); + if (IS_ERR(sdesc)) { + pr_info("encrypted_key: can't alloc %s\n", hash_alg); + return PTR_ERR(sdesc); + } + + ret = crypto_shash_digest(&sdesc->shash, buf, buflen, digest); + kfree(sdesc); + return ret; +} + +enum derived_key_type { ENC_KEY, AUTH_KEY }; + +/* Derive authentication/encryption key from trusted key */ +static int get_derived_key(u8 *derived_key, enum derived_key_type key_type, + const u8 *master_key, size_t master_keylen) +{ + u8 *derived_buf; + unsigned int derived_buf_len; + int ret; + + derived_buf_len = strlen("AUTH_KEY") + 1 + master_keylen; + if (derived_buf_len < HASH_SIZE) + derived_buf_len = HASH_SIZE; + + derived_buf = kzalloc(derived_buf_len, GFP_KERNEL); + if (!derived_buf) { + pr_err("encrypted_key: out of memory\n"); + return -ENOMEM; + } + if (key_type) + strcpy(derived_buf, "AUTH_KEY"); + else + strcpy(derived_buf, "ENC_KEY"); + + memcpy(derived_buf + strlen(derived_buf) + 1, master_key, + master_keylen); + ret = calc_hash(derived_key, derived_buf, derived_buf_len); + kfree(derived_buf); + return ret; +} + +static int init_blkcipher_desc(struct blkcipher_desc *desc, const u8 *key, + unsigned int key_len, const u8 *iv, + unsigned int ivsize) +{ + int ret; + + desc->tfm = crypto_alloc_blkcipher(blkcipher_alg, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(desc->tfm)) { + pr_err("encrypted_key: failed to load %s transform (%ld)\n", + blkcipher_alg, PTR_ERR(desc->tfm)); + return PTR_ERR(desc->tfm); + } + desc->flags = 0; + + ret = crypto_blkcipher_setkey(desc->tfm, key, key_len); + if (ret < 0) { + pr_err("encrypted_key: failed to setkey (%d)\n", ret); + crypto_free_blkcipher(desc->tfm); + return ret; + } + crypto_blkcipher_set_iv(desc->tfm, iv, ivsize); + return 0; +} + +static struct key *request_master_key(struct encrypted_key_payload *epayload, + u8 **master_key, size_t *master_keylen) +{ + struct key *mkey = NULL; + + if (!strncmp(epayload->master_desc, KEY_TRUSTED_PREFIX, + KEY_TRUSTED_PREFIX_LEN)) { + mkey = request_trusted_key(epayload->master_desc + + KEY_TRUSTED_PREFIX_LEN, + master_key, master_keylen); + } else if (!strncmp(epayload->master_desc, KEY_USER_PREFIX, + KEY_USER_PREFIX_LEN)) { + mkey = request_user_key(epayload->master_desc + + KEY_USER_PREFIX_LEN, + master_key, master_keylen); + } else + goto out; + + if (IS_ERR(mkey)) + pr_info("encrypted_key: key %s not found", + epayload->master_desc); + if (mkey) + dump_master_key(*master_key, *master_keylen); +out: + return mkey; +} + +/* Before returning data to userspace, encrypt decrypted data. */ +static int derived_key_encrypt(struct encrypted_key_payload *epayload, + const u8 *derived_key, + unsigned int derived_keylen) +{ + struct scatterlist sg_in[2]; + struct scatterlist sg_out[1]; + struct blkcipher_desc desc; + unsigned int encrypted_datalen; + unsigned int padlen; + char pad[16]; + int ret; + + encrypted_datalen = roundup(epayload->decrypted_datalen, blksize); + padlen = encrypted_datalen - epayload->decrypted_datalen; + + ret = init_blkcipher_desc(&desc, derived_key, derived_keylen, + epayload->iv, ivsize); + if (ret < 0) + goto out; + dump_decrypted_data(epayload); + + memset(pad, 0, sizeof pad); + sg_init_table(sg_in, 2); + sg_set_buf(&sg_in[0], epayload->decrypted_data, + epayload->decrypted_datalen); + sg_set_buf(&sg_in[1], pad, padlen); + + sg_init_table(sg_out, 1); + sg_set_buf(sg_out, epayload->encrypted_data, encrypted_datalen); + + ret = crypto_blkcipher_encrypt(&desc, sg_out, sg_in, encrypted_datalen); + crypto_free_blkcipher(desc.tfm); + if (ret < 0) + pr_err("encrypted_key: failed to encrypt (%d)\n", ret); + else + dump_encrypted_data(epayload, encrypted_datalen); +out: + return ret; +} + +static int datablob_hmac_append(struct encrypted_key_payload *epayload, + const u8 *master_key, size_t master_keylen) +{ + u8 derived_key[HASH_SIZE]; + u8 *digest; + int ret; + + ret = get_derived_key(derived_key, AUTH_KEY, master_key, master_keylen); + if (ret < 0) + goto out; + + digest = epayload->master_desc + epayload->datablob_len; + ret = calc_hmac(digest, derived_key, sizeof derived_key, + epayload->master_desc, epayload->datablob_len); + if (!ret) + dump_hmac(NULL, digest, HASH_SIZE); +out: + return ret; +} + +/* verify HMAC before decrypting encrypted key */ +static int datablob_hmac_verify(struct encrypted_key_payload *epayload, + const u8 *master_key, size_t master_keylen) +{ + u8 derived_key[HASH_SIZE]; + u8 digest[HASH_SIZE]; + int ret; + + ret = get_derived_key(derived_key, AUTH_KEY, master_key, master_keylen); + if (ret < 0) + goto out; + + ret = calc_hmac(digest, derived_key, sizeof derived_key, + epayload->master_desc, epayload->datablob_len); + if (ret < 0) + goto out; + ret = memcmp(digest, epayload->master_desc + epayload->datablob_len, + sizeof digest); + if (ret) { + ret = -EINVAL; + dump_hmac("datablob", + epayload->master_desc + epayload->datablob_len, + HASH_SIZE); + dump_hmac("calc", digest, HASH_SIZE); + } +out: + return ret; +} + +static int derived_key_decrypt(struct encrypted_key_payload *epayload, + const u8 *derived_key, + unsigned int derived_keylen) +{ + struct scatterlist sg_in[1]; + struct scatterlist sg_out[2]; + struct blkcipher_desc desc; + unsigned int encrypted_datalen; + char pad[16]; + int ret; + + encrypted_datalen = roundup(epayload->decrypted_datalen, blksize); + ret = init_blkcipher_desc(&desc, derived_key, derived_keylen, + epayload->iv, ivsize); + if (ret < 0) + goto out; + dump_encrypted_data(epayload, encrypted_datalen); + + memset(pad, 0, sizeof pad); + sg_init_table(sg_in, 1); + sg_init_table(sg_out, 2); + sg_set_buf(sg_in, epayload->encrypted_data, encrypted_datalen); + sg_set_buf(&sg_out[0], epayload->decrypted_data, + epayload->decrypted_datalen); + sg_set_buf(&sg_out[1], pad, sizeof pad); + + ret = crypto_blkcipher_decrypt(&desc, sg_out, sg_in, encrypted_datalen); + crypto_free_blkcipher(desc.tfm); + if (ret < 0) + goto out; + dump_decrypted_data(epayload); +out: + return ret; +} + +/* Allocate memory for decrypted key and datablob. */ +static struct encrypted_key_payload *encrypted_key_alloc(struct key *key, + const char *master_desc, + const char *datalen) +{ + struct encrypted_key_payload *epayload = NULL; + unsigned short datablob_len; + unsigned short decrypted_datalen; + unsigned int encrypted_datalen; + long dlen; + int ret; + + ret = strict_strtol(datalen, 10, &dlen); + if (ret < 0 || dlen < MIN_DATA_SIZE || dlen > MAX_DATA_SIZE) + return ERR_PTR(-EINVAL); + + decrypted_datalen = dlen; + encrypted_datalen = roundup(decrypted_datalen, blksize); + + datablob_len = strlen(master_desc) + 1 + strlen(datalen) + 1 + + ivsize + 1 + encrypted_datalen; + + ret = key_payload_reserve(key, decrypted_datalen + datablob_len + + HASH_SIZE + 1); + if (ret < 0) + return ERR_PTR(ret); + + epayload = kzalloc(sizeof(*epayload) + decrypted_datalen + + datablob_len + HASH_SIZE + 1, GFP_KERNEL); + if (!epayload) + return ERR_PTR(-ENOMEM); + + epayload->decrypted_datalen = decrypted_datalen; + epayload->datablob_len = datablob_len; + return epayload; +} + +static int encrypted_key_decrypt(struct encrypted_key_payload *epayload, + const char *hex_encoded_iv) +{ + struct key *mkey; + u8 derived_key[HASH_SIZE]; + u8 *master_key; + u8 *hmac; + const char *hex_encoded_data; + unsigned int encrypted_datalen; + size_t master_keylen; + size_t asciilen; + int ret; + + encrypted_datalen = roundup(epayload->decrypted_datalen, blksize); + asciilen = (ivsize + 1 + encrypted_datalen + HASH_SIZE) * 2; + if (strlen(hex_encoded_iv) != asciilen) + return -EINVAL; + + hex_encoded_data = hex_encoded_iv + (2 * ivsize) + 2; + hex2bin(epayload->iv, hex_encoded_iv, ivsize); + hex2bin(epayload->encrypted_data, hex_encoded_data, encrypted_datalen); + + hmac = epayload->master_desc + epayload->datablob_len; + hex2bin(hmac, hex_encoded_data + (encrypted_datalen * 2), HASH_SIZE); + + mkey = request_master_key(epayload, &master_key, &master_keylen); + if (IS_ERR(mkey)) + return PTR_ERR(mkey); + + ret = datablob_hmac_verify(epayload, master_key, master_keylen); + if (ret < 0) { + pr_err("encrypted_key: bad hmac (%d)\n", ret); + goto out; + } + + ret = get_derived_key(derived_key, ENC_KEY, master_key, master_keylen); + if (ret < 0) + goto out; + + ret = derived_key_decrypt(epayload, derived_key, sizeof derived_key); + if (ret < 0) + pr_err("encrypted_key: failed to decrypt key (%d)\n", ret); +out: + up_read(&mkey->sem); + key_put(mkey); + return ret; +} + +static void __ekey_init(struct encrypted_key_payload *epayload, + const char *master_desc, const char *datalen) +{ + epayload->master_desc = epayload->decrypted_data + + epayload->decrypted_datalen; + epayload->datalen = epayload->master_desc + strlen(master_desc) + 1; + epayload->iv = epayload->datalen + strlen(datalen) + 1; + epayload->encrypted_data = epayload->iv + ivsize + 1; + + memcpy(epayload->master_desc, master_desc, strlen(master_desc)); + memcpy(epayload->datalen, datalen, strlen(datalen)); +} + +/* + * encrypted_init - initialize an encrypted key + * + * For a new key, use a random number for both the iv and data + * itself. For an old key, decrypt the hex encoded data. + */ +static int encrypted_init(struct encrypted_key_payload *epayload, + const char *master_desc, const char *datalen, + const char *hex_encoded_iv) +{ + int ret = 0; + + __ekey_init(epayload, master_desc, datalen); + if (!hex_encoded_iv) { + get_random_bytes(epayload->iv, ivsize); + + get_random_bytes(epayload->decrypted_data, + epayload->decrypted_datalen); + } else + ret = encrypted_key_decrypt(epayload, hex_encoded_iv); + return ret; +} + +/* + * encrypted_instantiate - instantiate an encrypted key + * + * Decrypt an existing encrypted datablob or create a new encrypted key + * based on a kernel random number. + * + * On success, return 0. Otherwise return errno. + */ +static int encrypted_instantiate(struct key *key, const void *data, + size_t datalen) +{ + struct encrypted_key_payload *epayload = NULL; + char *datablob = NULL; + char *master_desc = NULL; + char *decrypted_datalen = NULL; + char *hex_encoded_iv = NULL; + int ret; + + if (datalen <= 0 || datalen > 32767 || !data) + return -EINVAL; + + datablob = kmalloc(datalen + 1, GFP_KERNEL); + if (!datablob) + return -ENOMEM; + datablob[datalen] = 0; + memcpy(datablob, data, datalen); + ret = datablob_parse(datablob, &master_desc, &decrypted_datalen, + &hex_encoded_iv); + if (ret < 0) + goto out; + + epayload = encrypted_key_alloc(key, master_desc, decrypted_datalen); + if (IS_ERR(epayload)) { + ret = PTR_ERR(epayload); + goto out; + } + ret = encrypted_init(epayload, master_desc, decrypted_datalen, + hex_encoded_iv); + if (ret < 0) { + kfree(epayload); + goto out; + } + + rcu_assign_pointer(key->payload.data, epayload); +out: + kfree(datablob); + return ret; +} + +static void encrypted_rcu_free(struct rcu_head *rcu) +{ + struct encrypted_key_payload *epayload; + + epayload = container_of(rcu, struct encrypted_key_payload, rcu); + memset(epayload->decrypted_data, 0, epayload->decrypted_datalen); + kfree(epayload); +} + +/* + * encrypted_update - update the master key description + * + * Change the master key description for an existing encrypted key. + * The next read will return an encrypted datablob using the new + * master key description. + * + * On success, return 0. Otherwise return errno. + */ +static int encrypted_update(struct key *key, const void *data, size_t datalen) +{ + struct encrypted_key_payload *epayload = key->payload.data; + struct encrypted_key_payload *new_epayload; + char *buf; + char *new_master_desc = NULL; + int ret = 0; + + if (datalen <= 0 || datalen > 32767 || !data) + return -EINVAL; + + buf = kmalloc(datalen + 1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[datalen] = 0; + memcpy(buf, data, datalen); + ret = datablob_parse(buf, &new_master_desc, NULL, NULL); + if (ret < 0) + goto out; + + ret = valid_master_desc(new_master_desc, epayload->master_desc); + if (ret < 0) + goto out; + + new_epayload = encrypted_key_alloc(key, new_master_desc, + epayload->datalen); + if (IS_ERR(new_epayload)) { + ret = PTR_ERR(new_epayload); + goto out; + } + + __ekey_init(new_epayload, new_master_desc, epayload->datalen); + + memcpy(new_epayload->iv, epayload->iv, ivsize); + memcpy(new_epayload->decrypted_data, epayload->decrypted_data, + epayload->decrypted_datalen); + + rcu_assign_pointer(key->payload.data, new_epayload); + call_rcu(&epayload->rcu, encrypted_rcu_free); +out: + kfree(buf); + return ret; +} + +/* + * encrypted_read - format and copy the encrypted data to userspace + * + * The resulting datablob format is: + * <master-key name> <decrypted data length> <encrypted iv> <encrypted data> + * + * On success, return to userspace the encrypted key datablob size. + */ +static long encrypted_read(const struct key *key, char __user *buffer, + size_t buflen) +{ + struct encrypted_key_payload *epayload; + struct key *mkey; + u8 *master_key; + size_t master_keylen; + char derived_key[HASH_SIZE]; + char *ascii_buf; + size_t asciiblob_len; + int ret; + + epayload = rcu_dereference_key(key); + + /* returns the hex encoded iv, encrypted-data, and hmac as ascii */ + asciiblob_len = epayload->datablob_len + ivsize + 1 + + roundup(epayload->decrypted_datalen, blksize) + + (HASH_SIZE * 2); + + if (!buffer || buflen < asciiblob_len) + return asciiblob_len; + + mkey = request_master_key(epayload, &master_key, &master_keylen); + if (IS_ERR(mkey)) + return PTR_ERR(mkey); + + ret = get_derived_key(derived_key, ENC_KEY, master_key, master_keylen); + if (ret < 0) + goto out; + + ret = derived_key_encrypt(epayload, derived_key, sizeof derived_key); + if (ret < 0) + goto out; + + ret = datablob_hmac_append(epayload, master_key, master_keylen); + if (ret < 0) + goto out; + + ascii_buf = datablob_format(epayload, asciiblob_len); + if (!ascii_buf) { + ret = -ENOMEM; + goto out; + } + + up_read(&mkey->sem); + key_put(mkey); + + if (copy_to_user(buffer, ascii_buf, asciiblob_len) != 0) + ret = -EFAULT; + kfree(ascii_buf); + + return asciiblob_len; +out: + up_read(&mkey->sem); + key_put(mkey); + return ret; +} + +/* + * encrypted_destroy - before freeing the key, clear the decrypted data + * + * Before freeing the key, clear the memory containing the decrypted + * key data. + */ +static void encrypted_destroy(struct key *key) +{ + struct encrypted_key_payload *epayload = key->payload.data; + + if (!epayload) + return; + + memset(epayload->decrypted_data, 0, epayload->decrypted_datalen); + kfree(key->payload.data); +} + +struct key_type key_type_encrypted = { + .name = "encrypted", + .instantiate = encrypted_instantiate, + .update = encrypted_update, + .match = user_match, + .destroy = encrypted_destroy, + .describe = user_describe, + .read = encrypted_read, +}; +EXPORT_SYMBOL_GPL(key_type_encrypted); + +static void encrypted_shash_release(void) +{ + if (hashalg) + crypto_free_shash(hashalg); + if (hmacalg) + crypto_free_shash(hmacalg); +} + +static int __init encrypted_shash_alloc(void) +{ + int ret; + + hmacalg = crypto_alloc_shash(hmac_alg, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(hmacalg)) { + pr_info("encrypted_key: could not allocate crypto %s\n", + hmac_alg); + return PTR_ERR(hmacalg); + } + + hashalg = crypto_alloc_shash(hash_alg, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(hashalg)) { + pr_info("encrypted_key: could not allocate crypto %s\n", + hash_alg); + ret = PTR_ERR(hashalg); + goto hashalg_fail; + } + + return 0; + +hashalg_fail: + crypto_free_shash(hmacalg); + return ret; +} + +static int __init init_encrypted(void) +{ + int ret; + + ret = encrypted_shash_alloc(); + if (ret < 0) + return ret; + ret = register_key_type(&key_type_encrypted); + if (ret < 0) + goto out; + return aes_get_sizes(); +out: + encrypted_shash_release(); + return ret; + +} + +static void __exit cleanup_encrypted(void) +{ + encrypted_shash_release(); + unregister_key_type(&key_type_encrypted); +} + +late_initcall(init_encrypted); +module_exit(cleanup_encrypted); + +MODULE_LICENSE("GPL"); diff --git a/security/keys/encrypted.h b/security/keys/encrypted.h new file mode 100644 index 00000000..cef5e2f2 --- /dev/null +++ b/security/keys/encrypted.h @@ -0,0 +1,54 @@ +#ifndef __ENCRYPTED_KEY_H +#define __ENCRYPTED_KEY_H + +#define ENCRYPTED_DEBUG 0 + +#if ENCRYPTED_DEBUG +static inline void dump_master_key(const u8 *master_key, size_t master_keylen) +{ + print_hex_dump(KERN_ERR, "master key: ", DUMP_PREFIX_NONE, 32, 1, + master_key, master_keylen, 0); +} + +static inline void dump_decrypted_data(struct encrypted_key_payload *epayload) +{ + print_hex_dump(KERN_ERR, "decrypted data: ", DUMP_PREFIX_NONE, 32, 1, + epayload->decrypted_data, + epayload->decrypted_datalen, 0); +} + +static inline void dump_encrypted_data(struct encrypted_key_payload *epayload, + unsigned int encrypted_datalen) +{ + print_hex_dump(KERN_ERR, "encrypted data: ", DUMP_PREFIX_NONE, 32, 1, + epayload->encrypted_data, encrypted_datalen, 0); +} + +static inline void dump_hmac(const char *str, const u8 *digest, + unsigned int hmac_size) +{ + if (str) + pr_info("encrypted_key: %s", str); + print_hex_dump(KERN_ERR, "hmac: ", DUMP_PREFIX_NONE, 32, 1, digest, + hmac_size, 0); +} +#else +static inline void dump_master_key(const u8 *master_key, size_t master_keylen) +{ +} + +static inline void dump_decrypted_data(struct encrypted_key_payload *epayload) +{ +} + +static inline void dump_encrypted_data(struct encrypted_key_payload *epayload, + unsigned int encrypted_datalen) +{ +} + +static inline void dump_hmac(const char *str, const u8 *digest, + unsigned int hmac_size) +{ +} +#endif +#endif diff --git a/security/keys/gc.c b/security/keys/gc.c new file mode 100644 index 00000000..89df6b5f --- /dev/null +++ b/security/keys/gc.c @@ -0,0 +1,222 @@ +/* Key garbage collector + * + * Copyright (C) 2009 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <keys/keyring-type.h> +#include "internal.h" + +/* + * Delay between key revocation/expiry in seconds + */ +unsigned key_gc_delay = 5 * 60; + +/* + * Reaper + */ +static void key_gc_timer_func(unsigned long); +static void key_garbage_collector(struct work_struct *); +static DEFINE_TIMER(key_gc_timer, key_gc_timer_func, 0, 0); +static DECLARE_WORK(key_gc_work, key_garbage_collector); +static key_serial_t key_gc_cursor; /* the last key the gc considered */ +static bool key_gc_again; +static unsigned long key_gc_executing; +static time_t key_gc_next_run = LONG_MAX; +static time_t key_gc_new_timer; + +/* + * Schedule a garbage collection run. + * - time precision isn't particularly important + */ +void key_schedule_gc(time_t gc_at) +{ + unsigned long expires; + time_t now = current_kernel_time().tv_sec; + + kenter("%ld", gc_at - now); + + if (gc_at <= now) { + schedule_work(&key_gc_work); + } else if (gc_at < key_gc_next_run) { + expires = jiffies + (gc_at - now) * HZ; + mod_timer(&key_gc_timer, expires); + } +} + +/* + * The garbage collector timer kicked off + */ +static void key_gc_timer_func(unsigned long data) +{ + kenter(""); + key_gc_next_run = LONG_MAX; + schedule_work(&key_gc_work); +} + +/* + * Garbage collect pointers from a keyring. + * + * Return true if we altered the keyring. + */ +static bool key_gc_keyring(struct key *keyring, time_t limit) + __releases(key_serial_lock) +{ + struct keyring_list *klist; + struct key *key; + int loop; + + kenter("%x", key_serial(keyring)); + + if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) + goto dont_gc; + + /* scan the keyring looking for dead keys */ + rcu_read_lock(); + klist = rcu_dereference(keyring->payload.subscriptions); + if (!klist) + goto unlock_dont_gc; + + for (loop = klist->nkeys - 1; loop >= 0; loop--) { + key = klist->keys[loop]; + if (test_bit(KEY_FLAG_DEAD, &key->flags) || + (key->expiry > 0 && key->expiry <= limit)) + goto do_gc; + } + +unlock_dont_gc: + rcu_read_unlock(); +dont_gc: + kleave(" = false"); + return false; + +do_gc: + rcu_read_unlock(); + key_gc_cursor = keyring->serial; + key_get(keyring); + spin_unlock(&key_serial_lock); + keyring_gc(keyring, limit); + key_put(keyring); + kleave(" = true"); + return true; +} + +/* + * Garbage collector for keys. This involves scanning the keyrings for dead, + * expired and revoked keys that have overstayed their welcome + */ +static void key_garbage_collector(struct work_struct *work) +{ + struct rb_node *rb; + key_serial_t cursor; + struct key *key, *xkey; + time_t new_timer = LONG_MAX, limit, now; + + now = current_kernel_time().tv_sec; + kenter("[%x,%ld]", key_gc_cursor, key_gc_new_timer - now); + + if (test_and_set_bit(0, &key_gc_executing)) { + key_schedule_gc(current_kernel_time().tv_sec + 1); + kleave(" [busy; deferring]"); + return; + } + + limit = now; + if (limit > key_gc_delay) + limit -= key_gc_delay; + else + limit = key_gc_delay; + + spin_lock(&key_serial_lock); + + if (unlikely(RB_EMPTY_ROOT(&key_serial_tree))) { + spin_unlock(&key_serial_lock); + clear_bit(0, &key_gc_executing); + return; + } + + cursor = key_gc_cursor; + if (cursor < 0) + cursor = 0; + if (cursor > 0) + new_timer = key_gc_new_timer; + else + key_gc_again = false; + + /* find the first key above the cursor */ + key = NULL; + rb = key_serial_tree.rb_node; + while (rb) { + xkey = rb_entry(rb, struct key, serial_node); + if (cursor < xkey->serial) { + key = xkey; + rb = rb->rb_left; + } else if (cursor > xkey->serial) { + rb = rb->rb_right; + } else { + rb = rb_next(rb); + if (!rb) + goto reached_the_end; + key = rb_entry(rb, struct key, serial_node); + break; + } + } + + if (!key) + goto reached_the_end; + + /* trawl through the keys looking for keyrings */ + for (;;) { + if (key->expiry > limit && key->expiry < new_timer) { + kdebug("will expire %x in %ld", + key_serial(key), key->expiry - limit); + new_timer = key->expiry; + } + + if (key->type == &key_type_keyring && + key_gc_keyring(key, limit)) + /* the gc had to release our lock so that the keyring + * could be modified, so we have to get it again */ + goto gc_released_our_lock; + + rb = rb_next(&key->serial_node); + if (!rb) + goto reached_the_end; + key = rb_entry(rb, struct key, serial_node); + } + +gc_released_our_lock: + kdebug("gc_released_our_lock"); + key_gc_new_timer = new_timer; + key_gc_again = true; + clear_bit(0, &key_gc_executing); + schedule_work(&key_gc_work); + kleave(" [continue]"); + return; + + /* when we reach the end of the run, we set the timer for the next one */ +reached_the_end: + kdebug("reached_the_end"); + spin_unlock(&key_serial_lock); + key_gc_new_timer = new_timer; + key_gc_cursor = 0; + clear_bit(0, &key_gc_executing); + + if (key_gc_again) { + /* there may have been a key that expired whilst we were + * scanning, so if we discarded any links we should do another + * scan */ + new_timer = now + 1; + key_schedule_gc(new_timer); + } else if (new_timer < LONG_MAX) { + new_timer += key_gc_delay; + key_schedule_gc(new_timer); + } + kleave(" [end]"); +} diff --git a/security/keys/internal.h b/security/keys/internal.h new file mode 100644 index 00000000..f375152a --- /dev/null +++ b/security/keys/internal.h @@ -0,0 +1,246 @@ +/* Authentication token and access key management internal defs + * + * Copyright (C) 2003-5, 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef _INTERNAL_H +#define _INTERNAL_H + +#include <linux/sched.h> +#include <linux/key-type.h> + +#ifdef __KDEBUG +#define kenter(FMT, ...) \ + printk(KERN_DEBUG "==> %s("FMT")\n", __func__, ##__VA_ARGS__) +#define kleave(FMT, ...) \ + printk(KERN_DEBUG "<== %s()"FMT"\n", __func__, ##__VA_ARGS__) +#define kdebug(FMT, ...) \ + printk(KERN_DEBUG " "FMT"\n", ##__VA_ARGS__) +#else +#define kenter(FMT, ...) \ + no_printk(KERN_DEBUG "==> %s("FMT")\n", __func__, ##__VA_ARGS__) +#define kleave(FMT, ...) \ + no_printk(KERN_DEBUG "<== %s()"FMT"\n", __func__, ##__VA_ARGS__) +#define kdebug(FMT, ...) \ + no_printk(KERN_DEBUG FMT"\n", ##__VA_ARGS__) +#endif + +extern struct key_type key_type_user; + +/*****************************************************************************/ +/* + * Keep track of keys for a user. + * + * This needs to be separate to user_struct to avoid a refcount-loop + * (user_struct pins some keyrings which pin this struct). + * + * We also keep track of keys under request from userspace for this UID here. + */ +struct key_user { + struct rb_node node; + struct mutex cons_lock; /* construction initiation lock */ + spinlock_t lock; + atomic_t usage; /* for accessing qnkeys & qnbytes */ + atomic_t nkeys; /* number of keys */ + atomic_t nikeys; /* number of instantiated keys */ + uid_t uid; + struct user_namespace *user_ns; + int qnkeys; /* number of keys allocated to this user */ + int qnbytes; /* number of bytes allocated to this user */ +}; + +extern struct rb_root key_user_tree; +extern spinlock_t key_user_lock; +extern struct key_user root_key_user; + +extern struct key_user *key_user_lookup(uid_t uid, + struct user_namespace *user_ns); +extern void key_user_put(struct key_user *user); + +/* + * Key quota limits. + * - root has its own separate limits to everyone else + */ +extern unsigned key_quota_root_maxkeys; +extern unsigned key_quota_root_maxbytes; +extern unsigned key_quota_maxkeys; +extern unsigned key_quota_maxbytes; + +#define KEYQUOTA_LINK_BYTES 4 /* a link in a keyring is worth 4 bytes */ + + +extern struct rb_root key_serial_tree; +extern spinlock_t key_serial_lock; +extern struct mutex key_construction_mutex; +extern wait_queue_head_t request_key_conswq; + + +extern struct key_type *key_type_lookup(const char *type); +extern void key_type_put(struct key_type *ktype); + +extern int __key_link_begin(struct key *keyring, + const struct key_type *type, + const char *description, + unsigned long *_prealloc); +extern int __key_link_check_live_key(struct key *keyring, struct key *key); +extern void __key_link(struct key *keyring, struct key *key, + unsigned long *_prealloc); +extern void __key_link_end(struct key *keyring, + struct key_type *type, + unsigned long prealloc); + +extern key_ref_t __keyring_search_one(key_ref_t keyring_ref, + const struct key_type *type, + const char *description, + key_perm_t perm); + +extern struct key *keyring_search_instkey(struct key *keyring, + key_serial_t target_id); + +typedef int (*key_match_func_t)(const struct key *, const void *); + +extern key_ref_t keyring_search_aux(key_ref_t keyring_ref, + const struct cred *cred, + struct key_type *type, + const void *description, + key_match_func_t match, + bool no_state_check); + +extern key_ref_t search_my_process_keyrings(struct key_type *type, + const void *description, + key_match_func_t match, + bool no_state_check, + const struct cred *cred); +extern key_ref_t search_process_keyrings(struct key_type *type, + const void *description, + key_match_func_t match, + const struct cred *cred); + +extern struct key *find_keyring_by_name(const char *name, bool skip_perm_check); + +extern int install_user_keyrings(void); +extern int install_thread_keyring_to_cred(struct cred *); +extern int install_process_keyring_to_cred(struct cred *); +extern int install_session_keyring_to_cred(struct cred *, struct key *); + +extern struct key *request_key_and_link(struct key_type *type, + const char *description, + const void *callout_info, + size_t callout_len, + void *aux, + 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 +#define KEY_LOOKUP_PARTIAL 0x02 +#define KEY_LOOKUP_FOR_UNLINK 0x04 + +extern long join_session_keyring(const char *name); + +extern unsigned key_gc_delay; +extern void keyring_gc(struct key *keyring, time_t limit); +extern void key_schedule_gc(time_t expiry_at); + +extern int key_task_permission(const key_ref_t key_ref, + const struct cred *cred, + key_perm_t perm); + +/* + * Check to see whether permission is granted to use a key in the desired way. + */ +static inline int key_permission(const key_ref_t key_ref, key_perm_t perm) +{ + return key_task_permission(key_ref, current_cred(), perm); +} + +/* required permissions */ +#define KEY_VIEW 0x01 /* require permission to view attributes */ +#define KEY_READ 0x02 /* require permission to read content */ +#define KEY_WRITE 0x04 /* require permission to update / modify */ +#define KEY_SEARCH 0x08 /* require permission to search (keyring) or find (key) */ +#define KEY_LINK 0x10 /* require permission to link */ +#define KEY_SETATTR 0x20 /* require permission to change attributes */ +#define KEY_ALL 0x3f /* all the above permissions */ + +/* + * Authorisation record for request_key(). + */ +struct request_key_auth { + struct key *target_key; + struct key *dest_keyring; + const struct cred *cred; + void *callout_info; + size_t callout_len; + pid_t pid; +}; + +extern struct key_type key_type_request_key_auth; +extern struct key *request_key_auth_new(struct key *target, + const void *callout_info, + size_t callout_len, + struct key *dest_keyring); + +extern struct key *key_get_instantiation_authkey(key_serial_t target_id); + +/* + * keyctl() functions + */ +extern long keyctl_get_keyring_ID(key_serial_t, int); +extern long keyctl_join_session_keyring(const char __user *); +extern long keyctl_update_key(key_serial_t, const void __user *, size_t); +extern long keyctl_revoke_key(key_serial_t); +extern long keyctl_keyring_clear(key_serial_t); +extern long keyctl_keyring_link(key_serial_t, key_serial_t); +extern long keyctl_keyring_unlink(key_serial_t, key_serial_t); +extern long keyctl_describe_key(key_serial_t, char __user *, size_t); +extern long keyctl_keyring_search(key_serial_t, const char __user *, + const char __user *, key_serial_t); +extern long keyctl_read_key(key_serial_t, char __user *, size_t); +extern long keyctl_chown_key(key_serial_t, uid_t, gid_t); +extern long keyctl_setperm_key(key_serial_t, key_perm_t); +extern long keyctl_instantiate_key(key_serial_t, const void __user *, + size_t, key_serial_t); +extern long keyctl_negate_key(key_serial_t, unsigned, key_serial_t); +extern long keyctl_set_reqkey_keyring(int); +extern long keyctl_set_timeout(key_serial_t, unsigned); +extern long keyctl_assume_authority(key_serial_t); +extern long keyctl_get_security(key_serial_t keyid, char __user *buffer, + size_t buflen); +extern long keyctl_session_to_parent(void); +extern long keyctl_reject_key(key_serial_t, unsigned, unsigned, key_serial_t); +extern long keyctl_instantiate_key_iov(key_serial_t, + const struct iovec __user *, + unsigned, key_serial_t); + +extern long keyctl_instantiate_key_common(key_serial_t, + const struct iovec __user *, + unsigned, size_t, key_serial_t); + +/* + * Debugging key validation + */ +#ifdef KEY_DEBUGGING +extern void __key_check(const struct key *); + +static inline void key_check(const struct key *key) +{ + if (key && (IS_ERR(key) || key->magic != KEY_DEBUG_MAGIC)) + __key_check(key); +} + +#else + +#define key_check(key) do {} while(0) + +#endif + +#endif /* _INTERNAL_H */ diff --git a/security/keys/key.c b/security/keys/key.c new file mode 100644 index 00000000..f7f9d93f --- /dev/null +++ b/security/keys/key.c @@ -0,0 +1,1118 @@ +/* Basic authentication token and access key management + * + * Copyright (C) 2004-2008 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/poison.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/security.h> +#include <linux/workqueue.h> +#include <linux/random.h> +#include <linux/err.h> +#include <linux/user_namespace.h> +#include "internal.h" + +static struct kmem_cache *key_jar; +struct rb_root key_serial_tree; /* tree of keys indexed by serial */ +DEFINE_SPINLOCK(key_serial_lock); + +struct rb_root key_user_tree; /* tree of quota records indexed by UID */ +DEFINE_SPINLOCK(key_user_lock); + +unsigned int key_quota_root_maxkeys = 200; /* root's key count quota */ +unsigned int key_quota_root_maxbytes = 20000; /* root's key space quota */ +unsigned int key_quota_maxkeys = 200; /* general key count quota */ +unsigned int key_quota_maxbytes = 20000; /* general key space quota */ + +static LIST_HEAD(key_types_list); +static DECLARE_RWSEM(key_types_sem); + +static void key_cleanup(struct work_struct *work); +static DECLARE_WORK(key_cleanup_task, key_cleanup); + +/* We serialise key instantiation and link */ +DEFINE_MUTEX(key_construction_mutex); + +/* Any key who's type gets unegistered will be re-typed to this */ +static struct key_type key_type_dead = { + .name = "dead", +}; + +#ifdef KEY_DEBUGGING +void __key_check(const struct key *key) +{ + printk("__key_check: key %p {%08x} should be {%08x}\n", + key, key->magic, KEY_DEBUG_MAGIC); + BUG(); +} +#endif + +/* + * Get the key quota record for a user, allocating a new record if one doesn't + * already exist. + */ +struct key_user *key_user_lookup(uid_t uid, struct user_namespace *user_ns) +{ + struct key_user *candidate = NULL, *user; + struct rb_node *parent = NULL; + struct rb_node **p; + +try_again: + p = &key_user_tree.rb_node; + spin_lock(&key_user_lock); + + /* search the tree for a user record with a matching UID */ + while (*p) { + parent = *p; + user = rb_entry(parent, struct key_user, node); + + if (uid < user->uid) + p = &(*p)->rb_left; + else if (uid > user->uid) + p = &(*p)->rb_right; + else if (user_ns < user->user_ns) + p = &(*p)->rb_left; + else if (user_ns > user->user_ns) + p = &(*p)->rb_right; + else + goto found; + } + + /* if we get here, we failed to find a match in the tree */ + if (!candidate) { + /* allocate a candidate user record if we don't already have + * one */ + spin_unlock(&key_user_lock); + + user = NULL; + candidate = kmalloc(sizeof(struct key_user), GFP_KERNEL); + if (unlikely(!candidate)) + goto out; + + /* the allocation may have scheduled, so we need to repeat the + * search lest someone else added the record whilst we were + * asleep */ + goto try_again; + } + + /* if we get here, then the user record still hadn't appeared on the + * second pass - so we use the candidate record */ + atomic_set(&candidate->usage, 1); + atomic_set(&candidate->nkeys, 0); + atomic_set(&candidate->nikeys, 0); + candidate->uid = uid; + candidate->user_ns = get_user_ns(user_ns); + candidate->qnkeys = 0; + candidate->qnbytes = 0; + spin_lock_init(&candidate->lock); + mutex_init(&candidate->cons_lock); + + rb_link_node(&candidate->node, parent, p); + rb_insert_color(&candidate->node, &key_user_tree); + spin_unlock(&key_user_lock); + user = candidate; + goto out; + + /* okay - we found a user record for this UID */ +found: + atomic_inc(&user->usage); + spin_unlock(&key_user_lock); + kfree(candidate); +out: + return user; +} + +/* + * Dispose of a user structure + */ +void key_user_put(struct key_user *user) +{ + if (atomic_dec_and_lock(&user->usage, &key_user_lock)) { + rb_erase(&user->node, &key_user_tree); + spin_unlock(&key_user_lock); + put_user_ns(user->user_ns); + + kfree(user); + } +} + +/* + * Allocate a serial number for a key. These are assigned randomly to avoid + * security issues through covert channel problems. + */ +static inline void key_alloc_serial(struct key *key) +{ + struct rb_node *parent, **p; + struct key *xkey; + + /* propose a random serial number and look for a hole for it in the + * serial number tree */ + do { + get_random_bytes(&key->serial, sizeof(key->serial)); + + key->serial >>= 1; /* negative numbers are not permitted */ + } while (key->serial < 3); + + spin_lock(&key_serial_lock); + +attempt_insertion: + parent = NULL; + p = &key_serial_tree.rb_node; + + while (*p) { + parent = *p; + xkey = rb_entry(parent, struct key, serial_node); + + if (key->serial < xkey->serial) + p = &(*p)->rb_left; + else if (key->serial > xkey->serial) + p = &(*p)->rb_right; + else + goto serial_exists; + } + + /* we've found a suitable hole - arrange for this key to occupy it */ + rb_link_node(&key->serial_node, parent, p); + rb_insert_color(&key->serial_node, &key_serial_tree); + + spin_unlock(&key_serial_lock); + return; + + /* we found a key with the proposed serial number - walk the tree from + * that point looking for the next unused serial number */ +serial_exists: + for (;;) { + key->serial++; + if (key->serial < 3) { + key->serial = 3; + goto attempt_insertion; + } + + parent = rb_next(parent); + if (!parent) + goto attempt_insertion; + + xkey = rb_entry(parent, struct key, serial_node); + if (key->serial < xkey->serial) + goto attempt_insertion; + } +} + +/** + * key_alloc - Allocate a key of the specified type. + * @type: The type of key to allocate. + * @desc: The key description to allow the key to be searched out. + * @uid: The owner of the new key. + * @gid: The group ID for the new key's group permissions. + * @cred: The credentials specifying UID namespace. + * @perm: The permissions mask of the new key. + * @flags: Flags specifying quota properties. + * + * Allocate a key of the specified type with the attributes given. The key is + * returned in an uninstantiated state and the caller needs to instantiate the + * key before returning. + * + * The user's key count quota is updated to reflect the creation of the key and + * the user's key data quota has the default for the key type reserved. The + * instantiation function should amend this as necessary. If insufficient + * quota is available, -EDQUOT will be returned. + * + * The LSM security modules can prevent a key being created, in which case + * -EACCES will be returned. + * + * Returns a pointer to the new key if successful and an error code otherwise. + * + * Note that the caller needs to ensure the key type isn't uninstantiated. + * Internally this can be done by locking key_types_sem. Externally, this can + * be done by either never unregistering the key type, or making sure + * key_alloc() calls don't race with module unloading. + */ +struct key *key_alloc(struct key_type *type, const char *desc, + uid_t uid, gid_t gid, const struct cred *cred, + key_perm_t perm, unsigned long flags) +{ + struct key_user *user = NULL; + struct key *key; + size_t desclen, quotalen; + int ret; + + key = ERR_PTR(-EINVAL); + if (!desc || !*desc) + goto error; + + if (type->vet_description) { + ret = type->vet_description(desc); + if (ret < 0) { + key = ERR_PTR(ret); + goto error; + } + } + + desclen = strlen(desc) + 1; + quotalen = desclen + type->def_datalen; + + /* get hold of the key tracking for this user */ + user = key_user_lookup(uid, cred->user->user_ns); + if (!user) + goto no_memory_1; + + /* check that the user's quota permits allocation of another key and + * its description */ + if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) { + unsigned maxkeys = (uid == 0) ? + key_quota_root_maxkeys : key_quota_maxkeys; + unsigned maxbytes = (uid == 0) ? + key_quota_root_maxbytes : key_quota_maxbytes; + + spin_lock(&user->lock); + if (!(flags & KEY_ALLOC_QUOTA_OVERRUN)) { + if (user->qnkeys + 1 >= maxkeys || + user->qnbytes + quotalen >= maxbytes || + user->qnbytes + quotalen < user->qnbytes) + goto no_quota; + } + + user->qnkeys++; + user->qnbytes += quotalen; + spin_unlock(&user->lock); + } + + /* allocate and initialise the key and its description */ + key = kmem_cache_alloc(key_jar, GFP_KERNEL); + if (!key) + goto no_memory_2; + + if (desc) { + key->description = kmemdup(desc, desclen, GFP_KERNEL); + if (!key->description) + goto no_memory_3; + } + + atomic_set(&key->usage, 1); + init_rwsem(&key->sem); + key->type = type; + key->user = user; + key->quotalen = quotalen; + key->datalen = type->def_datalen; + key->uid = uid; + key->gid = gid; + key->perm = perm; + key->flags = 0; + key->expiry = 0; + key->payload.data = NULL; + key->security = NULL; + + if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) + key->flags |= 1 << KEY_FLAG_IN_QUOTA; + + memset(&key->type_data, 0, sizeof(key->type_data)); + +#ifdef KEY_DEBUGGING + key->magic = KEY_DEBUG_MAGIC; +#endif + + /* let the security module know about the key */ + ret = security_key_alloc(key, cred, flags); + if (ret < 0) + goto security_error; + + /* publish the key by giving it a serial number */ + atomic_inc(&user->nkeys); + key_alloc_serial(key); + +error: + return key; + +security_error: + kfree(key->description); + kmem_cache_free(key_jar, key); + if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) { + spin_lock(&user->lock); + user->qnkeys--; + user->qnbytes -= quotalen; + spin_unlock(&user->lock); + } + key_user_put(user); + key = ERR_PTR(ret); + goto error; + +no_memory_3: + kmem_cache_free(key_jar, key); +no_memory_2: + if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) { + spin_lock(&user->lock); + user->qnkeys--; + user->qnbytes -= quotalen; + spin_unlock(&user->lock); + } + key_user_put(user); +no_memory_1: + key = ERR_PTR(-ENOMEM); + goto error; + +no_quota: + spin_unlock(&user->lock); + key_user_put(user); + key = ERR_PTR(-EDQUOT); + goto error; +} +EXPORT_SYMBOL(key_alloc); + +/** + * key_payload_reserve - Adjust data quota reservation for the key's payload + * @key: The key to make the reservation for. + * @datalen: The amount of data payload the caller now wants. + * + * Adjust the amount of the owning user's key data quota that a key reserves. + * If the amount is increased, then -EDQUOT may be returned if there isn't + * enough free quota available. + * + * If successful, 0 is returned. + */ +int key_payload_reserve(struct key *key, size_t datalen) +{ + int delta = (int)datalen - key->datalen; + int ret = 0; + + key_check(key); + + /* contemplate the quota adjustment */ + if (delta != 0 && test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { + unsigned maxbytes = (key->user->uid == 0) ? + key_quota_root_maxbytes : key_quota_maxbytes; + + spin_lock(&key->user->lock); + + if (delta > 0 && + (key->user->qnbytes + delta >= maxbytes || + key->user->qnbytes + delta < key->user->qnbytes)) { + ret = -EDQUOT; + } + else { + key->user->qnbytes += delta; + key->quotalen += delta; + } + spin_unlock(&key->user->lock); + } + + /* change the recorded data length if that didn't generate an error */ + if (ret == 0) + key->datalen = datalen; + + return ret; +} +EXPORT_SYMBOL(key_payload_reserve); + +/* + * Instantiate a key and link it into the target keyring atomically. Must be + * called with the target keyring's semaphore writelocked. The target key's + * semaphore need not be locked as instantiation is serialised by + * key_construction_mutex. + */ +static int __key_instantiate_and_link(struct key *key, + const void *data, + size_t datalen, + struct key *keyring, + struct key *authkey, + unsigned long *_prealloc) +{ + int ret, awaken; + + key_check(key); + key_check(keyring); + + awaken = 0; + ret = -EBUSY; + + mutex_lock(&key_construction_mutex); + + /* can't instantiate twice */ + if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) { + /* instantiate the key */ + ret = key->type->instantiate(key, data, datalen); + + if (ret == 0) { + /* mark the key as being instantiated */ + atomic_inc(&key->user->nikeys); + set_bit(KEY_FLAG_INSTANTIATED, &key->flags); + + if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags)) + awaken = 1; + + /* and link it into the destination keyring */ + if (keyring) + __key_link(keyring, key, _prealloc); + + /* disable the authorisation key */ + if (authkey) + key_revoke(authkey); + } + } + + mutex_unlock(&key_construction_mutex); + + /* wake up anyone waiting for a key to be constructed */ + if (awaken) + wake_up_bit(&key->flags, KEY_FLAG_USER_CONSTRUCT); + + return ret; +} + +/** + * key_instantiate_and_link - Instantiate a key and link it into the keyring. + * @key: The key to instantiate. + * @data: The data to use to instantiate the keyring. + * @datalen: The length of @data. + * @keyring: Keyring to create a link in on success (or NULL). + * @authkey: The authorisation token permitting instantiation. + * + * Instantiate a key that's in the uninstantiated state using the provided data + * and, if successful, link it in to the destination keyring if one is + * supplied. + * + * If successful, 0 is returned, the authorisation token is revoked and anyone + * waiting for the key is woken up. If the key was already instantiated, + * -EBUSY will be returned. + */ +int key_instantiate_and_link(struct key *key, + const void *data, + size_t datalen, + struct key *keyring, + struct key *authkey) +{ + unsigned long prealloc; + int ret; + + if (keyring) { + ret = __key_link_begin(keyring, key->type, key->description, + &prealloc); + if (ret < 0) + return ret; + } + + ret = __key_instantiate_and_link(key, data, datalen, keyring, authkey, + &prealloc); + + if (keyring) + __key_link_end(keyring, key->type, prealloc); + + return ret; +} + +EXPORT_SYMBOL(key_instantiate_and_link); + +/** + * key_reject_and_link - Negatively instantiate a key and link it into the keyring. + * @key: The key to instantiate. + * @timeout: The timeout on the negative key. + * @error: The error to return when the key is hit. + * @keyring: Keyring to create a link in on success (or NULL). + * @authkey: The authorisation token permitting instantiation. + * + * Negatively instantiate a key that's in the uninstantiated state and, if + * successful, set its timeout and stored error and link it in to the + * destination keyring if one is supplied. The key and any links to the key + * will be automatically garbage collected after the timeout expires. + * + * Negative keys are used to rate limit repeated request_key() calls by causing + * them to return the stored error code (typically ENOKEY) until the negative + * key expires. + * + * If successful, 0 is returned, the authorisation token is revoked and anyone + * waiting for the key is woken up. If the key was already instantiated, + * -EBUSY will be returned. + */ +int key_reject_and_link(struct key *key, + unsigned timeout, + unsigned error, + struct key *keyring, + struct key *authkey) +{ + unsigned long prealloc; + struct timespec now; + int ret, awaken, link_ret = 0; + + key_check(key); + key_check(keyring); + + awaken = 0; + ret = -EBUSY; + + if (keyring) + link_ret = __key_link_begin(keyring, key->type, + key->description, &prealloc); + + mutex_lock(&key_construction_mutex); + + /* can't instantiate twice */ + if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) { + /* mark the key as being negatively instantiated */ + atomic_inc(&key->user->nikeys); + set_bit(KEY_FLAG_NEGATIVE, &key->flags); + set_bit(KEY_FLAG_INSTANTIATED, &key->flags); + key->type_data.reject_error = -error; + now = current_kernel_time(); + key->expiry = now.tv_sec + timeout; + key_schedule_gc(key->expiry + key_gc_delay); + + if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags)) + awaken = 1; + + ret = 0; + + /* and link it into the destination keyring */ + if (keyring && link_ret == 0) + __key_link(keyring, key, &prealloc); + + /* disable the authorisation key */ + if (authkey) + key_revoke(authkey); + } + + mutex_unlock(&key_construction_mutex); + + if (keyring) + __key_link_end(keyring, key->type, prealloc); + + /* wake up anyone waiting for a key to be constructed */ + if (awaken) + wake_up_bit(&key->flags, KEY_FLAG_USER_CONSTRUCT); + + return ret == 0 ? link_ret : ret; +} +EXPORT_SYMBOL(key_reject_and_link); + +/* + * Garbage collect keys in process context so that we don't have to disable + * interrupts all over the place. + * + * key_put() schedules this rather than trying to do the cleanup itself, which + * means key_put() doesn't have to sleep. + */ +static void key_cleanup(struct work_struct *work) +{ + struct rb_node *_n; + struct key *key; + +go_again: + /* look for a dead key in the tree */ + spin_lock(&key_serial_lock); + + for (_n = rb_first(&key_serial_tree); _n; _n = rb_next(_n)) { + key = rb_entry(_n, struct key, serial_node); + + if (atomic_read(&key->usage) == 0) + goto found_dead_key; + } + + spin_unlock(&key_serial_lock); + return; + +found_dead_key: + /* we found a dead key - once we've removed it from the tree, we can + * drop the lock */ + rb_erase(&key->serial_node, &key_serial_tree); + spin_unlock(&key_serial_lock); + + key_check(key); + + security_key_free(key); + + /* deal with the user's key tracking and quota */ + if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { + spin_lock(&key->user->lock); + key->user->qnkeys--; + key->user->qnbytes -= key->quotalen; + spin_unlock(&key->user->lock); + } + + atomic_dec(&key->user->nkeys); + if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) + atomic_dec(&key->user->nikeys); + + key_user_put(key->user); + + /* now throw away the key memory */ + if (key->type->destroy) + key->type->destroy(key); + + kfree(key->description); + +#ifdef KEY_DEBUGGING + key->magic = KEY_DEBUG_MAGIC_X; +#endif + kmem_cache_free(key_jar, key); + + /* there may, of course, be more than one key to destroy */ + goto go_again; +} + +/** + * key_put - Discard a reference to a key. + * @key: The key to discard a reference from. + * + * Discard a reference to a key, and when all the references are gone, we + * schedule the cleanup task to come and pull it out of the tree in process + * context at some later time. + */ +void key_put(struct key *key) +{ + if (key) { + key_check(key); + + if (atomic_dec_and_test(&key->usage)) + schedule_work(&key_cleanup_task); + } +} +EXPORT_SYMBOL(key_put); + +/* + * Find a key by its serial number. + */ +struct key *key_lookup(key_serial_t id) +{ + struct rb_node *n; + struct key *key; + + spin_lock(&key_serial_lock); + + /* search the tree for the specified key */ + n = key_serial_tree.rb_node; + while (n) { + key = rb_entry(n, struct key, serial_node); + + if (id < key->serial) + n = n->rb_left; + else if (id > key->serial) + n = n->rb_right; + else + goto found; + } + +not_found: + key = ERR_PTR(-ENOKEY); + goto error; + +found: + /* pretend it doesn't exist if it is awaiting deletion */ + if (atomic_read(&key->usage) == 0) + goto not_found; + + /* this races with key_put(), but that doesn't matter since key_put() + * doesn't actually change the key + */ + atomic_inc(&key->usage); + +error: + spin_unlock(&key_serial_lock); + return key; +} + +/* + * Find and lock the specified key type against removal. + * + * We return with the sem read-locked if successful. If the type wasn't + * available -ENOKEY is returned instead. + */ +struct key_type *key_type_lookup(const char *type) +{ + struct key_type *ktype; + + down_read(&key_types_sem); + + /* look up the key type to see if it's one of the registered kernel + * types */ + list_for_each_entry(ktype, &key_types_list, link) { + if (strcmp(ktype->name, type) == 0) + goto found_kernel_type; + } + + up_read(&key_types_sem); + ktype = ERR_PTR(-ENOKEY); + +found_kernel_type: + return ktype; +} + +/* + * Unlock a key type locked by key_type_lookup(). + */ +void key_type_put(struct key_type *ktype) +{ + up_read(&key_types_sem); +} + +/* + * Attempt to update an existing key. + * + * The key is given to us with an incremented refcount that we need to discard + * if we get an error. + */ +static inline key_ref_t __key_update(key_ref_t key_ref, + const void *payload, size_t plen) +{ + struct key *key = key_ref_to_ptr(key_ref); + int ret; + + /* need write permission on the key to update it */ + ret = key_permission(key_ref, KEY_WRITE); + if (ret < 0) + goto error; + + ret = -EEXIST; + if (!key->type->update) + goto error; + + down_write(&key->sem); + + ret = key->type->update(key, payload, plen); + if (ret == 0) + /* updating a negative key instantiates it */ + clear_bit(KEY_FLAG_NEGATIVE, &key->flags); + + up_write(&key->sem); + + if (ret < 0) + goto error; +out: + return key_ref; + +error: + key_put(key); + key_ref = ERR_PTR(ret); + goto out; +} + +/** + * key_create_or_update - Update or create and instantiate a key. + * @keyring_ref: A pointer to the destination keyring with possession flag. + * @type: The type of key. + * @description: The searchable description for the key. + * @payload: The data to use to instantiate or update the key. + * @plen: The length of @payload. + * @perm: The permissions mask for a new key. + * @flags: The quota flags for a new key. + * + * Search the destination keyring for a key of the same description and if one + * is found, update it, otherwise create and instantiate a new one and create a + * link to it from that keyring. + * + * If perm is KEY_PERM_UNDEF then an appropriate key permissions mask will be + * concocted. + * + * Returns a pointer to the new key if successful, -ENODEV if the key type + * wasn't available, -ENOTDIR if the keyring wasn't a keyring, -EACCES if the + * caller isn't permitted to modify the keyring or the LSM did not permit + * creation of the key. + * + * On success, the possession flag from the keyring ref will be tacked on to + * the key ref before it is returned. + */ +key_ref_t key_create_or_update(key_ref_t keyring_ref, + const char *type, + const char *description, + const void *payload, + size_t plen, + key_perm_t perm, + unsigned long flags) +{ + unsigned long prealloc; + const struct cred *cred = current_cred(); + struct key_type *ktype; + struct key *keyring, *key = NULL; + key_ref_t key_ref; + int ret; + + /* look up the key type to see if it's one of the registered kernel + * types */ + ktype = key_type_lookup(type); + if (IS_ERR(ktype)) { + key_ref = ERR_PTR(-ENODEV); + goto error; + } + + key_ref = ERR_PTR(-EINVAL); + if (!ktype->match || !ktype->instantiate) + goto error_2; + + keyring = key_ref_to_ptr(keyring_ref); + + key_check(keyring); + + key_ref = ERR_PTR(-ENOTDIR); + if (keyring->type != &key_type_keyring) + goto error_2; + + ret = __key_link_begin(keyring, ktype, description, &prealloc); + if (ret < 0) + goto error_2; + + /* if we're going to allocate a new key, we're going to have + * to modify the keyring */ + ret = key_permission(keyring_ref, KEY_WRITE); + if (ret < 0) { + key_ref = ERR_PTR(ret); + goto error_3; + } + + /* if it's possible to update this type of key, search for an existing + * key of the same type and description in the destination keyring and + * update that instead if possible + */ + if (ktype->update) { + key_ref = __keyring_search_one(keyring_ref, ktype, description, + 0); + if (!IS_ERR(key_ref)) + goto found_matching_key; + } + + /* if the client doesn't provide, decide on the permissions we want */ + if (perm == KEY_PERM_UNDEF) { + perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR; + perm |= KEY_USR_VIEW | KEY_USR_SEARCH | KEY_USR_LINK | KEY_USR_SETATTR; + + if (ktype->read) + perm |= KEY_POS_READ | KEY_USR_READ; + + if (ktype == &key_type_keyring || ktype->update) + perm |= KEY_USR_WRITE; + } + + /* allocate a new key */ + key = key_alloc(ktype, description, cred->fsuid, cred->fsgid, cred, + perm, flags); + if (IS_ERR(key)) { + key_ref = ERR_CAST(key); + goto error_3; + } + + /* instantiate it and link it into the target keyring */ + ret = __key_instantiate_and_link(key, payload, plen, keyring, NULL, + &prealloc); + if (ret < 0) { + key_put(key); + key_ref = ERR_PTR(ret); + goto error_3; + } + + key_ref = make_key_ref(key, is_key_possessed(keyring_ref)); + + error_3: + __key_link_end(keyring, ktype, prealloc); + error_2: + key_type_put(ktype); + error: + return key_ref; + + found_matching_key: + /* we found a matching key, so we're going to try to update it + * - we can drop the locks first as we have the key pinned + */ + __key_link_end(keyring, ktype, prealloc); + key_type_put(ktype); + + key_ref = __key_update(key_ref, payload, plen); + goto error; +} +EXPORT_SYMBOL(key_create_or_update); + +/** + * key_update - Update a key's contents. + * @key_ref: The pointer (plus possession flag) to the key. + * @payload: The data to be used to update the key. + * @plen: The length of @payload. + * + * Attempt to update the contents of a key with the given payload data. The + * caller must be granted Write permission on the key. Negative keys can be + * instantiated by this method. + * + * Returns 0 on success, -EACCES if not permitted and -EOPNOTSUPP if the key + * type does not support updating. The key type may return other errors. + */ +int key_update(key_ref_t key_ref, const void *payload, size_t plen) +{ + struct key *key = key_ref_to_ptr(key_ref); + int ret; + + key_check(key); + + /* the key must be writable */ + ret = key_permission(key_ref, KEY_WRITE); + if (ret < 0) + goto error; + + /* attempt to update it if supported */ + ret = -EOPNOTSUPP; + if (key->type->update) { + down_write(&key->sem); + + ret = key->type->update(key, payload, plen); + if (ret == 0) + /* updating a negative key instantiates it */ + clear_bit(KEY_FLAG_NEGATIVE, &key->flags); + + up_write(&key->sem); + } + + error: + return ret; +} +EXPORT_SYMBOL(key_update); + +/** + * key_revoke - Revoke a key. + * @key: The key to be revoked. + * + * Mark a key as being revoked and ask the type to free up its resources. The + * revocation timeout is set and the key and all its links will be + * automatically garbage collected after key_gc_delay amount of time if they + * are not manually dealt with first. + */ +void key_revoke(struct key *key) +{ + struct timespec now; + time_t time; + + key_check(key); + + /* make sure no one's trying to change or use the key when we mark it + * - we tell lockdep that we might nest because we might be revoking an + * authorisation key whilst holding the sem on a key we've just + * instantiated + */ + down_write_nested(&key->sem, 1); + if (!test_and_set_bit(KEY_FLAG_REVOKED, &key->flags) && + key->type->revoke) + key->type->revoke(key); + + /* set the death time to no more than the expiry time */ + now = current_kernel_time(); + time = now.tv_sec; + if (key->revoked_at == 0 || key->revoked_at > time) { + key->revoked_at = time; + key_schedule_gc(key->revoked_at + key_gc_delay); + } + + up_write(&key->sem); +} +EXPORT_SYMBOL(key_revoke); + +/** + * register_key_type - Register a type of key. + * @ktype: The new key type. + * + * Register a new key type. + * + * Returns 0 on success or -EEXIST if a type of this name already exists. + */ +int register_key_type(struct key_type *ktype) +{ + struct key_type *p; + int ret; + + ret = -EEXIST; + down_write(&key_types_sem); + + /* disallow key types with the same name */ + list_for_each_entry(p, &key_types_list, link) { + if (strcmp(p->name, ktype->name) == 0) + goto out; + } + + /* store the type */ + list_add(&ktype->link, &key_types_list); + ret = 0; + +out: + up_write(&key_types_sem); + return ret; +} +EXPORT_SYMBOL(register_key_type); + +/** + * unregister_key_type - Unregister a type of key. + * @ktype: The key type. + * + * Unregister a key type and mark all the extant keys of this type as dead. + * Those keys of this type are then destroyed to get rid of their payloads and + * they and their links will be garbage collected as soon as possible. + */ +void unregister_key_type(struct key_type *ktype) +{ + struct rb_node *_n; + struct key *key; + + down_write(&key_types_sem); + + /* withdraw the key type */ + list_del_init(&ktype->link); + + /* mark all the keys of this type dead */ + spin_lock(&key_serial_lock); + + for (_n = rb_first(&key_serial_tree); _n; _n = rb_next(_n)) { + key = rb_entry(_n, struct key, serial_node); + + if (key->type == ktype) { + key->type = &key_type_dead; + set_bit(KEY_FLAG_DEAD, &key->flags); + } + } + + spin_unlock(&key_serial_lock); + + /* make sure everyone revalidates their keys */ + synchronize_rcu(); + + /* we should now be able to destroy the payloads of all the keys of + * this type with impunity */ + spin_lock(&key_serial_lock); + + for (_n = rb_first(&key_serial_tree); _n; _n = rb_next(_n)) { + key = rb_entry(_n, struct key, serial_node); + + if (key->type == ktype) { + if (ktype->destroy) + ktype->destroy(key); + memset(&key->payload, KEY_DESTROY, sizeof(key->payload)); + } + } + + spin_unlock(&key_serial_lock); + up_write(&key_types_sem); + + key_schedule_gc(0); +} +EXPORT_SYMBOL(unregister_key_type); + +/* + * Initialise the key management state. + */ +void __init key_init(void) +{ + /* allocate a slab in which we can store keys */ + key_jar = kmem_cache_create("key_jar", sizeof(struct key), + 0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL); + + /* add the special key types */ + list_add_tail(&key_type_keyring.link, &key_types_list); + list_add_tail(&key_type_dead.link, &key_types_list); + list_add_tail(&key_type_user.link, &key_types_list); + + /* record the root user tracking */ + rb_link_node(&root_key_user.node, + NULL, + &key_user_tree.rb_node); + + rb_insert_color(&root_key_user.node, + &key_user_tree); +} diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c new file mode 100644 index 00000000..eca51918 --- /dev/null +++ b/security/keys/keyctl.c @@ -0,0 +1,1629 @@ +/* Userspace key control operations + * + * Copyright (C) 2004-5 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/syscalls.h> +#include <linux/keyctl.h> +#include <linux/fs.h> +#include <linux/capability.h> +#include <linux/string.h> +#include <linux/err.h> +#include <linux/vmalloc.h> +#include <linux/security.h> +#include <asm/uaccess.h> +#include "internal.h" + +static int key_get_type_from_user(char *type, + const char __user *_type, + unsigned len) +{ + int ret; + + ret = strncpy_from_user(type, _type, len); + if (ret < 0) + return ret; + if (ret == 0 || ret >= len) + return -EINVAL; + if (type[0] == '.') + return -EPERM; + type[len - 1] = '\0'; + return 0; +} + +/* + * Extract the description of a new key from userspace and either add it as a + * new key to the specified keyring or update a matching key in that keyring. + * + * The keyring must be writable so that we can attach the key to it. + * + * If successful, the new key's serial number is returned, otherwise an error + * code is returned. + */ +SYSCALL_DEFINE5(add_key, const char __user *, _type, + const char __user *, _description, + const void __user *, _payload, + size_t, plen, + key_serial_t, ringid) +{ + key_ref_t keyring_ref, key_ref; + char type[32], *description; + void *payload; + long ret; + bool vm; + + ret = -EINVAL; + if (plen > 1024 * 1024 - 1) + goto error; + + /* draw all the data into kernel space */ + ret = key_get_type_from_user(type, _type, sizeof(type)); + if (ret < 0) + goto error; + + description = strndup_user(_description, PAGE_SIZE); + if (IS_ERR(description)) { + ret = PTR_ERR(description); + goto error; + } + + /* pull the payload in if one was supplied */ + payload = NULL; + + vm = false; + if (_payload) { + ret = -ENOMEM; + payload = kmalloc(plen, GFP_KERNEL); + if (!payload) { + if (plen <= PAGE_SIZE) + goto error2; + vm = true; + payload = vmalloc(plen); + if (!payload) + goto error2; + } + + ret = -EFAULT; + if (copy_from_user(payload, _payload, plen) != 0) + goto error3; + } + + /* find the target keyring (which must be writable) */ + keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_WRITE); + if (IS_ERR(keyring_ref)) { + ret = PTR_ERR(keyring_ref); + goto error3; + } + + /* create or update the requested key and add it to the target + * keyring */ + key_ref = key_create_or_update(keyring_ref, type, description, + payload, plen, KEY_PERM_UNDEF, + KEY_ALLOC_IN_QUOTA); + if (!IS_ERR(key_ref)) { + ret = key_ref_to_ptr(key_ref)->serial; + key_ref_put(key_ref); + } + else { + ret = PTR_ERR(key_ref); + } + + key_ref_put(keyring_ref); + error3: + if (!vm) + kfree(payload); + else + vfree(payload); + error2: + kfree(description); + error: + return ret; +} + +/* + * Search the process keyrings and keyring trees linked from those for a + * matching key. Keyrings must have appropriate Search permission to be + * searched. + * + * If a key is found, it will be attached to the destination keyring if there's + * one specified and the serial number of the key will be returned. + * + * If no key is found, /sbin/request-key will be invoked if _callout_info is + * non-NULL in an attempt to create a key. The _callout_info string will be + * passed to /sbin/request-key to aid with completing the request. If the + * _callout_info string is "" then it will be changed to "-". + */ +SYSCALL_DEFINE4(request_key, const char __user *, _type, + const char __user *, _description, + const char __user *, _callout_info, + key_serial_t, destringid) +{ + struct key_type *ktype; + struct key *key; + key_ref_t dest_ref; + size_t callout_len; + char type[32], *description, *callout_info; + long ret; + + /* pull the type into kernel space */ + ret = key_get_type_from_user(type, _type, sizeof(type)); + if (ret < 0) + goto error; + + /* pull the description into kernel space */ + description = strndup_user(_description, PAGE_SIZE); + if (IS_ERR(description)) { + ret = PTR_ERR(description); + goto error; + } + + /* pull the callout info into kernel space */ + callout_info = NULL; + callout_len = 0; + if (_callout_info) { + callout_info = strndup_user(_callout_info, PAGE_SIZE); + if (IS_ERR(callout_info)) { + ret = PTR_ERR(callout_info); + goto error2; + } + callout_len = strlen(callout_info); + } + + /* get the destination keyring if specified */ + dest_ref = NULL; + if (destringid) { + dest_ref = lookup_user_key(destringid, KEY_LOOKUP_CREATE, + KEY_WRITE); + if (IS_ERR(dest_ref)) { + ret = PTR_ERR(dest_ref); + goto error3; + } + } + + /* find the key type */ + ktype = key_type_lookup(type); + if (IS_ERR(ktype)) { + ret = PTR_ERR(ktype); + goto error4; + } + + /* do the search */ + key = request_key_and_link(ktype, description, callout_info, + callout_len, NULL, key_ref_to_ptr(dest_ref), + KEY_ALLOC_IN_QUOTA); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error5; + } + + /* wait for the key to finish being constructed */ + ret = wait_for_key_construction(key, 1); + if (ret < 0) + goto error6; + + ret = key->serial; + +error6: + key_put(key); +error5: + key_type_put(ktype); +error4: + key_ref_put(dest_ref); +error3: + kfree(callout_info); +error2: + kfree(description); +error: + return ret; +} + +/* + * Get the ID of the specified process keyring. + * + * The requested keyring must have search permission to be found. + * + * If successful, the ID of the requested keyring will be returned. + */ +long keyctl_get_keyring_ID(key_serial_t id, int create) +{ + key_ref_t key_ref; + unsigned long lflags; + long ret; + + lflags = create ? KEY_LOOKUP_CREATE : 0; + key_ref = lookup_user_key(id, lflags, KEY_SEARCH); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error; + } + + ret = key_ref_to_ptr(key_ref)->serial; + key_ref_put(key_ref); +error: + return ret; +} + +/* + * Join a (named) session keyring. + * + * Create and join an anonymous session keyring or join a named session + * keyring, creating it if necessary. A named session keyring must have Search + * permission for it to be joined. Session keyrings without this permit will + * be skipped over. + * + * If successful, the ID of the joined session keyring will be returned. + */ +long keyctl_join_session_keyring(const char __user *_name) +{ + char *name; + long ret; + + /* fetch the name from userspace */ + name = NULL; + if (_name) { + name = strndup_user(_name, PAGE_SIZE); + if (IS_ERR(name)) { + ret = PTR_ERR(name); + goto error; + } + } + + /* join the session */ + ret = join_session_keyring(name); + kfree(name); + +error: + return ret; +} + +/* + * Update a key's data payload from the given data. + * + * The key must grant the caller Write permission and the key type must support + * updating for this to work. A negative key can be positively instantiated + * with this call. + * + * If successful, 0 will be returned. If the key type does not support + * updating, then -EOPNOTSUPP will be returned. + */ +long keyctl_update_key(key_serial_t id, + const void __user *_payload, + size_t plen) +{ + key_ref_t key_ref; + void *payload; + long ret; + + ret = -EINVAL; + if (plen > PAGE_SIZE) + goto error; + + /* pull the payload in if one was supplied */ + payload = NULL; + if (_payload) { + ret = -ENOMEM; + payload = kmalloc(plen, GFP_KERNEL); + if (!payload) + goto error; + + ret = -EFAULT; + if (copy_from_user(payload, _payload, plen) != 0) + goto error2; + } + + /* find the target key (which must be writable) */ + key_ref = lookup_user_key(id, 0, KEY_WRITE); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error2; + } + + /* update the key */ + ret = key_update(key_ref, payload, plen); + + key_ref_put(key_ref); +error2: + kfree(payload); +error: + return ret; +} + +/* + * Revoke a key. + * + * The key must be grant the caller Write or Setattr permission for this to + * work. The key type should give up its quota claim when revoked. The key + * and any links to the key will be automatically garbage collected after a + * certain amount of time (/proc/sys/kernel/keys/gc_delay). + * + * If successful, 0 is returned. + */ +long keyctl_revoke_key(key_serial_t id) +{ + key_ref_t key_ref; + long ret; + + key_ref = lookup_user_key(id, 0, KEY_WRITE); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + if (ret != -EACCES) + goto error; + key_ref = lookup_user_key(id, 0, KEY_SETATTR); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error; + } + } + + key_revoke(key_ref_to_ptr(key_ref)); + ret = 0; + + key_ref_put(key_ref); +error: + return ret; +} + +/* + * Clear the specified keyring, creating an empty process keyring if one of the + * special keyring IDs is used. + * + * The keyring must grant the caller Write permission for this to work. If + * successful, 0 will be returned. + */ +long keyctl_keyring_clear(key_serial_t ringid) +{ + key_ref_t keyring_ref; + long ret; + + keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_WRITE); + if (IS_ERR(keyring_ref)) { + ret = PTR_ERR(keyring_ref); + goto error; + } + + ret = keyring_clear(key_ref_to_ptr(keyring_ref)); + + key_ref_put(keyring_ref); +error: + return ret; +} + +/* + * Create a link from a keyring to a key if there's no matching key in the + * keyring, otherwise replace the link to the matching key with a link to the + * new key. + * + * The key must grant the caller Link permission and the the keyring must grant + * the caller Write permission. Furthermore, if an additional link is created, + * the keyring's quota will be extended. + * + * If successful, 0 will be returned. + */ +long keyctl_keyring_link(key_serial_t id, key_serial_t ringid) +{ + key_ref_t keyring_ref, key_ref; + long ret; + + keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_WRITE); + if (IS_ERR(keyring_ref)) { + ret = PTR_ERR(keyring_ref); + goto error; + } + + key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE, KEY_LINK); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error2; + } + + ret = key_link(key_ref_to_ptr(keyring_ref), key_ref_to_ptr(key_ref)); + + key_ref_put(key_ref); +error2: + key_ref_put(keyring_ref); +error: + return ret; +} + +/* + * Unlink a key from a keyring. + * + * The keyring must grant the caller Write permission for this to work; the key + * itself need not grant the caller anything. If the last link to a key is + * removed then that key will be scheduled for destruction. + * + * If successful, 0 will be returned. + */ +long keyctl_keyring_unlink(key_serial_t id, key_serial_t ringid) +{ + key_ref_t keyring_ref, key_ref; + long ret; + + keyring_ref = lookup_user_key(ringid, 0, KEY_WRITE); + if (IS_ERR(keyring_ref)) { + ret = PTR_ERR(keyring_ref); + goto error; + } + + key_ref = lookup_user_key(id, KEY_LOOKUP_FOR_UNLINK, 0); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error2; + } + + ret = key_unlink(key_ref_to_ptr(keyring_ref), key_ref_to_ptr(key_ref)); + + key_ref_put(key_ref); +error2: + key_ref_put(keyring_ref); +error: + return ret; +} + +/* + * Return a description of a key to userspace. + * + * The key must grant the caller View permission for this to work. + * + * If there's a buffer, we place up to buflen bytes of data into it formatted + * in the following way: + * + * type;uid;gid;perm;description<NUL> + * + * If successful, we return the amount of description available, irrespective + * of how much we may have copied into the buffer. + */ +long keyctl_describe_key(key_serial_t keyid, + char __user *buffer, + size_t buflen) +{ + struct key *key, *instkey; + key_ref_t key_ref; + char *tmpbuf; + long ret; + + key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, KEY_VIEW); + if (IS_ERR(key_ref)) { + /* viewing a key under construction is permitted if we have the + * authorisation token handy */ + if (PTR_ERR(key_ref) == -EACCES) { + instkey = key_get_instantiation_authkey(keyid); + if (!IS_ERR(instkey)) { + key_put(instkey); + key_ref = lookup_user_key(keyid, + KEY_LOOKUP_PARTIAL, + 0); + if (!IS_ERR(key_ref)) + goto okay; + } + } + + ret = PTR_ERR(key_ref); + goto error; + } + +okay: + /* calculate how much description we're going to return */ + ret = -ENOMEM; + tmpbuf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!tmpbuf) + goto error2; + + key = key_ref_to_ptr(key_ref); + + ret = snprintf(tmpbuf, PAGE_SIZE - 1, + "%s;%d;%d;%08x;%s", + 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) + ret = PAGE_SIZE - 1; + tmpbuf[ret] = 0; + ret++; + + /* consider returning the data */ + if (buffer && buflen > 0) { + if (buflen > ret) + buflen = ret; + + if (copy_to_user(buffer, tmpbuf, buflen) != 0) + ret = -EFAULT; + } + + kfree(tmpbuf); +error2: + key_ref_put(key_ref); +error: + return ret; +} + +/* + * Search the specified keyring and any keyrings it links to for a matching + * key. Only keyrings that grant the caller Search permission will be searched + * (this includes the starting keyring). Only keys with Search permission can + * be found. + * + * If successful, the found key will be linked to the destination keyring if + * supplied and the key has Link permission, and the found key ID will be + * returned. + */ +long keyctl_keyring_search(key_serial_t ringid, + const char __user *_type, + const char __user *_description, + key_serial_t destringid) +{ + struct key_type *ktype; + key_ref_t keyring_ref, key_ref, dest_ref; + char type[32], *description; + long ret; + + /* pull the type and description into kernel space */ + ret = key_get_type_from_user(type, _type, sizeof(type)); + if (ret < 0) + goto error; + + description = strndup_user(_description, PAGE_SIZE); + if (IS_ERR(description)) { + ret = PTR_ERR(description); + goto error; + } + + /* get the keyring at which to begin the search */ + keyring_ref = lookup_user_key(ringid, 0, KEY_SEARCH); + if (IS_ERR(keyring_ref)) { + ret = PTR_ERR(keyring_ref); + goto error2; + } + + /* get the destination keyring if specified */ + dest_ref = NULL; + if (destringid) { + dest_ref = lookup_user_key(destringid, KEY_LOOKUP_CREATE, + KEY_WRITE); + if (IS_ERR(dest_ref)) { + ret = PTR_ERR(dest_ref); + goto error3; + } + } + + /* find the key type */ + ktype = key_type_lookup(type); + if (IS_ERR(ktype)) { + ret = PTR_ERR(ktype); + goto error4; + } + + /* do the search */ + key_ref = keyring_search(keyring_ref, ktype, description); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + + /* treat lack or presence of a negative key the same */ + if (ret == -EAGAIN) + ret = -ENOKEY; + goto error5; + } + + /* link the resulting key to the destination keyring if we can */ + if (dest_ref) { + ret = key_permission(key_ref, KEY_LINK); + if (ret < 0) + goto error6; + + ret = key_link(key_ref_to_ptr(dest_ref), key_ref_to_ptr(key_ref)); + if (ret < 0) + goto error6; + } + + ret = key_ref_to_ptr(key_ref)->serial; + +error6: + key_ref_put(key_ref); +error5: + key_type_put(ktype); +error4: + key_ref_put(dest_ref); +error3: + key_ref_put(keyring_ref); +error2: + kfree(description); +error: + return ret; +} + +/* + * Read a key's payload. + * + * The key must either grant the caller Read permission, or it must grant the + * caller Search permission when searched for from the process keyrings. + * + * If successful, we place up to buflen bytes of data into the buffer, if one + * is provided, and return the amount of data that is available in the key, + * irrespective of how much we copied into the buffer. + */ +long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen) +{ + struct key *key; + key_ref_t key_ref; + long ret; + + /* find the key first */ + key_ref = lookup_user_key(keyid, 0, 0); + if (IS_ERR(key_ref)) { + ret = -ENOKEY; + goto error; + } + + key = key_ref_to_ptr(key_ref); + + /* see if we can read it directly */ + ret = key_permission(key_ref, KEY_READ); + if (ret == 0) + goto can_read_key; + if (ret != -EACCES) + goto error; + + /* we can't; see if it's searchable from this process's keyrings + * - we automatically take account of the fact that it may be + * dangling off an instantiation key + */ + if (!is_key_possessed(key_ref)) { + ret = -EACCES; + goto error2; + } + + /* the key is probably readable - now try to read it */ +can_read_key: + ret = key_validate(key); + if (ret == 0) { + ret = -EOPNOTSUPP; + if (key->type->read) { + /* read the data with the semaphore held (since we + * might sleep) */ + down_read(&key->sem); + ret = key->type->read(key, buffer, buflen); + up_read(&key->sem); + } + } + +error2: + key_put(key); +error: + return ret; +} + +/* + * Change the ownership of a key + * + * The key must grant the caller Setattr permission for this to work, though + * the key need not be fully instantiated yet. For the UID to be changed, or + * for the GID to be changed to a group the caller is not a member of, the + * caller must have sysadmin capability. If either uid or gid is -1 then that + * attribute is not changed. + * + * If the UID is to be changed, the new user must have sufficient quota to + * accept the key. The quota deduction will be removed from the old user to + * the new user should the attribute be changed. + * + * If successful, 0 will be returned. + */ +long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid) +{ + struct key_user *newowner, *zapowner = NULL; + struct key *key; + key_ref_t key_ref; + long ret; + + ret = 0; + if (uid == (uid_t) -1 && gid == (gid_t) -1) + goto error; + + key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL, + KEY_SETATTR); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error; + } + + key = key_ref_to_ptr(key_ref); + + /* make the changes with the locks held to prevent chown/chown races */ + ret = -EACCES; + down_write(&key->sem); + + if (!capable(CAP_SYS_ADMIN)) { + /* only the sysadmin can chown a key to some other UID */ + if (uid != (uid_t) -1 && key->uid != uid) + goto error_put; + + /* only the sysadmin can set the key's GID to a group other + * than one of those that the current process subscribes to */ + if (gid != (gid_t) -1 && gid != key->gid && !in_group_p(gid)) + goto error_put; + } + + /* change the UID */ + if (uid != (uid_t) -1 && uid != key->uid) { + ret = -ENOMEM; + newowner = key_user_lookup(uid, current_user_ns()); + if (!newowner) + goto error_put; + + /* transfer the quota burden to the new user */ + if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { + unsigned maxkeys = (uid == 0) ? + key_quota_root_maxkeys : key_quota_maxkeys; + unsigned maxbytes = (uid == 0) ? + key_quota_root_maxbytes : key_quota_maxbytes; + + spin_lock(&newowner->lock); + if (newowner->qnkeys + 1 >= maxkeys || + newowner->qnbytes + key->quotalen >= maxbytes || + newowner->qnbytes + key->quotalen < + newowner->qnbytes) + goto quota_overrun; + + newowner->qnkeys++; + newowner->qnbytes += key->quotalen; + spin_unlock(&newowner->lock); + + spin_lock(&key->user->lock); + key->user->qnkeys--; + key->user->qnbytes -= key->quotalen; + spin_unlock(&key->user->lock); + } + + atomic_dec(&key->user->nkeys); + atomic_inc(&newowner->nkeys); + + if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) { + atomic_dec(&key->user->nikeys); + atomic_inc(&newowner->nikeys); + } + + zapowner = key->user; + key->user = newowner; + key->uid = uid; + } + + /* change the GID */ + if (gid != (gid_t) -1) + key->gid = gid; + + ret = 0; + +error_put: + up_write(&key->sem); + key_put(key); + if (zapowner) + key_user_put(zapowner); +error: + return ret; + +quota_overrun: + spin_unlock(&newowner->lock); + zapowner = newowner; + ret = -EDQUOT; + goto error_put; +} + +/* + * Change the permission mask on a key. + * + * The key must grant the caller Setattr permission for this to work, though + * the key need not be fully instantiated yet. If the caller does not have + * sysadmin capability, it may only change the permission on keys that it owns. + */ +long keyctl_setperm_key(key_serial_t id, key_perm_t perm) +{ + struct key *key; + key_ref_t key_ref; + long ret; + + ret = -EINVAL; + if (perm & ~(KEY_POS_ALL | KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL)) + goto error; + + key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL, + KEY_SETATTR); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error; + } + + key = key_ref_to_ptr(key_ref); + + /* make the changes with the locks held to prevent chown/chmod races */ + ret = -EACCES; + down_write(&key->sem); + + /* if we're not the sysadmin, we can only change a key that we own */ + if (capable(CAP_SYS_ADMIN) || key->uid == current_fsuid()) { + key->perm = perm; + ret = 0; + } + + up_write(&key->sem); + key_put(key); +error: + return ret; +} + +/* + * Get the destination keyring for instantiation and check that the caller has + * Write permission on it. + */ +static long get_instantiation_keyring(key_serial_t ringid, + struct request_key_auth *rka, + struct key **_dest_keyring) +{ + key_ref_t dkref; + + *_dest_keyring = NULL; + + /* just return a NULL pointer if we weren't asked to make a link */ + if (ringid == 0) + return 0; + + /* if a specific keyring is nominated by ID, then use that */ + if (ringid > 0) { + dkref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_WRITE); + if (IS_ERR(dkref)) + return PTR_ERR(dkref); + *_dest_keyring = key_ref_to_ptr(dkref); + return 0; + } + + if (ringid == KEY_SPEC_REQKEY_AUTH_KEY) + return -EINVAL; + + /* otherwise specify the destination keyring recorded in the + * authorisation key (any KEY_SPEC_*_KEYRING) */ + if (ringid >= KEY_SPEC_REQUESTOR_KEYRING) { + *_dest_keyring = key_get(rka->dest_keyring); + return 0; + } + + return -ENOKEY; +} + +/* + * Change the request_key authorisation key on the current process. + */ +static int keyctl_change_reqkey_auth(struct key *key) +{ + struct cred *new; + + new = prepare_creds(); + if (!new) + return -ENOMEM; + + key_put(new->request_key_auth); + new->request_key_auth = key_get(key); + + return commit_creds(new); +} + +/* + * Copy the iovec data from userspace + */ +static long copy_from_user_iovec(void *buffer, const struct iovec *iov, + unsigned ioc) +{ + for (; ioc > 0; ioc--) { + if (copy_from_user(buffer, iov->iov_base, iov->iov_len) != 0) + return -EFAULT; + buffer += iov->iov_len; + iov++; + } + return 0; +} + +/* + * Instantiate a key with the specified payload and link the key into the + * destination keyring if one is given. + * + * The caller must have the appropriate instantiation permit set for this to + * work (see keyctl_assume_authority). No other permissions are required. + * + * If successful, 0 will be returned. + */ +long keyctl_instantiate_key_common(key_serial_t id, + const struct iovec *payload_iov, + unsigned ioc, + size_t plen, + key_serial_t ringid) +{ + const struct cred *cred = current_cred(); + struct request_key_auth *rka; + struct key *instkey, *dest_keyring; + void *payload; + long ret; + bool vm = false; + + kenter("%d,,%zu,%d", id, plen, ringid); + + ret = -EINVAL; + if (plen > 1024 * 1024 - 1) + goto error; + + /* the appropriate instantiation authorisation key must have been + * assumed before calling this */ + ret = -EPERM; + instkey = cred->request_key_auth; + if (!instkey) + goto error; + + rka = instkey->payload.data; + if (rka->target_key->serial != id) + goto error; + + /* pull the payload in if one was supplied */ + payload = NULL; + + if (payload_iov) { + ret = -ENOMEM; + payload = kmalloc(plen, GFP_KERNEL); + if (!payload) { + if (plen <= PAGE_SIZE) + goto error; + vm = true; + payload = vmalloc(plen); + if (!payload) + goto error; + } + + ret = copy_from_user_iovec(payload, payload_iov, ioc); + if (ret < 0) + goto error2; + } + + /* find the destination keyring amongst those belonging to the + * requesting task */ + ret = get_instantiation_keyring(ringid, rka, &dest_keyring); + if (ret < 0) + goto error2; + + /* instantiate the key and link it into a keyring */ + ret = key_instantiate_and_link(rka->target_key, payload, plen, + dest_keyring, instkey); + + key_put(dest_keyring); + + /* discard the assumed authority if it's just been disabled by + * instantiation of the key */ + if (ret == 0) + keyctl_change_reqkey_auth(NULL); + +error2: + if (!vm) + kfree(payload); + else + vfree(payload); +error: + return ret; +} + +/* + * Instantiate a key with the specified payload and link the key into the + * destination keyring if one is given. + * + * The caller must have the appropriate instantiation permit set for this to + * work (see keyctl_assume_authority). No other permissions are required. + * + * If successful, 0 will be returned. + */ +long keyctl_instantiate_key(key_serial_t id, + const void __user *_payload, + size_t plen, + key_serial_t ringid) +{ + if (_payload && plen) { + struct iovec iov[1] = { + [0].iov_base = (void __user *)_payload, + [0].iov_len = plen + }; + + return keyctl_instantiate_key_common(id, iov, 1, plen, ringid); + } + + return keyctl_instantiate_key_common(id, NULL, 0, 0, ringid); +} + +/* + * Instantiate a key with the specified multipart payload and link the key into + * the destination keyring if one is given. + * + * The caller must have the appropriate instantiation permit set for this to + * work (see keyctl_assume_authority). No other permissions are required. + * + * If successful, 0 will be returned. + */ +long keyctl_instantiate_key_iov(key_serial_t id, + const struct iovec __user *_payload_iov, + unsigned ioc, + key_serial_t ringid) +{ + struct iovec iovstack[UIO_FASTIOV], *iov = iovstack; + long ret; + + if (_payload_iov == 0 || ioc == 0) + goto no_payload; + + ret = rw_copy_check_uvector(WRITE, _payload_iov, ioc, + ARRAY_SIZE(iovstack), iovstack, &iov); + if (ret < 0) + return ret; + if (ret == 0) + goto no_payload_free; + + ret = keyctl_instantiate_key_common(id, iov, ioc, ret, ringid); + + if (iov != iovstack) + kfree(iov); + return ret; + +no_payload_free: + if (iov != iovstack) + kfree(iov); +no_payload: + return keyctl_instantiate_key_common(id, NULL, 0, 0, ringid); +} + +/* + * Negatively instantiate the key with the given timeout (in seconds) and link + * the key into the destination keyring if one is given. + * + * The caller must have the appropriate instantiation permit set for this to + * work (see keyctl_assume_authority). No other permissions are required. + * + * The key and any links to the key will be automatically garbage collected + * after the timeout expires. + * + * Negative keys are used to rate limit repeated request_key() calls by causing + * them to return -ENOKEY until the negative key expires. + * + * If successful, 0 will be returned. + */ +long keyctl_negate_key(key_serial_t id, unsigned timeout, key_serial_t ringid) +{ + return keyctl_reject_key(id, timeout, ENOKEY, ringid); +} + +/* + * Negatively instantiate the key with the given timeout (in seconds) and error + * code and link the key into the destination keyring if one is given. + * + * The caller must have the appropriate instantiation permit set for this to + * work (see keyctl_assume_authority). No other permissions are required. + * + * The key and any links to the key will be automatically garbage collected + * after the timeout expires. + * + * Negative keys are used to rate limit repeated request_key() calls by causing + * them to return the specified error code until the negative key expires. + * + * If successful, 0 will be returned. + */ +long keyctl_reject_key(key_serial_t id, unsigned timeout, unsigned error, + key_serial_t ringid) +{ + const struct cred *cred = current_cred(); + struct request_key_auth *rka; + struct key *instkey, *dest_keyring; + long ret; + + kenter("%d,%u,%u,%d", id, timeout, error, ringid); + + /* must be a valid error code and mustn't be a kernel special */ + if (error <= 0 || + error >= MAX_ERRNO || + error == ERESTARTSYS || + error == ERESTARTNOINTR || + error == ERESTARTNOHAND || + error == ERESTART_RESTARTBLOCK) + return -EINVAL; + + /* the appropriate instantiation authorisation key must have been + * assumed before calling this */ + ret = -EPERM; + instkey = cred->request_key_auth; + if (!instkey) + goto error; + + rka = instkey->payload.data; + if (rka->target_key->serial != id) + goto error; + + /* find the destination keyring if present (which must also be + * writable) */ + ret = get_instantiation_keyring(ringid, rka, &dest_keyring); + if (ret < 0) + goto error; + + /* instantiate the key and link it into a keyring */ + ret = key_reject_and_link(rka->target_key, timeout, error, + dest_keyring, instkey); + + key_put(dest_keyring); + + /* discard the assumed authority if it's just been disabled by + * instantiation of the key */ + if (ret == 0) + keyctl_change_reqkey_auth(NULL); + +error: + return ret; +} + +/* + * Read or set the default keyring in which request_key() will cache keys and + * return the old setting. + * + * If a process keyring is specified then this will be created if it doesn't + * yet exist. The old setting will be returned if successful. + */ +long keyctl_set_reqkey_keyring(int reqkey_defl) +{ + struct cred *new; + int ret, old_setting; + + old_setting = current_cred_xxx(jit_keyring); + + if (reqkey_defl == KEY_REQKEY_DEFL_NO_CHANGE) + return old_setting; + + new = prepare_creds(); + if (!new) + return -ENOMEM; + + switch (reqkey_defl) { + case KEY_REQKEY_DEFL_THREAD_KEYRING: + ret = install_thread_keyring_to_cred(new); + if (ret < 0) + goto error; + goto set; + + case KEY_REQKEY_DEFL_PROCESS_KEYRING: + ret = install_process_keyring_to_cred(new); + if (ret < 0) { + if (ret != -EEXIST) + goto error; + ret = 0; + } + goto set; + + case KEY_REQKEY_DEFL_DEFAULT: + case KEY_REQKEY_DEFL_SESSION_KEYRING: + case KEY_REQKEY_DEFL_USER_KEYRING: + case KEY_REQKEY_DEFL_USER_SESSION_KEYRING: + case KEY_REQKEY_DEFL_REQUESTOR_KEYRING: + goto set; + + case KEY_REQKEY_DEFL_NO_CHANGE: + case KEY_REQKEY_DEFL_GROUP_KEYRING: + default: + ret = -EINVAL; + goto error; + } + +set: + new->jit_keyring = reqkey_defl; + commit_creds(new); + return old_setting; +error: + abort_creds(new); + return ret; +} + +/* + * Set or clear the timeout on a key. + * + * Either the key must grant the caller Setattr permission or else the caller + * must hold an instantiation authorisation token for the key. + * + * The timeout is either 0 to clear the timeout, or a number of seconds from + * the current time. The key and any links to the key will be automatically + * garbage collected after the timeout expires. + * + * If successful, 0 is returned. + */ +long keyctl_set_timeout(key_serial_t id, unsigned timeout) +{ + struct timespec now; + struct key *key, *instkey; + key_ref_t key_ref; + time_t expiry; + long ret; + + 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 */ + down_write(&key->sem); + + expiry = 0; + if (timeout > 0) { + now = current_kernel_time(); + expiry = now.tv_sec + timeout; + } + + key->expiry = expiry; + key_schedule_gc(key->expiry + key_gc_delay); + + up_write(&key->sem); + key_put(key); + + ret = 0; +error: + return ret; +} + +/* + * Assume (or clear) the authority to instantiate the specified key. + * + * This sets the authoritative token currently in force for key instantiation. + * This must be done for a key to be instantiated. It has the effect of making + * available all the keys from the caller of the request_key() that created a + * key to request_key() calls made by the caller of this function. + * + * The caller must have the instantiation key in their process keyrings with a + * Search permission grant available to the caller. + * + * If the ID given is 0, then the setting will be cleared and 0 returned. + * + * If the ID given has a matching an authorisation key, then that key will be + * set and its ID will be returned. The authorisation key can be read to get + * the callout information passed to request_key(). + */ +long keyctl_assume_authority(key_serial_t id) +{ + struct key *authkey; + long ret; + + /* special key IDs aren't permitted */ + ret = -EINVAL; + if (id < 0) + goto error; + + /* we divest ourselves of authority if given an ID of 0 */ + if (id == 0) { + ret = keyctl_change_reqkey_auth(NULL); + goto error; + } + + /* attempt to assume the authority temporarily granted to us whilst we + * instantiate the specified key + * - the authorisation key must be in the current task's keyrings + * somewhere + */ + authkey = key_get_instantiation_authkey(id); + if (IS_ERR(authkey)) { + ret = PTR_ERR(authkey); + goto error; + } + + ret = keyctl_change_reqkey_auth(authkey); + if (ret < 0) + goto error; + key_put(authkey); + + ret = authkey->serial; +error: + return ret; +} + +/* + * Get a key's the LSM security label. + * + * The key must grant the caller View permission for this to work. + * + * If there's a buffer, then up to buflen bytes of data will be placed into it. + * + * If successful, the amount of information available will be returned, + * irrespective of how much was copied (including the terminal NUL). + */ +long keyctl_get_security(key_serial_t keyid, + char __user *buffer, + size_t buflen) +{ + struct key *key, *instkey; + key_ref_t key_ref; + char *context; + long ret; + + key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, KEY_VIEW); + if (IS_ERR(key_ref)) { + if (PTR_ERR(key_ref) != -EACCES) + return PTR_ERR(key_ref); + + /* viewing a key under construction is also permitted if we + * have the authorisation token handy */ + instkey = key_get_instantiation_authkey(keyid); + if (IS_ERR(instkey)) + return PTR_ERR(instkey); + key_put(instkey); + + key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, 0); + if (IS_ERR(key_ref)) + return PTR_ERR(key_ref); + } + + key = key_ref_to_ptr(key_ref); + ret = security_key_getsecurity(key, &context); + if (ret == 0) { + /* if no information was returned, give userspace an empty + * string */ + ret = 1; + if (buffer && buflen > 0 && + copy_to_user(buffer, "", 1) != 0) + ret = -EFAULT; + } else if (ret > 0) { + /* return as much data as there's room for */ + if (buffer && buflen > 0) { + if (buflen > ret) + buflen = ret; + + if (copy_to_user(buffer, context, buflen) != 0) + ret = -EFAULT; + } + + kfree(context); + } + + key_ref_put(key_ref); + return ret; +} + +/* + * Attempt to install the calling process's session keyring on the process's + * parent process. + * + * The keyring must exist and must grant the caller LINK permission, and the + * parent process must be single-threaded and must have the same effective + * ownership as this process and mustn't be SUID/SGID. + * + * The keyring will be emplaced on the parent when it next resumes userspace. + * + * If successful, 0 will be returned. + */ +long keyctl_session_to_parent(void) +{ +#ifdef TIF_NOTIFY_RESUME + struct task_struct *me, *parent; + const struct cred *mycred, *pcred; + struct cred *cred, *oldcred; + key_ref_t keyring_r; + int ret; + + keyring_r = lookup_user_key(KEY_SPEC_SESSION_KEYRING, 0, KEY_LINK); + if (IS_ERR(keyring_r)) + return PTR_ERR(keyring_r); + + /* our parent is going to need a new cred struct, a new tgcred struct + * and new security data, so we allocate them here to prevent ENOMEM in + * our parent */ + ret = -ENOMEM; + cred = cred_alloc_blank(); + if (!cred) + goto error_keyring; + + cred->tgcred->session_keyring = key_ref_to_ptr(keyring_r); + keyring_r = NULL; + + me = current; + rcu_read_lock(); + write_lock_irq(&tasklist_lock); + + parent = me->real_parent; + ret = -EPERM; + + /* the parent mustn't be init and mustn't be a kernel thread */ + if (parent->pid <= 1 || !parent->mm) + goto not_permitted; + + /* the parent must be single threaded */ + if (!thread_group_empty(parent)) + goto not_permitted; + + /* the parent and the child must have different session keyrings or + * there's no point */ + mycred = current_cred(); + pcred = __task_cred(parent); + if (mycred == pcred || + mycred->tgcred->session_keyring == pcred->tgcred->session_keyring) + goto already_same; + + /* the parent must have the same effective ownership and mustn't be + * SUID/SGID */ + if (pcred->uid != mycred->euid || + pcred->euid != mycred->euid || + pcred->suid != mycred->euid || + pcred->gid != mycred->egid || + pcred->egid != mycred->egid || + pcred->sgid != mycred->egid) + goto not_permitted; + + /* the keyrings must have the same UID */ + if ((pcred->tgcred->session_keyring && + pcred->tgcred->session_keyring->uid != mycred->euid) || + mycred->tgcred->session_keyring->uid != mycred->euid) + goto not_permitted; + + /* if there's an already pending keyring replacement, then we replace + * that */ + oldcred = parent->replacement_session_keyring; + + /* the replacement session keyring is applied just prior to userspace + * restarting */ + parent->replacement_session_keyring = cred; + cred = NULL; + set_ti_thread_flag(task_thread_info(parent), TIF_NOTIFY_RESUME); + + write_unlock_irq(&tasklist_lock); + rcu_read_unlock(); + if (oldcred) + put_cred(oldcred); + return 0; + +already_same: + ret = 0; +not_permitted: + write_unlock_irq(&tasklist_lock); + rcu_read_unlock(); + put_cred(cred); + return ret; + +error_keyring: + key_ref_put(keyring_r); + return ret; + +#else /* !TIF_NOTIFY_RESUME */ + /* + * To be removed when TIF_NOTIFY_RESUME has been implemented on + * m68k/xtensa + */ +#warning TIF_NOTIFY_RESUME not implemented + return -EOPNOTSUPP; +#endif /* !TIF_NOTIFY_RESUME */ +} + +/* + * The key control system call + */ +SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3, + unsigned long, arg4, unsigned long, arg5) +{ + switch (option) { + case KEYCTL_GET_KEYRING_ID: + return keyctl_get_keyring_ID((key_serial_t) arg2, + (int) arg3); + + case KEYCTL_JOIN_SESSION_KEYRING: + return keyctl_join_session_keyring((const char __user *) arg2); + + case KEYCTL_UPDATE: + return keyctl_update_key((key_serial_t) arg2, + (const void __user *) arg3, + (size_t) arg4); + + case KEYCTL_REVOKE: + return keyctl_revoke_key((key_serial_t) arg2); + + case KEYCTL_DESCRIBE: + return keyctl_describe_key((key_serial_t) arg2, + (char __user *) arg3, + (unsigned) arg4); + + case KEYCTL_CLEAR: + return keyctl_keyring_clear((key_serial_t) arg2); + + case KEYCTL_LINK: + return keyctl_keyring_link((key_serial_t) arg2, + (key_serial_t) arg3); + + case KEYCTL_UNLINK: + return keyctl_keyring_unlink((key_serial_t) arg2, + (key_serial_t) arg3); + + case KEYCTL_SEARCH: + return keyctl_keyring_search((key_serial_t) arg2, + (const char __user *) arg3, + (const char __user *) arg4, + (key_serial_t) arg5); + + case KEYCTL_READ: + return keyctl_read_key((key_serial_t) arg2, + (char __user *) arg3, + (size_t) arg4); + + case KEYCTL_CHOWN: + return keyctl_chown_key((key_serial_t) arg2, + (uid_t) arg3, + (gid_t) arg4); + + case KEYCTL_SETPERM: + return keyctl_setperm_key((key_serial_t) arg2, + (key_perm_t) arg3); + + case KEYCTL_INSTANTIATE: + return keyctl_instantiate_key((key_serial_t) arg2, + (const void __user *) arg3, + (size_t) arg4, + (key_serial_t) arg5); + + case KEYCTL_NEGATE: + return keyctl_negate_key((key_serial_t) arg2, + (unsigned) arg3, + (key_serial_t) arg4); + + case KEYCTL_SET_REQKEY_KEYRING: + return keyctl_set_reqkey_keyring(arg2); + + case KEYCTL_SET_TIMEOUT: + return keyctl_set_timeout((key_serial_t) arg2, + (unsigned) arg3); + + case KEYCTL_ASSUME_AUTHORITY: + return keyctl_assume_authority((key_serial_t) arg2); + + case KEYCTL_GET_SECURITY: + return keyctl_get_security((key_serial_t) arg2, + (char __user *) arg3, + (size_t) arg4); + + case KEYCTL_SESSION_TO_PARENT: + return keyctl_session_to_parent(); + + case KEYCTL_REJECT: + return keyctl_reject_key((key_serial_t) arg2, + (unsigned) arg3, + (unsigned) arg4, + (key_serial_t) arg5); + + case KEYCTL_INSTANTIATE_IOV: + return keyctl_instantiate_key_iov( + (key_serial_t) arg2, + (const struct iovec __user *) arg3, + (unsigned) arg4, + (key_serial_t) arg5); + + default: + return -EOPNOTSUPP; + } +} diff --git a/security/keys/keyring.c b/security/keys/keyring.c new file mode 100644 index 00000000..a06ffab3 --- /dev/null +++ b/security/keys/keyring.c @@ -0,0 +1,1226 @@ +/* Keyring handling + * + * Copyright (C) 2004-2005, 2008 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/security.h> +#include <linux/seq_file.h> +#include <linux/err.h> +#include <keys/keyring-type.h> +#include <linux/uaccess.h> +#include "internal.h" + +#define rcu_dereference_locked_keyring(keyring) \ + (rcu_dereference_protected( \ + (keyring)->payload.subscriptions, \ + rwsem_is_locked((struct rw_semaphore *)&(keyring)->sem))) + +#define KEY_LINK_FIXQUOTA 1UL + +/* + * When plumbing the depths of the key tree, this sets a hard limit + * set on how deep we're willing to go. + */ +#define KEYRING_SEARCH_MAX_DEPTH 6 + +/* + * We keep all named keyrings in a hash to speed looking them up. + */ +#define KEYRING_NAME_HASH_SIZE (1 << 5) + +static struct list_head keyring_name_hash[KEYRING_NAME_HASH_SIZE]; +static DEFINE_RWLOCK(keyring_name_lock); + +static inline unsigned keyring_hash(const char *desc) +{ + unsigned bucket = 0; + + for (; *desc; desc++) + bucket += (unsigned char)*desc; + + return bucket & (KEYRING_NAME_HASH_SIZE - 1); +} + +/* + * The keyring key type definition. Keyrings are simply keys of this type and + * can be treated as ordinary keys in addition to having their own special + * operations. + */ +static int keyring_instantiate(struct key *keyring, + const void *data, size_t datalen); +static int keyring_match(const struct key *keyring, const void *criterion); +static void keyring_revoke(struct key *keyring); +static void keyring_destroy(struct key *keyring); +static void keyring_describe(const struct key *keyring, struct seq_file *m); +static long keyring_read(const struct key *keyring, + char __user *buffer, size_t buflen); + +struct key_type key_type_keyring = { + .name = "keyring", + .def_datalen = sizeof(struct keyring_list), + .instantiate = keyring_instantiate, + .match = keyring_match, + .revoke = keyring_revoke, + .destroy = keyring_destroy, + .describe = keyring_describe, + .read = keyring_read, +}; +EXPORT_SYMBOL(key_type_keyring); + +/* + * Semaphore to serialise link/link calls to prevent two link calls in parallel + * introducing a cycle. + */ +static DECLARE_RWSEM(keyring_serialise_link_sem); + +/* + * Publish the name of a keyring so that it can be found by name (if it has + * one). + */ +static void keyring_publish_name(struct key *keyring) +{ + int bucket; + + if (keyring->description) { + bucket = keyring_hash(keyring->description); + + write_lock(&keyring_name_lock); + + if (!keyring_name_hash[bucket].next) + INIT_LIST_HEAD(&keyring_name_hash[bucket]); + + list_add_tail(&keyring->type_data.link, + &keyring_name_hash[bucket]); + + write_unlock(&keyring_name_lock); + } +} + +/* + * Initialise a keyring. + * + * Returns 0 on success, -EINVAL if given any data. + */ +static int keyring_instantiate(struct key *keyring, + const void *data, size_t datalen) +{ + int ret; + + ret = -EINVAL; + if (datalen == 0) { + /* make the keyring available by name if it has one */ + keyring_publish_name(keyring); + ret = 0; + } + + return ret; +} + +/* + * Match keyrings on their name + */ +static int keyring_match(const struct key *keyring, const void *description) +{ + return keyring->description && + strcmp(keyring->description, description) == 0; +} + +/* + * Clean up a keyring when it is destroyed. Unpublish its name if it had one + * and dispose of its data. + */ +static void keyring_destroy(struct key *keyring) +{ + struct keyring_list *klist; + int loop; + + if (keyring->description) { + write_lock(&keyring_name_lock); + + if (keyring->type_data.link.next != NULL && + !list_empty(&keyring->type_data.link)) + list_del(&keyring->type_data.link); + + write_unlock(&keyring_name_lock); + } + + klist = rcu_dereference_check(keyring->payload.subscriptions, + rcu_read_lock_held() || + atomic_read(&keyring->usage) == 0); + if (klist) { + for (loop = klist->nkeys - 1; loop >= 0; loop--) + key_put(klist->keys[loop]); + kfree(klist); + } +} + +/* + * Describe a keyring for /proc. + */ +static void keyring_describe(const struct key *keyring, struct seq_file *m) +{ + struct keyring_list *klist; + + if (keyring->description) + seq_puts(m, keyring->description); + else + seq_puts(m, "[anon]"); + + if (key_is_instantiated(keyring)) { + rcu_read_lock(); + klist = rcu_dereference(keyring->payload.subscriptions); + if (klist) + seq_printf(m, ": %u/%u", klist->nkeys, klist->maxkeys); + else + seq_puts(m, ": empty"); + rcu_read_unlock(); + } +} + +/* + * Read a list of key IDs from the keyring's contents in binary form + * + * The keyring's semaphore is read-locked by the caller. + */ +static long keyring_read(const struct key *keyring, + char __user *buffer, size_t buflen) +{ + struct keyring_list *klist; + struct key *key; + size_t qty, tmp; + int loop, ret; + + ret = 0; + klist = rcu_dereference_locked_keyring(keyring); + if (klist) { + /* calculate how much data we could return */ + qty = klist->nkeys * sizeof(key_serial_t); + + if (buffer && buflen > 0) { + if (buflen > qty) + buflen = qty; + + /* copy the IDs of the subscribed keys into the + * buffer */ + ret = -EFAULT; + + for (loop = 0; loop < klist->nkeys; loop++) { + key = klist->keys[loop]; + + tmp = sizeof(key_serial_t); + if (tmp > buflen) + tmp = buflen; + + if (copy_to_user(buffer, + &key->serial, + tmp) != 0) + goto error; + + buflen -= tmp; + if (buflen == 0) + break; + buffer += tmp; + } + } + + ret = qty; + } + +error: + return ret; +} + +/* + * Allocate a keyring and link into the destination keyring. + */ +struct key *keyring_alloc(const char *description, uid_t uid, gid_t gid, + const struct cred *cred, unsigned long flags, + struct key *dest) +{ + struct key *keyring; + int ret; + + keyring = key_alloc(&key_type_keyring, description, + uid, gid, cred, + (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL, + flags); + + if (!IS_ERR(keyring)) { + ret = key_instantiate_and_link(keyring, NULL, 0, dest, NULL); + if (ret < 0) { + key_put(keyring); + keyring = ERR_PTR(ret); + } + } + + return keyring; +} + +/** + * keyring_search_aux - Search a keyring tree for a key matching some criteria + * @keyring_ref: A pointer to the keyring with possession indicator. + * @cred: The credentials to use for permissions checks. + * @type: The type of key to search for. + * @description: Parameter for @match. + * @match: Function to rule on whether or not a key is the one required. + * @no_state_check: Don't check if a matching key is bad + * + * Search the supplied keyring tree for a key that matches the criteria given. + * The root keyring and any linked keyrings must grant Search permission to the + * caller to be searchable and keys can only be found if they too grant Search + * to the caller. The possession flag on the root keyring pointer controls use + * of the possessor bits in permissions checking of the entire tree. In + * addition, the LSM gets to forbid keyring searches and key matches. + * + * The search is performed as a breadth-then-depth search up to the prescribed + * limit (KEYRING_SEARCH_MAX_DEPTH). + * + * Keys are matched to the type provided and are then filtered by the match + * function, which is given the description to use in any way it sees fit. The + * match function may use any attributes of a key that it wishes to to + * determine the match. Normally the match function from the key type would be + * used. + * + * RCU is used to prevent the keyring key lists from disappearing without the + * need to take lots of locks. + * + * Returns a pointer to the found key and increments the key usage count if + * successful; -EAGAIN if no matching keys were found, or if expired or revoked + * keys were found; -ENOKEY if only negative keys were found; -ENOTDIR if the + * specified keyring wasn't a keyring. + * + * In the case of a successful return, the possession attribute from + * @keyring_ref is propagated to the returned key reference. + */ +key_ref_t keyring_search_aux(key_ref_t keyring_ref, + const struct cred *cred, + struct key_type *type, + const void *description, + key_match_func_t match, + bool no_state_check) +{ + struct { + struct keyring_list *keylist; + int kix; + } stack[KEYRING_SEARCH_MAX_DEPTH]; + + struct keyring_list *keylist; + struct timespec now; + unsigned long possessed, kflags; + struct key *keyring, *key; + key_ref_t key_ref; + long err; + int sp, kix; + + keyring = key_ref_to_ptr(keyring_ref); + possessed = is_key_possessed(keyring_ref); + key_check(keyring); + + /* top keyring must have search permission to begin the search */ + err = key_task_permission(keyring_ref, cred, KEY_SEARCH); + if (err < 0) { + key_ref = ERR_PTR(err); + goto error; + } + + key_ref = ERR_PTR(-ENOTDIR); + if (keyring->type != &key_type_keyring) + goto error; + + rcu_read_lock(); + + now = current_kernel_time(); + err = -EAGAIN; + sp = 0; + + /* firstly we should check to see if this top-level keyring is what we + * are looking for */ + key_ref = ERR_PTR(-EAGAIN); + kflags = keyring->flags; + if (keyring->type == type && match(keyring, description)) { + key = keyring; + if (no_state_check) + goto found; + + /* check it isn't negative and hasn't expired or been + * revoked */ + if (kflags & (1 << KEY_FLAG_REVOKED)) + goto error_2; + if (key->expiry && now.tv_sec >= key->expiry) + goto error_2; + key_ref = ERR_PTR(key->type_data.reject_error); + if (kflags & (1 << KEY_FLAG_NEGATIVE)) + goto error_2; + goto found; + } + + /* otherwise, the top keyring must not be revoked, expired, or + * negatively instantiated if we are to search it */ + key_ref = ERR_PTR(-EAGAIN); + if (kflags & ((1 << KEY_FLAG_REVOKED) | (1 << KEY_FLAG_NEGATIVE)) || + (keyring->expiry && now.tv_sec >= keyring->expiry)) + goto error_2; + + /* start processing a new keyring */ +descend: + if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) + goto not_this_keyring; + + keylist = rcu_dereference(keyring->payload.subscriptions); + if (!keylist) + goto not_this_keyring; + + /* iterate through the keys in this keyring first */ + for (kix = 0; kix < keylist->nkeys; kix++) { + key = keylist->keys[kix]; + kflags = key->flags; + + /* ignore keys not of this type */ + if (key->type != type) + continue; + + /* skip revoked keys and expired keys */ + if (!no_state_check) { + if (kflags & (1 << KEY_FLAG_REVOKED)) + continue; + + if (key->expiry && now.tv_sec >= key->expiry) + continue; + } + + /* keys that don't match */ + if (!match(key, description)) + continue; + + /* key must have search permissions */ + if (key_task_permission(make_key_ref(key, possessed), + cred, KEY_SEARCH) < 0) + continue; + + if (no_state_check) + goto found; + + /* we set a different error code if we pass a negative key */ + if (kflags & (1 << KEY_FLAG_NEGATIVE)) { + err = key->type_data.reject_error; + continue; + } + + goto found; + } + + /* search through the keyrings nested in this one */ + kix = 0; +ascend: + for (; kix < keylist->nkeys; kix++) { + key = keylist->keys[kix]; + if (key->type != &key_type_keyring) + continue; + + /* recursively search nested keyrings + * - only search keyrings for which we have search permission + */ + if (sp >= KEYRING_SEARCH_MAX_DEPTH) + continue; + + if (key_task_permission(make_key_ref(key, possessed), + cred, KEY_SEARCH) < 0) + continue; + + /* stack the current position */ + stack[sp].keylist = keylist; + stack[sp].kix = kix; + sp++; + + /* begin again with the new keyring */ + keyring = key; + goto descend; + } + + /* the keyring we're looking at was disqualified or didn't contain a + * matching key */ +not_this_keyring: + if (sp > 0) { + /* resume the processing of a keyring higher up in the tree */ + sp--; + keylist = stack[sp].keylist; + kix = stack[sp].kix + 1; + goto ascend; + } + + key_ref = ERR_PTR(err); + goto error_2; + + /* we found a viable match */ +found: + atomic_inc(&key->usage); + key_check(key); + key_ref = make_key_ref(key, possessed); +error_2: + rcu_read_unlock(); +error: + return key_ref; +} + +/** + * keyring_search - Search the supplied keyring tree for a matching key + * @keyring: The root of the keyring tree to be searched. + * @type: The type of keyring we want to find. + * @description: The name of the keyring we want to find. + * + * As keyring_search_aux() above, but using the current task's credentials and + * type's default matching function. + */ +key_ref_t keyring_search(key_ref_t keyring, + struct key_type *type, + const char *description) +{ + if (!type->match) + return ERR_PTR(-ENOKEY); + + return keyring_search_aux(keyring, current->cred, + type, description, type->match, false); +} +EXPORT_SYMBOL(keyring_search); + +/* + * Search the given keyring only (no recursion). + * + * The caller must guarantee that the keyring is a keyring and that the + * permission is granted to search the keyring as no check is made here. + * + * RCU is used to make it unnecessary to lock the keyring key list here. + * + * Returns a pointer to the found key with usage count incremented if + * successful and returns -ENOKEY if not found. Revoked keys and keys not + * providing the requested permission are skipped over. + * + * If successful, the possession indicator is propagated from the keyring ref + * to the returned key reference. + */ +key_ref_t __keyring_search_one(key_ref_t keyring_ref, + const struct key_type *ktype, + const char *description, + key_perm_t perm) +{ + struct keyring_list *klist; + unsigned long possessed; + struct key *keyring, *key; + int loop; + + keyring = key_ref_to_ptr(keyring_ref); + possessed = is_key_possessed(keyring_ref); + + rcu_read_lock(); + + klist = rcu_dereference(keyring->payload.subscriptions); + if (klist) { + for (loop = 0; loop < klist->nkeys; loop++) { + key = klist->keys[loop]; + + if (key->type == ktype && + (!key->type->match || + key->type->match(key, description)) && + key_permission(make_key_ref(key, possessed), + perm) == 0 && + !test_bit(KEY_FLAG_REVOKED, &key->flags) + ) + goto found; + } + } + + rcu_read_unlock(); + return ERR_PTR(-ENOKEY); + +found: + atomic_inc(&key->usage); + rcu_read_unlock(); + return make_key_ref(key, possessed); +} + +/* + * Find a keyring with the specified name. + * + * All named keyrings in the current user namespace are searched, provided they + * grant Search permission directly to the caller (unless this check is + * skipped). Keyrings whose usage points have reached zero or who have been + * revoked are skipped. + * + * Returns a pointer to the keyring with the keyring's refcount having being + * incremented on success. -ENOKEY is returned if a key could not be found. + */ +struct key *find_keyring_by_name(const char *name, bool skip_perm_check) +{ + struct key *keyring; + int bucket; + + if (!name) + return ERR_PTR(-EINVAL); + + bucket = keyring_hash(name); + + read_lock(&keyring_name_lock); + + if (keyring_name_hash[bucket].next) { + /* search this hash bucket for a keyring with a matching name + * that's readable and that hasn't been revoked */ + list_for_each_entry(keyring, + &keyring_name_hash[bucket], + type_data.link + ) { + if (keyring->user->user_ns != current_user_ns()) + continue; + + if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) + continue; + + if (strcmp(keyring->description, name) != 0) + continue; + + if (!skip_perm_check && + key_permission(make_key_ref(keyring, 0), + KEY_SEARCH) < 0) + continue; + + /* we've got a match but we might end up racing with + * key_cleanup() if the keyring is currently 'dead' + * (ie. it has a zero usage count) */ + if (!atomic_inc_not_zero(&keyring->usage)) + continue; + goto out; + } + } + + keyring = ERR_PTR(-ENOKEY); +out: + read_unlock(&keyring_name_lock); + return keyring; +} + +/* + * See if a cycle will will be created by inserting acyclic tree B in acyclic + * tree A at the topmost level (ie: as a direct child of A). + * + * Since we are adding B to A at the top level, checking for cycles should just + * be a matter of seeing if node A is somewhere in tree B. + */ +static int keyring_detect_cycle(struct key *A, struct key *B) +{ + struct { + struct keyring_list *keylist; + int kix; + } stack[KEYRING_SEARCH_MAX_DEPTH]; + + struct keyring_list *keylist; + struct key *subtree, *key; + int sp, kix, ret; + + rcu_read_lock(); + + ret = -EDEADLK; + if (A == B) + goto cycle_detected; + + subtree = B; + sp = 0; + + /* start processing a new keyring */ +descend: + if (test_bit(KEY_FLAG_REVOKED, &subtree->flags)) + goto not_this_keyring; + + keylist = rcu_dereference(subtree->payload.subscriptions); + if (!keylist) + goto not_this_keyring; + kix = 0; + +ascend: + /* iterate through the remaining keys in this keyring */ + for (; kix < keylist->nkeys; kix++) { + key = keylist->keys[kix]; + + if (key == A) + goto cycle_detected; + + /* recursively check nested keyrings */ + if (key->type == &key_type_keyring) { + if (sp >= KEYRING_SEARCH_MAX_DEPTH) + goto too_deep; + + /* stack the current position */ + stack[sp].keylist = keylist; + stack[sp].kix = kix; + sp++; + + /* begin again with the new keyring */ + subtree = key; + goto descend; + } + } + + /* the keyring we're looking at was disqualified or didn't contain a + * matching key */ +not_this_keyring: + if (sp > 0) { + /* resume the checking of a keyring higher up in the tree */ + sp--; + keylist = stack[sp].keylist; + kix = stack[sp].kix + 1; + goto ascend; + } + + ret = 0; /* no cycles detected */ + +error: + rcu_read_unlock(); + return ret; + +too_deep: + ret = -ELOOP; + goto error; + +cycle_detected: + ret = -EDEADLK; + goto error; +} + +/* + * Dispose of a keyring list after the RCU grace period, freeing the unlinked + * key + */ +static void keyring_unlink_rcu_disposal(struct rcu_head *rcu) +{ + struct keyring_list *klist = + container_of(rcu, struct keyring_list, rcu); + + if (klist->delkey != USHRT_MAX) + key_put(klist->keys[klist->delkey]); + kfree(klist); +} + +/* + * Preallocate memory so that a key can be linked into to a keyring. + */ +int __key_link_begin(struct key *keyring, const struct key_type *type, + const char *description, unsigned long *_prealloc) + __acquires(&keyring->sem) +{ + struct keyring_list *klist, *nklist; + unsigned long prealloc; + unsigned max; + size_t size; + int loop, ret; + + kenter("%d,%s,%s,", key_serial(keyring), type->name, description); + + if (keyring->type != &key_type_keyring) + return -ENOTDIR; + + down_write(&keyring->sem); + + ret = -EKEYREVOKED; + if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) + goto error_krsem; + + /* serialise link/link calls to prevent parallel calls causing a cycle + * when linking two keyring in opposite orders */ + if (type == &key_type_keyring) + down_write(&keyring_serialise_link_sem); + + klist = rcu_dereference_locked_keyring(keyring); + + /* see if there's a matching key we can displace */ + if (klist && klist->nkeys > 0) { + for (loop = klist->nkeys - 1; loop >= 0; loop--) { + if (klist->keys[loop]->type == type && + strcmp(klist->keys[loop]->description, + description) == 0 + ) { + /* found a match - we'll replace this one with + * the new key */ + size = sizeof(struct key *) * klist->maxkeys; + size += sizeof(*klist); + BUG_ON(size > PAGE_SIZE); + + ret = -ENOMEM; + nklist = kmemdup(klist, size, GFP_KERNEL); + if (!nklist) + goto error_sem; + + /* note replacement slot */ + klist->delkey = nklist->delkey = loop; + prealloc = (unsigned long)nklist; + goto done; + } + } + } + + /* check that we aren't going to overrun the user's quota */ + ret = key_payload_reserve(keyring, + keyring->datalen + KEYQUOTA_LINK_BYTES); + if (ret < 0) + goto error_sem; + + if (klist && klist->nkeys < klist->maxkeys) { + /* there's sufficient slack space to append directly */ + nklist = NULL; + prealloc = KEY_LINK_FIXQUOTA; + } else { + /* grow the key list */ + max = 4; + if (klist) + max += klist->maxkeys; + + ret = -ENFILE; + if (max > USHRT_MAX - 1) + goto error_quota; + size = sizeof(*klist) + sizeof(struct key *) * max; + if (size > PAGE_SIZE) + goto error_quota; + + ret = -ENOMEM; + nklist = kmalloc(size, GFP_KERNEL); + if (!nklist) + goto error_quota; + + nklist->maxkeys = max; + if (klist) { + memcpy(nklist->keys, klist->keys, + sizeof(struct key *) * klist->nkeys); + nklist->delkey = klist->nkeys; + nklist->nkeys = klist->nkeys + 1; + klist->delkey = USHRT_MAX; + } else { + nklist->nkeys = 1; + nklist->delkey = 0; + } + + /* add the key into the new space */ + nklist->keys[nklist->delkey] = NULL; + } + + prealloc = (unsigned long)nklist | KEY_LINK_FIXQUOTA; +done: + *_prealloc = prealloc; + kleave(" = 0"); + return 0; + +error_quota: + /* undo the quota changes */ + key_payload_reserve(keyring, + keyring->datalen - KEYQUOTA_LINK_BYTES); +error_sem: + if (type == &key_type_keyring) + up_write(&keyring_serialise_link_sem); +error_krsem: + up_write(&keyring->sem); + kleave(" = %d", ret); + return ret; +} + +/* + * Check already instantiated keys aren't going to be a problem. + * + * The caller must have called __key_link_begin(). Don't need to call this for + * keys that were created since __key_link_begin() was called. + */ +int __key_link_check_live_key(struct key *keyring, struct key *key) +{ + if (key->type == &key_type_keyring) + /* check that we aren't going to create a cycle by linking one + * keyring to another */ + return keyring_detect_cycle(keyring, key); + return 0; +} + +/* + * Link a key into to a keyring. + * + * Must be called with __key_link_begin() having being called. Discards any + * already extant link to matching key if there is one, so that each keyring + * holds at most one link to any given key of a particular type+description + * combination. + */ +void __key_link(struct key *keyring, struct key *key, + unsigned long *_prealloc) +{ + struct keyring_list *klist, *nklist; + + nklist = (struct keyring_list *)(*_prealloc & ~KEY_LINK_FIXQUOTA); + *_prealloc = 0; + + kenter("%d,%d,%p", keyring->serial, key->serial, nklist); + + klist = rcu_dereference_protected(keyring->payload.subscriptions, + rwsem_is_locked(&keyring->sem)); + + atomic_inc(&key->usage); + + /* there's a matching key we can displace or an empty slot in a newly + * allocated list we can fill */ + if (nklist) { + kdebug("replace %hu/%hu/%hu", + nklist->delkey, nklist->nkeys, nklist->maxkeys); + + nklist->keys[nklist->delkey] = key; + + rcu_assign_pointer(keyring->payload.subscriptions, nklist); + + /* dispose of the old keyring list and, if there was one, the + * displaced key */ + if (klist) { + kdebug("dispose %hu/%hu/%hu", + klist->delkey, klist->nkeys, klist->maxkeys); + call_rcu(&klist->rcu, keyring_unlink_rcu_disposal); + } + } else { + /* there's sufficient slack space to append directly */ + klist->keys[klist->nkeys] = key; + smp_wmb(); + klist->nkeys++; + } +} + +/* + * Finish linking a key into to a keyring. + * + * Must be called with __key_link_begin() having being called. + */ +void __key_link_end(struct key *keyring, struct key_type *type, + unsigned long prealloc) + __releases(&keyring->sem) +{ + BUG_ON(type == NULL); + BUG_ON(type->name == NULL); + kenter("%d,%s,%lx", keyring->serial, type->name, prealloc); + + if (type == &key_type_keyring) + up_write(&keyring_serialise_link_sem); + + if (prealloc) { + if (prealloc & KEY_LINK_FIXQUOTA) + key_payload_reserve(keyring, + keyring->datalen - + KEYQUOTA_LINK_BYTES); + kfree((struct keyring_list *)(prealloc & ~KEY_LINK_FIXQUOTA)); + } + up_write(&keyring->sem); +} + +/** + * key_link - Link a key to a keyring + * @keyring: The keyring to make the link in. + * @key: The key to link to. + * + * Make a link in a keyring to a key, such that the keyring holds a reference + * on that key and the key can potentially be found by searching that keyring. + * + * This function will write-lock the keyring's semaphore and will consume some + * of the user's key data quota to hold the link. + * + * Returns 0 if successful, -ENOTDIR if the keyring isn't a keyring, + * -EKEYREVOKED if the keyring has been revoked, -ENFILE if the keyring is + * full, -EDQUOT if there is insufficient key data quota remaining to add + * another link or -ENOMEM if there's insufficient memory. + * + * It is assumed that the caller has checked that it is permitted for a link to + * be made (the keyring should have Write permission and the key Link + * permission). + */ +int key_link(struct key *keyring, struct key *key) +{ + unsigned long prealloc; + int ret; + + key_check(keyring); + key_check(key); + + ret = __key_link_begin(keyring, key->type, key->description, &prealloc); + if (ret == 0) { + ret = __key_link_check_live_key(keyring, key); + if (ret == 0) + __key_link(keyring, key, &prealloc); + __key_link_end(keyring, key->type, prealloc); + } + + return ret; +} +EXPORT_SYMBOL(key_link); + +/** + * key_unlink - Unlink the first link to a key from a keyring. + * @keyring: The keyring to remove the link from. + * @key: The key the link is to. + * + * Remove a link from a keyring to a key. + * + * This function will write-lock the keyring's semaphore. + * + * Returns 0 if successful, -ENOTDIR if the keyring isn't a keyring, -ENOENT if + * the key isn't linked to by the keyring or -ENOMEM if there's insufficient + * memory. + * + * It is assumed that the caller has checked that it is permitted for a link to + * be removed (the keyring should have Write permission; no permissions are + * required on the key). + */ +int key_unlink(struct key *keyring, struct key *key) +{ + struct keyring_list *klist, *nklist; + int loop, ret; + + key_check(keyring); + key_check(key); + + ret = -ENOTDIR; + if (keyring->type != &key_type_keyring) + goto error; + + down_write(&keyring->sem); + + klist = rcu_dereference_locked_keyring(keyring); + if (klist) { + /* search the keyring for the key */ + for (loop = 0; loop < klist->nkeys; loop++) + if (klist->keys[loop] == key) + goto key_is_present; + } + + up_write(&keyring->sem); + ret = -ENOENT; + goto error; + +key_is_present: + /* we need to copy the key list for RCU purposes */ + nklist = kmalloc(sizeof(*klist) + + sizeof(struct key *) * klist->maxkeys, + GFP_KERNEL); + if (!nklist) + goto nomem; + nklist->maxkeys = klist->maxkeys; + nklist->nkeys = klist->nkeys - 1; + + if (loop > 0) + memcpy(&nklist->keys[0], + &klist->keys[0], + loop * sizeof(struct key *)); + + if (loop < nklist->nkeys) + memcpy(&nklist->keys[loop], + &klist->keys[loop + 1], + (nklist->nkeys - loop) * sizeof(struct key *)); + + /* adjust the user's quota */ + key_payload_reserve(keyring, + keyring->datalen - KEYQUOTA_LINK_BYTES); + + rcu_assign_pointer(keyring->payload.subscriptions, nklist); + + up_write(&keyring->sem); + + /* schedule for later cleanup */ + klist->delkey = loop; + call_rcu(&klist->rcu, keyring_unlink_rcu_disposal); + + ret = 0; + +error: + return ret; +nomem: + ret = -ENOMEM; + up_write(&keyring->sem); + goto error; +} +EXPORT_SYMBOL(key_unlink); + +/* + * Dispose of a keyring list after the RCU grace period, releasing the keys it + * links to. + */ +static void keyring_clear_rcu_disposal(struct rcu_head *rcu) +{ + struct keyring_list *klist; + int loop; + + klist = container_of(rcu, struct keyring_list, rcu); + + for (loop = klist->nkeys - 1; loop >= 0; loop--) + key_put(klist->keys[loop]); + + kfree(klist); +} + +/** + * keyring_clear - Clear a keyring + * @keyring: The keyring to clear. + * + * Clear the contents of the specified keyring. + * + * Returns 0 if successful or -ENOTDIR if the keyring isn't a keyring. + */ +int keyring_clear(struct key *keyring) +{ + struct keyring_list *klist; + int ret; + + ret = -ENOTDIR; + if (keyring->type == &key_type_keyring) { + /* detach the pointer block with the locks held */ + down_write(&keyring->sem); + + klist = rcu_dereference_locked_keyring(keyring); + if (klist) { + /* adjust the quota */ + key_payload_reserve(keyring, + sizeof(struct keyring_list)); + + rcu_assign_pointer(keyring->payload.subscriptions, + NULL); + } + + up_write(&keyring->sem); + + /* free the keys after the locks have been dropped */ + if (klist) + call_rcu(&klist->rcu, keyring_clear_rcu_disposal); + + ret = 0; + } + + return ret; +} +EXPORT_SYMBOL(keyring_clear); + +/* + * Dispose of the links from a revoked keyring. + * + * This is called with the key sem write-locked. + */ +static void keyring_revoke(struct key *keyring) +{ + struct keyring_list *klist; + + klist = rcu_dereference_locked_keyring(keyring); + + /* adjust the quota */ + key_payload_reserve(keyring, 0); + + if (klist) { + rcu_assign_pointer(keyring->payload.subscriptions, NULL); + call_rcu(&klist->rcu, keyring_clear_rcu_disposal); + } +} + +/* + * Determine whether a key is dead. + */ +static bool key_is_dead(struct key *key, time_t limit) +{ + return test_bit(KEY_FLAG_DEAD, &key->flags) || + (key->expiry > 0 && key->expiry <= limit); +} + +/* + * Collect garbage from the contents of a keyring, replacing the old list with + * a new one with the pointers all shuffled down. + * + * Dead keys are classed as oned that are flagged as being dead or are revoked, + * expired or negative keys that were revoked or expired before the specified + * limit. + */ +void keyring_gc(struct key *keyring, time_t limit) +{ + struct keyring_list *klist, *new; + struct key *key; + int loop, keep, max; + + kenter("{%x,%s}", key_serial(keyring), keyring->description); + + down_write(&keyring->sem); + + klist = rcu_dereference_locked_keyring(keyring); + if (!klist) + goto no_klist; + + /* work out how many subscriptions we're keeping */ + keep = 0; + for (loop = klist->nkeys - 1; loop >= 0; loop--) + if (!key_is_dead(klist->keys[loop], limit)) + keep++; + + if (keep == klist->nkeys) + goto just_return; + + /* allocate a new keyring payload */ + max = roundup(keep, 4); + new = kmalloc(sizeof(struct keyring_list) + max * sizeof(struct key *), + GFP_KERNEL); + if (!new) + goto nomem; + new->maxkeys = max; + new->nkeys = 0; + new->delkey = 0; + + /* install the live keys + * - must take care as expired keys may be updated back to life + */ + keep = 0; + for (loop = klist->nkeys - 1; loop >= 0; loop--) { + key = klist->keys[loop]; + if (!key_is_dead(key, limit)) { + if (keep >= max) + goto discard_new; + new->keys[keep++] = key_get(key); + } + } + new->nkeys = keep; + + /* adjust the quota */ + key_payload_reserve(keyring, + sizeof(struct keyring_list) + + KEYQUOTA_LINK_BYTES * keep); + + if (keep == 0) { + rcu_assign_pointer(keyring->payload.subscriptions, NULL); + kfree(new); + } else { + rcu_assign_pointer(keyring->payload.subscriptions, new); + } + + up_write(&keyring->sem); + + call_rcu(&klist->rcu, keyring_clear_rcu_disposal); + kleave(" [yes]"); + return; + +discard_new: + new->nkeys = keep; + keyring_clear_rcu_disposal(&new->rcu); + up_write(&keyring->sem); + kleave(" [discard]"); + return; + +just_return: + up_write(&keyring->sem); + kleave(" [no dead]"); + return; + +no_klist: + up_write(&keyring->sem); + kleave(" [no_klist]"); + return; + +nomem: + up_write(&keyring->sem); + kleave(" [oom]"); +} diff --git a/security/keys/permission.c b/security/keys/permission.c new file mode 100644 index 00000000..c35b5229 --- /dev/null +++ b/security/keys/permission.c @@ -0,0 +1,118 @@ +/* Key permission checking + * + * Copyright (C) 2005 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/security.h> +#include "internal.h" + +/** + * key_task_permission - Check a key can be used + * @key_ref: The key to check. + * @cred: The credentials to use. + * @perm: The permissions to check for. + * + * Check to see whether permission is granted to use a key in the desired way, + * but permit the security modules to override. + * + * The caller must hold either a ref on cred or must hold the RCU readlock. + * + * Returns 0 if successful, -EACCES if access is denied based on the + * permissions bits or the LSM check. + */ +int key_task_permission(const key_ref_t key_ref, const struct cred *cred, + key_perm_t perm) +{ + struct key *key; + key_perm_t kperm; + int ret; + + key = key_ref_to_ptr(key_ref); + + if (key->user->user_ns != cred->user->user_ns) + goto use_other_perms; + + /* use the second 8-bits of permissions for keys the caller owns */ + if (key->uid == cred->fsuid) { + kperm = key->perm >> 16; + goto use_these_perms; + } + + /* use the third 8-bits of permissions for keys the caller has a group + * membership in common with */ + if (key->gid != -1 && key->perm & KEY_GRP_ALL) { + if (key->gid == cred->fsgid) { + kperm = key->perm >> 8; + goto use_these_perms; + } + + ret = groups_search(cred->group_info, key->gid); + if (ret) { + kperm = key->perm >> 8; + goto use_these_perms; + } + } + +use_other_perms: + + /* otherwise use the least-significant 8-bits */ + kperm = key->perm; + +use_these_perms: + + /* use the top 8-bits of permissions for keys the caller possesses + * - possessor permissions are additive with other permissions + */ + if (is_key_possessed(key_ref)) + kperm |= key->perm >> 24; + + kperm = kperm & perm & KEY_ALL; + + if (kperm != perm) + return -EACCES; + + /* let LSM be the final arbiter */ + return security_key_permission(key_ref, cred, perm); +} +EXPORT_SYMBOL(key_task_permission); + +/** + * key_validate - Validate a key. + * @key: The key to be validated. + * + * Check that a key is valid, returning 0 if the key is okay, -EKEYREVOKED if + * the key's type has been removed or if the key has been revoked or + * -EKEYEXPIRED if the key has expired. + */ +int key_validate(struct key *key) +{ + struct timespec now; + int ret = 0; + + if (key) { + /* check it's still accessible */ + ret = -EKEYREVOKED; + if (test_bit(KEY_FLAG_REVOKED, &key->flags) || + test_bit(KEY_FLAG_DEAD, &key->flags)) + goto error; + + /* check it hasn't expired */ + ret = 0; + if (key->expiry) { + now = current_kernel_time(); + if (now.tv_sec >= key->expiry) + ret = -EKEYEXPIRED; + } + } + +error: + return ret; +} +EXPORT_SYMBOL(key_validate); diff --git a/security/keys/proc.c b/security/keys/proc.c new file mode 100644 index 00000000..49bbc979 --- /dev/null +++ b/security/keys/proc.c @@ -0,0 +1,352 @@ +/* procfs files for key database enumeration + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <asm/errno.h> +#include "internal.h" + +#ifdef CONFIG_KEYS_DEBUG_PROC_KEYS +static int proc_keys_open(struct inode *inode, struct file *file); +static void *proc_keys_start(struct seq_file *p, loff_t *_pos); +static void *proc_keys_next(struct seq_file *p, void *v, loff_t *_pos); +static void proc_keys_stop(struct seq_file *p, void *v); +static int proc_keys_show(struct seq_file *m, void *v); + +static const struct seq_operations proc_keys_ops = { + .start = proc_keys_start, + .next = proc_keys_next, + .stop = proc_keys_stop, + .show = proc_keys_show, +}; + +static const struct file_operations proc_keys_fops = { + .open = proc_keys_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; +#endif + +static int proc_key_users_open(struct inode *inode, struct file *file); +static void *proc_key_users_start(struct seq_file *p, loff_t *_pos); +static void *proc_key_users_next(struct seq_file *p, void *v, loff_t *_pos); +static void proc_key_users_stop(struct seq_file *p, void *v); +static int proc_key_users_show(struct seq_file *m, void *v); + +static const struct seq_operations proc_key_users_ops = { + .start = proc_key_users_start, + .next = proc_key_users_next, + .stop = proc_key_users_stop, + .show = proc_key_users_show, +}; + +static const struct file_operations proc_key_users_fops = { + .open = proc_key_users_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +/* + * Declare the /proc files. + */ +static int __init key_proc_init(void) +{ + struct proc_dir_entry *p; + +#ifdef CONFIG_KEYS_DEBUG_PROC_KEYS + p = proc_create("keys", 0, NULL, &proc_keys_fops); + if (!p) + panic("Cannot create /proc/keys\n"); +#endif + + p = proc_create("key-users", 0, NULL, &proc_key_users_fops); + if (!p) + panic("Cannot create /proc/key-users\n"); + + return 0; +} + +__initcall(key_proc_init); + +/* + * Implement "/proc/keys" to provide a list of the keys on the system that + * grant View permission to the caller. + */ +#ifdef CONFIG_KEYS_DEBUG_PROC_KEYS + +static struct rb_node *key_serial_next(struct rb_node *n) +{ + struct user_namespace *user_ns = current_user_ns(); + + n = rb_next(n); + while (n) { + struct key *key = rb_entry(n, struct key, serial_node); + if (key->user->user_ns == user_ns) + break; + n = rb_next(n); + } + return n; +} + +static int proc_keys_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &proc_keys_ops); +} + +static struct key *find_ge_key(key_serial_t id) +{ + struct user_namespace *user_ns = current_user_ns(); + struct rb_node *n = key_serial_tree.rb_node; + struct key *minkey = NULL; + + while (n) { + struct key *key = rb_entry(n, struct key, serial_node); + if (id < key->serial) { + if (!minkey || minkey->serial > key->serial) + minkey = key; + n = n->rb_left; + } else if (id > key->serial) { + n = n->rb_right; + } else { + minkey = key; + break; + } + key = NULL; + } + + if (!minkey) + return NULL; + + for (;;) { + if (minkey->user->user_ns == user_ns) + return minkey; + n = rb_next(&minkey->serial_node); + if (!n) + return NULL; + minkey = rb_entry(n, struct key, serial_node); + } +} + +static void *proc_keys_start(struct seq_file *p, loff_t *_pos) + __acquires(key_serial_lock) +{ + key_serial_t pos = *_pos; + struct key *key; + + spin_lock(&key_serial_lock); + + if (*_pos > INT_MAX) + return NULL; + key = find_ge_key(pos); + if (!key) + return NULL; + *_pos = key->serial; + return &key->serial_node; +} + +static inline key_serial_t key_node_serial(struct rb_node *n) +{ + struct key *key = rb_entry(n, struct key, serial_node); + return key->serial; +} + +static void *proc_keys_next(struct seq_file *p, void *v, loff_t *_pos) +{ + struct rb_node *n; + + n = key_serial_next(v); + if (n) + *_pos = key_node_serial(n); + return n; +} + +static void proc_keys_stop(struct seq_file *p, void *v) + __releases(key_serial_lock) +{ + spin_unlock(&key_serial_lock); +} + +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, + true, 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(key_ref, cred, KEY_VIEW); + if (rc < 0) + return 0; + + now = current_kernel_time(); + + rcu_read_lock(); + + /* come up with a suitable timeout value */ + if (key->expiry == 0) { + memcpy(xbuf, "perm", 5); + } else if (now.tv_sec >= key->expiry) { + memcpy(xbuf, "expd", 5); + } else { + timo = key->expiry - now.tv_sec; + + if (timo < 60) + sprintf(xbuf, "%lus", timo); + else if (timo < 60*60) + sprintf(xbuf, "%lum", timo / 60); + else if (timo < 60*60*24) + sprintf(xbuf, "%luh", timo / (60*60)); + else if (timo < 60*60*24*7) + sprintf(xbuf, "%lud", timo / (60*60*24)); + else + sprintf(xbuf, "%luw", timo / (60*60*24*7)); + } + +#define showflag(KEY, LETTER, FLAG) \ + (test_bit(FLAG, &(KEY)->flags) ? LETTER : '-') + + seq_printf(m, "%08x %c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ", + key->serial, + showflag(key, 'I', KEY_FLAG_INSTANTIATED), + showflag(key, 'R', KEY_FLAG_REVOKED), + showflag(key, 'D', KEY_FLAG_DEAD), + showflag(key, 'Q', KEY_FLAG_IN_QUOTA), + showflag(key, 'U', KEY_FLAG_USER_CONSTRUCT), + showflag(key, 'N', KEY_FLAG_NEGATIVE), + atomic_read(&key->usage), + xbuf, + key->perm, + key->uid, + key->gid, + key->type->name); + +#undef showflag + + if (key->type->describe) + key->type->describe(key, m); + seq_putc(m, '\n'); + + rcu_read_unlock(); + return 0; +} + +#endif /* CONFIG_KEYS_DEBUG_PROC_KEYS */ + +static struct rb_node *__key_user_next(struct rb_node *n) +{ + while (n) { + struct key_user *user = rb_entry(n, struct key_user, node); + if (user->user_ns == current_user_ns()) + break; + n = rb_next(n); + } + return n; +} + +static struct rb_node *key_user_next(struct rb_node *n) +{ + return __key_user_next(rb_next(n)); +} + +static struct rb_node *key_user_first(struct rb_root *r) +{ + struct rb_node *n = rb_first(r); + return __key_user_next(n); +} + +/* + * Implement "/proc/key-users" to provides a list of the key users and their + * quotas. + */ +static int proc_key_users_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &proc_key_users_ops); +} + +static void *proc_key_users_start(struct seq_file *p, loff_t *_pos) + __acquires(key_user_lock) +{ + struct rb_node *_p; + loff_t pos = *_pos; + + spin_lock(&key_user_lock); + + _p = key_user_first(&key_user_tree); + while (pos > 0 && _p) { + pos--; + _p = key_user_next(_p); + } + + return _p; +} + +static void *proc_key_users_next(struct seq_file *p, void *v, loff_t *_pos) +{ + (*_pos)++; + return key_user_next((struct rb_node *)v); +} + +static void proc_key_users_stop(struct seq_file *p, void *v) + __releases(key_user_lock) +{ + spin_unlock(&key_user_lock); +} + +static int proc_key_users_show(struct seq_file *m, void *v) +{ + struct rb_node *_p = v; + struct key_user *user = rb_entry(_p, struct key_user, node); + unsigned maxkeys = (user->uid == 0) ? + key_quota_root_maxkeys : key_quota_maxkeys; + unsigned maxbytes = (user->uid == 0) ? + key_quota_root_maxbytes : key_quota_maxbytes; + + seq_printf(m, "%5u: %5d %d/%d %d/%d %d/%d\n", + user->uid, + atomic_read(&user->usage), + atomic_read(&user->nkeys), + atomic_read(&user->nikeys), + user->qnkeys, + maxkeys, + user->qnbytes, + maxbytes); + + return 0; +} diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c new file mode 100644 index 00000000..a3063eb3 --- /dev/null +++ b/security/keys/process_keys.c @@ -0,0 +1,867 @@ +/* Manage a process's keyrings + * + * Copyright (C) 2004-2005, 2008 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/keyctl.h> +#include <linux/fs.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/security.h> +#include <linux/user_namespace.h> +#include <asm/uaccess.h> +#include "internal.h" + +/* Session keyring create vs join semaphore */ +static DEFINE_MUTEX(key_session_mutex); + +/* User keyring creation semaphore */ +static DEFINE_MUTEX(key_user_keyring_mutex); + +/* The root user's tracking struct */ +struct key_user root_key_user = { + .usage = ATOMIC_INIT(3), + .cons_lock = __MUTEX_INITIALIZER(root_key_user.cons_lock), + .lock = __SPIN_LOCK_UNLOCKED(root_key_user.lock), + .nkeys = ATOMIC_INIT(2), + .nikeys = ATOMIC_INIT(2), + .uid = 0, + .user_ns = &init_user_ns, +}; + +/* + * Install the user and user session keyrings for the current process's UID. + */ +int install_user_keyrings(void) +{ + struct user_struct *user; + const struct cred *cred; + struct key *uid_keyring, *session_keyring; + char buf[20]; + int ret; + + cred = current_cred(); + user = cred->user; + + kenter("%p{%u}", user, user->uid); + + if (user->uid_keyring) { + kleave(" = 0 [exist]"); + return 0; + } + + mutex_lock(&key_user_keyring_mutex); + ret = 0; + + if (!user->uid_keyring) { + /* get the UID-specific keyring + * - there may be one in existence already as it may have been + * pinned by a session, but the user_struct pointing to it + * may have been destroyed by setuid */ + sprintf(buf, "_uid.%u", user->uid); + + uid_keyring = find_keyring_by_name(buf, true); + if (IS_ERR(uid_keyring)) { + uid_keyring = keyring_alloc(buf, user->uid, (gid_t) -1, + cred, KEY_ALLOC_IN_QUOTA, + NULL); + if (IS_ERR(uid_keyring)) { + ret = PTR_ERR(uid_keyring); + goto error; + } + } + + /* get a default session keyring (which might also exist + * already) */ + sprintf(buf, "_uid_ses.%u", user->uid); + + session_keyring = find_keyring_by_name(buf, true); + if (IS_ERR(session_keyring)) { + session_keyring = + keyring_alloc(buf, user->uid, (gid_t) -1, + cred, KEY_ALLOC_IN_QUOTA, NULL); + if (IS_ERR(session_keyring)) { + ret = PTR_ERR(session_keyring); + goto error_release; + } + + /* we install a link from the user session keyring to + * the user keyring */ + ret = key_link(session_keyring, uid_keyring); + if (ret < 0) + goto error_release_both; + } + + /* install the keyrings */ + user->uid_keyring = uid_keyring; + user->session_keyring = session_keyring; + } + + mutex_unlock(&key_user_keyring_mutex); + kleave(" = 0"); + return 0; + +error_release_both: + key_put(session_keyring); +error_release: + key_put(uid_keyring); +error: + mutex_unlock(&key_user_keyring_mutex); + kleave(" = %d", ret); + return ret; +} + +/* + * Install a fresh thread keyring directly to new credentials. This keyring is + * allowed to overrun the quota. + */ +int install_thread_keyring_to_cred(struct cred *new) +{ + struct key *keyring; + + keyring = keyring_alloc("_tid", new->uid, new->gid, new, + KEY_ALLOC_QUOTA_OVERRUN, NULL); + if (IS_ERR(keyring)) + return PTR_ERR(keyring); + + new->thread_keyring = keyring; + return 0; +} + +/* + * Install a fresh thread keyring, discarding the old one. + */ +static int install_thread_keyring(void) +{ + struct cred *new; + int ret; + + new = prepare_creds(); + if (!new) + return -ENOMEM; + + BUG_ON(new->thread_keyring); + + ret = install_thread_keyring_to_cred(new); + if (ret < 0) { + abort_creds(new); + return ret; + } + + return commit_creds(new); +} + +/* + * Install a process keyring directly to a credentials struct. + * + * Returns -EEXIST if there was already a process keyring, 0 if one installed, + * and other value on any other error + */ +int install_process_keyring_to_cred(struct cred *new) +{ + struct key *keyring; + int ret; + + if (new->tgcred->process_keyring) + return -EEXIST; + + keyring = keyring_alloc("_pid", new->uid, new->gid, + new, KEY_ALLOC_QUOTA_OVERRUN, NULL); + if (IS_ERR(keyring)) + return PTR_ERR(keyring); + + spin_lock_irq(&new->tgcred->lock); + if (!new->tgcred->process_keyring) { + new->tgcred->process_keyring = keyring; + keyring = NULL; + ret = 0; + } else { + ret = -EEXIST; + } + spin_unlock_irq(&new->tgcred->lock); + key_put(keyring); + return ret; +} + +/* + * Make sure a process keyring is installed for the current process. The + * existing process keyring is not replaced. + * + * Returns 0 if there is a process keyring by the end of this function, some + * error otherwise. + */ +static int install_process_keyring(void) +{ + struct cred *new; + int ret; + + new = prepare_creds(); + if (!new) + return -ENOMEM; + + ret = install_process_keyring_to_cred(new); + if (ret < 0) { + abort_creds(new); + return ret != -EEXIST ? ret : 0; + } + + return commit_creds(new); +} + +/* + * Install a session keyring directly to a credentials struct. + */ +int install_session_keyring_to_cred(struct cred *cred, struct key *keyring) +{ + unsigned long flags; + struct key *old; + + might_sleep(); + + /* create an empty session keyring */ + if (!keyring) { + flags = KEY_ALLOC_QUOTA_OVERRUN; + if (cred->tgcred->session_keyring) + flags = KEY_ALLOC_IN_QUOTA; + + keyring = keyring_alloc("_ses", cred->uid, cred->gid, + cred, flags, NULL); + if (IS_ERR(keyring)) + return PTR_ERR(keyring); + } else { + atomic_inc(&keyring->usage); + } + + /* install the keyring */ + spin_lock_irq(&cred->tgcred->lock); + old = cred->tgcred->session_keyring; + rcu_assign_pointer(cred->tgcred->session_keyring, keyring); + spin_unlock_irq(&cred->tgcred->lock); + + /* we're using RCU on the pointer, but there's no point synchronising + * on it if it didn't previously point to anything */ + if (old) { + synchronize_rcu(); + key_put(old); + } + + return 0; +} + +/* + * Install a session keyring, discarding the old one. If a keyring is not + * supplied, an empty one is invented. + */ +static int install_session_keyring(struct key *keyring) +{ + struct cred *new; + int ret; + + new = prepare_creds(); + if (!new) + return -ENOMEM; + + ret = install_session_keyring_to_cred(new, NULL); + if (ret < 0) { + abort_creds(new); + return ret; + } + + return commit_creds(new); +} + +/* + * Handle the fsuid changing. + */ +void key_fsuid_changed(struct task_struct *tsk) +{ + /* update the ownership of the thread keyring */ + BUG_ON(!tsk->cred); + if (tsk->cred->thread_keyring) { + down_write(&tsk->cred->thread_keyring->sem); + tsk->cred->thread_keyring->uid = tsk->cred->fsuid; + up_write(&tsk->cred->thread_keyring->sem); + } +} + +/* + * Handle the fsgid changing. + */ +void key_fsgid_changed(struct task_struct *tsk) +{ + /* update the ownership of the thread keyring */ + BUG_ON(!tsk->cred); + if (tsk->cred->thread_keyring) { + down_write(&tsk->cred->thread_keyring->sem); + tsk->cred->thread_keyring->gid = tsk->cred->fsgid; + up_write(&tsk->cred->thread_keyring->sem); + } +} + +/* + * Search the process keyrings attached to the supplied cred for the first + * matching key. + * + * The search criteria are the type and the match function. The description is + * given to the match function as a parameter, but doesn't otherwise influence + * the search. Typically the match function will compare the description + * parameter to the key's description. + * + * This can only search keyrings that grant Search permission to the supplied + * credentials. Keyrings linked to searched keyrings will also be searched if + * they grant Search permission too. Keys can only be found if they grant + * Search permission to the credentials. + * + * Returns a pointer to the key with the key usage count incremented if + * successful, -EAGAIN if we didn't find any matching key or -ENOKEY if we only + * matched negative keys. + * + * In the case of a successful return, the possession attribute is set on the + * returned key reference. + */ +key_ref_t search_my_process_keyrings(struct key_type *type, + const void *description, + key_match_func_t match, + bool no_state_check, + const struct cred *cred) +{ + key_ref_t key_ref, ret, err; + + /* 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 + * none of the keyrings were searchable + * + * in terms of priority: success > -ENOKEY > -EAGAIN > other error + */ + key_ref = NULL; + ret = NULL; + err = ERR_PTR(-EAGAIN); + + /* search the thread keyring first */ + if (cred->thread_keyring) { + key_ref = keyring_search_aux( + make_key_ref(cred->thread_keyring, 1), + cred, type, description, match, no_state_check); + 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; + } + } + + /* search the process keyring second */ + if (cred->tgcred->process_keyring) { + key_ref = keyring_search_aux( + make_key_ref(cred->tgcred->process_keyring, 1), + cred, type, description, match, no_state_check); + 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; + } + } + + /* search the session keyring */ + if (cred->tgcred->session_keyring) { + rcu_read_lock(); + key_ref = keyring_search_aux( + make_key_ref(rcu_dereference( + cred->tgcred->session_keyring), + 1), + cred, type, description, match, no_state_check); + rcu_read_unlock(); + + 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; + } + } + /* or search the user-session keyring */ + else if (cred->user->session_keyring) { + key_ref = keyring_search_aux( + make_key_ref(cred->user->session_keyring, 1), + cred, type, description, match, no_state_check); + 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; + } + } + + /* 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 attached to the supplied cred for the first + * matching key in the manner of search_my_process_keyrings(), but also search + * the keys attached to the assumed authorisation key using its credentials if + * one is available. + * + * Return same as search_my_process_keyrings(). + */ +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, + false, 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 + */ + if (cred->request_key_auth && + cred == current_cred() && + type != &key_type_request_key_auth + ) { + /* defend against the auth key being revoked */ + down_read(&cred->request_key_auth->sem); + + if (key_validate(cred->request_key_auth) == 0) { + rka = cred->request_key_auth->payload.data; + + key_ref = search_process_keyrings(type, description, + match, rka->cred); + + up_read(&cred->request_key_auth->sem); + + if (!IS_ERR(key_ref)) + goto found; + + ret = key_ref; + } else { + up_read(&cred->request_key_auth->sem); + } + } + + /* no key - decide on the error we're going to go for */ + 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; +} + +/* + * See if the key we're looking at is the target key. + */ +int lookup_user_key_possessed(const struct key *key, const void *target) +{ + return key == target; +} + +/* + * Look up a key ID given us by userspace with a given permissions mask to get + * the key it refers to. + * + * Flags can be passed to request that special keyrings be created if referred + * to directly, to permit partially constructed keys to be found and to skip + * validity and permission checks on the found key. + * + * Returns a pointer to the key with an incremented usage count if successful; + * -EINVAL if the key ID is invalid; -ENOKEY if the key ID does not correspond + * to a key or the best found key was a negative key; -EKEYREVOKED or + * -EKEYEXPIRED if the best found key was revoked or expired; -EACCES if the + * found key doesn't grant the requested permit or the LSM denied access to it; + * or -ENOMEM if a special keyring couldn't be created. + * + * In the case of a successful return, the possession attribute is set on the + * returned key reference. + */ +key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags, + key_perm_t perm) +{ + struct request_key_auth *rka; + const struct cred *cred; + struct key *key; + key_ref_t key_ref, skey_ref; + int ret; + +try_again: + cred = get_current_cred(); + key_ref = ERR_PTR(-ENOKEY); + + switch (id) { + case KEY_SPEC_THREAD_KEYRING: + if (!cred->thread_keyring) { + if (!(lflags & KEY_LOOKUP_CREATE)) + goto error; + + ret = install_thread_keyring(); + if (ret < 0) { + key_ref = ERR_PTR(ret); + goto error; + } + goto reget_creds; + } + + key = cred->thread_keyring; + atomic_inc(&key->usage); + key_ref = make_key_ref(key, 1); + break; + + case KEY_SPEC_PROCESS_KEYRING: + if (!cred->tgcred->process_keyring) { + if (!(lflags & KEY_LOOKUP_CREATE)) + goto error; + + ret = install_process_keyring(); + if (ret < 0) { + key_ref = ERR_PTR(ret); + goto error; + } + goto reget_creds; + } + + key = cred->tgcred->process_keyring; + atomic_inc(&key->usage); + key_ref = make_key_ref(key, 1); + break; + + case KEY_SPEC_SESSION_KEYRING: + if (!cred->tgcred->session_keyring) { + /* always install a session keyring upon access if one + * doesn't exist yet */ + ret = install_user_keyrings(); + if (ret < 0) + goto error; + ret = install_session_keyring( + cred->user->session_keyring); + + if (ret < 0) + goto error; + goto reget_creds; + } + + rcu_read_lock(); + key = rcu_dereference(cred->tgcred->session_keyring); + atomic_inc(&key->usage); + rcu_read_unlock(); + key_ref = make_key_ref(key, 1); + break; + + case KEY_SPEC_USER_KEYRING: + if (!cred->user->uid_keyring) { + ret = install_user_keyrings(); + if (ret < 0) + goto error; + } + + key = cred->user->uid_keyring; + atomic_inc(&key->usage); + key_ref = make_key_ref(key, 1); + break; + + case KEY_SPEC_USER_SESSION_KEYRING: + if (!cred->user->session_keyring) { + ret = install_user_keyrings(); + if (ret < 0) + goto error; + } + + key = cred->user->session_keyring; + atomic_inc(&key->usage); + key_ref = make_key_ref(key, 1); + break; + + case KEY_SPEC_GROUP_KEYRING: + /* group keyrings are not yet supported */ + key_ref = ERR_PTR(-EINVAL); + goto error; + + case KEY_SPEC_REQKEY_AUTH_KEY: + key = cred->request_key_auth; + if (!key) + goto error; + + atomic_inc(&key->usage); + key_ref = make_key_ref(key, 1); + break; + + case KEY_SPEC_REQUESTOR_KEYRING: + if (!cred->request_key_auth) + goto error; + + down_read(&cred->request_key_auth->sem); + if (cred->request_key_auth->flags & KEY_FLAG_REVOKED) { + key_ref = ERR_PTR(-EKEYREVOKED); + key = NULL; + } else { + rka = cred->request_key_auth->payload.data; + key = rka->dest_keyring; + atomic_inc(&key->usage); + } + up_read(&cred->request_key_auth->sem); + if (!key) + goto error; + key_ref = make_key_ref(key, 1); + break; + + default: + key_ref = ERR_PTR(-EINVAL); + if (id < 1) + goto error; + + key = key_lookup(id); + if (IS_ERR(key)) { + key_ref = ERR_CAST(key); + goto error; + } + + key_ref = make_key_ref(key, 0); + + /* check to see if we possess the key */ + skey_ref = search_process_keyrings(key->type, key, + lookup_user_key_possessed, + cred); + + if (!IS_ERR(skey_ref)) { + key_put(key); + key_ref = skey_ref; + } + + break; + } + + /* unlink does not use the nominated key in any way, so can skip all + * the permission checks as it is only concerned with the keyring */ + if (lflags & KEY_LOOKUP_FOR_UNLINK) { + ret = 0; + goto error; + } + + if (!(lflags & KEY_LOOKUP_PARTIAL)) { + ret = wait_for_key_construction(key, true); + switch (ret) { + case -ERESTARTSYS: + goto invalid_key; + default: + if (perm) + goto invalid_key; + case 0: + break; + } + } else if (perm) { + ret = key_validate(key); + if (ret < 0) + goto invalid_key; + } + + ret = -EIO; + if (!(lflags & KEY_LOOKUP_PARTIAL) && + !test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) + goto invalid_key; + + /* check the permissions */ + ret = key_task_permission(key_ref, cred, perm); + if (ret < 0) + goto invalid_key; + +error: + put_cred(cred); + return key_ref; + +invalid_key: + key_ref_put(key_ref); + key_ref = ERR_PTR(ret); + goto error; + + /* if we attempted to install a keyring, then it may have caused new + * creds to be installed */ +reget_creds: + put_cred(cred); + goto try_again; +} + +/* + * Join the named keyring as the session keyring if possible else attempt to + * create a new one of that name and join that. + * + * If the name is NULL, an empty anonymous keyring will be installed as the + * session keyring. + * + * Named session keyrings are joined with a semaphore held to prevent the + * keyrings from going away whilst the attempt is made to going them and also + * to prevent a race in creating compatible session keyrings. + */ +long join_session_keyring(const char *name) +{ + const struct cred *old; + struct cred *new; + struct key *keyring; + long ret, serial; + + /* only permit this if there's a single thread in the thread group - + * this avoids us having to adjust the creds on all threads and risking + * ENOMEM */ + if (!current_is_single_threaded()) + return -EMLINK; + + new = prepare_creds(); + if (!new) + return -ENOMEM; + old = current_cred(); + + /* if no name is provided, install an anonymous keyring */ + if (!name) { + ret = install_session_keyring_to_cred(new, NULL); + if (ret < 0) + goto error; + + serial = new->tgcred->session_keyring->serial; + ret = commit_creds(new); + if (ret == 0) + ret = serial; + goto okay; + } + + /* allow the user to join or create a named keyring */ + mutex_lock(&key_session_mutex); + + /* look for an existing keyring of this name */ + keyring = find_keyring_by_name(name, false); + if (PTR_ERR(keyring) == -ENOKEY) { + /* not found - try and create a new one */ + keyring = keyring_alloc(name, old->uid, old->gid, old, + KEY_ALLOC_IN_QUOTA, NULL); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error2; + } + } else if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error2; + } + + /* we've got a keyring - now to install it */ + ret = install_session_keyring_to_cred(new, keyring); + if (ret < 0) + goto error2; + + commit_creds(new); + mutex_unlock(&key_session_mutex); + + ret = keyring->serial; + key_put(keyring); +okay: + return ret; + +error2: + mutex_unlock(&key_session_mutex); +error: + abort_creds(new); + return ret; +} + +/* + * Replace a process's session keyring on behalf of one of its children when + * the target process is about to resume userspace execution. + */ +void key_replace_session_keyring(void) +{ + const struct cred *old; + struct cred *new; + + if (!current->replacement_session_keyring) + return; + + write_lock_irq(&tasklist_lock); + new = current->replacement_session_keyring; + current->replacement_session_keyring = NULL; + write_unlock_irq(&tasklist_lock); + + if (!new) + return; + + old = current_cred(); + new-> uid = old-> uid; + new-> euid = old-> euid; + new-> suid = old-> suid; + new->fsuid = old->fsuid; + new-> gid = old-> gid; + new-> egid = old-> egid; + new-> sgid = old-> sgid; + new->fsgid = old->fsgid; + new->user = get_uid(old->user); + new->user_ns = new->user->user_ns; + new->group_info = get_group_info(old->group_info); + + new->securebits = old->securebits; + new->cap_inheritable = old->cap_inheritable; + new->cap_permitted = old->cap_permitted; + new->cap_effective = old->cap_effective; + new->cap_bset = old->cap_bset; + + new->jit_keyring = old->jit_keyring; + new->thread_keyring = key_get(old->thread_keyring); + new->tgcred->tgid = old->tgcred->tgid; + new->tgcred->process_keyring = key_get(old->tgcred->process_keyring); + + security_transfer_creds(new, old); + + commit_creds(new); +} diff --git a/security/keys/request_key.c b/security/keys/request_key.c new file mode 100644 index 00000000..82465328 --- /dev/null +++ b/security/keys/request_key.c @@ -0,0 +1,713 @@ +/* Request a key from userspace + * + * Copyright (C) 2004-2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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; either version + * 2 of the License, or (at your option) any later version. + * + * See Documentation/security/keys-request-key.txt + */ + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/kmod.h> +#include <linux/err.h> +#include <linux/keyctl.h> +#include <linux/slab.h> +#include "internal.h" + +#define key_negative_timeout 60 /* default timeout on a negative key's existence */ + +/* + * wait_on_bit() sleep function for uninterruptible waiting + */ +static int key_wait_bit(void *flags) +{ + schedule(); + return 0; +} + +/* + * wait_on_bit() sleep function for interruptible waiting + */ +static int key_wait_bit_intr(void *flags) +{ + schedule(); + return signal_pending(current) ? -ERESTARTSYS : 0; +} + +/** + * complete_request_key - Complete the construction of a key. + * @cons: The key construction record. + * @error: The success or failute of the construction. + * + * Complete the attempt to construct a key. The key will be negated + * if an error is indicated. The authorisation key will be revoked + * unconditionally. + */ +void complete_request_key(struct key_construction *cons, int error) +{ + kenter("{%d,%d},%d", cons->key->serial, cons->authkey->serial, error); + + if (error < 0) + key_negate_and_link(cons->key, key_negative_timeout, NULL, + cons->authkey); + else + key_revoke(cons->authkey); + + key_put(cons->key); + key_put(cons->authkey); + kfree(cons); +} +EXPORT_SYMBOL(complete_request_key); + +/* + * Initialise a usermode helper that is going to have a specific session + * keyring. + * + * This is called in context of freshly forked kthread before kernel_execve(), + * so we can simply install the desired session_keyring at this point. + */ +static int umh_keys_init(struct subprocess_info *info, struct cred *cred) +{ + struct key *keyring = info->data; + + return install_session_keyring_to_cred(cred, keyring); +} + +/* + * Clean up a usermode helper with session keyring. + */ +static void umh_keys_cleanup(struct subprocess_info *info) +{ + struct key *keyring = info->data; + key_put(keyring); +} + +/* + * Call a usermode helper with a specific session keyring. + */ +static int call_usermodehelper_keys(char *path, char **argv, char **envp, + struct key *session_keyring, enum umh_wait wait) +{ + gfp_t gfp_mask = (wait == UMH_NO_WAIT) ? GFP_ATOMIC : GFP_KERNEL; + struct subprocess_info *info = + call_usermodehelper_setup(path, argv, envp, gfp_mask); + + if (!info) + return -ENOMEM; + + call_usermodehelper_setfns(info, umh_keys_init, umh_keys_cleanup, + key_get(session_keyring)); + return call_usermodehelper_exec(info, wait); +} + +/* + * Request userspace finish the construction of a key + * - execute "/sbin/request-key <op> <key> <uid> <gid> <keyring> <keyring> <keyring>" + */ +static int call_sbin_request_key(struct key_construction *cons, + const char *op, + void *aux) +{ + const struct cred *cred = current_cred(); + key_serial_t prkey, sskey; + struct key *key = cons->key, *authkey = cons->authkey, *keyring, + *session; + char *argv[9], *envp[3], uid_str[12], gid_str[12]; + char key_str[12], keyring_str[3][12]; + char desc[20]; + int ret, i; + + kenter("{%d},{%d},%s", key->serial, authkey->serial, op); + + ret = install_user_keyrings(); + if (ret < 0) + goto error_alloc; + + /* allocate a new session keyring */ + sprintf(desc, "_req.%u", key->serial); + + cred = get_current_cred(); + keyring = keyring_alloc(desc, cred->fsuid, cred->fsgid, cred, + KEY_ALLOC_QUOTA_OVERRUN, NULL); + put_cred(cred); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error_alloc; + } + + /* attach the auth key to the session keyring */ + ret = key_link(keyring, authkey); + if (ret < 0) + goto error_link; + + /* record the UID and GID */ + sprintf(uid_str, "%d", cred->fsuid); + sprintf(gid_str, "%d", cred->fsgid); + + /* we say which key is under construction */ + sprintf(key_str, "%d", key->serial); + + /* we specify the process's default keyrings */ + sprintf(keyring_str[0], "%d", + cred->thread_keyring ? cred->thread_keyring->serial : 0); + + 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); + if (!session) + session = cred->user->session_keyring; + sskey = session->serial; + rcu_read_unlock(); + + sprintf(keyring_str[2], "%d", sskey); + + /* set up a minimal environment */ + i = 0; + envp[i++] = "HOME=/"; + envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; + envp[i] = NULL; + + /* set up the argument list */ + i = 0; + argv[i++] = "/sbin/request-key"; + argv[i++] = (char *) op; + argv[i++] = key_str; + argv[i++] = uid_str; + argv[i++] = gid_str; + argv[i++] = keyring_str[0]; + argv[i++] = keyring_str[1]; + argv[i++] = keyring_str[2]; + argv[i] = NULL; + + /* do it */ + ret = call_usermodehelper_keys(argv[0], argv, envp, keyring, + UMH_WAIT_PROC); + kdebug("usermode -> 0x%x", ret); + if (ret >= 0) { + /* ret is the exit/wait code */ + if (test_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags) || + key_validate(key) < 0) + ret = -ENOKEY; + else + /* ignore any errors from userspace if the key was + * instantiated */ + ret = 0; + } + +error_link: + key_put(keyring); + +error_alloc: + complete_request_key(cons, ret); + kleave(" = %d", ret); + return ret; +} + +/* + * Call out to userspace for key construction. + * + * Program failure is ignored in favour of key status. + */ +static int construct_key(struct key *key, const void *callout_info, + size_t callout_len, void *aux, + struct key *dest_keyring) +{ + struct key_construction *cons; + request_key_actor_t actor; + struct key *authkey; + int ret; + + kenter("%d,%p,%zu,%p", key->serial, callout_info, callout_len, aux); + + cons = kmalloc(sizeof(*cons), GFP_KERNEL); + if (!cons) + return -ENOMEM; + + /* allocate an authorisation key */ + authkey = request_key_auth_new(key, callout_info, callout_len, + dest_keyring); + if (IS_ERR(authkey)) { + kfree(cons); + ret = PTR_ERR(authkey); + authkey = NULL; + } else { + cons->authkey = key_get(authkey); + cons->key = key_get(key); + + /* make the call */ + actor = call_sbin_request_key; + if (key->type->request_key) + actor = key->type->request_key; + + ret = actor(cons, "create", aux); + + /* check that the actor called complete_request_key() prior to + * returning an error */ + WARN_ON(ret < 0 && + !test_bit(KEY_FLAG_REVOKED, &authkey->flags)); + key_put(authkey); + } + + kleave(" = %d", ret); + return ret; +} + +/* + * Get the appropriate destination keyring for the request. + * + * The keyring selected is returned with an extra reference upon it which the + * caller must release. + */ +static void construct_get_dest_keyring(struct key **_dest_keyring) +{ + struct request_key_auth *rka; + const struct cred *cred = current_cred(); + struct key *dest_keyring = *_dest_keyring, *authkey; + + kenter("%p", dest_keyring); + + /* find the appropriate keyring */ + if (dest_keyring) { + /* the caller supplied one */ + key_get(dest_keyring); + } else { + /* use a default keyring; falling through the cases until we + * find one that we actually have */ + switch (cred->jit_keyring) { + case KEY_REQKEY_DEFL_DEFAULT: + case KEY_REQKEY_DEFL_REQUESTOR_KEYRING: + if (cred->request_key_auth) { + authkey = cred->request_key_auth; + down_read(&authkey->sem); + rka = authkey->payload.data; + if (!test_bit(KEY_FLAG_REVOKED, + &authkey->flags)) + dest_keyring = + key_get(rka->dest_keyring); + up_read(&authkey->sem); + if (dest_keyring) + break; + } + + case KEY_REQKEY_DEFL_THREAD_KEYRING: + dest_keyring = key_get(cred->thread_keyring); + if (dest_keyring) + break; + + case KEY_REQKEY_DEFL_PROCESS_KEYRING: + dest_keyring = key_get(cred->tgcred->process_keyring); + if (dest_keyring) + break; + + case KEY_REQKEY_DEFL_SESSION_KEYRING: + rcu_read_lock(); + dest_keyring = key_get( + rcu_dereference(cred->tgcred->session_keyring)); + rcu_read_unlock(); + + if (dest_keyring) + break; + + case KEY_REQKEY_DEFL_USER_SESSION_KEYRING: + dest_keyring = + key_get(cred->user->session_keyring); + break; + + case KEY_REQKEY_DEFL_USER_KEYRING: + dest_keyring = key_get(cred->user->uid_keyring); + break; + + case KEY_REQKEY_DEFL_GROUP_KEYRING: + default: + BUG(); + } + } + + *_dest_keyring = dest_keyring; + kleave(" [dk %d]", key_serial(dest_keyring)); + return; +} + +/* + * Allocate a new key in under-construction state and attempt to link it in to + * the requested keyring. + * + * May return a key that's already under construction instead if there was a + * race between two thread calling request_key(). + */ +static int construct_alloc_key(struct key_type *type, + const char *description, + struct key *dest_keyring, + unsigned long flags, + struct key_user *user, + struct key **_key) +{ + const struct cred *cred = current_cred(); + unsigned long prealloc; + struct key *key; + key_ref_t key_ref; + int ret; + + kenter("%s,%s,,,", type->name, description); + + *_key = NULL; + mutex_lock(&user->cons_lock); + + key = key_alloc(type, description, cred->fsuid, cred->fsgid, cred, + KEY_POS_ALL, flags); + if (IS_ERR(key)) + goto alloc_failed; + + set_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags); + + if (dest_keyring) { + ret = __key_link_begin(dest_keyring, type, description, + &prealloc); + if (ret < 0) + goto link_prealloc_failed; + } + + /* attach the key to the destination keyring under lock, but we do need + * to do another check just in case someone beat us to it whilst we + * waited for locks */ + mutex_lock(&key_construction_mutex); + + key_ref = search_process_keyrings(type, description, type->match, cred); + if (!IS_ERR(key_ref)) + goto key_already_present; + + if (dest_keyring) + __key_link(dest_keyring, key, &prealloc); + + mutex_unlock(&key_construction_mutex); + if (dest_keyring) + __key_link_end(dest_keyring, type, prealloc); + mutex_unlock(&user->cons_lock); + *_key = key; + kleave(" = 0 [%d]", key_serial(key)); + return 0; + + /* the key is now present - we tell the caller that we found it by + * returning -EINPROGRESS */ +key_already_present: + key_put(key); + mutex_unlock(&key_construction_mutex); + key = key_ref_to_ptr(key_ref); + if (dest_keyring) { + ret = __key_link_check_live_key(dest_keyring, key); + if (ret == 0) + __key_link(dest_keyring, key, &prealloc); + __key_link_end(dest_keyring, type, prealloc); + if (ret < 0) + goto link_check_failed; + } + mutex_unlock(&user->cons_lock); + *_key = key; + kleave(" = -EINPROGRESS [%d]", key_serial(key)); + return -EINPROGRESS; + +link_check_failed: + mutex_unlock(&user->cons_lock); + key_put(key); + kleave(" = %d [linkcheck]", ret); + return ret; + +link_prealloc_failed: + mutex_unlock(&user->cons_lock); + kleave(" = %d [prelink]", ret); + return ret; + +alloc_failed: + mutex_unlock(&user->cons_lock); + kleave(" = %ld", PTR_ERR(key)); + return PTR_ERR(key); +} + +/* + * Commence key construction. + */ +static struct key *construct_key_and_link(struct key_type *type, + const char *description, + const char *callout_info, + size_t callout_len, + void *aux, + struct key *dest_keyring, + unsigned long flags) +{ + struct key_user *user; + struct key *key; + int ret; + + kenter(""); + + user = key_user_lookup(current_fsuid(), current_user_ns()); + if (!user) + return ERR_PTR(-ENOMEM); + + construct_get_dest_keyring(&dest_keyring); + + ret = construct_alloc_key(type, description, dest_keyring, flags, user, + &key); + key_user_put(user); + + if (ret == 0) { + ret = construct_key(key, callout_info, callout_len, aux, + dest_keyring); + if (ret < 0) { + kdebug("cons failed"); + goto construction_failed; + } + } else if (ret == -EINPROGRESS) { + ret = 0; + } else { + goto couldnt_alloc_key; + } + + key_put(dest_keyring); + kleave(" = key %d", key_serial(key)); + return key; + +construction_failed: + key_negate_and_link(key, key_negative_timeout, NULL, NULL); + key_put(key); +couldnt_alloc_key: + key_put(dest_keyring); + kleave(" = %d", ret); + return ERR_PTR(ret); +} + +/** + * request_key_and_link - Request a key and cache it in a keyring. + * @type: The type of key we want. + * @description: The searchable description of the key. + * @callout_info: The data to pass to the instantiation upcall (or NULL). + * @callout_len: The length of callout_info. + * @aux: Auxiliary data for the upcall. + * @dest_keyring: Where to cache the key. + * @flags: Flags to key_alloc(). + * + * A key matching the specified criteria is searched for in the process's + * keyrings and returned with its usage count incremented if found. Otherwise, + * if callout_info is not NULL, a key will be allocated and some service + * (probably in userspace) will be asked to instantiate it. + * + * If successfully found or created, the key will be linked to the destination + * keyring if one is provided. + * + * Returns a pointer to the key if successful; -EACCES, -ENOKEY, -EKEYREVOKED + * or -EKEYEXPIRED if an inaccessible, negative, revoked or expired key was + * found; -ENOKEY if no key was found and no @callout_info was given; -EDQUOT + * if insufficient key quota was available to create a new key; or -ENOMEM if + * insufficient memory was available. + * + * If the returned key was created, then it may still be under construction, + * and wait_for_key_construction() should be used to wait for that to complete. + */ +struct key *request_key_and_link(struct key_type *type, + const char *description, + const void *callout_info, + size_t callout_len, + void *aux, + struct key *dest_keyring, + unsigned long flags) +{ + const struct cred *cred = current_cred(); + struct key *key; + key_ref_t key_ref; + int ret; + + kenter("%s,%s,%p,%zu,%p,%p,%lx", + type->name, description, callout_info, callout_len, aux, + dest_keyring, flags); + + /* search all the process keyrings for a key */ + key_ref = search_process_keyrings(type, description, type->match, cred); + + if (!IS_ERR(key_ref)) { + key = key_ref_to_ptr(key_ref); + if (dest_keyring) { + construct_get_dest_keyring(&dest_keyring); + ret = key_link(dest_keyring, key); + key_put(dest_keyring); + if (ret < 0) { + key_put(key); + key = ERR_PTR(ret); + goto error; + } + } + } else if (PTR_ERR(key_ref) != -EAGAIN) { + key = ERR_CAST(key_ref); + } else { + /* the search failed, but the keyrings were searchable, so we + * should consult userspace if we can */ + key = ERR_PTR(-ENOKEY); + if (!callout_info) + goto error; + + key = construct_key_and_link(type, description, callout_info, + callout_len, aux, dest_keyring, + flags); + } + +error: + kleave(" = %p", key); + return key; +} + +/** + * wait_for_key_construction - Wait for construction of a key to complete + * @key: The key being waited for. + * @intr: Whether to wait interruptibly. + * + * Wait for a key to finish being constructed. + * + * Returns 0 if successful; -ERESTARTSYS if the wait was interrupted; -ENOKEY + * if the key was negated; or -EKEYREVOKED or -EKEYEXPIRED if the key was + * revoked or expired. + */ +int wait_for_key_construction(struct key *key, bool intr) +{ + int ret; + + ret = wait_on_bit(&key->flags, KEY_FLAG_USER_CONSTRUCT, + intr ? key_wait_bit_intr : key_wait_bit, + intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE); + if (ret < 0) + return ret; + if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) + return key->type_data.reject_error; + return key_validate(key); +} +EXPORT_SYMBOL(wait_for_key_construction); + +/** + * request_key - Request a key and wait for construction + * @type: Type of key. + * @description: The searchable description of the key. + * @callout_info: The data to pass to the instantiation upcall (or NULL). + * + * As for request_key_and_link() except that it does not add the returned key + * to a keyring if found, new keys are always allocated in the user's quota, + * the callout_info must be a NUL-terminated string and no auxiliary data can + * be passed. + * + * Furthermore, it then works as wait_for_key_construction() to wait for the + * completion of keys undergoing construction with a non-interruptible wait. + */ +struct key *request_key(struct key_type *type, + const char *description, + const char *callout_info) +{ + struct key *key; + size_t callout_len = 0; + int ret; + + if (callout_info) + callout_len = strlen(callout_info); + key = request_key_and_link(type, description, callout_info, callout_len, + NULL, NULL, KEY_ALLOC_IN_QUOTA); + if (!IS_ERR(key)) { + ret = wait_for_key_construction(key, false); + if (ret < 0) { + key_put(key); + return ERR_PTR(ret); + } + } + return key; +} +EXPORT_SYMBOL(request_key); + +/** + * request_key_with_auxdata - Request a key with auxiliary data for the upcaller + * @type: The type of key we want. + * @description: The searchable description of the key. + * @callout_info: The data to pass to the instantiation upcall (or NULL). + * @callout_len: The length of callout_info. + * @aux: Auxiliary data for the upcall. + * + * As for request_key_and_link() except that it does not add the returned key + * to a keyring if found and new keys are always allocated in the user's quota. + * + * Furthermore, it then works as wait_for_key_construction() to wait for the + * completion of keys undergoing construction with a non-interruptible wait. + */ +struct key *request_key_with_auxdata(struct key_type *type, + const char *description, + const void *callout_info, + size_t callout_len, + void *aux) +{ + struct key *key; + int ret; + + key = request_key_and_link(type, description, callout_info, callout_len, + aux, NULL, KEY_ALLOC_IN_QUOTA); + if (!IS_ERR(key)) { + ret = wait_for_key_construction(key, false); + if (ret < 0) { + key_put(key); + return ERR_PTR(ret); + } + } + return key; +} +EXPORT_SYMBOL(request_key_with_auxdata); + +/* + * request_key_async - Request a key (allow async construction) + * @type: Type of key. + * @description: The searchable description of the key. + * @callout_info: The data to pass to the instantiation upcall (or NULL). + * @callout_len: The length of callout_info. + * + * As for request_key_and_link() except that it does not add the returned key + * to a keyring if found, new keys are always allocated in the user's quota and + * no auxiliary data can be passed. + * + * The caller should call wait_for_key_construction() to wait for the + * completion of the returned key if it is still undergoing construction. + */ +struct key *request_key_async(struct key_type *type, + const char *description, + const void *callout_info, + size_t callout_len) +{ + return request_key_and_link(type, description, callout_info, + callout_len, NULL, NULL, + KEY_ALLOC_IN_QUOTA); +} +EXPORT_SYMBOL(request_key_async); + +/* + * request a key with auxiliary data for the upcaller (allow async construction) + * @type: Type of key. + * @description: The searchable description of the key. + * @callout_info: The data to pass to the instantiation upcall (or NULL). + * @callout_len: The length of callout_info. + * @aux: Auxiliary data for the upcall. + * + * As for request_key_and_link() except that it does not add the returned key + * to a keyring if found and new keys are always allocated in the user's quota. + * + * The caller should call wait_for_key_construction() to wait for the + * completion of the returned key if it is still undergoing construction. + */ +struct key *request_key_async_with_auxdata(struct key_type *type, + const char *description, + const void *callout_info, + size_t callout_len, + void *aux) +{ + return request_key_and_link(type, description, callout_info, + callout_len, aux, NULL, KEY_ALLOC_IN_QUOTA); +} +EXPORT_SYMBOL(request_key_async_with_auxdata); diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c new file mode 100644 index 00000000..6cff3752 --- /dev/null +++ b/security/keys/request_key_auth.c @@ -0,0 +1,265 @@ +/* Request key authorisation token key definition. + * + * Copyright (C) 2005 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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; either version + * 2 of the License, or (at your option) any later version. + * + * See Documentation/security/keys-request-key.txt + */ + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/err.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <asm/uaccess.h> +#include "internal.h" + +static int request_key_auth_instantiate(struct key *, const void *, size_t); +static void request_key_auth_describe(const struct key *, struct seq_file *); +static void request_key_auth_revoke(struct key *); +static void request_key_auth_destroy(struct key *); +static long request_key_auth_read(const struct key *, char __user *, size_t); + +/* + * The request-key authorisation key type definition. + */ +struct key_type key_type_request_key_auth = { + .name = ".request_key_auth", + .def_datalen = sizeof(struct request_key_auth), + .instantiate = request_key_auth_instantiate, + .describe = request_key_auth_describe, + .revoke = request_key_auth_revoke, + .destroy = request_key_auth_destroy, + .read = request_key_auth_read, +}; + +/* + * Instantiate a request-key authorisation key. + */ +static int request_key_auth_instantiate(struct key *key, + const void *data, + size_t datalen) +{ + key->payload.data = (struct request_key_auth *) data; + return 0; +} + +/* + * Describe an authorisation token. + */ +static void request_key_auth_describe(const struct key *key, + struct seq_file *m) +{ + struct request_key_auth *rka = key->payload.data; + + seq_puts(m, "key:"); + seq_puts(m, key->description); + if (key_is_instantiated(key)) + seq_printf(m, " pid:%d ci:%zu", rka->pid, rka->callout_len); +} + +/* + * Read the callout_info data (retrieves the callout information). + * - the key's semaphore is read-locked + */ +static long request_key_auth_read(const struct key *key, + char __user *buffer, size_t buflen) +{ + struct request_key_auth *rka = key->payload.data; + size_t datalen; + long ret; + + datalen = rka->callout_len; + ret = datalen; + + /* we can return the data as is */ + if (buffer && buflen > 0) { + if (buflen > datalen) + buflen = datalen; + + if (copy_to_user(buffer, rka->callout_info, buflen) != 0) + ret = -EFAULT; + } + + return ret; +} + +/* + * Handle revocation of an authorisation token key. + * + * Called with the key sem write-locked. + */ +static void request_key_auth_revoke(struct key *key) +{ + struct request_key_auth *rka = key->payload.data; + + kenter("{%d}", key->serial); + + if (rka->cred) { + put_cred(rka->cred); + rka->cred = NULL; + } +} + +/* + * Destroy an instantiation authorisation token key. + */ +static void request_key_auth_destroy(struct key *key) +{ + struct request_key_auth *rka = key->payload.data; + + kenter("{%d}", key->serial); + + if (rka->cred) { + put_cred(rka->cred); + rka->cred = NULL; + } + + key_put(rka->target_key); + key_put(rka->dest_keyring); + kfree(rka->callout_info); + kfree(rka); +} + +/* + * Create an authorisation token for /sbin/request-key or whoever to gain + * access to the caller's security data. + */ +struct key *request_key_auth_new(struct key *target, const void *callout_info, + size_t callout_len, struct key *dest_keyring) +{ + struct request_key_auth *rka, *irka; + const struct cred *cred = current->cred; + struct key *authkey = NULL; + char desc[20]; + int ret; + + kenter("%d,", target->serial); + + /* allocate a auth record */ + rka = kmalloc(sizeof(*rka), GFP_KERNEL); + if (!rka) { + kleave(" = -ENOMEM"); + return ERR_PTR(-ENOMEM); + } + rka->callout_info = kmalloc(callout_len, GFP_KERNEL); + if (!rka->callout_info) { + kleave(" = -ENOMEM"); + kfree(rka); + return ERR_PTR(-ENOMEM); + } + + /* see if the calling process is already servicing the key request of + * another process */ + if (cred->request_key_auth) { + /* it is - use that instantiation context here too */ + down_read(&cred->request_key_auth->sem); + + /* if the auth key has been revoked, then the key we're + * servicing is already instantiated */ + if (test_bit(KEY_FLAG_REVOKED, &cred->request_key_auth->flags)) + goto auth_key_revoked; + + irka = cred->request_key_auth->payload.data; + rka->cred = get_cred(irka->cred); + rka->pid = irka->pid; + + up_read(&cred->request_key_auth->sem); + } + else { + /* it isn't - use this process as the context */ + rka->cred = get_cred(cred); + rka->pid = current->pid; + } + + rka->target_key = key_get(target); + rka->dest_keyring = key_get(dest_keyring); + memcpy(rka->callout_info, callout_info, callout_len); + rka->callout_len = callout_len; + + /* allocate the auth key */ + sprintf(desc, "%x", target->serial); + + authkey = key_alloc(&key_type_request_key_auth, desc, + cred->fsuid, cred->fsgid, cred, + KEY_POS_VIEW | KEY_POS_READ | KEY_POS_SEARCH | + KEY_USR_VIEW, KEY_ALLOC_NOT_IN_QUOTA); + if (IS_ERR(authkey)) { + ret = PTR_ERR(authkey); + goto error_alloc; + } + + /* construct the auth key */ + ret = key_instantiate_and_link(authkey, rka, 0, NULL, NULL); + if (ret < 0) + goto error_inst; + + kleave(" = {%d,%d}", authkey->serial, atomic_read(&authkey->usage)); + return authkey; + +auth_key_revoked: + up_read(&cred->request_key_auth->sem); + kfree(rka->callout_info); + kfree(rka); + kleave("= -EKEYREVOKED"); + return ERR_PTR(-EKEYREVOKED); + +error_inst: + key_revoke(authkey); + key_put(authkey); +error_alloc: + key_put(rka->target_key); + key_put(rka->dest_keyring); + kfree(rka->callout_info); + kfree(rka); + kleave("= %d", ret); + return ERR_PTR(ret); +} + +/* + * See if an authorisation key is associated with a particular key. + */ +static int key_get_instantiation_authkey_match(const struct key *key, + const void *_id) +{ + struct request_key_auth *rka = key->payload.data; + key_serial_t id = (key_serial_t)(unsigned long) _id; + + return rka->target_key->serial == id; +} + +/* + * Search the current process's keyrings for the authorisation key for + * instantiation of a key. + */ +struct key *key_get_instantiation_authkey(key_serial_t target_id) +{ + const struct cred *cred = current_cred(); + struct key *authkey; + key_ref_t authkey_ref; + + authkey_ref = search_process_keyrings( + &key_type_request_key_auth, + (void *) (unsigned long) target_id, + key_get_instantiation_authkey_match, + cred); + + if (IS_ERR(authkey_ref)) { + authkey = ERR_CAST(authkey_ref); + goto error; + } + + authkey = key_ref_to_ptr(authkey_ref); + if (test_bit(KEY_FLAG_REVOKED, &authkey->flags)) { + key_put(authkey); + authkey = ERR_PTR(-EKEYREVOKED); + } + +error: + return authkey; +} diff --git a/security/keys/sysctl.c b/security/keys/sysctl.c new file mode 100644 index 00000000..ee32d181 --- /dev/null +++ b/security/keys/sysctl.c @@ -0,0 +1,65 @@ +/* Key management controls + * + * Copyright (C) 2008 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include <linux/key.h> +#include <linux/sysctl.h> +#include "internal.h" + +static const int zero, one = 1, max = INT_MAX; + +ctl_table key_sysctls[] = { + { + .procname = "maxkeys", + .data = &key_quota_maxkeys, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = (void *) &one, + .extra2 = (void *) &max, + }, + { + .procname = "maxbytes", + .data = &key_quota_maxbytes, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = (void *) &one, + .extra2 = (void *) &max, + }, + { + .procname = "root_maxkeys", + .data = &key_quota_root_maxkeys, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = (void *) &one, + .extra2 = (void *) &max, + }, + { + .procname = "root_maxbytes", + .data = &key_quota_root_maxbytes, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = (void *) &one, + .extra2 = (void *) &max, + }, + { + .procname = "gc_delay", + .data = &key_gc_delay, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = (void *) &zero, + .extra2 = (void *) &max, + }, + { } +}; diff --git a/security/keys/trusted.c b/security/keys/trusted.c new file mode 100644 index 00000000..0c33e2ea --- /dev/null +++ b/security/keys/trusted.c @@ -0,0 +1,1180 @@ +/* + * Copyright (C) 2010 IBM Corporation + * + * Author: + * David Safford <safford@us.ibm.com> + * + * 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. + * + * See Documentation/security/keys-trusted-encrypted.txt + */ + +#include <linux/uaccess.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/parser.h> +#include <linux/string.h> +#include <linux/err.h> +#include <keys/user-type.h> +#include <keys/trusted-type.h> +#include <linux/key-type.h> +#include <linux/rcupdate.h> +#include <linux/crypto.h> +#include <crypto/hash.h> +#include <crypto/sha.h> +#include <linux/capability.h> +#include <linux/tpm.h> +#include <linux/tpm_command.h> + +#include "trusted.h" + +static const char hmac_alg[] = "hmac(sha1)"; +static const char hash_alg[] = "sha1"; + +struct sdesc { + struct shash_desc shash; + char ctx[]; +}; + +static struct crypto_shash *hashalg; +static struct crypto_shash *hmacalg; + +static struct sdesc *init_sdesc(struct crypto_shash *alg) +{ + struct sdesc *sdesc; + int size; + + size = sizeof(struct shash_desc) + crypto_shash_descsize(alg); + sdesc = kmalloc(size, GFP_KERNEL); + if (!sdesc) + return ERR_PTR(-ENOMEM); + sdesc->shash.tfm = alg; + sdesc->shash.flags = 0x0; + return sdesc; +} + +static int TSS_sha1(const unsigned char *data, unsigned int datalen, + unsigned char *digest) +{ + struct sdesc *sdesc; + int ret; + + sdesc = init_sdesc(hashalg); + if (IS_ERR(sdesc)) { + pr_info("trusted_key: can't alloc %s\n", hash_alg); + return PTR_ERR(sdesc); + } + + ret = crypto_shash_digest(&sdesc->shash, data, datalen, digest); + kfree(sdesc); + return ret; +} + +static int TSS_rawhmac(unsigned char *digest, const unsigned char *key, + unsigned int keylen, ...) +{ + struct sdesc *sdesc; + va_list argp; + unsigned int dlen; + unsigned char *data; + int ret; + + sdesc = init_sdesc(hmacalg); + if (IS_ERR(sdesc)) { + pr_info("trusted_key: can't alloc %s\n", hmac_alg); + return PTR_ERR(sdesc); + } + + ret = crypto_shash_setkey(hmacalg, key, keylen); + if (ret < 0) + goto out; + ret = crypto_shash_init(&sdesc->shash); + if (ret < 0) + goto out; + + va_start(argp, keylen); + for (;;) { + dlen = va_arg(argp, unsigned int); + if (dlen == 0) + break; + data = va_arg(argp, unsigned char *); + if (data == NULL) { + ret = -EINVAL; + break; + } + ret = crypto_shash_update(&sdesc->shash, data, dlen); + if (ret < 0) + break; + } + va_end(argp); + if (!ret) + ret = crypto_shash_final(&sdesc->shash, digest); +out: + kfree(sdesc); + return ret; +} + +/* + * calculate authorization info fields to send to TPM + */ +static int TSS_authhmac(unsigned char *digest, const unsigned char *key, + unsigned int keylen, unsigned char *h1, + unsigned char *h2, unsigned char h3, ...) +{ + unsigned char paramdigest[SHA1_DIGEST_SIZE]; + struct sdesc *sdesc; + unsigned int dlen; + unsigned char *data; + unsigned char c; + int ret; + va_list argp; + + sdesc = init_sdesc(hashalg); + if (IS_ERR(sdesc)) { + pr_info("trusted_key: can't alloc %s\n", hash_alg); + return PTR_ERR(sdesc); + } + + c = h3; + ret = crypto_shash_init(&sdesc->shash); + if (ret < 0) + goto out; + va_start(argp, h3); + for (;;) { + dlen = va_arg(argp, unsigned int); + if (dlen == 0) + break; + data = va_arg(argp, unsigned char *); + if (!data) { + ret = -EINVAL; + break; + } + ret = crypto_shash_update(&sdesc->shash, data, dlen); + if (ret < 0) + break; + } + va_end(argp); + if (!ret) + ret = crypto_shash_final(&sdesc->shash, paramdigest); + if (!ret) + ret = TSS_rawhmac(digest, key, keylen, SHA1_DIGEST_SIZE, + paramdigest, TPM_NONCE_SIZE, h1, + TPM_NONCE_SIZE, h2, 1, &c, 0, 0); +out: + kfree(sdesc); + return ret; +} + +/* + * verify the AUTH1_COMMAND (Seal) result from TPM + */ +static int TSS_checkhmac1(unsigned char *buffer, + const uint32_t command, + const unsigned char *ononce, + const unsigned char *key, + unsigned int keylen, ...) +{ + uint32_t bufsize; + uint16_t tag; + uint32_t ordinal; + uint32_t result; + unsigned char *enonce; + unsigned char *continueflag; + unsigned char *authdata; + unsigned char testhmac[SHA1_DIGEST_SIZE]; + unsigned char paramdigest[SHA1_DIGEST_SIZE]; + struct sdesc *sdesc; + unsigned int dlen; + unsigned int dpos; + va_list argp; + int ret; + + bufsize = LOAD32(buffer, TPM_SIZE_OFFSET); + tag = LOAD16(buffer, 0); + ordinal = command; + result = LOAD32N(buffer, TPM_RETURN_OFFSET); + if (tag == TPM_TAG_RSP_COMMAND) + return 0; + if (tag != TPM_TAG_RSP_AUTH1_COMMAND) + return -EINVAL; + authdata = buffer + bufsize - SHA1_DIGEST_SIZE; + continueflag = authdata - 1; + enonce = continueflag - TPM_NONCE_SIZE; + + sdesc = init_sdesc(hashalg); + if (IS_ERR(sdesc)) { + pr_info("trusted_key: can't alloc %s\n", hash_alg); + return PTR_ERR(sdesc); + } + ret = crypto_shash_init(&sdesc->shash); + if (ret < 0) + goto out; + ret = crypto_shash_update(&sdesc->shash, (const u8 *)&result, + sizeof result); + if (ret < 0) + goto out; + ret = crypto_shash_update(&sdesc->shash, (const u8 *)&ordinal, + sizeof ordinal); + if (ret < 0) + goto out; + va_start(argp, keylen); + for (;;) { + dlen = va_arg(argp, unsigned int); + if (dlen == 0) + break; + dpos = va_arg(argp, unsigned int); + ret = crypto_shash_update(&sdesc->shash, buffer + dpos, dlen); + if (ret < 0) + break; + } + va_end(argp); + if (!ret) + ret = crypto_shash_final(&sdesc->shash, paramdigest); + if (ret < 0) + goto out; + + ret = TSS_rawhmac(testhmac, key, keylen, SHA1_DIGEST_SIZE, paramdigest, + TPM_NONCE_SIZE, enonce, TPM_NONCE_SIZE, ononce, + 1, continueflag, 0, 0); + if (ret < 0) + goto out; + + if (memcmp(testhmac, authdata, SHA1_DIGEST_SIZE)) + ret = -EINVAL; +out: + kfree(sdesc); + return ret; +} + +/* + * verify the AUTH2_COMMAND (unseal) result from TPM + */ +static int TSS_checkhmac2(unsigned char *buffer, + const uint32_t command, + const unsigned char *ononce, + const unsigned char *key1, + unsigned int keylen1, + const unsigned char *key2, + unsigned int keylen2, ...) +{ + uint32_t bufsize; + uint16_t tag; + uint32_t ordinal; + uint32_t result; + unsigned char *enonce1; + unsigned char *continueflag1; + unsigned char *authdata1; + unsigned char *enonce2; + unsigned char *continueflag2; + unsigned char *authdata2; + unsigned char testhmac1[SHA1_DIGEST_SIZE]; + unsigned char testhmac2[SHA1_DIGEST_SIZE]; + unsigned char paramdigest[SHA1_DIGEST_SIZE]; + struct sdesc *sdesc; + unsigned int dlen; + unsigned int dpos; + va_list argp; + int ret; + + bufsize = LOAD32(buffer, TPM_SIZE_OFFSET); + tag = LOAD16(buffer, 0); + ordinal = command; + result = LOAD32N(buffer, TPM_RETURN_OFFSET); + + if (tag == TPM_TAG_RSP_COMMAND) + return 0; + if (tag != TPM_TAG_RSP_AUTH2_COMMAND) + return -EINVAL; + authdata1 = buffer + bufsize - (SHA1_DIGEST_SIZE + 1 + + SHA1_DIGEST_SIZE + SHA1_DIGEST_SIZE); + authdata2 = buffer + bufsize - (SHA1_DIGEST_SIZE); + continueflag1 = authdata1 - 1; + continueflag2 = authdata2 - 1; + enonce1 = continueflag1 - TPM_NONCE_SIZE; + enonce2 = continueflag2 - TPM_NONCE_SIZE; + + sdesc = init_sdesc(hashalg); + if (IS_ERR(sdesc)) { + pr_info("trusted_key: can't alloc %s\n", hash_alg); + return PTR_ERR(sdesc); + } + ret = crypto_shash_init(&sdesc->shash); + if (ret < 0) + goto out; + ret = crypto_shash_update(&sdesc->shash, (const u8 *)&result, + sizeof result); + if (ret < 0) + goto out; + ret = crypto_shash_update(&sdesc->shash, (const u8 *)&ordinal, + sizeof ordinal); + if (ret < 0) + goto out; + + va_start(argp, keylen2); + for (;;) { + dlen = va_arg(argp, unsigned int); + if (dlen == 0) + break; + dpos = va_arg(argp, unsigned int); + ret = crypto_shash_update(&sdesc->shash, buffer + dpos, dlen); + if (ret < 0) + break; + } + va_end(argp); + if (!ret) + ret = crypto_shash_final(&sdesc->shash, paramdigest); + if (ret < 0) + goto out; + + ret = TSS_rawhmac(testhmac1, key1, keylen1, SHA1_DIGEST_SIZE, + paramdigest, TPM_NONCE_SIZE, enonce1, + TPM_NONCE_SIZE, ononce, 1, continueflag1, 0, 0); + if (ret < 0) + goto out; + if (memcmp(testhmac1, authdata1, SHA1_DIGEST_SIZE)) { + ret = -EINVAL; + goto out; + } + ret = TSS_rawhmac(testhmac2, key2, keylen2, SHA1_DIGEST_SIZE, + paramdigest, TPM_NONCE_SIZE, enonce2, + TPM_NONCE_SIZE, ononce, 1, continueflag2, 0, 0); + if (ret < 0) + goto out; + if (memcmp(testhmac2, authdata2, SHA1_DIGEST_SIZE)) + ret = -EINVAL; +out: + kfree(sdesc); + return ret; +} + +/* + * For key specific tpm requests, we will generate and send our + * own TPM command packets using the drivers send function. + */ +static int trusted_tpm_send(const u32 chip_num, unsigned char *cmd, + size_t buflen) +{ + int rc; + + dump_tpm_buf(cmd); + rc = tpm_send(chip_num, cmd, buflen); + dump_tpm_buf(cmd); + if (rc > 0) + /* Can't return positive return codes values to keyctl */ + rc = -EPERM; + return rc; +} + +/* + * get a random value from TPM + */ +static int tpm_get_random(struct tpm_buf *tb, unsigned char *buf, uint32_t len) +{ + int ret; + + INIT_BUF(tb); + store16(tb, TPM_TAG_RQU_COMMAND); + store32(tb, TPM_GETRANDOM_SIZE); + store32(tb, TPM_ORD_GETRANDOM); + store32(tb, len); + ret = trusted_tpm_send(TPM_ANY_NUM, tb->data, sizeof tb->data); + if (!ret) + memcpy(buf, tb->data + TPM_GETRANDOM_SIZE, len); + return ret; +} + +static int my_get_random(unsigned char *buf, int len) +{ + struct tpm_buf *tb; + int ret; + + tb = kmalloc(sizeof *tb, GFP_KERNEL); + if (!tb) + return -ENOMEM; + ret = tpm_get_random(tb, buf, len); + + kfree(tb); + return ret; +} + +/* + * Lock a trusted key, by extending a selected PCR. + * + * Prevents a trusted key that is sealed to PCRs from being accessed. + * This uses the tpm driver's extend function. + */ +static int pcrlock(const int pcrnum) +{ + unsigned char hash[SHA1_DIGEST_SIZE]; + int ret; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + ret = my_get_random(hash, SHA1_DIGEST_SIZE); + if (ret < 0) + return ret; + return tpm_pcr_extend(TPM_ANY_NUM, pcrnum, hash) ? -EINVAL : 0; +} + +/* + * Create an object specific authorisation protocol (OSAP) session + */ +static int osap(struct tpm_buf *tb, struct osapsess *s, + const unsigned char *key, uint16_t type, uint32_t handle) +{ + unsigned char enonce[TPM_NONCE_SIZE]; + unsigned char ononce[TPM_NONCE_SIZE]; + int ret; + + ret = tpm_get_random(tb, ononce, TPM_NONCE_SIZE); + if (ret < 0) + return ret; + + INIT_BUF(tb); + store16(tb, TPM_TAG_RQU_COMMAND); + store32(tb, TPM_OSAP_SIZE); + store32(tb, TPM_ORD_OSAP); + store16(tb, type); + store32(tb, handle); + storebytes(tb, ononce, TPM_NONCE_SIZE); + + ret = trusted_tpm_send(TPM_ANY_NUM, tb->data, MAX_BUF_SIZE); + if (ret < 0) + return ret; + + s->handle = LOAD32(tb->data, TPM_DATA_OFFSET); + memcpy(s->enonce, &(tb->data[TPM_DATA_OFFSET + sizeof(uint32_t)]), + TPM_NONCE_SIZE); + memcpy(enonce, &(tb->data[TPM_DATA_OFFSET + sizeof(uint32_t) + + TPM_NONCE_SIZE]), TPM_NONCE_SIZE); + return TSS_rawhmac(s->secret, key, SHA1_DIGEST_SIZE, TPM_NONCE_SIZE, + enonce, TPM_NONCE_SIZE, ononce, 0, 0); +} + +/* + * Create an object independent authorisation protocol (oiap) session + */ +static int oiap(struct tpm_buf *tb, uint32_t *handle, unsigned char *nonce) +{ + int ret; + + INIT_BUF(tb); + store16(tb, TPM_TAG_RQU_COMMAND); + store32(tb, TPM_OIAP_SIZE); + store32(tb, TPM_ORD_OIAP); + ret = trusted_tpm_send(TPM_ANY_NUM, tb->data, MAX_BUF_SIZE); + if (ret < 0) + return ret; + + *handle = LOAD32(tb->data, TPM_DATA_OFFSET); + memcpy(nonce, &tb->data[TPM_DATA_OFFSET + sizeof(uint32_t)], + TPM_NONCE_SIZE); + return 0; +} + +struct tpm_digests { + unsigned char encauth[SHA1_DIGEST_SIZE]; + unsigned char pubauth[SHA1_DIGEST_SIZE]; + unsigned char xorwork[SHA1_DIGEST_SIZE * 2]; + unsigned char xorhash[SHA1_DIGEST_SIZE]; + unsigned char nonceodd[TPM_NONCE_SIZE]; +}; + +/* + * Have the TPM seal(encrypt) the trusted key, possibly based on + * Platform Configuration Registers (PCRs). AUTH1 for sealing key. + */ +static int tpm_seal(struct tpm_buf *tb, uint16_t keytype, + uint32_t keyhandle, const unsigned char *keyauth, + const unsigned char *data, uint32_t datalen, + unsigned char *blob, uint32_t *bloblen, + const unsigned char *blobauth, + const unsigned char *pcrinfo, uint32_t pcrinfosize) +{ + struct osapsess sess; + struct tpm_digests *td; + unsigned char cont; + uint32_t ordinal; + uint32_t pcrsize; + uint32_t datsize; + int sealinfosize; + int encdatasize; + int storedsize; + int ret; + int i; + + /* alloc some work space for all the hashes */ + td = kmalloc(sizeof *td, GFP_KERNEL); + if (!td) + return -ENOMEM; + + /* get session for sealing key */ + ret = osap(tb, &sess, keyauth, keytype, keyhandle); + if (ret < 0) + goto out; + dump_sess(&sess); + + /* calculate encrypted authorization value */ + memcpy(td->xorwork, sess.secret, SHA1_DIGEST_SIZE); + memcpy(td->xorwork + SHA1_DIGEST_SIZE, sess.enonce, SHA1_DIGEST_SIZE); + ret = TSS_sha1(td->xorwork, SHA1_DIGEST_SIZE * 2, td->xorhash); + if (ret < 0) + goto out; + + ret = tpm_get_random(tb, td->nonceodd, TPM_NONCE_SIZE); + if (ret < 0) + goto out; + ordinal = htonl(TPM_ORD_SEAL); + datsize = htonl(datalen); + pcrsize = htonl(pcrinfosize); + cont = 0; + + /* encrypt data authorization key */ + for (i = 0; i < SHA1_DIGEST_SIZE; ++i) + td->encauth[i] = td->xorhash[i] ^ blobauth[i]; + + /* calculate authorization HMAC value */ + if (pcrinfosize == 0) { + /* no pcr info specified */ + ret = TSS_authhmac(td->pubauth, sess.secret, SHA1_DIGEST_SIZE, + sess.enonce, td->nonceodd, cont, + sizeof(uint32_t), &ordinal, SHA1_DIGEST_SIZE, + td->encauth, sizeof(uint32_t), &pcrsize, + sizeof(uint32_t), &datsize, datalen, data, 0, + 0); + } else { + /* pcr info specified */ + ret = TSS_authhmac(td->pubauth, sess.secret, SHA1_DIGEST_SIZE, + sess.enonce, td->nonceodd, cont, + sizeof(uint32_t), &ordinal, SHA1_DIGEST_SIZE, + td->encauth, sizeof(uint32_t), &pcrsize, + pcrinfosize, pcrinfo, sizeof(uint32_t), + &datsize, datalen, data, 0, 0); + } + if (ret < 0) + goto out; + + /* build and send the TPM request packet */ + INIT_BUF(tb); + store16(tb, TPM_TAG_RQU_AUTH1_COMMAND); + store32(tb, TPM_SEAL_SIZE + pcrinfosize + datalen); + store32(tb, TPM_ORD_SEAL); + store32(tb, keyhandle); + storebytes(tb, td->encauth, SHA1_DIGEST_SIZE); + store32(tb, pcrinfosize); + storebytes(tb, pcrinfo, pcrinfosize); + store32(tb, datalen); + storebytes(tb, data, datalen); + store32(tb, sess.handle); + storebytes(tb, td->nonceodd, TPM_NONCE_SIZE); + store8(tb, cont); + storebytes(tb, td->pubauth, SHA1_DIGEST_SIZE); + + ret = trusted_tpm_send(TPM_ANY_NUM, tb->data, MAX_BUF_SIZE); + if (ret < 0) + goto out; + + /* calculate the size of the returned Blob */ + sealinfosize = LOAD32(tb->data, TPM_DATA_OFFSET + sizeof(uint32_t)); + encdatasize = LOAD32(tb->data, TPM_DATA_OFFSET + sizeof(uint32_t) + + sizeof(uint32_t) + sealinfosize); + storedsize = sizeof(uint32_t) + sizeof(uint32_t) + sealinfosize + + sizeof(uint32_t) + encdatasize; + + /* check the HMAC in the response */ + ret = TSS_checkhmac1(tb->data, ordinal, td->nonceodd, sess.secret, + SHA1_DIGEST_SIZE, storedsize, TPM_DATA_OFFSET, 0, + 0); + + /* copy the returned blob to caller */ + if (!ret) { + memcpy(blob, tb->data + TPM_DATA_OFFSET, storedsize); + *bloblen = storedsize; + } +out: + kfree(td); + return ret; +} + +/* + * use the AUTH2_COMMAND form of unseal, to authorize both key and blob + */ +static int tpm_unseal(struct tpm_buf *tb, + uint32_t keyhandle, const unsigned char *keyauth, + const unsigned char *blob, int bloblen, + const unsigned char *blobauth, + unsigned char *data, unsigned int *datalen) +{ + unsigned char nonceodd[TPM_NONCE_SIZE]; + unsigned char enonce1[TPM_NONCE_SIZE]; + unsigned char enonce2[TPM_NONCE_SIZE]; + unsigned char authdata1[SHA1_DIGEST_SIZE]; + unsigned char authdata2[SHA1_DIGEST_SIZE]; + uint32_t authhandle1 = 0; + uint32_t authhandle2 = 0; + unsigned char cont = 0; + uint32_t ordinal; + uint32_t keyhndl; + int ret; + + /* sessions for unsealing key and data */ + ret = oiap(tb, &authhandle1, enonce1); + if (ret < 0) { + pr_info("trusted_key: oiap failed (%d)\n", ret); + return ret; + } + ret = oiap(tb, &authhandle2, enonce2); + if (ret < 0) { + pr_info("trusted_key: oiap failed (%d)\n", ret); + return ret; + } + + ordinal = htonl(TPM_ORD_UNSEAL); + keyhndl = htonl(SRKHANDLE); + ret = tpm_get_random(tb, nonceodd, TPM_NONCE_SIZE); + if (ret < 0) { + pr_info("trusted_key: tpm_get_random failed (%d)\n", ret); + return ret; + } + ret = TSS_authhmac(authdata1, keyauth, TPM_NONCE_SIZE, + enonce1, nonceodd, cont, sizeof(uint32_t), + &ordinal, bloblen, blob, 0, 0); + if (ret < 0) + return ret; + ret = TSS_authhmac(authdata2, blobauth, TPM_NONCE_SIZE, + enonce2, nonceodd, cont, sizeof(uint32_t), + &ordinal, bloblen, blob, 0, 0); + if (ret < 0) + return ret; + + /* build and send TPM request packet */ + INIT_BUF(tb); + store16(tb, TPM_TAG_RQU_AUTH2_COMMAND); + store32(tb, TPM_UNSEAL_SIZE + bloblen); + store32(tb, TPM_ORD_UNSEAL); + store32(tb, keyhandle); + storebytes(tb, blob, bloblen); + store32(tb, authhandle1); + storebytes(tb, nonceodd, TPM_NONCE_SIZE); + store8(tb, cont); + storebytes(tb, authdata1, SHA1_DIGEST_SIZE); + store32(tb, authhandle2); + storebytes(tb, nonceodd, TPM_NONCE_SIZE); + store8(tb, cont); + storebytes(tb, authdata2, SHA1_DIGEST_SIZE); + + ret = trusted_tpm_send(TPM_ANY_NUM, tb->data, MAX_BUF_SIZE); + if (ret < 0) { + pr_info("trusted_key: authhmac failed (%d)\n", ret); + return ret; + } + + *datalen = LOAD32(tb->data, TPM_DATA_OFFSET); + ret = TSS_checkhmac2(tb->data, ordinal, nonceodd, + keyauth, SHA1_DIGEST_SIZE, + blobauth, SHA1_DIGEST_SIZE, + sizeof(uint32_t), TPM_DATA_OFFSET, + *datalen, TPM_DATA_OFFSET + sizeof(uint32_t), 0, + 0); + if (ret < 0) { + pr_info("trusted_key: TSS_checkhmac2 failed (%d)\n", ret); + return ret; + } + memcpy(data, tb->data + TPM_DATA_OFFSET + sizeof(uint32_t), *datalen); + return 0; +} + +/* + * Have the TPM seal(encrypt) the symmetric key + */ +static int key_seal(struct trusted_key_payload *p, + struct trusted_key_options *o) +{ + struct tpm_buf *tb; + int ret; + + tb = kzalloc(sizeof *tb, GFP_KERNEL); + if (!tb) + return -ENOMEM; + + /* include migratable flag at end of sealed key */ + p->key[p->key_len] = p->migratable; + + ret = tpm_seal(tb, o->keytype, o->keyhandle, o->keyauth, + p->key, p->key_len + 1, p->blob, &p->blob_len, + o->blobauth, o->pcrinfo, o->pcrinfo_len); + if (ret < 0) + pr_info("trusted_key: srkseal failed (%d)\n", ret); + + kfree(tb); + return ret; +} + +/* + * Have the TPM unseal(decrypt) the symmetric key + */ +static int key_unseal(struct trusted_key_payload *p, + struct trusted_key_options *o) +{ + struct tpm_buf *tb; + int ret; + + tb = kzalloc(sizeof *tb, GFP_KERNEL); + if (!tb) + return -ENOMEM; + + ret = tpm_unseal(tb, o->keyhandle, o->keyauth, p->blob, p->blob_len, + o->blobauth, p->key, &p->key_len); + if (ret < 0) + pr_info("trusted_key: srkunseal failed (%d)\n", ret); + else + /* pull migratable flag out of sealed key */ + p->migratable = p->key[--p->key_len]; + + kfree(tb); + return ret; +} + +enum { + Opt_err = -1, + Opt_new, Opt_load, Opt_update, + Opt_keyhandle, Opt_keyauth, Opt_blobauth, + Opt_pcrinfo, Opt_pcrlock, Opt_migratable +}; + +static const match_table_t key_tokens = { + {Opt_new, "new"}, + {Opt_load, "load"}, + {Opt_update, "update"}, + {Opt_keyhandle, "keyhandle=%s"}, + {Opt_keyauth, "keyauth=%s"}, + {Opt_blobauth, "blobauth=%s"}, + {Opt_pcrinfo, "pcrinfo=%s"}, + {Opt_pcrlock, "pcrlock=%s"}, + {Opt_migratable, "migratable=%s"}, + {Opt_err, NULL} +}; + +/* can have zero or more token= options */ +static int getoptions(char *c, struct trusted_key_payload *pay, + struct trusted_key_options *opt) +{ + substring_t args[MAX_OPT_ARGS]; + char *p = c; + int token; + int res; + unsigned long handle; + unsigned long lock; + + while ((p = strsep(&c, " \t"))) { + if (*p == '\0' || *p == ' ' || *p == '\t') + continue; + token = match_token(p, key_tokens, args); + + switch (token) { + case Opt_pcrinfo: + opt->pcrinfo_len = strlen(args[0].from) / 2; + if (opt->pcrinfo_len > MAX_PCRINFO_SIZE) + return -EINVAL; + hex2bin(opt->pcrinfo, args[0].from, opt->pcrinfo_len); + break; + case Opt_keyhandle: + res = strict_strtoul(args[0].from, 16, &handle); + if (res < 0) + return -EINVAL; + opt->keytype = SEAL_keytype; + opt->keyhandle = handle; + break; + case Opt_keyauth: + if (strlen(args[0].from) != 2 * SHA1_DIGEST_SIZE) + return -EINVAL; + hex2bin(opt->keyauth, args[0].from, SHA1_DIGEST_SIZE); + break; + case Opt_blobauth: + if (strlen(args[0].from) != 2 * SHA1_DIGEST_SIZE) + return -EINVAL; + hex2bin(opt->blobauth, args[0].from, SHA1_DIGEST_SIZE); + break; + case Opt_migratable: + if (*args[0].from == '0') + pay->migratable = 0; + else + return -EINVAL; + break; + case Opt_pcrlock: + res = strict_strtoul(args[0].from, 10, &lock); + if (res < 0) + return -EINVAL; + opt->pcrlock = lock; + break; + default: + return -EINVAL; + } + } + return 0; +} + +/* + * datablob_parse - parse the keyctl data and fill in the + * payload and options structures + * + * On success returns 0, otherwise -EINVAL. + */ +static int datablob_parse(char *datablob, struct trusted_key_payload *p, + struct trusted_key_options *o) +{ + substring_t args[MAX_OPT_ARGS]; + long keylen; + int ret = -EINVAL; + int key_cmd; + char *c; + + /* main command */ + c = strsep(&datablob, " \t"); + if (!c) + return -EINVAL; + key_cmd = match_token(c, key_tokens, args); + switch (key_cmd) { + case Opt_new: + /* first argument is key size */ + c = strsep(&datablob, " \t"); + if (!c) + return -EINVAL; + ret = strict_strtol(c, 10, &keylen); + if (ret < 0 || keylen < MIN_KEY_SIZE || keylen > MAX_KEY_SIZE) + return -EINVAL; + p->key_len = keylen; + ret = getoptions(datablob, p, o); + if (ret < 0) + return ret; + ret = Opt_new; + break; + case Opt_load: + /* first argument is sealed blob */ + c = strsep(&datablob, " \t"); + if (!c) + return -EINVAL; + p->blob_len = strlen(c) / 2; + if (p->blob_len > MAX_BLOB_SIZE) + return -EINVAL; + hex2bin(p->blob, c, p->blob_len); + ret = getoptions(datablob, p, o); + if (ret < 0) + return ret; + ret = Opt_load; + break; + case Opt_update: + /* all arguments are options */ + ret = getoptions(datablob, p, o); + if (ret < 0) + return ret; + ret = Opt_update; + break; + case Opt_err: + return -EINVAL; + break; + } + return ret; +} + +static struct trusted_key_options *trusted_options_alloc(void) +{ + struct trusted_key_options *options; + + options = kzalloc(sizeof *options, GFP_KERNEL); + if (options) { + /* set any non-zero defaults */ + options->keytype = SRK_keytype; + options->keyhandle = SRKHANDLE; + } + return options; +} + +static struct trusted_key_payload *trusted_payload_alloc(struct key *key) +{ + struct trusted_key_payload *p = NULL; + int ret; + + ret = key_payload_reserve(key, sizeof *p); + if (ret < 0) + return p; + p = kzalloc(sizeof *p, GFP_KERNEL); + if (p) + p->migratable = 1; /* migratable by default */ + return p; +} + +/* + * trusted_instantiate - create a new trusted key + * + * Unseal an existing trusted blob or, for a new key, get a + * random key, then seal and create a trusted key-type key, + * adding it to the specified keyring. + * + * On success, return 0. Otherwise return errno. + */ +static int trusted_instantiate(struct key *key, const void *data, + size_t datalen) +{ + struct trusted_key_payload *payload = NULL; + struct trusted_key_options *options = NULL; + char *datablob; + int ret = 0; + int key_cmd; + + if (datalen <= 0 || datalen > 32767 || !data) + return -EINVAL; + + datablob = kmalloc(datalen + 1, GFP_KERNEL); + if (!datablob) + return -ENOMEM; + memcpy(datablob, data, datalen); + datablob[datalen] = '\0'; + + options = trusted_options_alloc(); + if (!options) { + ret = -ENOMEM; + goto out; + } + payload = trusted_payload_alloc(key); + if (!payload) { + ret = -ENOMEM; + goto out; + } + + key_cmd = datablob_parse(datablob, payload, options); + if (key_cmd < 0) { + ret = key_cmd; + goto out; + } + + dump_payload(payload); + dump_options(options); + + switch (key_cmd) { + case Opt_load: + ret = key_unseal(payload, options); + dump_payload(payload); + dump_options(options); + if (ret < 0) + pr_info("trusted_key: key_unseal failed (%d)\n", ret); + break; + case Opt_new: + ret = my_get_random(payload->key, payload->key_len); + if (ret < 0) { + pr_info("trusted_key: key_create failed (%d)\n", ret); + goto out; + } + ret = key_seal(payload, options); + if (ret < 0) + pr_info("trusted_key: key_seal failed (%d)\n", ret); + break; + default: + ret = -EINVAL; + goto out; + } + if (!ret && options->pcrlock) + ret = pcrlock(options->pcrlock); +out: + kfree(datablob); + kfree(options); + if (!ret) + rcu_assign_pointer(key->payload.data, payload); + else + kfree(payload); + return ret; +} + +static void trusted_rcu_free(struct rcu_head *rcu) +{ + struct trusted_key_payload *p; + + p = container_of(rcu, struct trusted_key_payload, rcu); + memset(p->key, 0, p->key_len); + kfree(p); +} + +/* + * trusted_update - reseal an existing key with new PCR values + */ +static int trusted_update(struct key *key, const void *data, size_t datalen) +{ + struct trusted_key_payload *p = key->payload.data; + struct trusted_key_payload *new_p; + struct trusted_key_options *new_o; + char *datablob; + int ret = 0; + + if (!p->migratable) + return -EPERM; + if (datalen <= 0 || datalen > 32767 || !data) + return -EINVAL; + + datablob = kmalloc(datalen + 1, GFP_KERNEL); + if (!datablob) + return -ENOMEM; + new_o = trusted_options_alloc(); + if (!new_o) { + ret = -ENOMEM; + goto out; + } + new_p = trusted_payload_alloc(key); + if (!new_p) { + ret = -ENOMEM; + goto out; + } + + memcpy(datablob, data, datalen); + datablob[datalen] = '\0'; + ret = datablob_parse(datablob, new_p, new_o); + if (ret != Opt_update) { + ret = -EINVAL; + kfree(new_p); + goto out; + } + /* copy old key values, and reseal with new pcrs */ + new_p->migratable = p->migratable; + new_p->key_len = p->key_len; + memcpy(new_p->key, p->key, p->key_len); + dump_payload(p); + dump_payload(new_p); + + ret = key_seal(new_p, new_o); + if (ret < 0) { + pr_info("trusted_key: key_seal failed (%d)\n", ret); + kfree(new_p); + goto out; + } + if (new_o->pcrlock) { + ret = pcrlock(new_o->pcrlock); + if (ret < 0) { + pr_info("trusted_key: pcrlock failed (%d)\n", ret); + kfree(new_p); + goto out; + } + } + rcu_assign_pointer(key->payload.data, new_p); + call_rcu(&p->rcu, trusted_rcu_free); +out: + kfree(datablob); + kfree(new_o); + return ret; +} + +/* + * trusted_read - copy the sealed blob data to userspace in hex. + * On success, return to userspace the trusted key datablob size. + */ +static long trusted_read(const struct key *key, char __user *buffer, + size_t buflen) +{ + struct trusted_key_payload *p; + char *ascii_buf; + char *bufp; + int i; + + p = rcu_dereference_key(key); + if (!p) + return -EINVAL; + if (!buffer || buflen <= 0) + return 2 * p->blob_len; + ascii_buf = kmalloc(2 * p->blob_len, GFP_KERNEL); + if (!ascii_buf) + return -ENOMEM; + + bufp = ascii_buf; + for (i = 0; i < p->blob_len; i++) + bufp = pack_hex_byte(bufp, p->blob[i]); + if ((copy_to_user(buffer, ascii_buf, 2 * p->blob_len)) != 0) { + kfree(ascii_buf); + return -EFAULT; + } + kfree(ascii_buf); + return 2 * p->blob_len; +} + +/* + * trusted_destroy - before freeing the key, clear the decrypted data + */ +static void trusted_destroy(struct key *key) +{ + struct trusted_key_payload *p = key->payload.data; + + if (!p) + return; + memset(p->key, 0, p->key_len); + kfree(key->payload.data); +} + +struct key_type key_type_trusted = { + .name = "trusted", + .instantiate = trusted_instantiate, + .update = trusted_update, + .match = user_match, + .destroy = trusted_destroy, + .describe = user_describe, + .read = trusted_read, +}; + +EXPORT_SYMBOL_GPL(key_type_trusted); + +static void trusted_shash_release(void) +{ + if (hashalg) + crypto_free_shash(hashalg); + if (hmacalg) + crypto_free_shash(hmacalg); +} + +static int __init trusted_shash_alloc(void) +{ + int ret; + + hmacalg = crypto_alloc_shash(hmac_alg, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(hmacalg)) { + pr_info("trusted_key: could not allocate crypto %s\n", + hmac_alg); + return PTR_ERR(hmacalg); + } + + hashalg = crypto_alloc_shash(hash_alg, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(hashalg)) { + pr_info("trusted_key: could not allocate crypto %s\n", + hash_alg); + ret = PTR_ERR(hashalg); + goto hashalg_fail; + } + + return 0; + +hashalg_fail: + crypto_free_shash(hmacalg); + return ret; +} + +static int __init init_trusted(void) +{ + int ret; + + ret = trusted_shash_alloc(); + if (ret < 0) + return ret; + ret = register_key_type(&key_type_trusted); + if (ret < 0) + trusted_shash_release(); + return ret; +} + +static void __exit cleanup_trusted(void) +{ + trusted_shash_release(); + unregister_key_type(&key_type_trusted); +} + +late_initcall(init_trusted); +module_exit(cleanup_trusted); + +MODULE_LICENSE("GPL"); diff --git a/security/keys/trusted.h b/security/keys/trusted.h new file mode 100644 index 00000000..3249fbd2 --- /dev/null +++ b/security/keys/trusted.h @@ -0,0 +1,134 @@ +#ifndef __TRUSTED_KEY_H +#define __TRUSTED_KEY_H + +/* implementation specific TPM constants */ +#define MAX_PCRINFO_SIZE 64 +#define MAX_BUF_SIZE 512 +#define TPM_GETRANDOM_SIZE 14 +#define TPM_OSAP_SIZE 36 +#define TPM_OIAP_SIZE 10 +#define TPM_SEAL_SIZE 87 +#define TPM_UNSEAL_SIZE 104 +#define TPM_SIZE_OFFSET 2 +#define TPM_RETURN_OFFSET 6 +#define TPM_DATA_OFFSET 10 + +#define LOAD32(buffer, offset) (ntohl(*(uint32_t *)&buffer[offset])) +#define LOAD32N(buffer, offset) (*(uint32_t *)&buffer[offset]) +#define LOAD16(buffer, offset) (ntohs(*(uint16_t *)&buffer[offset])) + +struct tpm_buf { + int len; + unsigned char data[MAX_BUF_SIZE]; +}; + +#define INIT_BUF(tb) (tb->len = 0) + +struct osapsess { + uint32_t handle; + unsigned char secret[SHA1_DIGEST_SIZE]; + unsigned char enonce[TPM_NONCE_SIZE]; +}; + +/* discrete values, but have to store in uint16_t for TPM use */ +enum { + SEAL_keytype = 1, + SRK_keytype = 4 +}; + +struct trusted_key_options { + uint16_t keytype; + uint32_t keyhandle; + unsigned char keyauth[SHA1_DIGEST_SIZE]; + unsigned char blobauth[SHA1_DIGEST_SIZE]; + uint32_t pcrinfo_len; + unsigned char pcrinfo[MAX_PCRINFO_SIZE]; + int pcrlock; +}; + +#define TPM_DEBUG 0 + +#if TPM_DEBUG +static inline void dump_options(struct trusted_key_options *o) +{ + pr_info("trusted_key: sealing key type %d\n", o->keytype); + pr_info("trusted_key: sealing key handle %0X\n", o->keyhandle); + pr_info("trusted_key: pcrlock %d\n", o->pcrlock); + pr_info("trusted_key: pcrinfo %d\n", o->pcrinfo_len); + print_hex_dump(KERN_INFO, "pcrinfo ", DUMP_PREFIX_NONE, + 16, 1, o->pcrinfo, o->pcrinfo_len, 0); +} + +static inline void dump_payload(struct trusted_key_payload *p) +{ + pr_info("trusted_key: key_len %d\n", p->key_len); + print_hex_dump(KERN_INFO, "key ", DUMP_PREFIX_NONE, + 16, 1, p->key, p->key_len, 0); + pr_info("trusted_key: bloblen %d\n", p->blob_len); + print_hex_dump(KERN_INFO, "blob ", DUMP_PREFIX_NONE, + 16, 1, p->blob, p->blob_len, 0); + pr_info("trusted_key: migratable %d\n", p->migratable); +} + +static inline void dump_sess(struct osapsess *s) +{ + print_hex_dump(KERN_INFO, "trusted-key: handle ", DUMP_PREFIX_NONE, + 16, 1, &s->handle, 4, 0); + pr_info("trusted-key: secret:\n"); + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, + 16, 1, &s->secret, SHA1_DIGEST_SIZE, 0); + pr_info("trusted-key: enonce:\n"); + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, + 16, 1, &s->enonce, SHA1_DIGEST_SIZE, 0); +} + +static inline void dump_tpm_buf(unsigned char *buf) +{ + int len; + + pr_info("\ntrusted-key: tpm buffer\n"); + len = LOAD32(buf, TPM_SIZE_OFFSET); + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, 16, 1, buf, len, 0); +} +#else +static inline void dump_options(struct trusted_key_options *o) +{ +} + +static inline void dump_payload(struct trusted_key_payload *p) +{ +} + +static inline void dump_sess(struct osapsess *s) +{ +} + +static inline void dump_tpm_buf(unsigned char *buf) +{ +} +#endif + +static inline void store8(struct tpm_buf *buf, const unsigned char value) +{ + buf->data[buf->len++] = value; +} + +static inline void store16(struct tpm_buf *buf, const uint16_t value) +{ + *(uint16_t *) & buf->data[buf->len] = htons(value); + buf->len += sizeof value; +} + +static inline void store32(struct tpm_buf *buf, const uint32_t value) +{ + *(uint32_t *) & buf->data[buf->len] = htonl(value); + buf->len += sizeof value; +} + +static inline void storebytes(struct tpm_buf *buf, const unsigned char *in, + const int len) +{ + memcpy(buf->data + buf->len, in, len); + buf->len += len; +} +#endif diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c new file mode 100644 index 00000000..69ff52c0 --- /dev/null +++ b/security/keys/user_defined.c @@ -0,0 +1,191 @@ +/* user_defined.c: user defined key type + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * 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; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/seq_file.h> +#include <linux/err.h> +#include <keys/user-type.h> +#include <asm/uaccess.h> +#include "internal.h" + +/* + * user defined keys take an arbitrary string as the description and an + * arbitrary blob of data as the payload + */ +struct key_type key_type_user = { + .name = "user", + .instantiate = user_instantiate, + .update = user_update, + .match = user_match, + .revoke = user_revoke, + .destroy = user_destroy, + .describe = user_describe, + .read = user_read, +}; + +EXPORT_SYMBOL_GPL(key_type_user); + +/* + * instantiate a user defined key + */ +int user_instantiate(struct key *key, const void *data, size_t datalen) +{ + struct user_key_payload *upayload; + int ret; + + ret = -EINVAL; + if (datalen <= 0 || datalen > 32767 || !data) + goto error; + + ret = key_payload_reserve(key, datalen); + if (ret < 0) + goto error; + + ret = -ENOMEM; + upayload = kmalloc(sizeof(*upayload) + datalen, GFP_KERNEL); + if (!upayload) + goto error; + + /* attach the data */ + upayload->datalen = datalen; + memcpy(upayload->data, data, datalen); + rcu_assign_pointer(key->payload.data, upayload); + ret = 0; + +error: + return ret; +} + +EXPORT_SYMBOL_GPL(user_instantiate); + +/* + * update a user defined key + * - the key's semaphore is write-locked + */ +int user_update(struct key *key, const void *data, size_t datalen) +{ + struct user_key_payload *upayload, *zap; + int ret; + + ret = -EINVAL; + if (datalen <= 0 || datalen > 32767 || !data) + goto error; + + /* construct a replacement payload */ + ret = -ENOMEM; + upayload = kmalloc(sizeof(*upayload) + datalen, GFP_KERNEL); + if (!upayload) + goto error; + + upayload->datalen = datalen; + memcpy(upayload->data, data, datalen); + + /* check the quota and attach the new data */ + zap = upayload; + + ret = key_payload_reserve(key, datalen); + + if (ret == 0) { + /* attach the new data, displacing the old */ + zap = key->payload.data; + rcu_assign_pointer(key->payload.data, upayload); + key->expiry = 0; + } + + if (zap) + kfree_rcu(zap, rcu); + +error: + return ret; +} + +EXPORT_SYMBOL_GPL(user_update); + +/* + * match users on their name + */ +int user_match(const struct key *key, const void *description) +{ + return strcmp(key->description, description) == 0; +} + +EXPORT_SYMBOL_GPL(user_match); + +/* + * dispose of the links from a revoked keyring + * - called with the key sem write-locked + */ +void user_revoke(struct key *key) +{ + struct user_key_payload *upayload = key->payload.data; + + /* clear the quota */ + key_payload_reserve(key, 0); + + if (upayload) { + rcu_assign_pointer(key->payload.data, NULL); + kfree_rcu(upayload, rcu); + } +} + +EXPORT_SYMBOL(user_revoke); + +/* + * dispose of the data dangling from the corpse of a user key + */ +void user_destroy(struct key *key) +{ + struct user_key_payload *upayload = key->payload.data; + + kfree(upayload); +} + +EXPORT_SYMBOL_GPL(user_destroy); + +/* + * describe the user key + */ +void user_describe(const struct key *key, struct seq_file *m) +{ + seq_puts(m, key->description); + if (key_is_instantiated(key)) + seq_printf(m, ": %u", key->datalen); +} + +EXPORT_SYMBOL_GPL(user_describe); + +/* + * read the key data + * - the key's semaphore is read-locked + */ +long user_read(const struct key *key, char __user *buffer, size_t buflen) +{ + struct user_key_payload *upayload; + long ret; + + upayload = rcu_dereference_key(key); + ret = upayload->datalen; + + /* we can return the data as is */ + if (buffer && buflen > 0) { + if (buflen > upayload->datalen) + buflen = upayload->datalen; + + if (copy_to_user(buffer, upayload->data, buflen) != 0) + ret = -EFAULT; + } + + return ret; +} + +EXPORT_SYMBOL_GPL(user_read); diff --git a/security/lsm_audit.c b/security/lsm_audit.c new file mode 100644 index 00000000..893af8a2 --- /dev/null +++ b/security/lsm_audit.c @@ -0,0 +1,406 @@ +/* + * common LSM auditing functions + * + * Based on code written for SELinux by : + * Stephen Smalley, <sds@epoch.ncsc.mil> + * James Morris <jmorris@redhat.com> + * Author : Etienne Basset, <etienne.basset@ensta.org> + * + * 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/types.h> +#include <linux/stddef.h> +#include <linux/kernel.h> +#include <linux/gfp.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <net/sock.h> +#include <linux/un.h> +#include <net/af_unix.h> +#include <linux/audit.h> +#include <linux/ipv6.h> +#include <linux/ip.h> +#include <net/ip.h> +#include <net/ipv6.h> +#include <linux/tcp.h> +#include <linux/udp.h> +#include <linux/dccp.h> +#include <linux/sctp.h> +#include <linux/lsm_audit.h> + +/** + * ipv4_skb_to_auditdata : fill auditdata from skb + * @skb : the skb + * @ad : the audit data to fill + * @proto : the layer 4 protocol + * + * return 0 on success + */ +int ipv4_skb_to_auditdata(struct sk_buff *skb, + struct common_audit_data *ad, u8 *proto) +{ + int ret = 0; + struct iphdr *ih; + + ih = ip_hdr(skb); + if (ih == NULL) + return -EINVAL; + + ad->u.net.v4info.saddr = ih->saddr; + ad->u.net.v4info.daddr = ih->daddr; + + if (proto) + *proto = ih->protocol; + /* non initial fragment */ + if (ntohs(ih->frag_off) & IP_OFFSET) + return 0; + + switch (ih->protocol) { + case IPPROTO_TCP: { + struct tcphdr *th = tcp_hdr(skb); + if (th == NULL) + break; + + ad->u.net.sport = th->source; + ad->u.net.dport = th->dest; + break; + } + case IPPROTO_UDP: { + struct udphdr *uh = udp_hdr(skb); + if (uh == NULL) + break; + + ad->u.net.sport = uh->source; + ad->u.net.dport = uh->dest; + break; + } + case IPPROTO_DCCP: { + struct dccp_hdr *dh = dccp_hdr(skb); + if (dh == NULL) + break; + + ad->u.net.sport = dh->dccph_sport; + ad->u.net.dport = dh->dccph_dport; + break; + } + case IPPROTO_SCTP: { + struct sctphdr *sh = sctp_hdr(skb); + if (sh == NULL) + break; + ad->u.net.sport = sh->source; + ad->u.net.dport = sh->dest; + break; + } + default: + ret = -EINVAL; + } + return ret; +} +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) +/** + * ipv6_skb_to_auditdata : fill auditdata from skb + * @skb : the skb + * @ad : the audit data to fill + * @proto : the layer 4 protocol + * + * return 0 on success + */ +int ipv6_skb_to_auditdata(struct sk_buff *skb, + struct common_audit_data *ad, u8 *proto) +{ + int offset, ret = 0; + struct ipv6hdr *ip6; + u8 nexthdr; + + ip6 = ipv6_hdr(skb); + if (ip6 == NULL) + return -EINVAL; + ipv6_addr_copy(&ad->u.net.v6info.saddr, &ip6->saddr); + ipv6_addr_copy(&ad->u.net.v6info.daddr, &ip6->daddr); + ret = 0; + /* IPv6 can have several extension header before the Transport header + * skip them */ + offset = skb_network_offset(skb); + offset += sizeof(*ip6); + nexthdr = ip6->nexthdr; + offset = ipv6_skip_exthdr(skb, offset, &nexthdr); + if (offset < 0) + return 0; + if (proto) + *proto = nexthdr; + switch (nexthdr) { + case IPPROTO_TCP: { + struct tcphdr _tcph, *th; + + th = skb_header_pointer(skb, offset, sizeof(_tcph), &_tcph); + if (th == NULL) + break; + + ad->u.net.sport = th->source; + ad->u.net.dport = th->dest; + break; + } + case IPPROTO_UDP: { + struct udphdr _udph, *uh; + + uh = skb_header_pointer(skb, offset, sizeof(_udph), &_udph); + if (uh == NULL) + break; + + ad->u.net.sport = uh->source; + ad->u.net.dport = uh->dest; + break; + } + case IPPROTO_DCCP: { + struct dccp_hdr _dccph, *dh; + + dh = skb_header_pointer(skb, offset, sizeof(_dccph), &_dccph); + if (dh == NULL) + break; + + ad->u.net.sport = dh->dccph_sport; + ad->u.net.dport = dh->dccph_dport; + break; + } + case IPPROTO_SCTP: { + struct sctphdr _sctph, *sh; + + sh = skb_header_pointer(skb, offset, sizeof(_sctph), &_sctph); + if (sh == NULL) + break; + ad->u.net.sport = sh->source; + ad->u.net.dport = sh->dest; + break; + } + default: + ret = -EINVAL; + } + return ret; +} +#endif + + +static inline void print_ipv6_addr(struct audit_buffer *ab, + struct in6_addr *addr, __be16 port, + char *name1, char *name2) +{ + if (!ipv6_addr_any(addr)) + audit_log_format(ab, " %s=%pI6c", name1, addr); + if (port) + audit_log_format(ab, " %s=%d", name2, ntohs(port)); +} + +static inline void print_ipv4_addr(struct audit_buffer *ab, __be32 addr, + __be16 port, char *name1, char *name2) +{ + if (addr) + audit_log_format(ab, " %s=%pI4", name1, &addr); + if (port) + audit_log_format(ab, " %s=%d", name2, ntohs(port)); +} + +/** + * dump_common_audit_data - helper to dump common audit data + * @a : common audit data + * + */ +static void dump_common_audit_data(struct audit_buffer *ab, + struct common_audit_data *a) +{ + struct task_struct *tsk = current; + + if (a->tsk) + tsk = a->tsk; + if (tsk && tsk->pid) { + audit_log_format(ab, " pid=%d comm=", tsk->pid); + audit_log_untrustedstring(ab, tsk->comm); + } + + switch (a->type) { + case LSM_AUDIT_DATA_NONE: + return; + case LSM_AUDIT_DATA_IPC: + audit_log_format(ab, " key=%d ", a->u.ipc_id); + break; + case LSM_AUDIT_DATA_CAP: + audit_log_format(ab, " capability=%d ", a->u.cap); + break; + case LSM_AUDIT_DATA_PATH: { + struct inode *inode; + + audit_log_d_path(ab, "path=", &a->u.path); + + inode = a->u.path.dentry->d_inode; + if (inode) + audit_log_format(ab, " dev=%s ino=%lu", + inode->i_sb->s_id, + inode->i_ino); + break; + } + case LSM_AUDIT_DATA_DENTRY: { + struct inode *inode; + + audit_log_format(ab, " name="); + audit_log_untrustedstring(ab, a->u.dentry->d_name.name); + + inode = a->u.dentry->d_inode; + if (inode) + audit_log_format(ab, " dev=%s ino=%lu", + inode->i_sb->s_id, + inode->i_ino); + break; + } + case LSM_AUDIT_DATA_INODE: { + struct dentry *dentry; + struct inode *inode; + + inode = a->u.inode; + dentry = d_find_alias(inode); + if (dentry) { + audit_log_format(ab, " name="); + audit_log_untrustedstring(ab, + dentry->d_name.name); + dput(dentry); + } + audit_log_format(ab, " dev=%s ino=%lu", inode->i_sb->s_id, + inode->i_ino); + break; + } + case LSM_AUDIT_DATA_TASK: + tsk = a->u.tsk; + if (tsk && tsk->pid) { + audit_log_format(ab, " pid=%d comm=", tsk->pid); + audit_log_untrustedstring(ab, tsk->comm); + } + break; + case LSM_AUDIT_DATA_NET: + if (a->u.net.sk) { + struct sock *sk = a->u.net.sk; + struct unix_sock *u; + int len = 0; + char *p = NULL; + + switch (sk->sk_family) { + case AF_INET: { + struct inet_sock *inet = inet_sk(sk); + + print_ipv4_addr(ab, inet->inet_rcv_saddr, + inet->inet_sport, + "laddr", "lport"); + print_ipv4_addr(ab, inet->inet_daddr, + inet->inet_dport, + "faddr", "fport"); + break; + } + case AF_INET6: { + struct inet_sock *inet = inet_sk(sk); + struct ipv6_pinfo *inet6 = inet6_sk(sk); + + print_ipv6_addr(ab, &inet6->rcv_saddr, + inet->inet_sport, + "laddr", "lport"); + print_ipv6_addr(ab, &inet6->daddr, + inet->inet_dport, + "faddr", "fport"); + break; + } + case AF_UNIX: + u = unix_sk(sk); + if (u->dentry) { + struct path path = { + .dentry = u->dentry, + .mnt = u->mnt + }; + audit_log_d_path(ab, "path=", &path); + break; + } + if (!u->addr) + break; + len = u->addr->len-sizeof(short); + p = &u->addr->name->sun_path[0]; + audit_log_format(ab, " path="); + if (*p) + audit_log_untrustedstring(ab, p); + else + audit_log_n_hex(ab, p, len); + break; + } + } + + switch (a->u.net.family) { + case AF_INET: + print_ipv4_addr(ab, a->u.net.v4info.saddr, + a->u.net.sport, + "saddr", "src"); + print_ipv4_addr(ab, a->u.net.v4info.daddr, + a->u.net.dport, + "daddr", "dest"); + break; + case AF_INET6: + print_ipv6_addr(ab, &a->u.net.v6info.saddr, + a->u.net.sport, + "saddr", "src"); + print_ipv6_addr(ab, &a->u.net.v6info.daddr, + a->u.net.dport, + "daddr", "dest"); + break; + } + if (a->u.net.netif > 0) { + struct net_device *dev; + + /* NOTE: we always use init's namespace */ + dev = dev_get_by_index(&init_net, a->u.net.netif); + if (dev) { + audit_log_format(ab, " netif=%s", dev->name); + dev_put(dev); + } + } + break; +#ifdef CONFIG_KEYS + case LSM_AUDIT_DATA_KEY: + audit_log_format(ab, " key_serial=%u", a->u.key_struct.key); + if (a->u.key_struct.key_desc) { + audit_log_format(ab, " key_desc="); + audit_log_untrustedstring(ab, a->u.key_struct.key_desc); + } + break; +#endif + case LSM_AUDIT_DATA_KMOD: + audit_log_format(ab, " kmod="); + audit_log_untrustedstring(ab, a->u.kmod_name); + break; + } /* switch (a->type) */ +} + +/** + * common_lsm_audit - generic LSM auditing function + * @a: auxiliary audit data + * + * setup the audit buffer for common security information + * uses callback to print LSM specific information + */ +void common_lsm_audit(struct common_audit_data *a) +{ + struct audit_buffer *ab; + + if (a == NULL) + return; + /* we use GFP_ATOMIC so we won't sleep */ + ab = audit_log_start(current->audit_context, GFP_ATOMIC, AUDIT_AVC); + + if (ab == NULL) + return; + + if (a->lsm_pre_audit) + a->lsm_pre_audit(ab, a); + + dump_common_audit_data(ab, a); + + if (a->lsm_post_audit) + a->lsm_post_audit(ab, a); + + audit_log_end(ab); +} diff --git a/security/min_addr.c b/security/min_addr.c new file mode 100644 index 00000000..f728728f --- /dev/null +++ b/security/min_addr.c @@ -0,0 +1,52 @@ +#include <linux/init.h> +#include <linux/mm.h> +#include <linux/security.h> +#include <linux/sysctl.h> + +/* amount of vm to protect from userspace access by both DAC and the LSM*/ +unsigned long mmap_min_addr; +/* amount of vm to protect from userspace using CAP_SYS_RAWIO (DAC) */ +unsigned long dac_mmap_min_addr = CONFIG_DEFAULT_MMAP_MIN_ADDR; +/* amount of vm to protect from userspace using the LSM = CONFIG_LSM_MMAP_MIN_ADDR */ + +/* + * Update mmap_min_addr = max(dac_mmap_min_addr, CONFIG_LSM_MMAP_MIN_ADDR) + */ +static void update_mmap_min_addr(void) +{ +#ifdef CONFIG_LSM_MMAP_MIN_ADDR + if (dac_mmap_min_addr > CONFIG_LSM_MMAP_MIN_ADDR) + mmap_min_addr = dac_mmap_min_addr; + else + mmap_min_addr = CONFIG_LSM_MMAP_MIN_ADDR; +#else + mmap_min_addr = dac_mmap_min_addr; +#endif +} + +/* + * sysctl handler which just sets dac_mmap_min_addr = the new value and then + * calls update_mmap_min_addr() so non MAP_FIXED hints get rounded properly + */ +int mmap_min_addr_handler(struct ctl_table *table, int write, + void __user *buffer, size_t *lenp, loff_t *ppos) +{ + int ret; + + if (write && !capable(CAP_SYS_RAWIO)) + return -EPERM; + + ret = proc_doulongvec_minmax(table, write, buffer, lenp, ppos); + + update_mmap_min_addr(); + + return ret; +} + +static int __init init_mmap_min_addr(void) +{ + update_mmap_min_addr(); + + return 0; +} +pure_initcall(init_mmap_min_addr); diff --git a/security/security.c b/security/security.c new file mode 100644 index 00000000..4ba6d4cc --- /dev/null +++ b/security/security.c @@ -0,0 +1,1313 @@ +/* + * Security plug functions + * + * Copyright (C) 2001 WireX Communications, Inc <chris@wirex.com> + * Copyright (C) 2001-2002 Greg Kroah-Hartman <greg@kroah.com> + * Copyright (C) 2001 Networks Associates Technology, Inc <ssmalley@nai.com> + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/capability.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/security.h> +#include <linux/ima.h> + +/* Boot-time LSM user choice */ +static __initdata char chosen_lsm[SECURITY_NAME_MAX + 1] = + CONFIG_DEFAULT_SECURITY; + +/* things that live in capability.c */ +extern void __init security_fixup_ops(struct security_operations *ops); + +static struct security_operations *security_ops; +static struct security_operations default_security_ops = { + .name = "default", +}; + +static inline int __init verify(struct security_operations *ops) +{ + /* verify the security_operations structure exists */ + if (!ops) + return -EINVAL; + security_fixup_ops(ops); + return 0; +} + +static void __init do_security_initcalls(void) +{ + initcall_t *call; + call = __security_initcall_start; + while (call < __security_initcall_end) { + (*call) (); + call++; + } +} + +/** + * security_init - initializes the security framework + * + * This should be called early in the kernel initialization sequence. + */ +int __init security_init(void) +{ + printk(KERN_INFO "Security Framework initialized\n"); + + security_fixup_ops(&default_security_ops); + security_ops = &default_security_ops; + do_security_initcalls(); + + return 0; +} + +void reset_security_ops(void) +{ + security_ops = &default_security_ops; +} + +/* Save user chosen LSM */ +static int __init choose_lsm(char *str) +{ + strncpy(chosen_lsm, str, SECURITY_NAME_MAX); + return 1; +} +__setup("security=", choose_lsm); + +/** + * security_module_enable - Load given security module on boot ? + * @ops: a pointer to the struct security_operations that is to be checked. + * + * Each LSM must pass this method before registering its own operations + * to avoid security registration races. This method may also be used + * to check if your LSM is currently loaded during kernel initialization. + * + * Return true if: + * -The passed LSM is the one chosen by user at boot time, + * -or the passed LSM is configured as the default and the user did not + * choose an alternate LSM at boot time. + * Otherwise, return false. + */ +int __init security_module_enable(struct security_operations *ops) +{ + return !strcmp(ops->name, chosen_lsm); +} + +/** + * register_security - registers a security framework with the kernel + * @ops: a pointer to the struct security_options that is to be registered + * + * This function allows a security module to register itself with the + * kernel security subsystem. Some rudimentary checking is done on the @ops + * value passed to this function. You'll need to check first if your LSM + * is allowed to register its @ops by calling security_module_enable(@ops). + * + * If there is already a security module registered with the kernel, + * an error will be returned. Otherwise %0 is returned on success. + */ +int __init register_security(struct security_operations *ops) +{ + if (verify(ops)) { + printk(KERN_DEBUG "%s could not verify " + "security_operations structure.\n", __func__); + return -EINVAL; + } + + if (security_ops != &default_security_ops) + return -EAGAIN; + + security_ops = ops; + + return 0; +} + +/* Security operations */ + +int security_ptrace_access_check(struct task_struct *child, unsigned int mode) +{ + return security_ops->ptrace_access_check(child, mode); +} + +int security_ptrace_traceme(struct task_struct *parent) +{ + return security_ops->ptrace_traceme(parent); +} + +int security_capget(struct task_struct *target, + kernel_cap_t *effective, + kernel_cap_t *inheritable, + kernel_cap_t *permitted) +{ + return security_ops->capget(target, effective, inheritable, permitted); +} + +int security_capset(struct cred *new, const struct cred *old, + const kernel_cap_t *effective, + const kernel_cap_t *inheritable, + const kernel_cap_t *permitted) +{ + return security_ops->capset(new, old, + effective, inheritable, permitted); +} + +int security_capable(struct user_namespace *ns, const struct cred *cred, + int cap) +{ + return security_ops->capable(current, cred, ns, cap, + SECURITY_CAP_AUDIT); +} + +int security_real_capable(struct task_struct *tsk, struct user_namespace *ns, + int cap) +{ + const struct cred *cred; + int ret; + + cred = get_task_cred(tsk); + ret = security_ops->capable(tsk, cred, ns, cap, SECURITY_CAP_AUDIT); + put_cred(cred); + return ret; +} + +int security_real_capable_noaudit(struct task_struct *tsk, + struct user_namespace *ns, int cap) +{ + const struct cred *cred; + int ret; + + cred = get_task_cred(tsk); + ret = security_ops->capable(tsk, cred, ns, cap, SECURITY_CAP_NOAUDIT); + put_cred(cred); + return ret; +} + +int security_quotactl(int cmds, int type, int id, struct super_block *sb) +{ + return security_ops->quotactl(cmds, type, id, sb); +} + +int security_quota_on(struct dentry *dentry) +{ + return security_ops->quota_on(dentry); +} + +int security_syslog(int type) +{ + return security_ops->syslog(type); +} + +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); +} + +int security_bprm_check(struct linux_binprm *bprm) +{ + int ret; + + ret = security_ops->bprm_check_security(bprm); + if (ret) + return ret; + return ima_bprm_check(bprm); +} + +void security_bprm_committing_creds(struct linux_binprm *bprm) +{ + security_ops->bprm_committing_creds(bprm); +} + +void security_bprm_committed_creds(struct linux_binprm *bprm) +{ + security_ops->bprm_committed_creds(bprm); +} + +int security_bprm_secureexec(struct linux_binprm *bprm) +{ + return security_ops->bprm_secureexec(bprm); +} + +int security_sb_alloc(struct super_block *sb) +{ + return security_ops->sb_alloc_security(sb); +} + +void security_sb_free(struct super_block *sb) +{ + security_ops->sb_free_security(sb); +} + +int security_sb_copy_data(char *orig, char *copy) +{ + return security_ops->sb_copy_data(orig, copy); +} +EXPORT_SYMBOL(security_sb_copy_data); + +int security_sb_remount(struct super_block *sb, void *data) +{ + return security_ops->sb_remount(sb, data); +} + +int security_sb_kern_mount(struct super_block *sb, int flags, void *data) +{ + return security_ops->sb_kern_mount(sb, flags, data); +} + +int security_sb_show_options(struct seq_file *m, struct super_block *sb) +{ + return security_ops->sb_show_options(m, sb); +} + +int security_sb_statfs(struct dentry *dentry) +{ + return security_ops->sb_statfs(dentry); +} + +int security_sb_mount(char *dev_name, struct path *path, + char *type, unsigned long flags, void *data) +{ + return security_ops->sb_mount(dev_name, path, type, flags, data); +} + +int security_sb_umount(struct vfsmount *mnt, int flags) +{ + return security_ops->sb_umount(mnt, flags); +} + +int security_sb_pivotroot(struct path *old_path, struct path *new_path) +{ + return security_ops->sb_pivotroot(old_path, new_path); +} + +int security_sb_set_mnt_opts(struct super_block *sb, + struct security_mnt_opts *opts) +{ + return security_ops->sb_set_mnt_opts(sb, opts); +} +EXPORT_SYMBOL(security_sb_set_mnt_opts); + +void security_sb_clone_mnt_opts(const struct super_block *oldsb, + struct super_block *newsb) +{ + security_ops->sb_clone_mnt_opts(oldsb, newsb); +} +EXPORT_SYMBOL(security_sb_clone_mnt_opts); + +int security_sb_parse_opts_str(char *options, struct security_mnt_opts *opts) +{ + return security_ops->sb_parse_opts_str(options, opts); +} +EXPORT_SYMBOL(security_sb_parse_opts_str); + +int security_inode_alloc(struct inode *inode) +{ + inode->i_security = NULL; + return security_ops->inode_alloc_security(inode); +} + +void security_inode_free(struct inode *inode) +{ + ima_inode_free(inode); + security_ops->inode_free_security(inode); +} + +int security_inode_init_security(struct inode *inode, struct inode *dir, + const struct qstr *qstr, char **name, + void **value, size_t *len) +{ + if (unlikely(IS_PRIVATE(inode))) + return -EOPNOTSUPP; + return security_ops->inode_init_security(inode, dir, qstr, name, value, + len); +} +EXPORT_SYMBOL(security_inode_init_security); + +#ifdef CONFIG_SECURITY_PATH +int security_path_mknod(struct path *dir, struct dentry *dentry, int mode, + unsigned int dev) +{ + if (unlikely(IS_PRIVATE(dir->dentry->d_inode))) + return 0; + return security_ops->path_mknod(dir, dentry, mode, dev); +} +EXPORT_SYMBOL(security_path_mknod); + +int security_path_mkdir(struct path *dir, struct dentry *dentry, int mode) +{ + if (unlikely(IS_PRIVATE(dir->dentry->d_inode))) + return 0; + return security_ops->path_mkdir(dir, dentry, mode); +} +EXPORT_SYMBOL(security_path_mkdir); + +int security_path_rmdir(struct path *dir, struct dentry *dentry) +{ + if (unlikely(IS_PRIVATE(dir->dentry->d_inode))) + return 0; + return security_ops->path_rmdir(dir, dentry); +} + +int security_path_unlink(struct path *dir, struct dentry *dentry) +{ + if (unlikely(IS_PRIVATE(dir->dentry->d_inode))) + return 0; + return security_ops->path_unlink(dir, dentry); +} +EXPORT_SYMBOL(security_path_unlink); + +int security_path_symlink(struct path *dir, struct dentry *dentry, + const char *old_name) +{ + if (unlikely(IS_PRIVATE(dir->dentry->d_inode))) + return 0; + return security_ops->path_symlink(dir, dentry, old_name); +} + +int security_path_link(struct dentry *old_dentry, struct path *new_dir, + struct dentry *new_dentry) +{ + if (unlikely(IS_PRIVATE(old_dentry->d_inode))) + return 0; + return security_ops->path_link(old_dentry, new_dir, new_dentry); +} + +int security_path_rename(struct path *old_dir, struct dentry *old_dentry, + struct path *new_dir, struct dentry *new_dentry) +{ + if (unlikely(IS_PRIVATE(old_dentry->d_inode) || + (new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode)))) + return 0; + return security_ops->path_rename(old_dir, old_dentry, new_dir, + new_dentry); +} +EXPORT_SYMBOL(security_path_rename); + +int security_path_truncate(struct path *path) +{ + if (unlikely(IS_PRIVATE(path->dentry->d_inode))) + return 0; + return security_ops->path_truncate(path); +} + +int security_path_chmod(struct dentry *dentry, struct vfsmount *mnt, + mode_t mode) +{ + if (unlikely(IS_PRIVATE(dentry->d_inode))) + return 0; + return security_ops->path_chmod(dentry, mnt, mode); +} + +int security_path_chown(struct path *path, uid_t uid, gid_t gid) +{ + if (unlikely(IS_PRIVATE(path->dentry->d_inode))) + return 0; + return security_ops->path_chown(path, uid, gid); +} + +int security_path_chroot(struct path *path) +{ + return security_ops->path_chroot(path); +} +#endif + +int security_inode_create(struct inode *dir, struct dentry *dentry, int mode) +{ + if (unlikely(IS_PRIVATE(dir))) + return 0; + return security_ops->inode_create(dir, dentry, mode); +} +EXPORT_SYMBOL_GPL(security_inode_create); + +int security_inode_link(struct dentry *old_dentry, struct inode *dir, + struct dentry *new_dentry) +{ + if (unlikely(IS_PRIVATE(old_dentry->d_inode))) + return 0; + return security_ops->inode_link(old_dentry, dir, new_dentry); +} + +int security_inode_unlink(struct inode *dir, struct dentry *dentry) +{ + if (unlikely(IS_PRIVATE(dentry->d_inode))) + return 0; + return security_ops->inode_unlink(dir, dentry); +} + +int security_inode_symlink(struct inode *dir, struct dentry *dentry, + const char *old_name) +{ + if (unlikely(IS_PRIVATE(dir))) + return 0; + return security_ops->inode_symlink(dir, dentry, old_name); +} + +int security_inode_mkdir(struct inode *dir, struct dentry *dentry, int mode) +{ + if (unlikely(IS_PRIVATE(dir))) + return 0; + return security_ops->inode_mkdir(dir, dentry, mode); +} +EXPORT_SYMBOL_GPL(security_inode_mkdir); + +int security_inode_rmdir(struct inode *dir, struct dentry *dentry) +{ + if (unlikely(IS_PRIVATE(dentry->d_inode))) + return 0; + return security_ops->inode_rmdir(dir, dentry); +} + +int security_inode_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev) +{ + if (unlikely(IS_PRIVATE(dir))) + return 0; + return security_ops->inode_mknod(dir, dentry, mode, dev); +} + +int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry) +{ + if (unlikely(IS_PRIVATE(old_dentry->d_inode) || + (new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode)))) + return 0; + return security_ops->inode_rename(old_dir, old_dentry, + new_dir, new_dentry); +} + +int security_inode_readlink(struct dentry *dentry) +{ + if (unlikely(IS_PRIVATE(dentry->d_inode))) + return 0; + return security_ops->inode_readlink(dentry); +} + +int security_inode_follow_link(struct dentry *dentry, struct nameidata *nd) +{ + if (unlikely(IS_PRIVATE(dentry->d_inode))) + return 0; + return security_ops->inode_follow_link(dentry, nd); +} + +int security_inode_permission(struct inode *inode, int mask) +{ + if (unlikely(IS_PRIVATE(inode))) + return 0; + return security_ops->inode_permission(inode, mask, 0); +} + +int security_inode_exec_permission(struct inode *inode, unsigned int flags) +{ + if (unlikely(IS_PRIVATE(inode))) + return 0; + return security_ops->inode_permission(inode, MAY_EXEC, flags); +} + +int security_inode_setattr(struct dentry *dentry, struct iattr *attr) +{ + if (unlikely(IS_PRIVATE(dentry->d_inode))) + return 0; + return security_ops->inode_setattr(dentry, attr); +} +EXPORT_SYMBOL_GPL(security_inode_setattr); + +int security_inode_getattr(struct vfsmount *mnt, struct dentry *dentry) +{ + if (unlikely(IS_PRIVATE(dentry->d_inode))) + return 0; + return security_ops->inode_getattr(mnt, dentry); +} + +int security_inode_setxattr(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags) +{ + if (unlikely(IS_PRIVATE(dentry->d_inode))) + return 0; + return security_ops->inode_setxattr(dentry, name, value, size, flags); +} + +void security_inode_post_setxattr(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags) +{ + if (unlikely(IS_PRIVATE(dentry->d_inode))) + return; + security_ops->inode_post_setxattr(dentry, name, value, size, flags); +} + +int security_inode_getxattr(struct dentry *dentry, const char *name) +{ + if (unlikely(IS_PRIVATE(dentry->d_inode))) + return 0; + return security_ops->inode_getxattr(dentry, name); +} + +int security_inode_listxattr(struct dentry *dentry) +{ + if (unlikely(IS_PRIVATE(dentry->d_inode))) + return 0; + return security_ops->inode_listxattr(dentry); +} + +int security_inode_removexattr(struct dentry *dentry, const char *name) +{ + if (unlikely(IS_PRIVATE(dentry->d_inode))) + return 0; + return security_ops->inode_removexattr(dentry, name); +} + +int security_inode_need_killpriv(struct dentry *dentry) +{ + return security_ops->inode_need_killpriv(dentry); +} + +int security_inode_killpriv(struct dentry *dentry) +{ + return security_ops->inode_killpriv(dentry); +} + +int security_inode_getsecurity(const struct inode *inode, const char *name, void **buffer, bool alloc) +{ + if (unlikely(IS_PRIVATE(inode))) + return -EOPNOTSUPP; + return security_ops->inode_getsecurity(inode, name, buffer, alloc); +} + +int security_inode_setsecurity(struct inode *inode, const char *name, const void *value, size_t size, int flags) +{ + if (unlikely(IS_PRIVATE(inode))) + return -EOPNOTSUPP; + return security_ops->inode_setsecurity(inode, name, value, size, flags); +} + +int security_inode_listsecurity(struct inode *inode, char *buffer, size_t buffer_size) +{ + if (unlikely(IS_PRIVATE(inode))) + return 0; + return security_ops->inode_listsecurity(inode, buffer, buffer_size); +} + +void security_inode_getsecid(const struct inode *inode, u32 *secid) +{ + security_ops->inode_getsecid(inode, secid); +} + +int security_file_permission(struct file *file, int mask) +{ + int ret; + + ret = security_ops->file_permission(file, mask); + if (ret) + return ret; + + return fsnotify_perm(file, mask); +} + +int security_file_alloc(struct file *file) +{ + return security_ops->file_alloc_security(file); +} + +void security_file_free(struct file *file) +{ + security_ops->file_free_security(file); +} + +int security_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + return security_ops->file_ioctl(file, cmd, arg); +} + +int security_file_mmap(struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags, + unsigned long addr, unsigned long addr_only) +{ + int ret; + + ret = security_ops->file_mmap(file, reqprot, prot, flags, addr, addr_only); + if (ret) + return ret; + return ima_file_mmap(file, prot); +} + +int security_file_mprotect(struct vm_area_struct *vma, unsigned long reqprot, + unsigned long prot) +{ + return security_ops->file_mprotect(vma, reqprot, prot); +} + +int security_file_lock(struct file *file, unsigned int cmd) +{ + return security_ops->file_lock(file, cmd); +} + +int security_file_fcntl(struct file *file, unsigned int cmd, unsigned long arg) +{ + return security_ops->file_fcntl(file, cmd, arg); +} + +int security_file_set_fowner(struct file *file) +{ + return security_ops->file_set_fowner(file); +} + +int security_file_send_sigiotask(struct task_struct *tsk, + struct fown_struct *fown, int sig) +{ + return security_ops->file_send_sigiotask(tsk, fown, sig); +} + +int security_file_receive(struct file *file) +{ + return security_ops->file_receive(file); +} + +int security_dentry_open(struct file *file, const struct cred *cred) +{ + int ret; + + ret = security_ops->dentry_open(file, cred); + if (ret) + return ret; + + return fsnotify_perm(file, MAY_OPEN); +} + +int security_task_create(unsigned long clone_flags) +{ + return security_ops->task_create(clone_flags); +} + +int security_cred_alloc_blank(struct cred *cred, gfp_t gfp) +{ + return security_ops->cred_alloc_blank(cred, gfp); +} + +void security_cred_free(struct cred *cred) +{ + security_ops->cred_free(cred); +} + +int security_prepare_creds(struct cred *new, const struct cred *old, gfp_t gfp) +{ + return security_ops->cred_prepare(new, old, gfp); +} + +void security_transfer_creds(struct cred *new, const struct cred *old) +{ + security_ops->cred_transfer(new, old); +} + +int security_kernel_act_as(struct cred *new, u32 secid) +{ + return security_ops->kernel_act_as(new, secid); +} + +int security_kernel_create_files_as(struct cred *new, struct inode *inode) +{ + return security_ops->kernel_create_files_as(new, inode); +} + +int security_kernel_module_request(char *kmod_name) +{ + return security_ops->kernel_module_request(kmod_name); +} + +int security_task_fix_setuid(struct cred *new, const struct cred *old, + int flags) +{ + return security_ops->task_fix_setuid(new, old, flags); +} + +int security_task_setpgid(struct task_struct *p, pid_t pgid) +{ + return security_ops->task_setpgid(p, pgid); +} + +int security_task_getpgid(struct task_struct *p) +{ + return security_ops->task_getpgid(p); +} + +int security_task_getsid(struct task_struct *p) +{ + return security_ops->task_getsid(p); +} + +void security_task_getsecid(struct task_struct *p, u32 *secid) +{ + security_ops->task_getsecid(p, secid); +} +EXPORT_SYMBOL(security_task_getsecid); + +int security_task_setnice(struct task_struct *p, int nice) +{ + return security_ops->task_setnice(p, nice); +} + +int security_task_setioprio(struct task_struct *p, int ioprio) +{ + return security_ops->task_setioprio(p, ioprio); +} + +int security_task_getioprio(struct task_struct *p) +{ + return security_ops->task_getioprio(p); +} + +int security_task_setrlimit(struct task_struct *p, unsigned int resource, + struct rlimit *new_rlim) +{ + return security_ops->task_setrlimit(p, resource, new_rlim); +} + +int security_task_setscheduler(struct task_struct *p) +{ + return security_ops->task_setscheduler(p); +} + +int security_task_getscheduler(struct task_struct *p) +{ + return security_ops->task_getscheduler(p); +} + +int security_task_movememory(struct task_struct *p) +{ + return security_ops->task_movememory(p); +} + +int security_task_kill(struct task_struct *p, struct siginfo *info, + int sig, u32 secid) +{ + return security_ops->task_kill(p, info, sig, secid); +} + +int security_task_wait(struct task_struct *p) +{ + return security_ops->task_wait(p); +} + +int security_task_prctl(int option, unsigned long arg2, unsigned long arg3, + unsigned long arg4, unsigned long arg5) +{ + return security_ops->task_prctl(option, arg2, arg3, arg4, arg5); +} + +void security_task_to_inode(struct task_struct *p, struct inode *inode) +{ + security_ops->task_to_inode(p, inode); +} + +int security_ipc_permission(struct kern_ipc_perm *ipcp, short flag) +{ + return security_ops->ipc_permission(ipcp, flag); +} + +void security_ipc_getsecid(struct kern_ipc_perm *ipcp, u32 *secid) +{ + security_ops->ipc_getsecid(ipcp, secid); +} + +int security_msg_msg_alloc(struct msg_msg *msg) +{ + return security_ops->msg_msg_alloc_security(msg); +} + +void security_msg_msg_free(struct msg_msg *msg) +{ + security_ops->msg_msg_free_security(msg); +} + +int security_msg_queue_alloc(struct msg_queue *msq) +{ + return security_ops->msg_queue_alloc_security(msq); +} + +void security_msg_queue_free(struct msg_queue *msq) +{ + security_ops->msg_queue_free_security(msq); +} + +int security_msg_queue_associate(struct msg_queue *msq, int msqflg) +{ + return security_ops->msg_queue_associate(msq, msqflg); +} + +int security_msg_queue_msgctl(struct msg_queue *msq, int cmd) +{ + return security_ops->msg_queue_msgctl(msq, cmd); +} + +int security_msg_queue_msgsnd(struct msg_queue *msq, + struct msg_msg *msg, int msqflg) +{ + return security_ops->msg_queue_msgsnd(msq, msg, msqflg); +} + +int security_msg_queue_msgrcv(struct msg_queue *msq, struct msg_msg *msg, + struct task_struct *target, long type, int mode) +{ + return security_ops->msg_queue_msgrcv(msq, msg, target, type, mode); +} + +int security_shm_alloc(struct shmid_kernel *shp) +{ + return security_ops->shm_alloc_security(shp); +} + +void security_shm_free(struct shmid_kernel *shp) +{ + security_ops->shm_free_security(shp); +} + +int security_shm_associate(struct shmid_kernel *shp, int shmflg) +{ + return security_ops->shm_associate(shp, shmflg); +} + +int security_shm_shmctl(struct shmid_kernel *shp, int cmd) +{ + return security_ops->shm_shmctl(shp, cmd); +} + +int security_shm_shmat(struct shmid_kernel *shp, char __user *shmaddr, int shmflg) +{ + return security_ops->shm_shmat(shp, shmaddr, shmflg); +} + +int security_sem_alloc(struct sem_array *sma) +{ + return security_ops->sem_alloc_security(sma); +} + +void security_sem_free(struct sem_array *sma) +{ + security_ops->sem_free_security(sma); +} + +int security_sem_associate(struct sem_array *sma, int semflg) +{ + return security_ops->sem_associate(sma, semflg); +} + +int security_sem_semctl(struct sem_array *sma, int cmd) +{ + return security_ops->sem_semctl(sma, cmd); +} + +int security_sem_semop(struct sem_array *sma, struct sembuf *sops, + unsigned nsops, int alter) +{ + return security_ops->sem_semop(sma, sops, nsops, alter); +} + +void security_d_instantiate(struct dentry *dentry, struct inode *inode) +{ + if (unlikely(inode && IS_PRIVATE(inode))) + return; + security_ops->d_instantiate(dentry, inode); +} +EXPORT_SYMBOL(security_d_instantiate); + +int security_getprocattr(struct task_struct *p, char *name, char **value) +{ + return security_ops->getprocattr(p, name, value); +} + +int security_setprocattr(struct task_struct *p, char *name, void *value, size_t size) +{ + return security_ops->setprocattr(p, name, value, size); +} + +int security_netlink_send(struct sock *sk, struct sk_buff *skb) +{ + return security_ops->netlink_send(sk, skb); +} + +int security_netlink_recv(struct sk_buff *skb, int cap) +{ + return security_ops->netlink_recv(skb, cap); +} +EXPORT_SYMBOL(security_netlink_recv); + +int security_secid_to_secctx(u32 secid, char **secdata, u32 *seclen) +{ + return security_ops->secid_to_secctx(secid, secdata, seclen); +} +EXPORT_SYMBOL(security_secid_to_secctx); + +int security_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid) +{ + return security_ops->secctx_to_secid(secdata, seclen, secid); +} +EXPORT_SYMBOL(security_secctx_to_secid); + +void security_release_secctx(char *secdata, u32 seclen) +{ + security_ops->release_secctx(secdata, seclen); +} +EXPORT_SYMBOL(security_release_secctx); + +int security_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen) +{ + return security_ops->inode_notifysecctx(inode, ctx, ctxlen); +} +EXPORT_SYMBOL(security_inode_notifysecctx); + +int security_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen) +{ + return security_ops->inode_setsecctx(dentry, ctx, ctxlen); +} +EXPORT_SYMBOL(security_inode_setsecctx); + +int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen) +{ + return security_ops->inode_getsecctx(inode, ctx, ctxlen); +} +EXPORT_SYMBOL(security_inode_getsecctx); + +#ifdef CONFIG_SECURITY_NETWORK + +int security_unix_stream_connect(struct sock *sock, struct sock *other, struct sock *newsk) +{ + return security_ops->unix_stream_connect(sock, other, newsk); +} +EXPORT_SYMBOL(security_unix_stream_connect); + +int security_unix_may_send(struct socket *sock, struct socket *other) +{ + return security_ops->unix_may_send(sock, other); +} +EXPORT_SYMBOL(security_unix_may_send); + +int security_socket_create(int family, int type, int protocol, int kern) +{ + return security_ops->socket_create(family, type, protocol, kern); +} + +int security_socket_post_create(struct socket *sock, int family, + int type, int protocol, int kern) +{ + return security_ops->socket_post_create(sock, family, type, + protocol, kern); +} + +int security_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen) +{ + return security_ops->socket_bind(sock, address, addrlen); +} + +int security_socket_connect(struct socket *sock, struct sockaddr *address, int addrlen) +{ + return security_ops->socket_connect(sock, address, addrlen); +} + +int security_socket_listen(struct socket *sock, int backlog) +{ + return security_ops->socket_listen(sock, backlog); +} + +int security_socket_accept(struct socket *sock, struct socket *newsock) +{ + return security_ops->socket_accept(sock, newsock); +} + +int security_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size) +{ + return security_ops->socket_sendmsg(sock, msg, size); +} + +int security_socket_recvmsg(struct socket *sock, struct msghdr *msg, + int size, int flags) +{ + return security_ops->socket_recvmsg(sock, msg, size, flags); +} + +int security_socket_getsockname(struct socket *sock) +{ + return security_ops->socket_getsockname(sock); +} + +int security_socket_getpeername(struct socket *sock) +{ + return security_ops->socket_getpeername(sock); +} + +int security_socket_getsockopt(struct socket *sock, int level, int optname) +{ + return security_ops->socket_getsockopt(sock, level, optname); +} + +int security_socket_setsockopt(struct socket *sock, int level, int optname) +{ + return security_ops->socket_setsockopt(sock, level, optname); +} + +int security_socket_shutdown(struct socket *sock, int how) +{ + return security_ops->socket_shutdown(sock, how); +} + +int security_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) +{ + return security_ops->socket_sock_rcv_skb(sk, skb); +} +EXPORT_SYMBOL(security_sock_rcv_skb); + +int security_socket_getpeersec_stream(struct socket *sock, char __user *optval, + int __user *optlen, unsigned len) +{ + return security_ops->socket_getpeersec_stream(sock, optval, optlen, len); +} + +int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb, u32 *secid) +{ + return security_ops->socket_getpeersec_dgram(sock, skb, secid); +} +EXPORT_SYMBOL(security_socket_getpeersec_dgram); + +int security_sk_alloc(struct sock *sk, int family, gfp_t priority) +{ + return security_ops->sk_alloc_security(sk, family, priority); +} + +void security_sk_free(struct sock *sk) +{ + security_ops->sk_free_security(sk); +} + +void security_sk_clone(const struct sock *sk, struct sock *newsk) +{ + security_ops->sk_clone_security(sk, newsk); +} + +void security_sk_classify_flow(struct sock *sk, struct flowi *fl) +{ + security_ops->sk_getsecid(sk, &fl->flowi_secid); +} +EXPORT_SYMBOL(security_sk_classify_flow); + +void security_req_classify_flow(const struct request_sock *req, struct flowi *fl) +{ + security_ops->req_classify_flow(req, fl); +} +EXPORT_SYMBOL(security_req_classify_flow); + +void security_sock_graft(struct sock *sk, struct socket *parent) +{ + security_ops->sock_graft(sk, parent); +} +EXPORT_SYMBOL(security_sock_graft); + +int security_inet_conn_request(struct sock *sk, + struct sk_buff *skb, struct request_sock *req) +{ + return security_ops->inet_conn_request(sk, skb, req); +} +EXPORT_SYMBOL(security_inet_conn_request); + +void security_inet_csk_clone(struct sock *newsk, + const struct request_sock *req) +{ + security_ops->inet_csk_clone(newsk, req); +} + +void security_inet_conn_established(struct sock *sk, + struct sk_buff *skb) +{ + security_ops->inet_conn_established(sk, skb); +} + +int security_secmark_relabel_packet(u32 secid) +{ + return security_ops->secmark_relabel_packet(secid); +} +EXPORT_SYMBOL(security_secmark_relabel_packet); + +void security_secmark_refcount_inc(void) +{ + security_ops->secmark_refcount_inc(); +} +EXPORT_SYMBOL(security_secmark_refcount_inc); + +void security_secmark_refcount_dec(void) +{ + security_ops->secmark_refcount_dec(); +} +EXPORT_SYMBOL(security_secmark_refcount_dec); + +int security_tun_dev_create(void) +{ + return security_ops->tun_dev_create(); +} +EXPORT_SYMBOL(security_tun_dev_create); + +void security_tun_dev_post_create(struct sock *sk) +{ + return security_ops->tun_dev_post_create(sk); +} +EXPORT_SYMBOL(security_tun_dev_post_create); + +int security_tun_dev_attach(struct sock *sk) +{ + return security_ops->tun_dev_attach(sk); +} +EXPORT_SYMBOL(security_tun_dev_attach); + +#endif /* CONFIG_SECURITY_NETWORK */ + +#ifdef CONFIG_SECURITY_NETWORK_XFRM + +int security_xfrm_policy_alloc(struct xfrm_sec_ctx **ctxp, struct xfrm_user_sec_ctx *sec_ctx) +{ + return security_ops->xfrm_policy_alloc_security(ctxp, sec_ctx); +} +EXPORT_SYMBOL(security_xfrm_policy_alloc); + +int security_xfrm_policy_clone(struct xfrm_sec_ctx *old_ctx, + struct xfrm_sec_ctx **new_ctxp) +{ + return security_ops->xfrm_policy_clone_security(old_ctx, new_ctxp); +} + +void security_xfrm_policy_free(struct xfrm_sec_ctx *ctx) +{ + security_ops->xfrm_policy_free_security(ctx); +} +EXPORT_SYMBOL(security_xfrm_policy_free); + +int security_xfrm_policy_delete(struct xfrm_sec_ctx *ctx) +{ + return security_ops->xfrm_policy_delete_security(ctx); +} + +int security_xfrm_state_alloc(struct xfrm_state *x, struct xfrm_user_sec_ctx *sec_ctx) +{ + return security_ops->xfrm_state_alloc_security(x, sec_ctx, 0); +} +EXPORT_SYMBOL(security_xfrm_state_alloc); + +int security_xfrm_state_alloc_acquire(struct xfrm_state *x, + struct xfrm_sec_ctx *polsec, u32 secid) +{ + if (!polsec) + return 0; + /* + * We want the context to be taken from secid which is usually + * from the sock. + */ + return security_ops->xfrm_state_alloc_security(x, NULL, secid); +} + +int security_xfrm_state_delete(struct xfrm_state *x) +{ + return security_ops->xfrm_state_delete_security(x); +} +EXPORT_SYMBOL(security_xfrm_state_delete); + +void security_xfrm_state_free(struct xfrm_state *x) +{ + security_ops->xfrm_state_free_security(x); +} + +int security_xfrm_policy_lookup(struct xfrm_sec_ctx *ctx, u32 fl_secid, u8 dir) +{ + return security_ops->xfrm_policy_lookup(ctx, fl_secid, dir); +} + +int security_xfrm_state_pol_flow_match(struct xfrm_state *x, + struct xfrm_policy *xp, + const struct flowi *fl) +{ + return security_ops->xfrm_state_pol_flow_match(x, xp, fl); +} + +int security_xfrm_decode_session(struct sk_buff *skb, u32 *secid) +{ + return security_ops->xfrm_decode_session(skb, secid, 1); +} + +void security_skb_classify_flow(struct sk_buff *skb, struct flowi *fl) +{ + int rc = security_ops->xfrm_decode_session(skb, &fl->flowi_secid, 0); + + BUG_ON(rc); +} +EXPORT_SYMBOL(security_skb_classify_flow); + +#endif /* CONFIG_SECURITY_NETWORK_XFRM */ + +#ifdef CONFIG_KEYS + +int security_key_alloc(struct key *key, const struct cred *cred, + unsigned long flags) +{ + return security_ops->key_alloc(key, cred, flags); +} + +void security_key_free(struct key *key) +{ + security_ops->key_free(key); +} + +int security_key_permission(key_ref_t key_ref, + const struct cred *cred, key_perm_t perm) +{ + return security_ops->key_permission(key_ref, cred, perm); +} + +int security_key_getsecurity(struct key *key, char **_buffer) +{ + return security_ops->key_getsecurity(key, _buffer); +} + +#endif /* CONFIG_KEYS */ + +#ifdef CONFIG_AUDIT + +int security_audit_rule_init(u32 field, u32 op, char *rulestr, void **lsmrule) +{ + return security_ops->audit_rule_init(field, op, rulestr, lsmrule); +} + +int security_audit_rule_known(struct audit_krule *krule) +{ + return security_ops->audit_rule_known(krule); +} + +void security_audit_rule_free(void *lsmrule) +{ + security_ops->audit_rule_free(lsmrule); +} + +int security_audit_rule_match(u32 secid, u32 field, u32 op, void *lsmrule, + struct audit_context *actx) +{ + return security_ops->audit_rule_match(secid, field, op, lsmrule, actx); +} + +#endif /* CONFIG_AUDIT */ diff --git a/security/selinux/.gitignore b/security/selinux/.gitignore new file mode 100644 index 00000000..2e5040a3 --- /dev/null +++ b/security/selinux/.gitignore @@ -0,0 +1,2 @@ +av_permissions.h +flask.h diff --git a/security/selinux/Kconfig b/security/selinux/Kconfig new file mode 100644 index 00000000..bca1b74a --- /dev/null +++ b/security/selinux/Kconfig @@ -0,0 +1,133 @@ +config SECURITY_SELINUX + bool "NSA SELinux Support" + depends on SECURITY_NETWORK && AUDIT && NET && INET + select NETWORK_SECMARK + default n + help + This selects NSA Security-Enhanced Linux (SELinux). + You will also need a policy configuration and a labeled filesystem. + If you are unsure how to answer this question, answer N. + +config SECURITY_SELINUX_BOOTPARAM + bool "NSA SELinux boot parameter" + depends on SECURITY_SELINUX + default n + help + This option adds a kernel parameter 'selinux', which allows SELinux + to be disabled at boot. If this option is selected, SELinux + functionality can be disabled with selinux=0 on the kernel + command line. The purpose of this option is to allow a single + kernel image to be distributed with SELinux built in, but not + necessarily enabled. + + If you are unsure how to answer this question, answer N. + +config SECURITY_SELINUX_BOOTPARAM_VALUE + int "NSA SELinux boot parameter default value" + depends on SECURITY_SELINUX_BOOTPARAM + range 0 1 + default 1 + help + This option sets the default value for the kernel parameter + 'selinux', which allows SELinux to be disabled at boot. If this + option is set to 0 (zero), the SELinux kernel parameter will + default to 0, disabling SELinux at bootup. If this option is + set to 1 (one), the SELinux kernel parameter will default to 1, + enabling SELinux at bootup. + + If you are unsure how to answer this question, answer 1. + +config SECURITY_SELINUX_DISABLE + bool "NSA SELinux runtime disable" + depends on SECURITY_SELINUX + default n + help + This option enables writing to a selinuxfs node 'disable', which + allows SELinux to be disabled at runtime prior to the policy load. + SELinux will then remain disabled until the next boot. + This option is similar to the selinux=0 boot parameter, but is to + support runtime disabling of SELinux, e.g. from /sbin/init, for + portability across platforms where boot parameters are difficult + to employ. + + If you are unsure how to answer this question, answer N. + +config SECURITY_SELINUX_DEVELOP + bool "NSA SELinux Development Support" + depends on SECURITY_SELINUX + default y + help + This enables the development support option of NSA SELinux, + which is useful for experimenting with SELinux and developing + policies. If unsure, say Y. With this option enabled, the + kernel will start in permissive mode (log everything, deny nothing) + unless you specify enforcing=1 on the kernel command line. You + can interactively toggle the kernel between enforcing mode and + permissive mode (if permitted by the policy) via /selinux/enforce. + +config SECURITY_SELINUX_AVC_STATS + bool "NSA SELinux AVC Statistics" + depends on SECURITY_SELINUX + default y + help + This option collects access vector cache statistics to + /selinux/avc/cache_stats, which may be monitored via + tools such as avcstat. + +config SECURITY_SELINUX_CHECKREQPROT_VALUE + int "NSA SELinux checkreqprot default value" + depends on SECURITY_SELINUX + range 0 1 + default 1 + help + This option sets the default value for the 'checkreqprot' flag + that determines whether SELinux checks the protection requested + by the application or the protection that will be applied by the + kernel (including any implied execute for read-implies-exec) for + mmap and mprotect calls. If this option is set to 0 (zero), + SELinux will default to checking the protection that will be applied + by the kernel. If this option is set to 1 (one), SELinux will + default to checking the protection requested by the application. + The checkreqprot flag may be changed from the default via the + 'checkreqprot=' boot parameter. It may also be changed at runtime + via /selinux/checkreqprot if authorized by policy. + + If you are unsure how to answer this question, answer 1. + +config SECURITY_SELINUX_POLICYDB_VERSION_MAX + bool "NSA SELinux maximum supported policy format version" + depends on SECURITY_SELINUX + default n + help + This option enables the maximum policy format version supported + by SELinux to be set to a particular value. This value is reported + to userspace via /selinux/policyvers and used at policy load time. + It can be adjusted downward to support legacy userland (init) that + does not correctly handle kernels that support newer policy versions. + + Examples: + For the Fedora Core 3 or 4 Linux distributions, enable this option + and set the value via the next option. For Fedora Core 5 and later, + do not enable this option. + + If you are unsure how to answer this question, answer N. + +config SECURITY_SELINUX_POLICYDB_VERSION_MAX_VALUE + int "NSA SELinux maximum supported policy format version value" + depends on SECURITY_SELINUX_POLICYDB_VERSION_MAX + range 15 23 + default 19 + help + This option sets the value for the maximum policy format version + supported by SELinux. + + Examples: + For Fedora Core 3, use 18. + For Fedora Core 4, use 19. + + If you are unsure how to answer this question, look for the + policy format version supported by your policy toolchain, by + running 'checkpolicy -V'. Or look at what policy you have + installed under /etc/selinux/$SELINUXTYPE/policy, where + SELINUXTYPE is defined in your /etc/selinux/config. + diff --git a/security/selinux/Makefile b/security/selinux/Makefile new file mode 100644 index 00000000..ad5cd76e --- /dev/null +++ b/security/selinux/Makefile @@ -0,0 +1,25 @@ +# +# Makefile for building the SELinux module as part of the kernel tree. +# + +obj-$(CONFIG_SECURITY_SELINUX) := selinux.o + +selinux-y := avc.o hooks.o selinuxfs.o netlink.o nlmsgtab.o netif.o \ + netnode.o netport.o exports.o \ + ss/ebitmap.o ss/hashtab.o ss/symtab.o ss/sidtab.o ss/avtab.o \ + ss/policydb.o ss/services.o ss/conditional.o ss/mls.o ss/status.o + +selinux-$(CONFIG_SECURITY_NETWORK_XFRM) += xfrm.o + +selinux-$(CONFIG_NETLABEL) += netlabel.o + +ccflags-y := -Isecurity/selinux -Isecurity/selinux/include + +$(addprefix $(obj)/,$(selinux-y)): $(obj)/flask.h + +quiet_cmd_flask = GEN $(obj)/flask.h $(obj)/av_permissions.h + cmd_flask = scripts/selinux/genheaders/genheaders $(obj)/flask.h $(obj)/av_permissions.h + +targets += flask.h av_permissions.h +$(obj)/flask.h: $(src)/include/classmap.h FORCE + $(call if_changed,flask) diff --git a/security/selinux/avc.c b/security/selinux/avc.c new file mode 100644 index 00000000..d515b212 --- /dev/null +++ b/security/selinux/avc.c @@ -0,0 +1,847 @@ +/* + * Implementation of the kernel access vector cache (AVC). + * + * Authors: Stephen Smalley, <sds@epoch.ncsc.mil> + * James Morris <jmorris@redhat.com> + * + * Update: KaiGai, Kohei <kaigai@ak.jp.nec.com> + * Replaced the avc_lock spinlock by RCU. + * + * Copyright (C) 2003 Red Hat, Inc., James Morris <jmorris@redhat.com> + * + * 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/types.h> +#include <linux/stddef.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/dcache.h> +#include <linux/init.h> +#include <linux/skbuff.h> +#include <linux/percpu.h> +#include <net/sock.h> +#include <linux/un.h> +#include <net/af_unix.h> +#include <linux/ip.h> +#include <linux/audit.h> +#include <linux/ipv6.h> +#include <net/ipv6.h> +#include "avc.h" +#include "avc_ss.h" +#include "classmap.h" + +#define AVC_CACHE_SLOTS 512 +#define AVC_DEF_CACHE_THRESHOLD 512 +#define AVC_CACHE_RECLAIM 16 + +#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS +#define avc_cache_stats_incr(field) this_cpu_inc(avc_cache_stats.field) +#else +#define avc_cache_stats_incr(field) do {} while (0) +#endif + +struct avc_entry { + u32 ssid; + u32 tsid; + u16 tclass; + struct av_decision avd; +}; + +struct avc_node { + struct avc_entry ae; + struct hlist_node list; /* anchored in avc_cache->slots[i] */ + struct rcu_head rhead; +}; + +struct avc_cache { + struct hlist_head slots[AVC_CACHE_SLOTS]; /* head for avc_node->list */ + spinlock_t slots_lock[AVC_CACHE_SLOTS]; /* lock for writes */ + atomic_t lru_hint; /* LRU hint for reclaim scan */ + atomic_t active_nodes; + u32 latest_notif; /* latest revocation notification */ +}; + +struct avc_callback_node { + int (*callback) (u32 event, u32 ssid, u32 tsid, + u16 tclass, u32 perms, + u32 *out_retained); + u32 events; + u32 ssid; + u32 tsid; + u16 tclass; + u32 perms; + struct avc_callback_node *next; +}; + +/* Exported via selinufs */ +unsigned int avc_cache_threshold = AVC_DEF_CACHE_THRESHOLD; + +#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS +DEFINE_PER_CPU(struct avc_cache_stats, avc_cache_stats) = { 0 }; +#endif + +static struct avc_cache avc_cache; +static struct avc_callback_node *avc_callbacks; +static struct kmem_cache *avc_node_cachep; + +static inline int avc_hash(u32 ssid, u32 tsid, u16 tclass) +{ + return (ssid ^ (tsid<<2) ^ (tclass<<4)) & (AVC_CACHE_SLOTS - 1); +} + +/** + * avc_dump_av - Display an access vector in human-readable form. + * @tclass: target security class + * @av: access vector + */ +static void avc_dump_av(struct audit_buffer *ab, u16 tclass, u32 av) +{ + const char **perms; + int i, perm; + + if (av == 0) { + audit_log_format(ab, " null"); + return; + } + + perms = secclass_map[tclass-1].perms; + + audit_log_format(ab, " {"); + i = 0; + perm = 1; + while (i < (sizeof(av) * 8)) { + if ((perm & av) && perms[i]) { + audit_log_format(ab, " %s", perms[i]); + av &= ~perm; + } + i++; + perm <<= 1; + } + + if (av) + audit_log_format(ab, " 0x%x", av); + + audit_log_format(ab, " }"); +} + +/** + * avc_dump_query - Display a SID pair and a class in human-readable form. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + */ +static void avc_dump_query(struct audit_buffer *ab, u32 ssid, u32 tsid, u16 tclass) +{ + int rc; + char *scontext; + u32 scontext_len; + + rc = security_sid_to_context(ssid, &scontext, &scontext_len); + if (rc) + audit_log_format(ab, "ssid=%d", ssid); + else { + audit_log_format(ab, "scontext=%s", scontext); + kfree(scontext); + } + + rc = security_sid_to_context(tsid, &scontext, &scontext_len); + if (rc) + audit_log_format(ab, " tsid=%d", tsid); + else { + audit_log_format(ab, " tcontext=%s", scontext); + kfree(scontext); + } + + BUG_ON(tclass >= ARRAY_SIZE(secclass_map)); + audit_log_format(ab, " tclass=%s", secclass_map[tclass-1].name); +} + +/** + * avc_init - Initialize the AVC. + * + * Initialize the access vector cache. + */ +void __init avc_init(void) +{ + int i; + + for (i = 0; i < AVC_CACHE_SLOTS; i++) { + INIT_HLIST_HEAD(&avc_cache.slots[i]); + spin_lock_init(&avc_cache.slots_lock[i]); + } + atomic_set(&avc_cache.active_nodes, 0); + atomic_set(&avc_cache.lru_hint, 0); + + avc_node_cachep = kmem_cache_create("avc_node", sizeof(struct avc_node), + 0, SLAB_PANIC, NULL); + + audit_log(current->audit_context, GFP_KERNEL, AUDIT_KERNEL, "AVC INITIALIZED\n"); +} + +int avc_get_hash_stats(char *page) +{ + int i, chain_len, max_chain_len, slots_used; + struct avc_node *node; + struct hlist_head *head; + + rcu_read_lock(); + + slots_used = 0; + max_chain_len = 0; + for (i = 0; i < AVC_CACHE_SLOTS; i++) { + head = &avc_cache.slots[i]; + if (!hlist_empty(head)) { + struct hlist_node *next; + + slots_used++; + chain_len = 0; + hlist_for_each_entry_rcu(node, next, head, list) + chain_len++; + if (chain_len > max_chain_len) + max_chain_len = chain_len; + } + } + + rcu_read_unlock(); + + return scnprintf(page, PAGE_SIZE, "entries: %d\nbuckets used: %d/%d\n" + "longest chain: %d\n", + atomic_read(&avc_cache.active_nodes), + slots_used, AVC_CACHE_SLOTS, max_chain_len); +} + +static void avc_node_free(struct rcu_head *rhead) +{ + struct avc_node *node = container_of(rhead, struct avc_node, rhead); + kmem_cache_free(avc_node_cachep, node); + avc_cache_stats_incr(frees); +} + +static void avc_node_delete(struct avc_node *node) +{ + hlist_del_rcu(&node->list); + call_rcu(&node->rhead, avc_node_free); + atomic_dec(&avc_cache.active_nodes); +} + +static void avc_node_kill(struct avc_node *node) +{ + kmem_cache_free(avc_node_cachep, node); + avc_cache_stats_incr(frees); + atomic_dec(&avc_cache.active_nodes); +} + +static void avc_node_replace(struct avc_node *new, struct avc_node *old) +{ + hlist_replace_rcu(&old->list, &new->list); + call_rcu(&old->rhead, avc_node_free); + atomic_dec(&avc_cache.active_nodes); +} + +static inline int avc_reclaim_node(void) +{ + struct avc_node *node; + int hvalue, try, ecx; + unsigned long flags; + struct hlist_head *head; + struct hlist_node *next; + spinlock_t *lock; + + for (try = 0, ecx = 0; try < AVC_CACHE_SLOTS; try++) { + hvalue = atomic_inc_return(&avc_cache.lru_hint) & (AVC_CACHE_SLOTS - 1); + head = &avc_cache.slots[hvalue]; + lock = &avc_cache.slots_lock[hvalue]; + + if (!spin_trylock_irqsave(lock, flags)) + continue; + + rcu_read_lock(); + hlist_for_each_entry(node, next, head, list) { + avc_node_delete(node); + avc_cache_stats_incr(reclaims); + ecx++; + if (ecx >= AVC_CACHE_RECLAIM) { + rcu_read_unlock(); + spin_unlock_irqrestore(lock, flags); + goto out; + } + } + rcu_read_unlock(); + spin_unlock_irqrestore(lock, flags); + } +out: + return ecx; +} + +static struct avc_node *avc_alloc_node(void) +{ + struct avc_node *node; + + node = kmem_cache_zalloc(avc_node_cachep, GFP_ATOMIC); + if (!node) + goto out; + + INIT_HLIST_NODE(&node->list); + avc_cache_stats_incr(allocations); + + if (atomic_inc_return(&avc_cache.active_nodes) > avc_cache_threshold) + avc_reclaim_node(); + +out: + return node; +} + +static void avc_node_populate(struct avc_node *node, u32 ssid, u32 tsid, u16 tclass, struct av_decision *avd) +{ + node->ae.ssid = ssid; + node->ae.tsid = tsid; + node->ae.tclass = tclass; + memcpy(&node->ae.avd, avd, sizeof(node->ae.avd)); +} + +static inline struct avc_node *avc_search_node(u32 ssid, u32 tsid, u16 tclass) +{ + struct avc_node *node, *ret = NULL; + int hvalue; + struct hlist_head *head; + struct hlist_node *next; + + hvalue = avc_hash(ssid, tsid, tclass); + head = &avc_cache.slots[hvalue]; + hlist_for_each_entry_rcu(node, next, head, list) { + if (ssid == node->ae.ssid && + tclass == node->ae.tclass && + tsid == node->ae.tsid) { + ret = node; + break; + } + } + + return ret; +} + +/** + * avc_lookup - Look up an AVC entry. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * + * Look up an AVC entry that is valid for the + * (@ssid, @tsid), interpreting the permissions + * based on @tclass. If a valid AVC entry exists, + * then this function returns the avc_node. + * Otherwise, this function returns NULL. + */ +static struct avc_node *avc_lookup(u32 ssid, u32 tsid, u16 tclass) +{ + struct avc_node *node; + + avc_cache_stats_incr(lookups); + node = avc_search_node(ssid, tsid, tclass); + + if (node) + return node; + + avc_cache_stats_incr(misses); + return NULL; +} + +static int avc_latest_notif_update(int seqno, int is_insert) +{ + int ret = 0; + static DEFINE_SPINLOCK(notif_lock); + unsigned long flag; + + spin_lock_irqsave(¬if_lock, flag); + if (is_insert) { + if (seqno < avc_cache.latest_notif) { + printk(KERN_WARNING "SELinux: avc: seqno %d < latest_notif %d\n", + seqno, avc_cache.latest_notif); + ret = -EAGAIN; + } + } else { + if (seqno > avc_cache.latest_notif) + avc_cache.latest_notif = seqno; + } + spin_unlock_irqrestore(¬if_lock, flag); + + return ret; +} + +/** + * avc_insert - Insert an AVC entry. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @avd: resulting av decision + * + * Insert an AVC entry for the SID pair + * (@ssid, @tsid) and class @tclass. + * The access vectors and the sequence number are + * normally provided by the security server in + * response to a security_compute_av() call. If the + * sequence number @avd->seqno is not less than the latest + * revocation notification, then the function copies + * the access vectors into a cache entry, returns + * avc_node inserted. Otherwise, this function returns NULL. + */ +static struct avc_node *avc_insert(u32 ssid, u32 tsid, u16 tclass, struct av_decision *avd) +{ + struct avc_node *pos, *node = NULL; + int hvalue; + unsigned long flag; + + if (avc_latest_notif_update(avd->seqno, 1)) + goto out; + + node = avc_alloc_node(); + if (node) { + struct hlist_head *head; + struct hlist_node *next; + spinlock_t *lock; + + hvalue = avc_hash(ssid, tsid, tclass); + avc_node_populate(node, ssid, tsid, tclass, avd); + + head = &avc_cache.slots[hvalue]; + lock = &avc_cache.slots_lock[hvalue]; + + spin_lock_irqsave(lock, flag); + hlist_for_each_entry(pos, next, head, list) { + if (pos->ae.ssid == ssid && + pos->ae.tsid == tsid && + pos->ae.tclass == tclass) { + avc_node_replace(node, pos); + goto found; + } + } + hlist_add_head_rcu(&node->list, head); +found: + spin_unlock_irqrestore(lock, flag); + } +out: + return node; +} + +/** + * avc_audit_pre_callback - SELinux specific information + * will be called by generic audit code + * @ab: the audit buffer + * @a: audit_data + */ +static void avc_audit_pre_callback(struct audit_buffer *ab, void *a) +{ + struct common_audit_data *ad = a; + audit_log_format(ab, "avc: %s ", + ad->selinux_audit_data.denied ? "denied" : "granted"); + avc_dump_av(ab, ad->selinux_audit_data.tclass, + ad->selinux_audit_data.audited); + audit_log_format(ab, " for "); +} + +/** + * avc_audit_post_callback - SELinux specific information + * will be called by generic audit code + * @ab: the audit buffer + * @a: audit_data + */ +static void avc_audit_post_callback(struct audit_buffer *ab, void *a) +{ + struct common_audit_data *ad = a; + audit_log_format(ab, " "); + avc_dump_query(ab, ad->selinux_audit_data.ssid, + ad->selinux_audit_data.tsid, + ad->selinux_audit_data.tclass); +} + +/** + * avc_audit - Audit the granting or denial of permissions. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @requested: requested permissions + * @avd: access vector decisions + * @result: result from avc_has_perm_noaudit + * @a: auxiliary audit data + * @flags: VFS walk flags + * + * Audit the granting or denial of permissions in accordance + * with the policy. This function is typically called by + * avc_has_perm() after a permission check, but can also be + * called directly by callers who use avc_has_perm_noaudit() + * in order to separate the permission check from the auditing. + * For example, this separation is useful when the permission check must + * be performed under a lock, to allow the lock to be released + * before calling the auditing code. + */ +int avc_audit(u32 ssid, u32 tsid, + u16 tclass, u32 requested, + struct av_decision *avd, int result, struct common_audit_data *a, + unsigned flags) +{ + struct common_audit_data stack_data; + u32 denied, audited; + denied = requested & ~avd->allowed; + if (denied) { + audited = denied & avd->auditdeny; + /* + * 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; + if (!audited) + return 0; + + if (!a) { + a = &stack_data; + COMMON_AUDIT_DATA_INIT(a, NONE); + } + + /* + * When in a RCU walk do the audit on the RCU retry. This is because + * the collection of the dname in an inode audit message is not RCU + * safe. Note this may drop some audits when the situation changes + * during retry. However this is logically just as if the operation + * happened a little later. + */ + if ((a->type == LSM_AUDIT_DATA_INODE) && + (flags & IPERM_FLAG_RCU)) + return -ECHILD; + + a->selinux_audit_data.tclass = tclass; + a->selinux_audit_data.requested = requested; + a->selinux_audit_data.ssid = ssid; + a->selinux_audit_data.tsid = tsid; + a->selinux_audit_data.audited = audited; + a->selinux_audit_data.denied = denied; + a->lsm_pre_audit = avc_audit_pre_callback; + a->lsm_post_audit = avc_audit_post_callback; + common_lsm_audit(a); + return 0; +} + +/** + * avc_add_callback - Register a callback for security events. + * @callback: callback function + * @events: security events + * @ssid: source security identifier or %SECSID_WILD + * @tsid: target security identifier or %SECSID_WILD + * @tclass: target security class + * @perms: permissions + * + * Register a callback function for events in the set @events + * related to the SID pair (@ssid, @tsid) + * and the permissions @perms, interpreting + * @perms based on @tclass. Returns %0 on success or + * -%ENOMEM if insufficient memory exists to add the callback. + */ +int avc_add_callback(int (*callback)(u32 event, u32 ssid, u32 tsid, + u16 tclass, u32 perms, + u32 *out_retained), + u32 events, u32 ssid, u32 tsid, + u16 tclass, u32 perms) +{ + struct avc_callback_node *c; + int rc = 0; + + c = kmalloc(sizeof(*c), GFP_ATOMIC); + if (!c) { + rc = -ENOMEM; + goto out; + } + + c->callback = callback; + c->events = events; + c->ssid = ssid; + c->tsid = tsid; + c->perms = perms; + c->next = avc_callbacks; + avc_callbacks = c; +out: + return rc; +} + +static inline int avc_sidcmp(u32 x, u32 y) +{ + return (x == y || x == SECSID_WILD || y == SECSID_WILD); +} + +/** + * avc_update_node Update an AVC entry + * @event : Updating event + * @perms : Permission mask bits + * @ssid,@tsid,@tclass : identifier of an AVC entry + * @seqno : sequence number when decision was made + * + * if a valid AVC entry doesn't exist,this function returns -ENOENT. + * if kmalloc() called internal returns NULL, this function returns -ENOMEM. + * otherwise, this function updates the AVC entry. The original AVC-entry object + * will release later by RCU. + */ +static int avc_update_node(u32 event, u32 perms, u32 ssid, u32 tsid, u16 tclass, + u32 seqno) +{ + int hvalue, rc = 0; + unsigned long flag; + struct avc_node *pos, *node, *orig = NULL; + struct hlist_head *head; + struct hlist_node *next; + spinlock_t *lock; + + node = avc_alloc_node(); + if (!node) { + rc = -ENOMEM; + goto out; + } + + /* Lock the target slot */ + hvalue = avc_hash(ssid, tsid, tclass); + + head = &avc_cache.slots[hvalue]; + lock = &avc_cache.slots_lock[hvalue]; + + spin_lock_irqsave(lock, flag); + + hlist_for_each_entry(pos, next, head, list) { + if (ssid == pos->ae.ssid && + tsid == pos->ae.tsid && + tclass == pos->ae.tclass && + seqno == pos->ae.avd.seqno){ + orig = pos; + break; + } + } + + if (!orig) { + rc = -ENOENT; + avc_node_kill(node); + goto out_unlock; + } + + /* + * Copy and replace original node. + */ + + avc_node_populate(node, ssid, tsid, tclass, &orig->ae.avd); + + switch (event) { + case AVC_CALLBACK_GRANT: + node->ae.avd.allowed |= perms; + break; + case AVC_CALLBACK_TRY_REVOKE: + case AVC_CALLBACK_REVOKE: + node->ae.avd.allowed &= ~perms; + break; + case AVC_CALLBACK_AUDITALLOW_ENABLE: + node->ae.avd.auditallow |= perms; + break; + case AVC_CALLBACK_AUDITALLOW_DISABLE: + node->ae.avd.auditallow &= ~perms; + break; + case AVC_CALLBACK_AUDITDENY_ENABLE: + node->ae.avd.auditdeny |= perms; + break; + case AVC_CALLBACK_AUDITDENY_DISABLE: + node->ae.avd.auditdeny &= ~perms; + break; + } + avc_node_replace(node, orig); +out_unlock: + spin_unlock_irqrestore(lock, flag); +out: + return rc; +} + +/** + * avc_flush - Flush the cache + */ +static void avc_flush(void) +{ + struct hlist_head *head; + struct hlist_node *next; + struct avc_node *node; + spinlock_t *lock; + unsigned long flag; + int i; + + for (i = 0; i < AVC_CACHE_SLOTS; i++) { + head = &avc_cache.slots[i]; + lock = &avc_cache.slots_lock[i]; + + spin_lock_irqsave(lock, flag); + /* + * With preemptable RCU, the outer spinlock does not + * prevent RCU grace periods from ending. + */ + rcu_read_lock(); + hlist_for_each_entry(node, next, head, list) + avc_node_delete(node); + rcu_read_unlock(); + spin_unlock_irqrestore(lock, flag); + } +} + +/** + * avc_ss_reset - Flush the cache and revalidate migrated permissions. + * @seqno: policy sequence number + */ +int avc_ss_reset(u32 seqno) +{ + struct avc_callback_node *c; + int rc = 0, tmprc; + + avc_flush(); + + for (c = avc_callbacks; c; c = c->next) { + if (c->events & AVC_CALLBACK_RESET) { + tmprc = c->callback(AVC_CALLBACK_RESET, + 0, 0, 0, 0, NULL); + /* save the first error encountered for the return + value and continue processing the callbacks */ + if (!rc) + rc = tmprc; + } + } + + avc_latest_notif_update(seqno, 0); + return rc; +} + +/** + * avc_has_perm_noaudit - Check permissions but perform no auditing. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @requested: requested permissions, interpreted based on @tclass + * @flags: AVC_STRICT or 0 + * @avd: access vector decisions + * + * Check the AVC to determine whether the @requested permissions are granted + * for the SID pair (@ssid, @tsid), interpreting the permissions + * based on @tclass, and call the security server on a cache miss to obtain + * a new decision and add it to the cache. Return a copy of the decisions + * in @avd. Return %0 if all @requested permissions are granted, + * -%EACCES if any permissions are denied, or another -errno upon + * other errors. This function is typically called by avc_has_perm(), + * but may also be called directly to separate permission checking from + * auditing, e.g. in cases where a lock must be held for the check but + * should be released for the auditing. + */ +int avc_has_perm_noaudit(u32 ssid, u32 tsid, + u16 tclass, u32 requested, + unsigned flags, + struct av_decision *avd) +{ + struct avc_node *node; + int rc = 0; + u32 denied; + + BUG_ON(!requested); + + rcu_read_lock(); + + node = avc_lookup(ssid, tsid, tclass); + if (unlikely(!node)) { + rcu_read_unlock(); + security_compute_av(ssid, tsid, tclass, avd); + rcu_read_lock(); + node = avc_insert(ssid, tsid, tclass, avd); + } else { + memcpy(avd, &node->ae.avd, sizeof(*avd)); + avd = &node->ae.avd; + } + + denied = requested & ~(avd->allowed); + + if (denied) { + if (flags & AVC_STRICT) + rc = -EACCES; + else if (!selinux_enforcing || (avd->flags & AVD_FLAGS_PERMISSIVE)) + avc_update_node(AVC_CALLBACK_GRANT, requested, ssid, + tsid, tclass, avd->seqno); + else + rc = -EACCES; + } + + rcu_read_unlock(); + return rc; +} + +/** + * avc_has_perm - Check permissions and perform any appropriate auditing. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @requested: requested permissions, interpreted based on @tclass + * @auditdata: auxiliary audit data + * @flags: VFS walk flags + * + * Check the AVC to determine whether the @requested permissions are granted + * for the SID pair (@ssid, @tsid), interpreting the permissions + * based on @tclass, and call the security server on a cache miss to obtain + * a new decision and add it to the cache. Audit the granting or denial of + * permissions in accordance with the policy. Return %0 if all @requested + * permissions are granted, -%EACCES if any permissions are denied, or + * another -errno upon other errors. + */ +int avc_has_perm_flags(u32 ssid, u32 tsid, u16 tclass, + u32 requested, struct common_audit_data *auditdata, + unsigned flags) +{ + struct av_decision avd; + int rc, rc2; + + rc = avc_has_perm_noaudit(ssid, tsid, tclass, requested, 0, &avd); + + rc2 = avc_audit(ssid, tsid, tclass, requested, &avd, rc, auditdata, + flags); + if (rc2) + return rc2; + return rc; +} + +u32 avc_policy_seqno(void) +{ + return avc_cache.latest_notif; +} + +void avc_disable(void) +{ + /* + * If you are looking at this because you have realized that we are + * not destroying the avc_node_cachep it might be easy to fix, but + * I don't know the memory barrier semantics well enough to know. It's + * possible that some other task dereferenced security_ops when + * it still pointed to selinux operations. If that is the case it's + * possible that it is about to use the avc and is about to need the + * avc_node_cachep. I know I could wrap the security.c security_ops call + * in an rcu_lock, but seriously, it's not worth it. Instead I just flush + * the cache and get that memory back. + */ + if (avc_node_cachep) { + avc_flush(); + /* kmem_cache_destroy(avc_node_cachep); */ + } +} diff --git a/security/selinux/exports.c b/security/selinux/exports.c new file mode 100644 index 00000000..90664385 --- /dev/null +++ b/security/selinux/exports.c @@ -0,0 +1,22 @@ +/* + * SELinux services exported to the rest of the kernel. + * + * Author: James Morris <jmorris@redhat.com> + * + * Copyright (C) 2005 Red Hat, Inc., James Morris <jmorris@redhat.com> + * Copyright (C) 2006 Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * Copyright (C) 2006 IBM Corporation, Timothy R. Chavez <tinytim@us.ibm.com> + * + * 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/module.h> + +#include "security.h" + +bool selinux_is_enabled(void) +{ + return selinux_enabled; +} +EXPORT_SYMBOL_GPL(selinux_is_enabled); diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c new file mode 100644 index 00000000..20219ef5 --- /dev/null +++ b/security/selinux/hooks.c @@ -0,0 +1,5835 @@ +/* + * NSA Security-Enhanced Linux (SELinux) security module + * + * This file contains the SELinux hook function implementations. + * + * Authors: Stephen Smalley, <sds@epoch.ncsc.mil> + * Chris Vance, <cvance@nai.com> + * Wayne Salamon, <wsalamon@nai.com> + * James Morris <jmorris@redhat.com> + * + * Copyright (C) 2001,2002 Networks Associates Technology, Inc. + * Copyright (C) 2003-2008 Red Hat, Inc., James Morris <jmorris@redhat.com> + * Eric Paris <eparis@redhat.com> + * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. + * <dgoeddel@trustedcs.com> + * Copyright (C) 2006, 2007, 2009 Hewlett-Packard Development Company, L.P. + * Paul Moore <paul.moore@hp.com> + * Copyright (C) 2007 Hitachi Software Engineering Co., Ltd. + * Yuichi Nakamura <ynakam@hitachisoft.jp> + * + * 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/init.h> +#include <linux/kd.h> +#include <linux/kernel.h> +#include <linux/tracehook.h> +#include <linux/errno.h> +#include <linux/ext2_fs.h> +#include <linux/sched.h> +#include <linux/security.h> +#include <linux/xattr.h> +#include <linux/capability.h> +#include <linux/unistd.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/slab.h> +#include <linux/pagemap.h> +#include <linux/proc_fs.h> +#include <linux/swap.h> +#include <linux/spinlock.h> +#include <linux/syscalls.h> +#include <linux/dcache.h> +#include <linux/file.h> +#include <linux/fdtable.h> +#include <linux/namei.h> +#include <linux/mount.h> +#include <linux/netfilter_ipv4.h> +#include <linux/netfilter_ipv6.h> +#include <linux/tty.h> +#include <net/icmp.h> +#include <net/ip.h> /* for local_port_range[] */ +#include <net/tcp.h> /* struct or_callable used in sock_rcv_skb */ +#include <net/net_namespace.h> +#include <net/netlabel.h> +#include <linux/uaccess.h> +#include <asm/ioctls.h> +#include <asm/atomic.h> +#include <linux/bitops.h> +#include <linux/interrupt.h> +#include <linux/netdevice.h> /* for network interface checks */ +#include <linux/netlink.h> +#include <linux/tcp.h> +#include <linux/udp.h> +#include <linux/dccp.h> +#include <linux/quota.h> +#include <linux/un.h> /* for Unix socket types */ +#include <net/af_unix.h> /* for Unix socket types */ +#include <linux/parser.h> +#include <linux/nfs_mount.h> +#include <net/ipv6.h> +#include <linux/hugetlb.h> +#include <linux/personality.h> +#include <linux/audit.h> +#include <linux/string.h> +#include <linux/selinux.h> +#include <linux/mutex.h> +#include <linux/posix-timers.h> +#include <linux/syslog.h> +#include <linux/user_namespace.h> + +#include "avc.h" +#include "objsec.h" +#include "netif.h" +#include "netnode.h" +#include "netport.h" +#include "xfrm.h" +#include "netlabel.h" +#include "audit.h" + +#define NUM_SEL_MNT_OPTS 5 + +extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm); +extern struct security_operations *security_ops; + +/* SECMARK reference count */ +atomic_t selinux_secmark_refcount = ATOMIC_INIT(0); + +#ifdef CONFIG_SECURITY_SELINUX_DEVELOP +int selinux_enforcing; + +static int __init enforcing_setup(char *str) +{ + unsigned long enforcing; + if (!strict_strtoul(str, 0, &enforcing)) + selinux_enforcing = enforcing ? 1 : 0; + return 1; +} +__setup("enforcing=", enforcing_setup); +#endif + +#ifdef CONFIG_SECURITY_SELINUX_BOOTPARAM +int selinux_enabled = CONFIG_SECURITY_SELINUX_BOOTPARAM_VALUE; + +static int __init selinux_enabled_setup(char *str) +{ + unsigned long enabled; + if (!strict_strtoul(str, 0, &enabled)) + selinux_enabled = enabled ? 1 : 0; + return 1; +} +__setup("selinux=", selinux_enabled_setup); +#else +int selinux_enabled = 1; +#endif + +static struct kmem_cache *sel_inode_cache; + +/** + * selinux_secmark_enabled - Check to see if SECMARK is currently enabled + * + * Description: + * This function checks the SECMARK reference counter to see if any SECMARK + * targets are currently configured, if the reference counter is greater than + * zero SECMARK is considered to be enabled. Returns true (1) if SECMARK is + * enabled, false (0) if SECMARK is disabled. + * + */ +static int selinux_secmark_enabled(void) +{ + return (atomic_read(&selinux_secmark_refcount) > 0); +} + +/* + * initialise the security for the init task + */ +static void cred_init_security(void) +{ + struct cred *cred = (struct cred *) current->real_cred; + struct task_security_struct *tsec; + + tsec = kzalloc(sizeof(struct task_security_struct), GFP_KERNEL); + if (!tsec) + panic("SELinux: Failed to initialize initial task.\n"); + + tsec->osid = tsec->sid = SECINITSID_KERNEL; + cred->security = tsec; +} + +/* + * get the security ID of a set of credentials + */ +static inline u32 cred_sid(const struct cred *cred) +{ + const struct task_security_struct *tsec; + + tsec = cred->security; + return tsec->sid; +} + +/* + * get the objective security ID of a task + */ +static inline u32 task_sid(const struct task_struct *task) +{ + u32 sid; + + rcu_read_lock(); + sid = cred_sid(__task_cred(task)); + rcu_read_unlock(); + return sid; +} + +/* + * get the subjective security ID of the current task + */ +static inline u32 current_sid(void) +{ + const struct task_security_struct *tsec = current_security(); + + return tsec->sid; +} + +/* Allocate and free functions for each kind of security blob. */ + +static int inode_alloc_security(struct inode *inode) +{ + struct inode_security_struct *isec; + u32 sid = current_sid(); + + isec = kmem_cache_zalloc(sel_inode_cache, GFP_NOFS); + if (!isec) + return -ENOMEM; + + mutex_init(&isec->lock); + INIT_LIST_HEAD(&isec->list); + isec->inode = inode; + isec->sid = SECINITSID_UNLABELED; + isec->sclass = SECCLASS_FILE; + isec->task_sid = sid; + inode->i_security = isec; + + return 0; +} + +static void inode_free_security(struct inode *inode) +{ + struct inode_security_struct *isec = inode->i_security; + struct superblock_security_struct *sbsec = inode->i_sb->s_security; + + spin_lock(&sbsec->isec_lock); + if (!list_empty(&isec->list)) + list_del_init(&isec->list); + spin_unlock(&sbsec->isec_lock); + + inode->i_security = NULL; + kmem_cache_free(sel_inode_cache, isec); +} + +static int file_alloc_security(struct file *file) +{ + struct file_security_struct *fsec; + u32 sid = current_sid(); + + fsec = kzalloc(sizeof(struct file_security_struct), GFP_KERNEL); + if (!fsec) + return -ENOMEM; + + fsec->sid = sid; + fsec->fown_sid = sid; + file->f_security = fsec; + + return 0; +} + +static void file_free_security(struct file *file) +{ + struct file_security_struct *fsec = file->f_security; + file->f_security = NULL; + kfree(fsec); +} + +static int superblock_alloc_security(struct super_block *sb) +{ + struct superblock_security_struct *sbsec; + + sbsec = kzalloc(sizeof(struct superblock_security_struct), GFP_KERNEL); + if (!sbsec) + return -ENOMEM; + + mutex_init(&sbsec->lock); + INIT_LIST_HEAD(&sbsec->isec_head); + spin_lock_init(&sbsec->isec_lock); + sbsec->sb = sb; + sbsec->sid = SECINITSID_UNLABELED; + sbsec->def_sid = SECINITSID_FILE; + sbsec->mntpoint_sid = SECINITSID_UNLABELED; + sb->s_security = sbsec; + + return 0; +} + +static void superblock_free_security(struct super_block *sb) +{ + struct superblock_security_struct *sbsec = sb->s_security; + sb->s_security = NULL; + kfree(sbsec); +} + +/* The security server must be initialized before + any labeling or access decisions can be provided. */ +extern int ss_initialized; + +/* The file system's label must be initialized prior to use. */ + +static const char *labeling_behaviors[6] = { + "uses xattr", + "uses transition SIDs", + "uses task SIDs", + "uses genfs_contexts", + "not configured for labeling", + "uses mountpoint labeling", +}; + +static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dentry); + +static inline int inode_doinit(struct inode *inode) +{ + return inode_doinit_with_dentry(inode, NULL); +} + +enum { + Opt_error = -1, + Opt_context = 1, + Opt_fscontext = 2, + Opt_defcontext = 3, + Opt_rootcontext = 4, + Opt_labelsupport = 5, +}; + +static const match_table_t tokens = { + {Opt_context, CONTEXT_STR "%s"}, + {Opt_fscontext, FSCONTEXT_STR "%s"}, + {Opt_defcontext, DEFCONTEXT_STR "%s"}, + {Opt_rootcontext, ROOTCONTEXT_STR "%s"}, + {Opt_labelsupport, LABELSUPP_STR}, + {Opt_error, NULL}, +}; + +#define SEL_MOUNT_FAIL_MSG "SELinux: duplicate or incompatible mount options\n" + +static int may_context_mount_sb_relabel(u32 sid, + struct superblock_security_struct *sbsec, + const struct cred *cred) +{ + const struct task_security_struct *tsec = cred->security; + int rc; + + rc = avc_has_perm(tsec->sid, sbsec->sid, SECCLASS_FILESYSTEM, + FILESYSTEM__RELABELFROM, NULL); + if (rc) + return rc; + + rc = avc_has_perm(tsec->sid, sid, SECCLASS_FILESYSTEM, + FILESYSTEM__RELABELTO, NULL); + return rc; +} + +static int may_context_mount_inode_relabel(u32 sid, + struct superblock_security_struct *sbsec, + const struct cred *cred) +{ + const struct task_security_struct *tsec = cred->security; + int rc; + rc = avc_has_perm(tsec->sid, sbsec->sid, SECCLASS_FILESYSTEM, + FILESYSTEM__RELABELFROM, NULL); + if (rc) + return rc; + + rc = avc_has_perm(sid, sbsec->sid, SECCLASS_FILESYSTEM, + FILESYSTEM__ASSOCIATE, NULL); + return rc; +} + +static int sb_finish_set_opts(struct super_block *sb) +{ + struct superblock_security_struct *sbsec = sb->s_security; + struct dentry *root = sb->s_root; + struct inode *root_inode = root->d_inode; + int rc = 0; + + if (sbsec->behavior == SECURITY_FS_USE_XATTR) { + /* Make sure that the xattr handler exists and that no + error other than -ENODATA is returned by getxattr on + the root directory. -ENODATA is ok, as this may be + the first boot of the SELinux kernel before we have + assigned xattr values to the filesystem. */ + if (!root_inode->i_op->getxattr) { + printk(KERN_WARNING "SELinux: (dev %s, type %s) has no " + "xattr support\n", sb->s_id, sb->s_type->name); + rc = -EOPNOTSUPP; + goto out; + } + rc = root_inode->i_op->getxattr(root, XATTR_NAME_SELINUX, NULL, 0); + if (rc < 0 && rc != -ENODATA) { + if (rc == -EOPNOTSUPP) + printk(KERN_WARNING "SELinux: (dev %s, type " + "%s) has no security xattr handler\n", + sb->s_id, sb->s_type->name); + else + printk(KERN_WARNING "SELinux: (dev %s, type " + "%s) getxattr errno %d\n", sb->s_id, + sb->s_type->name, -rc); + goto out; + } + } + + sbsec->flags |= (SE_SBINITIALIZED | SE_SBLABELSUPP); + + if (sbsec->behavior > ARRAY_SIZE(labeling_behaviors)) + printk(KERN_ERR "SELinux: initialized (dev %s, type %s), unknown behavior\n", + sb->s_id, sb->s_type->name); + else + printk(KERN_DEBUG "SELinux: initialized (dev %s, type %s), %s\n", + sb->s_id, sb->s_type->name, + labeling_behaviors[sbsec->behavior-1]); + + if (sbsec->behavior == SECURITY_FS_USE_GENFS || + sbsec->behavior == SECURITY_FS_USE_MNTPOINT || + sbsec->behavior == SECURITY_FS_USE_NONE || + sbsec->behavior > ARRAY_SIZE(labeling_behaviors)) + sbsec->flags &= ~SE_SBLABELSUPP; + + /* Special handling for sysfs. Is genfs but also has setxattr handler*/ + if (strncmp(sb->s_type->name, "sysfs", sizeof("sysfs")) == 0) + sbsec->flags |= SE_SBLABELSUPP; + + /* Initialize the root inode. */ + rc = inode_doinit_with_dentry(root_inode, root); + + /* Initialize any other inodes associated with the superblock, e.g. + inodes created prior to initial policy load or inodes created + during get_sb by a pseudo filesystem that directly + populates itself. */ + spin_lock(&sbsec->isec_lock); +next_inode: + if (!list_empty(&sbsec->isec_head)) { + struct inode_security_struct *isec = + list_entry(sbsec->isec_head.next, + struct inode_security_struct, list); + struct inode *inode = isec->inode; + spin_unlock(&sbsec->isec_lock); + inode = igrab(inode); + if (inode) { + if (!IS_PRIVATE(inode)) + inode_doinit(inode); + iput(inode); + } + spin_lock(&sbsec->isec_lock); + list_del_init(&isec->list); + goto next_inode; + } + spin_unlock(&sbsec->isec_lock); +out: + return rc; +} + +/* + * This function should allow an FS to ask what it's mount security + * options were so it can use those later for submounts, displaying + * mount options, or whatever. + */ +static int selinux_get_mnt_opts(const struct super_block *sb, + struct security_mnt_opts *opts) +{ + int rc = 0, i; + struct superblock_security_struct *sbsec = sb->s_security; + char *context = NULL; + u32 len; + char tmp; + + security_init_mnt_opts(opts); + + if (!(sbsec->flags & SE_SBINITIALIZED)) + return -EINVAL; + + if (!ss_initialized) + return -EINVAL; + + tmp = sbsec->flags & SE_MNTMASK; + /* count the number of mount options for this sb */ + for (i = 0; i < 8; i++) { + if (tmp & 0x01) + opts->num_mnt_opts++; + tmp >>= 1; + } + /* Check if the Label support flag is set */ + if (sbsec->flags & SE_SBLABELSUPP) + opts->num_mnt_opts++; + + opts->mnt_opts = kcalloc(opts->num_mnt_opts, sizeof(char *), GFP_ATOMIC); + if (!opts->mnt_opts) { + rc = -ENOMEM; + goto out_free; + } + + opts->mnt_opts_flags = kcalloc(opts->num_mnt_opts, sizeof(int), GFP_ATOMIC); + if (!opts->mnt_opts_flags) { + rc = -ENOMEM; + goto out_free; + } + + i = 0; + if (sbsec->flags & FSCONTEXT_MNT) { + rc = security_sid_to_context(sbsec->sid, &context, &len); + if (rc) + goto out_free; + opts->mnt_opts[i] = context; + opts->mnt_opts_flags[i++] = FSCONTEXT_MNT; + } + if (sbsec->flags & CONTEXT_MNT) { + rc = security_sid_to_context(sbsec->mntpoint_sid, &context, &len); + if (rc) + goto out_free; + opts->mnt_opts[i] = context; + opts->mnt_opts_flags[i++] = CONTEXT_MNT; + } + if (sbsec->flags & DEFCONTEXT_MNT) { + rc = security_sid_to_context(sbsec->def_sid, &context, &len); + if (rc) + goto out_free; + opts->mnt_opts[i] = context; + opts->mnt_opts_flags[i++] = DEFCONTEXT_MNT; + } + if (sbsec->flags & ROOTCONTEXT_MNT) { + struct inode *root = sbsec->sb->s_root->d_inode; + struct inode_security_struct *isec = root->i_security; + + rc = security_sid_to_context(isec->sid, &context, &len); + if (rc) + goto out_free; + opts->mnt_opts[i] = context; + opts->mnt_opts_flags[i++] = ROOTCONTEXT_MNT; + } + if (sbsec->flags & SE_SBLABELSUPP) { + opts->mnt_opts[i] = NULL; + opts->mnt_opts_flags[i++] = SE_SBLABELSUPP; + } + + BUG_ON(i != opts->num_mnt_opts); + + return 0; + +out_free: + security_free_mnt_opts(opts); + return rc; +} + +static int bad_option(struct superblock_security_struct *sbsec, char flag, + u32 old_sid, u32 new_sid) +{ + char mnt_flags = sbsec->flags & SE_MNTMASK; + + /* check if the old mount command had the same options */ + if (sbsec->flags & SE_SBINITIALIZED) + if (!(sbsec->flags & flag) || + (old_sid != new_sid)) + return 1; + + /* check if we were passed the same options twice, + * aka someone passed context=a,context=b + */ + if (!(sbsec->flags & SE_SBINITIALIZED)) + if (mnt_flags & flag) + return 1; + return 0; +} + +/* + * Allow filesystems with binary mount data to explicitly set mount point + * labeling information. + */ +static int selinux_set_mnt_opts(struct super_block *sb, + struct security_mnt_opts *opts) +{ + const struct cred *cred = current_cred(); + int rc = 0, i; + struct superblock_security_struct *sbsec = sb->s_security; + const char *name = sb->s_type->name; + struct inode *inode = sbsec->sb->s_root->d_inode; + struct inode_security_struct *root_isec = inode->i_security; + u32 fscontext_sid = 0, context_sid = 0, rootcontext_sid = 0; + u32 defcontext_sid = 0; + char **mount_options = opts->mnt_opts; + int *flags = opts->mnt_opts_flags; + int num_opts = opts->num_mnt_opts; + + mutex_lock(&sbsec->lock); + + if (!ss_initialized) { + if (!num_opts) { + /* Defer initialization until selinux_complete_init, + after the initial policy is loaded and the security + server is ready to handle calls. */ + goto out; + } + rc = -EINVAL; + printk(KERN_WARNING "SELinux: Unable to set superblock options " + "before the security server is initialized\n"); + goto out; + } + + /* + * Binary mount data FS will come through this function twice. Once + * from an explicit call and once from the generic calls from the vfs. + * Since the generic VFS calls will not contain any security mount data + * we need to skip the double mount verification. + * + * This does open a hole in which we will not notice if the first + * mount using this sb set explict options and a second mount using + * this sb does not set any security options. (The first options + * will be used for both mounts) + */ + if ((sbsec->flags & SE_SBINITIALIZED) && (sb->s_type->fs_flags & FS_BINARY_MOUNTDATA) + && (num_opts == 0)) + goto out; + + /* + * parse the mount options, check if they are valid sids. + * also check if someone is trying to mount the same sb more + * than once with different security options. + */ + for (i = 0; i < num_opts; i++) { + u32 sid; + + if (flags[i] == SE_SBLABELSUPP) + continue; + rc = security_context_to_sid(mount_options[i], + strlen(mount_options[i]), &sid); + if (rc) { + printk(KERN_WARNING "SELinux: security_context_to_sid" + "(%s) failed for (dev %s, type %s) errno=%d\n", + mount_options[i], sb->s_id, name, rc); + goto out; + } + switch (flags[i]) { + case FSCONTEXT_MNT: + fscontext_sid = sid; + + if (bad_option(sbsec, FSCONTEXT_MNT, sbsec->sid, + fscontext_sid)) + goto out_double_mount; + + sbsec->flags |= FSCONTEXT_MNT; + break; + case CONTEXT_MNT: + context_sid = sid; + + if (bad_option(sbsec, CONTEXT_MNT, sbsec->mntpoint_sid, + context_sid)) + goto out_double_mount; + + sbsec->flags |= CONTEXT_MNT; + break; + case ROOTCONTEXT_MNT: + rootcontext_sid = sid; + + if (bad_option(sbsec, ROOTCONTEXT_MNT, root_isec->sid, + rootcontext_sid)) + goto out_double_mount; + + sbsec->flags |= ROOTCONTEXT_MNT; + + break; + case DEFCONTEXT_MNT: + defcontext_sid = sid; + + if (bad_option(sbsec, DEFCONTEXT_MNT, sbsec->def_sid, + defcontext_sid)) + goto out_double_mount; + + sbsec->flags |= DEFCONTEXT_MNT; + + break; + default: + rc = -EINVAL; + goto out; + } + } + + if (sbsec->flags & SE_SBINITIALIZED) { + /* previously mounted with options, but not on this attempt? */ + if ((sbsec->flags & SE_MNTMASK) && !num_opts) + goto out_double_mount; + rc = 0; + goto out; + } + + if (strcmp(sb->s_type->name, "proc") == 0) + sbsec->flags |= SE_SBPROC; + + /* Determine the labeling behavior to use for this filesystem type. */ + rc = security_fs_use((sbsec->flags & SE_SBPROC) ? "proc" : sb->s_type->name, &sbsec->behavior, &sbsec->sid); + if (rc) { + printk(KERN_WARNING "%s: security_fs_use(%s) returned %d\n", + __func__, sb->s_type->name, rc); + goto out; + } + + /* sets the context of the superblock for the fs being mounted. */ + if (fscontext_sid) { + rc = may_context_mount_sb_relabel(fscontext_sid, sbsec, cred); + if (rc) + goto out; + + sbsec->sid = fscontext_sid; + } + + /* + * Switch to using mount point labeling behavior. + * sets the label used on all file below the mountpoint, and will set + * the superblock context if not already set. + */ + if (context_sid) { + if (!fscontext_sid) { + rc = may_context_mount_sb_relabel(context_sid, sbsec, + cred); + if (rc) + goto out; + sbsec->sid = context_sid; + } else { + rc = may_context_mount_inode_relabel(context_sid, sbsec, + cred); + if (rc) + goto out; + } + if (!rootcontext_sid) + rootcontext_sid = context_sid; + + sbsec->mntpoint_sid = context_sid; + sbsec->behavior = SECURITY_FS_USE_MNTPOINT; + } + + if (rootcontext_sid) { + rc = may_context_mount_inode_relabel(rootcontext_sid, sbsec, + cred); + if (rc) + goto out; + + root_isec->sid = rootcontext_sid; + root_isec->initialized = 1; + } + + if (defcontext_sid) { + if (sbsec->behavior != SECURITY_FS_USE_XATTR) { + rc = -EINVAL; + printk(KERN_WARNING "SELinux: defcontext option is " + "invalid for this filesystem type\n"); + goto out; + } + + if (defcontext_sid != sbsec->def_sid) { + rc = may_context_mount_inode_relabel(defcontext_sid, + sbsec, cred); + if (rc) + goto out; + } + + sbsec->def_sid = defcontext_sid; + } + + rc = sb_finish_set_opts(sb); +out: + mutex_unlock(&sbsec->lock); + return rc; +out_double_mount: + rc = -EINVAL; + printk(KERN_WARNING "SELinux: mount invalid. Same superblock, different " + "security settings for (dev %s, type %s)\n", sb->s_id, name); + goto out; +} + +static void selinux_sb_clone_mnt_opts(const struct super_block *oldsb, + struct super_block *newsb) +{ + const struct superblock_security_struct *oldsbsec = oldsb->s_security; + struct superblock_security_struct *newsbsec = newsb->s_security; + + int set_fscontext = (oldsbsec->flags & FSCONTEXT_MNT); + int set_context = (oldsbsec->flags & CONTEXT_MNT); + int set_rootcontext = (oldsbsec->flags & ROOTCONTEXT_MNT); + + /* + * if the parent was able to be mounted it clearly had no special lsm + * mount options. thus we can safely deal with this superblock later + */ + if (!ss_initialized) + return; + + /* how can we clone if the old one wasn't set up?? */ + BUG_ON(!(oldsbsec->flags & SE_SBINITIALIZED)); + + /* if fs is reusing a sb, just let its options stand... */ + if (newsbsec->flags & SE_SBINITIALIZED) + return; + + mutex_lock(&newsbsec->lock); + + newsbsec->flags = oldsbsec->flags; + + newsbsec->sid = oldsbsec->sid; + newsbsec->def_sid = oldsbsec->def_sid; + newsbsec->behavior = oldsbsec->behavior; + + if (set_context) { + u32 sid = oldsbsec->mntpoint_sid; + + if (!set_fscontext) + newsbsec->sid = sid; + if (!set_rootcontext) { + struct inode *newinode = newsb->s_root->d_inode; + struct inode_security_struct *newisec = newinode->i_security; + newisec->sid = sid; + } + newsbsec->mntpoint_sid = sid; + } + if (set_rootcontext) { + const struct inode *oldinode = oldsb->s_root->d_inode; + const struct inode_security_struct *oldisec = oldinode->i_security; + struct inode *newinode = newsb->s_root->d_inode; + struct inode_security_struct *newisec = newinode->i_security; + + newisec->sid = oldisec->sid; + } + + sb_finish_set_opts(newsb); + mutex_unlock(&newsbsec->lock); +} + +static int selinux_parse_opts_str(char *options, + struct security_mnt_opts *opts) +{ + char *p; + char *context = NULL, *defcontext = NULL; + char *fscontext = NULL, *rootcontext = NULL; + int rc, num_mnt_opts = 0; + + opts->num_mnt_opts = 0; + + /* Standard string-based options. */ + while ((p = strsep(&options, "|")) != NULL) { + int token; + substring_t args[MAX_OPT_ARGS]; + + if (!*p) + continue; + + token = match_token(p, tokens, args); + + switch (token) { + case Opt_context: + if (context || defcontext) { + rc = -EINVAL; + printk(KERN_WARNING SEL_MOUNT_FAIL_MSG); + goto out_err; + } + context = match_strdup(&args[0]); + if (!context) { + rc = -ENOMEM; + goto out_err; + } + break; + + case Opt_fscontext: + if (fscontext) { + rc = -EINVAL; + printk(KERN_WARNING SEL_MOUNT_FAIL_MSG); + goto out_err; + } + fscontext = match_strdup(&args[0]); + if (!fscontext) { + rc = -ENOMEM; + goto out_err; + } + break; + + case Opt_rootcontext: + if (rootcontext) { + rc = -EINVAL; + printk(KERN_WARNING SEL_MOUNT_FAIL_MSG); + goto out_err; + } + rootcontext = match_strdup(&args[0]); + if (!rootcontext) { + rc = -ENOMEM; + goto out_err; + } + break; + + case Opt_defcontext: + if (context || defcontext) { + rc = -EINVAL; + printk(KERN_WARNING SEL_MOUNT_FAIL_MSG); + goto out_err; + } + defcontext = match_strdup(&args[0]); + if (!defcontext) { + rc = -ENOMEM; + goto out_err; + } + break; + case Opt_labelsupport: + break; + default: + rc = -EINVAL; + printk(KERN_WARNING "SELinux: unknown mount option\n"); + goto out_err; + + } + } + + rc = -ENOMEM; + opts->mnt_opts = kcalloc(NUM_SEL_MNT_OPTS, sizeof(char *), GFP_ATOMIC); + if (!opts->mnt_opts) + goto out_err; + + opts->mnt_opts_flags = kcalloc(NUM_SEL_MNT_OPTS, sizeof(int), GFP_ATOMIC); + if (!opts->mnt_opts_flags) { + kfree(opts->mnt_opts); + goto out_err; + } + + if (fscontext) { + opts->mnt_opts[num_mnt_opts] = fscontext; + opts->mnt_opts_flags[num_mnt_opts++] = FSCONTEXT_MNT; + } + if (context) { + opts->mnt_opts[num_mnt_opts] = context; + opts->mnt_opts_flags[num_mnt_opts++] = CONTEXT_MNT; + } + if (rootcontext) { + opts->mnt_opts[num_mnt_opts] = rootcontext; + opts->mnt_opts_flags[num_mnt_opts++] = ROOTCONTEXT_MNT; + } + if (defcontext) { + opts->mnt_opts[num_mnt_opts] = defcontext; + opts->mnt_opts_flags[num_mnt_opts++] = DEFCONTEXT_MNT; + } + + opts->num_mnt_opts = num_mnt_opts; + return 0; + +out_err: + kfree(context); + kfree(defcontext); + kfree(fscontext); + kfree(rootcontext); + return rc; +} +/* + * string mount options parsing and call set the sbsec + */ +static int superblock_doinit(struct super_block *sb, void *data) +{ + int rc = 0; + char *options = data; + struct security_mnt_opts opts; + + security_init_mnt_opts(&opts); + + if (!data) + goto out; + + BUG_ON(sb->s_type->fs_flags & FS_BINARY_MOUNTDATA); + + rc = selinux_parse_opts_str(options, &opts); + if (rc) + goto out_err; + +out: + rc = selinux_set_mnt_opts(sb, &opts); + +out_err: + security_free_mnt_opts(&opts); + return rc; +} + +static void selinux_write_opts(struct seq_file *m, + struct security_mnt_opts *opts) +{ + int i; + char *prefix; + + for (i = 0; i < opts->num_mnt_opts; i++) { + char *has_comma; + + if (opts->mnt_opts[i]) + has_comma = strchr(opts->mnt_opts[i], ','); + else + has_comma = NULL; + + switch (opts->mnt_opts_flags[i]) { + case CONTEXT_MNT: + prefix = CONTEXT_STR; + break; + case FSCONTEXT_MNT: + prefix = FSCONTEXT_STR; + break; + case ROOTCONTEXT_MNT: + prefix = ROOTCONTEXT_STR; + break; + case DEFCONTEXT_MNT: + prefix = DEFCONTEXT_STR; + break; + case SE_SBLABELSUPP: + seq_putc(m, ','); + seq_puts(m, LABELSUPP_STR); + continue; + default: + BUG(); + return; + }; + /* we need a comma before each option */ + seq_putc(m, ','); + seq_puts(m, prefix); + if (has_comma) + seq_putc(m, '\"'); + seq_puts(m, opts->mnt_opts[i]); + if (has_comma) + seq_putc(m, '\"'); + } +} + +static int selinux_sb_show_options(struct seq_file *m, struct super_block *sb) +{ + struct security_mnt_opts opts; + int rc; + + rc = selinux_get_mnt_opts(sb, &opts); + if (rc) { + /* before policy load we may get EINVAL, don't show anything */ + if (rc == -EINVAL) + rc = 0; + return rc; + } + + selinux_write_opts(m, &opts); + + security_free_mnt_opts(&opts); + + return rc; +} + +static inline u16 inode_mode_to_security_class(umode_t mode) +{ + switch (mode & S_IFMT) { + case S_IFSOCK: + return SECCLASS_SOCK_FILE; + case S_IFLNK: + return SECCLASS_LNK_FILE; + case S_IFREG: + return SECCLASS_FILE; + case S_IFBLK: + return SECCLASS_BLK_FILE; + case S_IFDIR: + return SECCLASS_DIR; + case S_IFCHR: + return SECCLASS_CHR_FILE; + case S_IFIFO: + return SECCLASS_FIFO_FILE; + + } + + return SECCLASS_FILE; +} + +static inline int default_protocol_stream(int protocol) +{ + return (protocol == IPPROTO_IP || protocol == IPPROTO_TCP); +} + +static inline int default_protocol_dgram(int protocol) +{ + return (protocol == IPPROTO_IP || protocol == IPPROTO_UDP); +} + +static inline u16 socket_type_to_security_class(int family, int type, int protocol) +{ + switch (family) { + case PF_UNIX: + switch (type) { + case SOCK_STREAM: + case SOCK_SEQPACKET: + return SECCLASS_UNIX_STREAM_SOCKET; + case SOCK_DGRAM: + return SECCLASS_UNIX_DGRAM_SOCKET; + } + break; + case PF_INET: + case PF_INET6: + switch (type) { + case SOCK_STREAM: + if (default_protocol_stream(protocol)) + return SECCLASS_TCP_SOCKET; + else + return SECCLASS_RAWIP_SOCKET; + case SOCK_DGRAM: + if (default_protocol_dgram(protocol)) + return SECCLASS_UDP_SOCKET; + else + return SECCLASS_RAWIP_SOCKET; + case SOCK_DCCP: + return SECCLASS_DCCP_SOCKET; + default: + return SECCLASS_RAWIP_SOCKET; + } + break; + case PF_NETLINK: + switch (protocol) { + case NETLINK_ROUTE: + return SECCLASS_NETLINK_ROUTE_SOCKET; + case NETLINK_FIREWALL: + return SECCLASS_NETLINK_FIREWALL_SOCKET; + case NETLINK_INET_DIAG: + return SECCLASS_NETLINK_TCPDIAG_SOCKET; + case NETLINK_NFLOG: + return SECCLASS_NETLINK_NFLOG_SOCKET; + case NETLINK_XFRM: + return SECCLASS_NETLINK_XFRM_SOCKET; + case NETLINK_SELINUX: + return SECCLASS_NETLINK_SELINUX_SOCKET; + case NETLINK_AUDIT: + return SECCLASS_NETLINK_AUDIT_SOCKET; + case NETLINK_IP6_FW: + return SECCLASS_NETLINK_IP6FW_SOCKET; + case NETLINK_DNRTMSG: + return SECCLASS_NETLINK_DNRT_SOCKET; + case NETLINK_KOBJECT_UEVENT: + return SECCLASS_NETLINK_KOBJECT_UEVENT_SOCKET; + default: + return SECCLASS_NETLINK_SOCKET; + } + case PF_PACKET: + return SECCLASS_PACKET_SOCKET; + case PF_KEY: + return SECCLASS_KEY_SOCKET; + case PF_APPLETALK: + return SECCLASS_APPLETALK_SOCKET; + } + + return SECCLASS_SOCKET; +} + +#ifdef CONFIG_PROC_FS +static int selinux_proc_get_sid(struct dentry *dentry, + u16 tclass, + u32 *sid) +{ + int rc; + char *buffer, *path; + + buffer = (char *)__get_free_page(GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + path = dentry_path_raw(dentry, buffer, PAGE_SIZE); + if (IS_ERR(path)) + rc = PTR_ERR(path); + else { + /* each process gets a /proc/PID/ entry. Strip off the + * PID part to get a valid selinux labeling. + * e.g. /proc/1/net/rpc/nfs -> /net/rpc/nfs */ + while (path[1] >= '0' && path[1] <= '9') { + path[1] = '/'; + path++; + } + rc = security_genfs_sid("proc", path, tclass, sid); + } + free_page((unsigned long)buffer); + return rc; +} +#else +static int selinux_proc_get_sid(struct dentry *dentry, + u16 tclass, + u32 *sid) +{ + return -EINVAL; +} +#endif + +/* The inode's security attributes must be initialized before first use. */ +static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dentry) +{ + struct superblock_security_struct *sbsec = NULL; + struct inode_security_struct *isec = inode->i_security; + u32 sid; + struct dentry *dentry; +#define INITCONTEXTLEN 255 + char *context = NULL; + unsigned len = 0; + int rc = 0; + + if (isec->initialized) + goto out; + + mutex_lock(&isec->lock); + if (isec->initialized) + goto out_unlock; + + sbsec = inode->i_sb->s_security; + if (!(sbsec->flags & SE_SBINITIALIZED)) { + /* Defer initialization until selinux_complete_init, + after the initial policy is loaded and the security + server is ready to handle calls. */ + spin_lock(&sbsec->isec_lock); + if (list_empty(&isec->list)) + list_add(&isec->list, &sbsec->isec_head); + spin_unlock(&sbsec->isec_lock); + goto out_unlock; + } + + switch (sbsec->behavior) { + case SECURITY_FS_USE_XATTR: + if (!inode->i_op->getxattr) { + isec->sid = sbsec->def_sid; + break; + } + + /* Need a dentry, since the xattr API requires one. + Life would be simpler if we could just pass the inode. */ + if (opt_dentry) { + /* Called from d_instantiate or d_splice_alias. */ + dentry = dget(opt_dentry); + } else { + /* Called from selinux_complete_init, try to find a dentry. */ + dentry = d_find_alias(inode); + } + if (!dentry) { + /* + * this is can be hit on boot when a file is accessed + * before the policy is loaded. When we load policy we + * may find inodes that have no dentry on the + * sbsec->isec_head list. No reason to complain as these + * will get fixed up the next time we go through + * inode_doinit with a dentry, before these inodes could + * be used again by userspace. + */ + goto out_unlock; + } + + len = INITCONTEXTLEN; + context = kmalloc(len+1, GFP_NOFS); + if (!context) { + rc = -ENOMEM; + dput(dentry); + goto out_unlock; + } + context[len] = '\0'; + rc = inode->i_op->getxattr(dentry, XATTR_NAME_SELINUX, + context, len); + if (rc == -ERANGE) { + kfree(context); + + /* Need a larger buffer. Query for the right size. */ + rc = inode->i_op->getxattr(dentry, XATTR_NAME_SELINUX, + NULL, 0); + if (rc < 0) { + dput(dentry); + goto out_unlock; + } + len = rc; + context = kmalloc(len+1, GFP_NOFS); + if (!context) { + rc = -ENOMEM; + dput(dentry); + goto out_unlock; + } + context[len] = '\0'; + rc = inode->i_op->getxattr(dentry, + XATTR_NAME_SELINUX, + context, len); + } + dput(dentry); + if (rc < 0) { + if (rc != -ENODATA) { + printk(KERN_WARNING "SELinux: %s: getxattr returned " + "%d for dev=%s ino=%ld\n", __func__, + -rc, inode->i_sb->s_id, inode->i_ino); + kfree(context); + goto out_unlock; + } + /* Map ENODATA to the default file SID */ + sid = sbsec->def_sid; + rc = 0; + } else { + rc = security_context_to_sid_default(context, rc, &sid, + sbsec->def_sid, + GFP_NOFS); + if (rc) { + char *dev = inode->i_sb->s_id; + unsigned long ino = inode->i_ino; + + if (rc == -EINVAL) { + if (printk_ratelimit()) + printk(KERN_NOTICE "SELinux: inode=%lu on dev=%s was found to have an invalid " + "context=%s. This indicates you may need to relabel the inode or the " + "filesystem in question.\n", ino, dev, context); + } else { + printk(KERN_WARNING "SELinux: %s: context_to_sid(%s) " + "returned %d for dev=%s ino=%ld\n", + __func__, context, -rc, dev, ino); + } + kfree(context); + /* Leave with the unlabeled SID */ + rc = 0; + break; + } + } + kfree(context); + isec->sid = sid; + break; + case SECURITY_FS_USE_TASK: + isec->sid = isec->task_sid; + break; + case SECURITY_FS_USE_TRANS: + /* Default to the fs SID. */ + isec->sid = sbsec->sid; + + /* Try to obtain a transition SID. */ + isec->sclass = inode_mode_to_security_class(inode->i_mode); + rc = security_transition_sid(isec->task_sid, sbsec->sid, + isec->sclass, NULL, &sid); + if (rc) + goto out_unlock; + isec->sid = sid; + break; + case SECURITY_FS_USE_MNTPOINT: + isec->sid = sbsec->mntpoint_sid; + break; + default: + /* Default to the fs superblock SID. */ + isec->sid = sbsec->sid; + + if ((sbsec->flags & SE_SBPROC) && !S_ISLNK(inode->i_mode)) { + if (opt_dentry) { + isec->sclass = inode_mode_to_security_class(inode->i_mode); + rc = selinux_proc_get_sid(opt_dentry, + isec->sclass, + &sid); + if (rc) + goto out_unlock; + isec->sid = sid; + } + } + break; + } + + isec->initialized = 1; + +out_unlock: + mutex_unlock(&isec->lock); +out: + if (isec->sclass == SECCLASS_FILE) + isec->sclass = inode_mode_to_security_class(inode->i_mode); + return rc; +} + +/* Convert a Linux signal to an access vector. */ +static inline u32 signal_to_av(int sig) +{ + u32 perm = 0; + + switch (sig) { + case SIGCHLD: + /* Commonly granted from child to parent. */ + perm = PROCESS__SIGCHLD; + break; + case SIGKILL: + /* Cannot be caught or ignored */ + perm = PROCESS__SIGKILL; + break; + case SIGSTOP: + /* Cannot be caught or ignored */ + perm = PROCESS__SIGSTOP; + break; + default: + /* All other signals. */ + perm = PROCESS__SIGNAL; + break; + } + + return perm; +} + +/* + * Check permission between a pair of credentials + * fork check, ptrace check, etc. + */ +static int cred_has_perm(const struct cred *actor, + const struct cred *target, + u32 perms) +{ + u32 asid = cred_sid(actor), tsid = cred_sid(target); + + return avc_has_perm(asid, tsid, SECCLASS_PROCESS, perms, NULL); +} + +/* + * Check permission between a pair of tasks, e.g. signal checks, + * fork check, ptrace check, etc. + * tsk1 is the actor and tsk2 is the target + * - this uses the default subjective creds of tsk1 + */ +static int task_has_perm(const struct task_struct *tsk1, + const struct task_struct *tsk2, + u32 perms) +{ + const struct task_security_struct *__tsec1, *__tsec2; + u32 sid1, sid2; + + rcu_read_lock(); + __tsec1 = __task_cred(tsk1)->security; sid1 = __tsec1->sid; + __tsec2 = __task_cred(tsk2)->security; sid2 = __tsec2->sid; + rcu_read_unlock(); + return avc_has_perm(sid1, sid2, SECCLASS_PROCESS, perms, NULL); +} + +/* + * Check permission between current and another task, e.g. signal checks, + * fork check, ptrace check, etc. + * current is the actor and tsk2 is the target + * - this uses current's subjective creds + */ +static int current_has_perm(const struct task_struct *tsk, + u32 perms) +{ + u32 sid, tsid; + + sid = current_sid(); + tsid = task_sid(tsk); + return avc_has_perm(sid, tsid, SECCLASS_PROCESS, perms, NULL); +} + +#if CAP_LAST_CAP > 63 +#error Fix SELinux to handle capabilities > 63. +#endif + +/* Check whether a task is allowed to use a capability. */ +static int task_has_capability(struct task_struct *tsk, + const struct cred *cred, + int cap, int audit) +{ + struct common_audit_data ad; + struct av_decision avd; + u16 sclass; + u32 sid = cred_sid(cred); + u32 av = CAP_TO_MASK(cap); + int rc; + + COMMON_AUDIT_DATA_INIT(&ad, CAP); + ad.tsk = tsk; + ad.u.cap = cap; + + switch (CAP_TO_INDEX(cap)) { + case 0: + sclass = SECCLASS_CAPABILITY; + break; + case 1: + sclass = SECCLASS_CAPABILITY2; + break; + default: + printk(KERN_ERR + "SELinux: out of range capability %d\n", cap); + BUG(); + return -EINVAL; + } + + rc = avc_has_perm_noaudit(sid, sid, sclass, av, 0, &avd); + if (audit == SECURITY_CAP_AUDIT) { + int rc2 = avc_audit(sid, sid, sclass, av, &avd, rc, &ad, 0); + if (rc2) + return rc2; + } + return rc; +} + +/* Check whether a task is allowed to use a system operation. */ +static int task_has_system(struct task_struct *tsk, + u32 perms) +{ + u32 sid = task_sid(tsk); + + return avc_has_perm(sid, SECINITSID_KERNEL, + SECCLASS_SYSTEM, perms, NULL); +} + +/* Check whether a task has a particular permission to an inode. + The 'adp' parameter is optional and allows other audit + data to be passed (e.g. the dentry). */ +static int inode_has_perm(const struct cred *cred, + struct inode *inode, + u32 perms, + struct common_audit_data *adp, + unsigned flags) +{ + struct inode_security_struct *isec; + u32 sid; + + validate_creds(cred); + + if (unlikely(IS_PRIVATE(inode))) + return 0; + + sid = cred_sid(cred); + isec = inode->i_security; + + return avc_has_perm_flags(sid, isec->sid, isec->sclass, perms, adp, flags); +} + +static int inode_has_perm_noadp(const struct cred *cred, + struct inode *inode, + u32 perms, + unsigned flags) +{ + struct common_audit_data ad; + + COMMON_AUDIT_DATA_INIT(&ad, INODE); + ad.u.inode = inode; + return inode_has_perm(cred, inode, perms, &ad, flags); +} + +/* Same as inode_has_perm, but pass explicit audit data containing + the dentry to help the auditing code to more easily generate the + pathname if needed. */ +static inline int dentry_has_perm(const struct cred *cred, + struct dentry *dentry, + u32 av) +{ + struct inode *inode = dentry->d_inode; + struct common_audit_data ad; + + COMMON_AUDIT_DATA_INIT(&ad, DENTRY); + ad.u.dentry = dentry; + return inode_has_perm(cred, inode, av, &ad, 0); +} + +/* Same as inode_has_perm, but pass explicit audit data containing + the path to help the auditing code to more easily generate the + pathname if needed. */ +static inline int path_has_perm(const struct cred *cred, + struct path *path, + u32 av) +{ + struct inode *inode = path->dentry->d_inode; + struct common_audit_data ad; + + COMMON_AUDIT_DATA_INIT(&ad, PATH); + ad.u.path = *path; + return inode_has_perm(cred, inode, av, &ad, 0); +} + +/* Check whether a task can use an open file descriptor to + access an inode in a given way. Check access to the + descriptor itself, and then use dentry_has_perm to + check a particular permission to the file. + Access to the descriptor is implicitly granted if it + has the same SID as the process. If av is zero, then + access to the file is not checked, e.g. for cases + where only the descriptor is affected like seek. */ +static int file_has_perm(const struct cred *cred, + struct file *file, + u32 av) +{ + struct file_security_struct *fsec = file->f_security; + struct inode *inode = file->f_path.dentry->d_inode; + struct common_audit_data ad; + u32 sid = cred_sid(cred); + int rc; + + COMMON_AUDIT_DATA_INIT(&ad, PATH); + ad.u.path = file->f_path; + + if (sid != fsec->sid) { + rc = avc_has_perm(sid, fsec->sid, + SECCLASS_FD, + FD__USE, + &ad); + if (rc) + goto out; + } + + /* av is zero if only checking access to the descriptor. */ + rc = 0; + if (av) + rc = inode_has_perm(cred, inode, av, &ad, 0); + +out: + return rc; +} + +/* Check whether a task can create a file. */ +static int may_create(struct inode *dir, + struct dentry *dentry, + u16 tclass) +{ + const struct task_security_struct *tsec = current_security(); + struct inode_security_struct *dsec; + struct superblock_security_struct *sbsec; + u32 sid, newsid; + struct common_audit_data ad; + int rc; + + dsec = dir->i_security; + sbsec = dir->i_sb->s_security; + + sid = tsec->sid; + newsid = tsec->create_sid; + + COMMON_AUDIT_DATA_INIT(&ad, DENTRY); + ad.u.dentry = dentry; + + rc = avc_has_perm(sid, dsec->sid, SECCLASS_DIR, + DIR__ADD_NAME | DIR__SEARCH, + &ad); + if (rc) + return rc; + + if (!newsid || !(sbsec->flags & SE_SBLABELSUPP)) { + rc = security_transition_sid(sid, dsec->sid, tclass, + &dentry->d_name, &newsid); + if (rc) + return rc; + } + + rc = avc_has_perm(sid, newsid, tclass, FILE__CREATE, &ad); + if (rc) + return rc; + + return avc_has_perm(newsid, sbsec->sid, + SECCLASS_FILESYSTEM, + FILESYSTEM__ASSOCIATE, &ad); +} + +/* Check whether a task can create a key. */ +static int may_create_key(u32 ksid, + struct task_struct *ctx) +{ + u32 sid = task_sid(ctx); + + return avc_has_perm(sid, ksid, SECCLASS_KEY, KEY__CREATE, NULL); +} + +#define MAY_LINK 0 +#define MAY_UNLINK 1 +#define MAY_RMDIR 2 + +/* Check whether a task can link, unlink, or rmdir a file/directory. */ +static int may_link(struct inode *dir, + struct dentry *dentry, + int kind) + +{ + struct inode_security_struct *dsec, *isec; + struct common_audit_data ad; + u32 sid = current_sid(); + u32 av; + int rc; + + dsec = dir->i_security; + isec = dentry->d_inode->i_security; + + COMMON_AUDIT_DATA_INIT(&ad, DENTRY); + ad.u.dentry = dentry; + + av = DIR__SEARCH; + av |= (kind ? DIR__REMOVE_NAME : DIR__ADD_NAME); + rc = avc_has_perm(sid, dsec->sid, SECCLASS_DIR, av, &ad); + if (rc) + return rc; + + switch (kind) { + case MAY_LINK: + av = FILE__LINK; + break; + case MAY_UNLINK: + av = FILE__UNLINK; + break; + case MAY_RMDIR: + av = DIR__RMDIR; + break; + default: + printk(KERN_WARNING "SELinux: %s: unrecognized kind %d\n", + __func__, kind); + return 0; + } + + rc = avc_has_perm(sid, isec->sid, isec->sclass, av, &ad); + return rc; +} + +static inline int may_rename(struct inode *old_dir, + struct dentry *old_dentry, + struct inode *new_dir, + struct dentry *new_dentry) +{ + struct inode_security_struct *old_dsec, *new_dsec, *old_isec, *new_isec; + struct common_audit_data ad; + u32 sid = current_sid(); + u32 av; + int old_is_dir, new_is_dir; + int rc; + + old_dsec = old_dir->i_security; + old_isec = old_dentry->d_inode->i_security; + old_is_dir = S_ISDIR(old_dentry->d_inode->i_mode); + new_dsec = new_dir->i_security; + + COMMON_AUDIT_DATA_INIT(&ad, DENTRY); + + ad.u.dentry = old_dentry; + rc = avc_has_perm(sid, old_dsec->sid, SECCLASS_DIR, + DIR__REMOVE_NAME | DIR__SEARCH, &ad); + if (rc) + return rc; + rc = avc_has_perm(sid, old_isec->sid, + old_isec->sclass, FILE__RENAME, &ad); + if (rc) + return rc; + if (old_is_dir && new_dir != old_dir) { + rc = avc_has_perm(sid, old_isec->sid, + old_isec->sclass, DIR__REPARENT, &ad); + if (rc) + return rc; + } + + ad.u.dentry = new_dentry; + av = DIR__ADD_NAME | DIR__SEARCH; + if (new_dentry->d_inode) + av |= DIR__REMOVE_NAME; + rc = avc_has_perm(sid, new_dsec->sid, SECCLASS_DIR, av, &ad); + if (rc) + return rc; + if (new_dentry->d_inode) { + new_isec = new_dentry->d_inode->i_security; + new_is_dir = S_ISDIR(new_dentry->d_inode->i_mode); + rc = avc_has_perm(sid, new_isec->sid, + new_isec->sclass, + (new_is_dir ? DIR__RMDIR : FILE__UNLINK), &ad); + if (rc) + return rc; + } + + return 0; +} + +/* Check whether a task can perform a filesystem operation. */ +static int superblock_has_perm(const struct cred *cred, + struct super_block *sb, + u32 perms, + struct common_audit_data *ad) +{ + struct superblock_security_struct *sbsec; + u32 sid = cred_sid(cred); + + sbsec = sb->s_security; + return avc_has_perm(sid, sbsec->sid, SECCLASS_FILESYSTEM, perms, ad); +} + +/* Convert a Linux mode and permission mask to an access vector. */ +static inline u32 file_mask_to_av(int mode, int mask) +{ + u32 av = 0; + + if ((mode & S_IFMT) != S_IFDIR) { + if (mask & MAY_EXEC) + av |= FILE__EXECUTE; + if (mask & MAY_READ) + av |= FILE__READ; + + if (mask & MAY_APPEND) + av |= FILE__APPEND; + else if (mask & MAY_WRITE) + av |= FILE__WRITE; + + } else { + if (mask & MAY_EXEC) + av |= DIR__SEARCH; + if (mask & MAY_WRITE) + av |= DIR__WRITE; + if (mask & MAY_READ) + av |= DIR__READ; + } + + return av; +} + +/* Convert a Linux file to an access vector. */ +static inline u32 file_to_av(struct file *file) +{ + u32 av = 0; + + if (file->f_mode & FMODE_READ) + av |= FILE__READ; + if (file->f_mode & FMODE_WRITE) { + if (file->f_flags & O_APPEND) + av |= FILE__APPEND; + else + av |= FILE__WRITE; + } + if (!av) { + /* + * Special file opened with flags 3 for ioctl-only use. + */ + av = FILE__IOCTL; + } + + return av; +} + +/* + * Convert a file to an access vector and include the correct open + * open permission. + */ +static inline u32 open_file_to_av(struct file *file) +{ + u32 av = file_to_av(file); + + if (selinux_policycap_openperm) + av |= FILE__OPEN; + + return av; +} + +/* Hook functions begin here. */ + +static int selinux_ptrace_access_check(struct task_struct *child, + unsigned int mode) +{ + int rc; + + rc = cap_ptrace_access_check(child, mode); + if (rc) + return rc; + + if (mode == PTRACE_MODE_READ) { + u32 sid = current_sid(); + u32 csid = task_sid(child); + return avc_has_perm(sid, csid, SECCLASS_FILE, FILE__READ, NULL); + } + + return current_has_perm(child, PROCESS__PTRACE); +} + +static int selinux_ptrace_traceme(struct task_struct *parent) +{ + int rc; + + rc = cap_ptrace_traceme(parent); + if (rc) + return rc; + + return task_has_perm(parent, current, PROCESS__PTRACE); +} + +static int selinux_capget(struct task_struct *target, kernel_cap_t *effective, + kernel_cap_t *inheritable, kernel_cap_t *permitted) +{ + int error; + + error = current_has_perm(target, PROCESS__GETCAP); + if (error) + return error; + + return cap_capget(target, effective, inheritable, permitted); +} + +static int selinux_capset(struct cred *new, const struct cred *old, + const kernel_cap_t *effective, + const kernel_cap_t *inheritable, + const kernel_cap_t *permitted) +{ + int error; + + error = cap_capset(new, old, + effective, inheritable, permitted); + if (error) + return error; + + return cred_has_perm(old, new, PROCESS__SETCAP); +} + +/* + * (This comment used to live with the selinux_task_setuid hook, + * which was removed). + * + * Since setuid only affects the current process, and since the SELinux + * controls are not based on the Linux identity attributes, SELinux does not + * need to control this operation. However, SELinux does control the use of + * the CAP_SETUID and CAP_SETGID capabilities using the capable hook. + */ + +static int selinux_capable(struct task_struct *tsk, const struct cred *cred, + struct user_namespace *ns, int cap, int audit) +{ + int rc; + + rc = cap_capable(tsk, cred, ns, cap, audit); + if (rc) + return rc; + + return task_has_capability(tsk, cred, cap, audit); +} + +static int selinux_quotactl(int cmds, int type, int id, struct super_block *sb) +{ + const struct cred *cred = current_cred(); + int rc = 0; + + if (!sb) + return 0; + + switch (cmds) { + case Q_SYNC: + case Q_QUOTAON: + case Q_QUOTAOFF: + case Q_SETINFO: + case Q_SETQUOTA: + rc = superblock_has_perm(cred, sb, FILESYSTEM__QUOTAMOD, NULL); + break; + case Q_GETFMT: + case Q_GETINFO: + case Q_GETQUOTA: + rc = superblock_has_perm(cred, sb, FILESYSTEM__QUOTAGET, NULL); + break; + default: + rc = 0; /* let the kernel handle invalid cmds */ + break; + } + return rc; +} + +static int selinux_quota_on(struct dentry *dentry) +{ + const struct cred *cred = current_cred(); + + return dentry_has_perm(cred, dentry, FILE__QUOTAON); +} + +static int selinux_syslog(int type) +{ + int rc; + + switch (type) { + case SYSLOG_ACTION_READ_ALL: /* Read last kernel messages */ + case SYSLOG_ACTION_SIZE_BUFFER: /* Return size of the log buffer */ + rc = task_has_system(current, SYSTEM__SYSLOG_READ); + break; + case SYSLOG_ACTION_CONSOLE_OFF: /* Disable logging to console */ + case SYSLOG_ACTION_CONSOLE_ON: /* Enable logging to console */ + /* Set level of messages printed to console */ + case SYSLOG_ACTION_CONSOLE_LEVEL: + rc = task_has_system(current, SYSTEM__SYSLOG_CONSOLE); + break; + case SYSLOG_ACTION_CLOSE: /* Close log */ + case SYSLOG_ACTION_OPEN: /* Open log */ + case SYSLOG_ACTION_READ: /* Read from log */ + case SYSLOG_ACTION_READ_CLEAR: /* Read/clear last kernel messages */ + case SYSLOG_ACTION_CLEAR: /* Clear ring buffer */ + default: + rc = task_has_system(current, SYSTEM__SYSLOG_MOD); + break; + } + return rc; +} + +/* + * Check that a process has enough memory to allocate a new virtual + * mapping. 0 means there is enough memory for the allocation to + * succeed and -ENOMEM implies there is not. + * + * Do not audit the selinux permission check, as this is applied to all + * processes that allocate mappings. + */ +static int selinux_vm_enough_memory(struct mm_struct *mm, long pages) +{ + int rc, cap_sys_admin = 0; + + rc = selinux_capable(current, current_cred(), + &init_user_ns, CAP_SYS_ADMIN, + SECURITY_CAP_NOAUDIT); + if (rc == 0) + cap_sys_admin = 1; + + return __vm_enough_memory(mm, pages, cap_sys_admin); +} + +/* binprm security operations */ + +static int selinux_bprm_set_creds(struct linux_binprm *bprm) +{ + const struct task_security_struct *old_tsec; + struct task_security_struct *new_tsec; + struct inode_security_struct *isec; + struct common_audit_data ad; + struct inode *inode = bprm->file->f_path.dentry->d_inode; + int rc; + + rc = cap_bprm_set_creds(bprm); + if (rc) + return rc; + + /* SELinux context only depends on initial program or script and not + * the script interpreter */ + if (bprm->cred_prepared) + return 0; + + old_tsec = current_security(); + new_tsec = bprm->cred->security; + isec = inode->i_security; + + /* Default to the current task SID. */ + new_tsec->sid = old_tsec->sid; + new_tsec->osid = old_tsec->sid; + + /* Reset fs, key, and sock SIDs on execve. */ + new_tsec->create_sid = 0; + new_tsec->keycreate_sid = 0; + new_tsec->sockcreate_sid = 0; + + if (old_tsec->exec_sid) { + new_tsec->sid = old_tsec->exec_sid; + /* Reset exec SID on execve. */ + new_tsec->exec_sid = 0; + } else { + /* Check for a default transition on this program. */ + rc = security_transition_sid(old_tsec->sid, isec->sid, + SECCLASS_PROCESS, NULL, + &new_tsec->sid); + if (rc) + return rc; + } + + COMMON_AUDIT_DATA_INIT(&ad, PATH); + ad.u.path = bprm->file->f_path; + + if (bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID) + new_tsec->sid = old_tsec->sid; + + if (new_tsec->sid == old_tsec->sid) { + rc = avc_has_perm(old_tsec->sid, isec->sid, + SECCLASS_FILE, FILE__EXECUTE_NO_TRANS, &ad); + if (rc) + return rc; + } else { + /* Check permissions for the transition. */ + rc = avc_has_perm(old_tsec->sid, new_tsec->sid, + SECCLASS_PROCESS, PROCESS__TRANSITION, &ad); + if (rc) + return rc; + + rc = avc_has_perm(new_tsec->sid, isec->sid, + SECCLASS_FILE, FILE__ENTRYPOINT, &ad); + if (rc) + return rc; + + /* Check for shared state */ + if (bprm->unsafe & LSM_UNSAFE_SHARE) { + rc = avc_has_perm(old_tsec->sid, new_tsec->sid, + SECCLASS_PROCESS, PROCESS__SHARE, + NULL); + if (rc) + return -EPERM; + } + + /* Make sure that anyone attempting to ptrace over a task that + * changes its SID has the appropriate permit */ + if (bprm->unsafe & + (LSM_UNSAFE_PTRACE | LSM_UNSAFE_PTRACE_CAP)) { + struct task_struct *tracer; + struct task_security_struct *sec; + u32 ptsid = 0; + + rcu_read_lock(); + tracer = tracehook_tracer_task(current); + if (likely(tracer != NULL)) { + sec = __task_cred(tracer)->security; + ptsid = sec->sid; + } + rcu_read_unlock(); + + if (ptsid != 0) { + rc = avc_has_perm(ptsid, new_tsec->sid, + SECCLASS_PROCESS, + PROCESS__PTRACE, NULL); + if (rc) + return -EPERM; + } + } + + /* Clear any possibly unsafe personality bits on exec: */ + bprm->per_clear |= PER_CLEAR_ON_SETID; + } + + return 0; +} + +static int selinux_bprm_secureexec(struct linux_binprm *bprm) +{ + const struct task_security_struct *tsec = current_security(); + u32 sid, osid; + int atsecure = 0; + + sid = tsec->sid; + osid = tsec->osid; + + if (osid != sid) { + /* Enable secure mode for SIDs transitions unless + the noatsecure permission is granted between + the two SIDs, i.e. ahp returns 0. */ + atsecure = avc_has_perm(osid, sid, + SECCLASS_PROCESS, + PROCESS__NOATSECURE, NULL); + } + + return (atsecure || cap_bprm_secureexec(bprm)); +} + +extern struct vfsmount *selinuxfs_mount; +extern struct dentry *selinux_null; + +/* Derived from fs/exec.c:flush_old_files. */ +static inline void flush_unauthorized_files(const struct cred *cred, + struct files_struct *files) +{ + struct common_audit_data ad; + struct file *file, *devnull = NULL; + struct tty_struct *tty; + struct fdtable *fdt; + long j = -1; + int drop_tty = 0; + + tty = get_current_tty(); + if (tty) { + spin_lock(&tty_files_lock); + if (!list_empty(&tty->tty_files)) { + struct tty_file_private *file_priv; + struct inode *inode; + + /* Revalidate access to controlling tty. + Use inode_has_perm on the tty inode directly rather + than using file_has_perm, as this particular open + file may belong to another process and we are only + interested in the inode-based check here. */ + file_priv = list_first_entry(&tty->tty_files, + struct tty_file_private, list); + file = file_priv->file; + inode = file->f_path.dentry->d_inode; + if (inode_has_perm_noadp(cred, inode, + FILE__READ | FILE__WRITE, 0)) { + drop_tty = 1; + } + } + spin_unlock(&tty_files_lock); + tty_kref_put(tty); + } + /* Reset controlling tty. */ + if (drop_tty) + no_tty(); + + /* Revalidate access to inherited open files. */ + + COMMON_AUDIT_DATA_INIT(&ad, INODE); + + spin_lock(&files->file_lock); + for (;;) { + unsigned long set, i; + int fd; + + j++; + i = j * __NFDBITS; + fdt = files_fdtable(files); + if (i >= fdt->max_fds) + break; + set = fdt->open_fds->fds_bits[j]; + if (!set) + continue; + spin_unlock(&files->file_lock); + for ( ; set ; i++, set >>= 1) { + if (set & 1) { + file = fget(i); + if (!file) + continue; + if (file_has_perm(cred, + file, + file_to_av(file))) { + sys_close(i); + fd = get_unused_fd(); + if (fd != i) { + if (fd >= 0) + put_unused_fd(fd); + fput(file); + continue; + } + if (devnull) { + get_file(devnull); + } else { + devnull = dentry_open( + dget(selinux_null), + mntget(selinuxfs_mount), + O_RDWR, cred); + if (IS_ERR(devnull)) { + devnull = NULL; + put_unused_fd(fd); + fput(file); + continue; + } + } + fd_install(fd, devnull); + } + fput(file); + } + } + spin_lock(&files->file_lock); + + } + spin_unlock(&files->file_lock); +} + +/* + * Prepare a process for imminent new credential changes due to exec + */ +static void selinux_bprm_committing_creds(struct linux_binprm *bprm) +{ + struct task_security_struct *new_tsec; + struct rlimit *rlim, *initrlim; + int rc, i; + + new_tsec = bprm->cred->security; + if (new_tsec->sid == new_tsec->osid) + return; + + /* Close files for which the new task SID is not authorized. */ + flush_unauthorized_files(bprm->cred, current->files); + + /* Always clear parent death signal on SID transitions. */ + current->pdeath_signal = 0; + + /* Check whether the new SID can inherit resource limits from the old + * SID. If not, reset all soft limits to the lower of the current + * task's hard limit and the init task's soft limit. + * + * Note that the setting of hard limits (even to lower them) can be + * controlled by the setrlimit check. The inclusion of the init task's + * soft limit into the computation is to avoid resetting soft limits + * higher than the default soft limit for cases where the default is + * lower than the hard limit, e.g. RLIMIT_CORE or RLIMIT_STACK. + */ + rc = avc_has_perm(new_tsec->osid, new_tsec->sid, SECCLASS_PROCESS, + PROCESS__RLIMITINH, NULL); + if (rc) { + /* protect against do_prlimit() */ + task_lock(current); + for (i = 0; i < RLIM_NLIMITS; i++) { + rlim = current->signal->rlim + i; + initrlim = init_task.signal->rlim + i; + rlim->rlim_cur = min(rlim->rlim_max, initrlim->rlim_cur); + } + task_unlock(current); + update_rlimit_cpu(current, rlimit(RLIMIT_CPU)); + } +} + +/* + * Clean up the process immediately after the installation of new credentials + * due to exec + */ +static void selinux_bprm_committed_creds(struct linux_binprm *bprm) +{ + const struct task_security_struct *tsec = current_security(); + struct itimerval itimer; + u32 osid, sid; + int rc, i; + + osid = tsec->osid; + sid = tsec->sid; + + if (sid == osid) + return; + + /* Check whether the new SID can inherit signal state from the old SID. + * If not, clear itimers to avoid subsequent signal generation and + * flush and unblock signals. + * + * This must occur _after_ the task SID has been updated so that any + * kill done after the flush will be checked against the new SID. + */ + rc = avc_has_perm(osid, sid, SECCLASS_PROCESS, PROCESS__SIGINH, NULL); + if (rc) { + memset(&itimer, 0, sizeof itimer); + for (i = 0; i < 3; i++) + do_setitimer(i, &itimer, NULL); + spin_lock_irq(¤t->sighand->siglock); + if (!(current->signal->flags & SIGNAL_GROUP_EXIT)) { + __flush_signals(current); + flush_signal_handlers(current, 1); + sigemptyset(¤t->blocked); + } + spin_unlock_irq(¤t->sighand->siglock); + } + + /* Wake up the parent if it is waiting so that it can recheck + * wait permission to the new task SID. */ + read_lock(&tasklist_lock); + __wake_up_parent(current, current->real_parent); + read_unlock(&tasklist_lock); +} + +/* superblock security operations */ + +static int selinux_sb_alloc_security(struct super_block *sb) +{ + return superblock_alloc_security(sb); +} + +static void selinux_sb_free_security(struct super_block *sb) +{ + superblock_free_security(sb); +} + +static inline int match_prefix(char *prefix, int plen, char *option, int olen) +{ + if (plen > olen) + return 0; + + return !memcmp(prefix, option, plen); +} + +static inline int selinux_option(char *option, int len) +{ + return (match_prefix(CONTEXT_STR, sizeof(CONTEXT_STR)-1, option, len) || + match_prefix(FSCONTEXT_STR, sizeof(FSCONTEXT_STR)-1, option, len) || + match_prefix(DEFCONTEXT_STR, sizeof(DEFCONTEXT_STR)-1, option, len) || + match_prefix(ROOTCONTEXT_STR, sizeof(ROOTCONTEXT_STR)-1, option, len) || + match_prefix(LABELSUPP_STR, sizeof(LABELSUPP_STR)-1, option, len)); +} + +static inline void take_option(char **to, char *from, int *first, int len) +{ + if (!*first) { + **to = ','; + *to += 1; + } else + *first = 0; + memcpy(*to, from, len); + *to += len; +} + +static inline void take_selinux_option(char **to, char *from, int *first, + int len) +{ + int current_size = 0; + + if (!*first) { + **to = '|'; + *to += 1; + } else + *first = 0; + + while (current_size < len) { + if (*from != '"') { + **to = *from; + *to += 1; + } + from += 1; + current_size += 1; + } +} + +static int selinux_sb_copy_data(char *orig, char *copy) +{ + int fnosec, fsec, rc = 0; + char *in_save, *in_curr, *in_end; + char *sec_curr, *nosec_save, *nosec; + int open_quote = 0; + + in_curr = orig; + sec_curr = copy; + + nosec = (char *)get_zeroed_page(GFP_KERNEL); + if (!nosec) { + rc = -ENOMEM; + goto out; + } + + nosec_save = nosec; + fnosec = fsec = 1; + in_save = in_end = orig; + + do { + if (*in_end == '"') + open_quote = !open_quote; + if ((*in_end == ',' && open_quote == 0) || + *in_end == '\0') { + int len = in_end - in_curr; + + if (selinux_option(in_curr, len)) + take_selinux_option(&sec_curr, in_curr, &fsec, len); + else + take_option(&nosec, in_curr, &fnosec, len); + + in_curr = in_end + 1; + } + } while (*in_end++); + + strcpy(in_save, nosec_save); + free_page((unsigned long)nosec_save); +out: + return rc; +} + +static int selinux_sb_remount(struct super_block *sb, void *data) +{ + int rc, i, *flags; + struct security_mnt_opts opts; + char *secdata, **mount_options; + struct superblock_security_struct *sbsec = sb->s_security; + + if (!(sbsec->flags & SE_SBINITIALIZED)) + return 0; + + if (!data) + return 0; + + if (sb->s_type->fs_flags & FS_BINARY_MOUNTDATA) + return 0; + + security_init_mnt_opts(&opts); + secdata = alloc_secdata(); + if (!secdata) + return -ENOMEM; + rc = selinux_sb_copy_data(data, secdata); + if (rc) + goto out_free_secdata; + + rc = selinux_parse_opts_str(secdata, &opts); + if (rc) + goto out_free_secdata; + + mount_options = opts.mnt_opts; + flags = opts.mnt_opts_flags; + + for (i = 0; i < opts.num_mnt_opts; i++) { + u32 sid; + size_t len; + + if (flags[i] == SE_SBLABELSUPP) + continue; + len = strlen(mount_options[i]); + rc = security_context_to_sid(mount_options[i], len, &sid); + if (rc) { + printk(KERN_WARNING "SELinux: security_context_to_sid" + "(%s) failed for (dev %s, type %s) errno=%d\n", + mount_options[i], sb->s_id, sb->s_type->name, rc); + goto out_free_opts; + } + rc = -EINVAL; + switch (flags[i]) { + case FSCONTEXT_MNT: + if (bad_option(sbsec, FSCONTEXT_MNT, sbsec->sid, sid)) + goto out_bad_option; + break; + case CONTEXT_MNT: + if (bad_option(sbsec, CONTEXT_MNT, sbsec->mntpoint_sid, sid)) + goto out_bad_option; + break; + case ROOTCONTEXT_MNT: { + struct inode_security_struct *root_isec; + root_isec = sb->s_root->d_inode->i_security; + + if (bad_option(sbsec, ROOTCONTEXT_MNT, root_isec->sid, sid)) + goto out_bad_option; + break; + } + case DEFCONTEXT_MNT: + if (bad_option(sbsec, DEFCONTEXT_MNT, sbsec->def_sid, sid)) + goto out_bad_option; + break; + default: + goto out_free_opts; + } + } + + rc = 0; +out_free_opts: + security_free_mnt_opts(&opts); +out_free_secdata: + free_secdata(secdata); + return rc; +out_bad_option: + printk(KERN_WARNING "SELinux: unable to change security options " + "during remount (dev %s, type=%s)\n", sb->s_id, + sb->s_type->name); + goto out_free_opts; +} + +static int selinux_sb_kern_mount(struct super_block *sb, int flags, void *data) +{ + const struct cred *cred = current_cred(); + struct common_audit_data ad; + int rc; + + rc = superblock_doinit(sb, data); + if (rc) + return rc; + + /* Allow all mounts performed by the kernel */ + if (flags & MS_KERNMOUNT) + return 0; + + COMMON_AUDIT_DATA_INIT(&ad, DENTRY); + ad.u.dentry = sb->s_root; + return superblock_has_perm(cred, sb, FILESYSTEM__MOUNT, &ad); +} + +static int selinux_sb_statfs(struct dentry *dentry) +{ + const struct cred *cred = current_cred(); + struct common_audit_data ad; + + COMMON_AUDIT_DATA_INIT(&ad, DENTRY); + ad.u.dentry = dentry->d_sb->s_root; + return superblock_has_perm(cred, dentry->d_sb, FILESYSTEM__GETATTR, &ad); +} + +static int selinux_mount(char *dev_name, + struct path *path, + char *type, + unsigned long flags, + void *data) +{ + const struct cred *cred = current_cred(); + + if (flags & MS_REMOUNT) + return superblock_has_perm(cred, path->mnt->mnt_sb, + FILESYSTEM__REMOUNT, NULL); + else + return path_has_perm(cred, path, FILE__MOUNTON); +} + +static int selinux_umount(struct vfsmount *mnt, int flags) +{ + const struct cred *cred = current_cred(); + + return superblock_has_perm(cred, mnt->mnt_sb, + FILESYSTEM__UNMOUNT, NULL); +} + +/* inode security operations */ + +static int selinux_inode_alloc_security(struct inode *inode) +{ + return inode_alloc_security(inode); +} + +static void selinux_inode_free_security(struct inode *inode) +{ + inode_free_security(inode); +} + +static int selinux_inode_init_security(struct inode *inode, struct inode *dir, + const struct qstr *qstr, char **name, + void **value, size_t *len) +{ + const struct task_security_struct *tsec = current_security(); + struct inode_security_struct *dsec; + struct superblock_security_struct *sbsec; + u32 sid, newsid, clen; + int rc; + char *namep = NULL, *context; + + dsec = dir->i_security; + sbsec = dir->i_sb->s_security; + + sid = tsec->sid; + newsid = tsec->create_sid; + + if ((sbsec->flags & SE_SBINITIALIZED) && + (sbsec->behavior == SECURITY_FS_USE_MNTPOINT)) + newsid = sbsec->mntpoint_sid; + else if (!newsid || !(sbsec->flags & SE_SBLABELSUPP)) { + rc = security_transition_sid(sid, dsec->sid, + inode_mode_to_security_class(inode->i_mode), + qstr, &newsid); + if (rc) { + printk(KERN_WARNING "%s: " + "security_transition_sid failed, rc=%d (dev=%s " + "ino=%ld)\n", + __func__, + -rc, inode->i_sb->s_id, inode->i_ino); + return rc; + } + } + + /* Possibly defer initialization to selinux_complete_init. */ + if (sbsec->flags & SE_SBINITIALIZED) { + struct inode_security_struct *isec = inode->i_security; + isec->sclass = inode_mode_to_security_class(inode->i_mode); + isec->sid = newsid; + isec->initialized = 1; + } + + if (!ss_initialized || !(sbsec->flags & SE_SBLABELSUPP)) + return -EOPNOTSUPP; + + if (name) { + namep = kstrdup(XATTR_SELINUX_SUFFIX, GFP_NOFS); + if (!namep) + return -ENOMEM; + *name = namep; + } + + if (value && len) { + rc = security_sid_to_context_force(newsid, &context, &clen); + if (rc) { + kfree(namep); + return rc; + } + *value = context; + *len = clen; + } + + return 0; +} + +static int selinux_inode_create(struct inode *dir, struct dentry *dentry, int mask) +{ + return may_create(dir, dentry, SECCLASS_FILE); +} + +static int selinux_inode_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) +{ + return may_link(dir, old_dentry, MAY_LINK); +} + +static int selinux_inode_unlink(struct inode *dir, struct dentry *dentry) +{ + return may_link(dir, dentry, MAY_UNLINK); +} + +static int selinux_inode_symlink(struct inode *dir, struct dentry *dentry, const char *name) +{ + return may_create(dir, dentry, SECCLASS_LNK_FILE); +} + +static int selinux_inode_mkdir(struct inode *dir, struct dentry *dentry, int mask) +{ + return may_create(dir, dentry, SECCLASS_DIR); +} + +static int selinux_inode_rmdir(struct inode *dir, struct dentry *dentry) +{ + return may_link(dir, dentry, MAY_RMDIR); +} + +static int selinux_inode_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev) +{ + return may_create(dir, dentry, inode_mode_to_security_class(mode)); +} + +static int selinux_inode_rename(struct inode *old_inode, struct dentry *old_dentry, + struct inode *new_inode, struct dentry *new_dentry) +{ + return may_rename(old_inode, old_dentry, new_inode, new_dentry); +} + +static int selinux_inode_readlink(struct dentry *dentry) +{ + const struct cred *cred = current_cred(); + + return dentry_has_perm(cred, dentry, FILE__READ); +} + +static int selinux_inode_follow_link(struct dentry *dentry, struct nameidata *nameidata) +{ + const struct cred *cred = current_cred(); + + return dentry_has_perm(cred, dentry, FILE__READ); +} + +static int selinux_inode_permission(struct inode *inode, int mask, unsigned flags) +{ + const struct cred *cred = current_cred(); + struct common_audit_data ad; + u32 perms; + bool from_access; + + from_access = mask & MAY_ACCESS; + mask &= (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND); + + /* No permission to check. Existence test. */ + if (!mask) + return 0; + + COMMON_AUDIT_DATA_INIT(&ad, INODE); + ad.u.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, flags); +} + +static int selinux_inode_setattr(struct dentry *dentry, struct iattr *iattr) +{ + const struct cred *cred = current_cred(); + unsigned int ia_valid = iattr->ia_valid; + + /* ATTR_FORCE is just used for ATTR_KILL_S[UG]ID. */ + if (ia_valid & ATTR_FORCE) { + ia_valid &= ~(ATTR_KILL_SUID | ATTR_KILL_SGID | ATTR_MODE | + ATTR_FORCE); + if (!ia_valid) + return 0; + } + + if (ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID | + ATTR_ATIME_SET | ATTR_MTIME_SET | ATTR_TIMES_SET)) + return dentry_has_perm(cred, dentry, FILE__SETATTR); + + return dentry_has_perm(cred, dentry, FILE__WRITE); +} + +static int selinux_inode_getattr(struct vfsmount *mnt, struct dentry *dentry) +{ + const struct cred *cred = current_cred(); + struct path path; + + path.dentry = dentry; + path.mnt = mnt; + + return path_has_perm(cred, &path, FILE__GETATTR); +} + +static int selinux_inode_setotherxattr(struct dentry *dentry, const char *name) +{ + const struct cred *cred = current_cred(); + + if (!strncmp(name, XATTR_SECURITY_PREFIX, + sizeof XATTR_SECURITY_PREFIX - 1)) { + if (!strcmp(name, XATTR_NAME_CAPS)) { + if (!capable(CAP_SETFCAP)) + return -EPERM; + } else if (!capable(CAP_SYS_ADMIN)) { + /* A different attribute in the security namespace. + Restrict to administrator. */ + return -EPERM; + } + } + + /* Not an attribute we recognize, so just check the + ordinary setattr permission. */ + return dentry_has_perm(cred, dentry, FILE__SETATTR); +} + +static int selinux_inode_setxattr(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags) +{ + struct inode *inode = dentry->d_inode; + struct inode_security_struct *isec = inode->i_security; + struct superblock_security_struct *sbsec; + struct common_audit_data ad; + u32 newsid, sid = current_sid(); + int rc = 0; + + if (strcmp(name, XATTR_NAME_SELINUX)) + return selinux_inode_setotherxattr(dentry, name); + + sbsec = inode->i_sb->s_security; + if (!(sbsec->flags & SE_SBLABELSUPP)) + return -EOPNOTSUPP; + + if (!inode_owner_or_capable(inode)) + return -EPERM; + + COMMON_AUDIT_DATA_INIT(&ad, DENTRY); + ad.u.dentry = dentry; + + rc = avc_has_perm(sid, isec->sid, isec->sclass, + FILE__RELABELFROM, &ad); + if (rc) + return rc; + + rc = security_context_to_sid(value, size, &newsid); + if (rc == -EINVAL) { + if (!capable(CAP_MAC_ADMIN)) + return rc; + rc = security_context_to_sid_force(value, size, &newsid); + } + if (rc) + return rc; + + rc = avc_has_perm(sid, newsid, isec->sclass, + FILE__RELABELTO, &ad); + if (rc) + return rc; + + rc = security_validate_transition(isec->sid, newsid, sid, + isec->sclass); + if (rc) + return rc; + + return avc_has_perm(newsid, + sbsec->sid, + SECCLASS_FILESYSTEM, + FILESYSTEM__ASSOCIATE, + &ad); +} + +static void selinux_inode_post_setxattr(struct dentry *dentry, const char *name, + const void *value, size_t size, + int flags) +{ + struct inode *inode = dentry->d_inode; + struct inode_security_struct *isec = inode->i_security; + u32 newsid; + int rc; + + if (strcmp(name, XATTR_NAME_SELINUX)) { + /* Not an attribute we recognize, so nothing to do. */ + return; + } + + rc = security_context_to_sid_force(value, size, &newsid); + if (rc) { + printk(KERN_ERR "SELinux: unable to map context to SID" + "for (%s, %lu), rc=%d\n", + inode->i_sb->s_id, inode->i_ino, -rc); + return; + } + + isec->sid = newsid; + return; +} + +static int selinux_inode_getxattr(struct dentry *dentry, const char *name) +{ + const struct cred *cred = current_cred(); + + return dentry_has_perm(cred, dentry, FILE__GETATTR); +} + +static int selinux_inode_listxattr(struct dentry *dentry) +{ + const struct cred *cred = current_cred(); + + return dentry_has_perm(cred, dentry, FILE__GETATTR); +} + +static int selinux_inode_removexattr(struct dentry *dentry, const char *name) +{ + if (strcmp(name, XATTR_NAME_SELINUX)) + return selinux_inode_setotherxattr(dentry, name); + + /* No one is allowed to remove a SELinux security label. + You can change the label, but all data must be labeled. */ + return -EACCES; +} + +/* + * Copy the inode security context value to the user. + * + * Permission check is handled by selinux_inode_getxattr hook. + */ +static int selinux_inode_getsecurity(const struct inode *inode, const char *name, void **buffer, bool alloc) +{ + u32 size; + int error; + char *context = NULL; + struct inode_security_struct *isec = inode->i_security; + + if (strcmp(name, XATTR_SELINUX_SUFFIX)) + return -EOPNOTSUPP; + + /* + * If the caller has CAP_MAC_ADMIN, then get the raw context + * value even if it is not defined by current policy; otherwise, + * use the in-core value under current policy. + * Use the non-auditing forms of the permission checks since + * getxattr may be called by unprivileged processes commonly + * and lack of permission just means that we fall back to the + * in-core context value, not a denial. + */ + error = selinux_capable(current, current_cred(), + &init_user_ns, CAP_MAC_ADMIN, + SECURITY_CAP_NOAUDIT); + if (!error) + error = security_sid_to_context_force(isec->sid, &context, + &size); + else + error = security_sid_to_context(isec->sid, &context, &size); + if (error) + return error; + error = size; + if (alloc) { + *buffer = context; + goto out_nofree; + } + kfree(context); +out_nofree: + return error; +} + +static int selinux_inode_setsecurity(struct inode *inode, const char *name, + const void *value, size_t size, int flags) +{ + struct inode_security_struct *isec = inode->i_security; + u32 newsid; + int rc; + + if (strcmp(name, XATTR_SELINUX_SUFFIX)) + return -EOPNOTSUPP; + + if (!value || !size) + return -EACCES; + + rc = security_context_to_sid((void *)value, size, &newsid); + if (rc) + return rc; + + isec->sid = newsid; + isec->initialized = 1; + return 0; +} + +static int selinux_inode_listsecurity(struct inode *inode, char *buffer, size_t buffer_size) +{ + const int len = sizeof(XATTR_NAME_SELINUX); + if (buffer && len <= buffer_size) + memcpy(buffer, XATTR_NAME_SELINUX, len); + return len; +} + +static void selinux_inode_getsecid(const struct inode *inode, u32 *secid) +{ + struct inode_security_struct *isec = inode->i_security; + *secid = isec->sid; +} + +/* file security operations */ + +static int selinux_revalidate_file_permission(struct file *file, int mask) +{ + const struct cred *cred = current_cred(); + struct inode *inode = file->f_path.dentry->d_inode; + + /* file_mask_to_av won't add FILE__WRITE if MAY_APPEND is set */ + if ((file->f_flags & O_APPEND) && (mask & MAY_WRITE)) + mask |= MAY_APPEND; + + return file_has_perm(cred, file, + file_mask_to_av(inode->i_mode, mask)); +} + +static int selinux_file_permission(struct file *file, int mask) +{ + struct inode *inode = file->f_path.dentry->d_inode; + struct file_security_struct *fsec = file->f_security; + struct inode_security_struct *isec = inode->i_security; + u32 sid = current_sid(); + + if (!mask) + /* No permission to check. Existence test. */ + return 0; + + if (sid == fsec->sid && fsec->isid == isec->sid && + fsec->pseqno == avc_policy_seqno()) + /* No change since dentry_open check. */ + return 0; + + return selinux_revalidate_file_permission(file, mask); +} + +static int selinux_file_alloc_security(struct file *file) +{ + return file_alloc_security(file); +} + +static void selinux_file_free_security(struct file *file) +{ + file_free_security(file); +} + +static int selinux_file_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + const struct cred *cred = current_cred(); + int error = 0; + + switch (cmd) { + case FIONREAD: + /* fall through */ + case FIBMAP: + /* fall through */ + case FIGETBSZ: + /* fall through */ + case EXT2_IOC_GETFLAGS: + /* fall through */ + case EXT2_IOC_GETVERSION: + error = file_has_perm(cred, file, FILE__GETATTR); + break; + + case EXT2_IOC_SETFLAGS: + /* fall through */ + case EXT2_IOC_SETVERSION: + error = file_has_perm(cred, file, FILE__SETATTR); + break; + + /* sys_ioctl() checks */ + case FIONBIO: + /* fall through */ + case FIOASYNC: + error = file_has_perm(cred, file, 0); + break; + + case KDSKBENT: + case KDSKBSENT: + error = task_has_capability(current, cred, CAP_SYS_TTY_CONFIG, + SECURITY_CAP_AUDIT); + break; + + /* default case assumes that the command will go + * to the file's ioctl() function. + */ + default: + error = file_has_perm(cred, file, FILE__IOCTL); + } + return error; +} + +static int default_noexec; + +static int file_map_prot_check(struct file *file, unsigned long prot, int shared) +{ + const struct cred *cred = current_cred(); + int rc = 0; + + if (default_noexec && + (prot & PROT_EXEC) && (!file || (!shared && (prot & PROT_WRITE)))) { + /* + * We are making executable an anonymous mapping or a + * private file mapping that will also be writable. + * This has an additional check. + */ + rc = cred_has_perm(cred, cred, PROCESS__EXECMEM); + if (rc) + goto error; + } + + if (file) { + /* read access is always possible with a mapping */ + u32 av = FILE__READ; + + /* write access only matters if the mapping is shared */ + if (shared && (prot & PROT_WRITE)) + av |= FILE__WRITE; + + if (prot & PROT_EXEC) + av |= FILE__EXECUTE; + + return file_has_perm(cred, file, av); + } + +error: + return rc; +} + +static int selinux_file_mmap(struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags, + unsigned long addr, unsigned long addr_only) +{ + int rc = 0; + u32 sid = current_sid(); + + /* + * notice that we are intentionally putting the SELinux check before + * the secondary cap_file_mmap check. This is such a likely attempt + * at bad behaviour/exploit that we always want to get the AVC, even + * if DAC would have also denied the operation. + */ + if (addr < CONFIG_LSM_MMAP_MIN_ADDR) { + rc = avc_has_perm(sid, sid, SECCLASS_MEMPROTECT, + MEMPROTECT__MMAP_ZERO, NULL); + if (rc) + return rc; + } + + /* do DAC check on address space usage */ + rc = cap_file_mmap(file, reqprot, prot, flags, addr, addr_only); + if (rc || addr_only) + return rc; + + if (selinux_checkreqprot) + prot = reqprot; + + return file_map_prot_check(file, prot, + (flags & MAP_TYPE) == MAP_SHARED); +} + +static int selinux_file_mprotect(struct vm_area_struct *vma, + unsigned long reqprot, + unsigned long prot) +{ + const struct cred *cred = current_cred(); + + if (selinux_checkreqprot) + prot = reqprot; + + if (default_noexec && + (prot & PROT_EXEC) && !(vma->vm_flags & VM_EXEC)) { + int rc = 0; + if (vma->vm_start >= vma->vm_mm->start_brk && + vma->vm_end <= vma->vm_mm->brk) { + rc = cred_has_perm(cred, cred, PROCESS__EXECHEAP); + } else if (!vma->vm_file && + vma->vm_start <= vma->vm_mm->start_stack && + vma->vm_end >= vma->vm_mm->start_stack) { + rc = current_has_perm(current, PROCESS__EXECSTACK); + } else if (vma->vm_file && vma->anon_vma) { + /* + * We are making executable a file mapping that has + * had some COW done. Since pages might have been + * written, check ability to execute the possibly + * modified content. This typically should only + * occur for text relocations. + */ + rc = file_has_perm(cred, vma->vm_file, FILE__EXECMOD); + } + if (rc) + return rc; + } + + return file_map_prot_check(vma->vm_file, prot, vma->vm_flags&VM_SHARED); +} + +static int selinux_file_lock(struct file *file, unsigned int cmd) +{ + const struct cred *cred = current_cred(); + + return file_has_perm(cred, file, FILE__LOCK); +} + +static int selinux_file_fcntl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + const struct cred *cred = current_cred(); + int err = 0; + + switch (cmd) { + case F_SETFL: + if (!file->f_path.dentry || !file->f_path.dentry->d_inode) { + err = -EINVAL; + break; + } + + if ((file->f_flags & O_APPEND) && !(arg & O_APPEND)) { + err = file_has_perm(cred, file, FILE__WRITE); + break; + } + /* fall through */ + case F_SETOWN: + case F_SETSIG: + case F_GETFL: + case F_GETOWN: + case F_GETSIG: + /* Just check FD__USE permission */ + err = file_has_perm(cred, file, 0); + break; + case F_GETLK: + case F_SETLK: + case F_SETLKW: +#if BITS_PER_LONG == 32 + case F_GETLK64: + case F_SETLK64: + case F_SETLKW64: +#endif + if (!file->f_path.dentry || !file->f_path.dentry->d_inode) { + err = -EINVAL; + break; + } + err = file_has_perm(cred, file, FILE__LOCK); + break; + } + + return err; +} + +static int selinux_file_set_fowner(struct file *file) +{ + struct file_security_struct *fsec; + + fsec = file->f_security; + fsec->fown_sid = current_sid(); + + return 0; +} + +static int selinux_file_send_sigiotask(struct task_struct *tsk, + struct fown_struct *fown, int signum) +{ + struct file *file; + u32 sid = task_sid(tsk); + u32 perm; + struct file_security_struct *fsec; + + /* struct fown_struct is never outside the context of a struct file */ + file = container_of(fown, struct file, f_owner); + + fsec = file->f_security; + + if (!signum) + perm = signal_to_av(SIGIO); /* as per send_sigio_to_task */ + else + perm = signal_to_av(signum); + + return avc_has_perm(fsec->fown_sid, sid, + SECCLASS_PROCESS, perm, NULL); +} + +static int selinux_file_receive(struct file *file) +{ + const struct cred *cred = current_cred(); + + return file_has_perm(cred, file, file_to_av(file)); +} + +static int selinux_dentry_open(struct file *file, const struct cred *cred) +{ + struct file_security_struct *fsec; + struct inode *inode; + struct inode_security_struct *isec; + + inode = file->f_path.dentry->d_inode; + fsec = file->f_security; + isec = inode->i_security; + /* + * Save inode label and policy sequence number + * at open-time so that selinux_file_permission + * can determine whether revalidation is necessary. + * Task label is already saved in the file security + * struct as its SID. + */ + fsec->isid = isec->sid; + fsec->pseqno = avc_policy_seqno(); + /* + * Since the inode label or policy seqno may have changed + * between the selinux_inode_permission check and the saving + * of state above, recheck that access is still permitted. + * Otherwise, access might never be revalidated against the + * new inode label or new policy. + * This check is not redundant - do not remove. + */ + return inode_has_perm_noadp(cred, inode, open_file_to_av(file), 0); +} + +/* task security operations */ + +static int selinux_task_create(unsigned long clone_flags) +{ + return current_has_perm(current, PROCESS__FORK); +} + +/* + * allocate the SELinux part of blank credentials + */ +static int selinux_cred_alloc_blank(struct cred *cred, gfp_t gfp) +{ + struct task_security_struct *tsec; + + tsec = kzalloc(sizeof(struct task_security_struct), gfp); + if (!tsec) + return -ENOMEM; + + cred->security = tsec; + return 0; +} + +/* + * detach and free the LSM part of a set of credentials + */ +static void selinux_cred_free(struct cred *cred) +{ + struct task_security_struct *tsec = cred->security; + + /* + * cred->security == NULL if security_cred_alloc_blank() or + * security_prepare_creds() returned an error. + */ + BUG_ON(cred->security && (unsigned long) cred->security < PAGE_SIZE); + cred->security = (void *) 0x7UL; + kfree(tsec); +} + +/* + * prepare a new set of credentials for modification + */ +static int selinux_cred_prepare(struct cred *new, const struct cred *old, + gfp_t gfp) +{ + const struct task_security_struct *old_tsec; + struct task_security_struct *tsec; + + old_tsec = old->security; + + tsec = kmemdup(old_tsec, sizeof(struct task_security_struct), gfp); + if (!tsec) + return -ENOMEM; + + new->security = tsec; + return 0; +} + +/* + * transfer the SELinux data to a blank set of creds + */ +static void selinux_cred_transfer(struct cred *new, const struct cred *old) +{ + const struct task_security_struct *old_tsec = old->security; + struct task_security_struct *tsec = new->security; + + *tsec = *old_tsec; +} + +/* + * set the security data for a kernel service + * - all the creation contexts are set to unlabelled + */ +static int selinux_kernel_act_as(struct cred *new, u32 secid) +{ + struct task_security_struct *tsec = new->security; + u32 sid = current_sid(); + int ret; + + ret = avc_has_perm(sid, secid, + SECCLASS_KERNEL_SERVICE, + KERNEL_SERVICE__USE_AS_OVERRIDE, + NULL); + if (ret == 0) { + tsec->sid = secid; + tsec->create_sid = 0; + tsec->keycreate_sid = 0; + tsec->sockcreate_sid = 0; + } + return ret; +} + +/* + * set the file creation context in a security record to the same as the + * objective context of the specified inode + */ +static int selinux_kernel_create_files_as(struct cred *new, struct inode *inode) +{ + struct inode_security_struct *isec = inode->i_security; + struct task_security_struct *tsec = new->security; + u32 sid = current_sid(); + int ret; + + ret = avc_has_perm(sid, isec->sid, + SECCLASS_KERNEL_SERVICE, + KERNEL_SERVICE__CREATE_FILES_AS, + NULL); + + if (ret == 0) + tsec->create_sid = isec->sid; + return ret; +} + +static int selinux_kernel_module_request(char *kmod_name) +{ + u32 sid; + struct common_audit_data ad; + + sid = task_sid(current); + + COMMON_AUDIT_DATA_INIT(&ad, KMOD); + ad.u.kmod_name = kmod_name; + + return avc_has_perm(sid, SECINITSID_KERNEL, SECCLASS_SYSTEM, + SYSTEM__MODULE_REQUEST, &ad); +} + +static int selinux_task_setpgid(struct task_struct *p, pid_t pgid) +{ + return current_has_perm(p, PROCESS__SETPGID); +} + +static int selinux_task_getpgid(struct task_struct *p) +{ + return current_has_perm(p, PROCESS__GETPGID); +} + +static int selinux_task_getsid(struct task_struct *p) +{ + return current_has_perm(p, PROCESS__GETSESSION); +} + +static void selinux_task_getsecid(struct task_struct *p, u32 *secid) +{ + *secid = task_sid(p); +} + +static int selinux_task_setnice(struct task_struct *p, int nice) +{ + int rc; + + rc = cap_task_setnice(p, nice); + if (rc) + return rc; + + return current_has_perm(p, PROCESS__SETSCHED); +} + +static int selinux_task_setioprio(struct task_struct *p, int ioprio) +{ + int rc; + + rc = cap_task_setioprio(p, ioprio); + if (rc) + return rc; + + return current_has_perm(p, PROCESS__SETSCHED); +} + +static int selinux_task_getioprio(struct task_struct *p) +{ + return current_has_perm(p, PROCESS__GETSCHED); +} + +static int selinux_task_setrlimit(struct task_struct *p, unsigned int resource, + struct rlimit *new_rlim) +{ + struct rlimit *old_rlim = p->signal->rlim + resource; + + /* Control the ability to change the hard limit (whether + lowering or raising it), so that the hard limit can + later be used as a safe reset point for the soft limit + upon context transitions. See selinux_bprm_committing_creds. */ + if (old_rlim->rlim_max != new_rlim->rlim_max) + return current_has_perm(p, PROCESS__SETRLIMIT); + + return 0; +} + +static int selinux_task_setscheduler(struct task_struct *p) +{ + int rc; + + rc = cap_task_setscheduler(p); + if (rc) + return rc; + + return current_has_perm(p, PROCESS__SETSCHED); +} + +static int selinux_task_getscheduler(struct task_struct *p) +{ + return current_has_perm(p, PROCESS__GETSCHED); +} + +static int selinux_task_movememory(struct task_struct *p) +{ + return current_has_perm(p, PROCESS__SETSCHED); +} + +static int selinux_task_kill(struct task_struct *p, struct siginfo *info, + int sig, u32 secid) +{ + u32 perm; + int rc; + + if (!sig) + perm = PROCESS__SIGNULL; /* null signal; existence test */ + else + perm = signal_to_av(sig); + if (secid) + rc = avc_has_perm(secid, task_sid(p), + SECCLASS_PROCESS, perm, NULL); + else + rc = current_has_perm(p, perm); + return rc; +} + +static int selinux_task_wait(struct task_struct *p) +{ + return task_has_perm(p, current, PROCESS__SIGCHLD); +} + +static void selinux_task_to_inode(struct task_struct *p, + struct inode *inode) +{ + struct inode_security_struct *isec = inode->i_security; + u32 sid = task_sid(p); + + isec->sid = sid; + isec->initialized = 1; +} + +/* Returns error only if unable to parse addresses */ +static int selinux_parse_skb_ipv4(struct sk_buff *skb, + struct common_audit_data *ad, u8 *proto) +{ + int offset, ihlen, ret = -EINVAL; + struct iphdr _iph, *ih; + + offset = skb_network_offset(skb); + ih = skb_header_pointer(skb, offset, sizeof(_iph), &_iph); + if (ih == NULL) + goto out; + + ihlen = ih->ihl * 4; + if (ihlen < sizeof(_iph)) + goto out; + + ad->u.net.v4info.saddr = ih->saddr; + ad->u.net.v4info.daddr = ih->daddr; + ret = 0; + + if (proto) + *proto = ih->protocol; + + switch (ih->protocol) { + case IPPROTO_TCP: { + struct tcphdr _tcph, *th; + + if (ntohs(ih->frag_off) & IP_OFFSET) + break; + + offset += ihlen; + th = skb_header_pointer(skb, offset, sizeof(_tcph), &_tcph); + if (th == NULL) + break; + + ad->u.net.sport = th->source; + ad->u.net.dport = th->dest; + break; + } + + case IPPROTO_UDP: { + struct udphdr _udph, *uh; + + if (ntohs(ih->frag_off) & IP_OFFSET) + break; + + offset += ihlen; + uh = skb_header_pointer(skb, offset, sizeof(_udph), &_udph); + if (uh == NULL) + break; + + ad->u.net.sport = uh->source; + ad->u.net.dport = uh->dest; + break; + } + + case IPPROTO_DCCP: { + struct dccp_hdr _dccph, *dh; + + if (ntohs(ih->frag_off) & IP_OFFSET) + break; + + offset += ihlen; + dh = skb_header_pointer(skb, offset, sizeof(_dccph), &_dccph); + if (dh == NULL) + break; + + ad->u.net.sport = dh->dccph_sport; + ad->u.net.dport = dh->dccph_dport; + break; + } + + default: + break; + } +out: + return ret; +} + +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + +/* Returns error only if unable to parse addresses */ +static int selinux_parse_skb_ipv6(struct sk_buff *skb, + struct common_audit_data *ad, u8 *proto) +{ + u8 nexthdr; + int ret = -EINVAL, offset; + struct ipv6hdr _ipv6h, *ip6; + + offset = skb_network_offset(skb); + ip6 = skb_header_pointer(skb, offset, sizeof(_ipv6h), &_ipv6h); + if (ip6 == NULL) + goto out; + + ipv6_addr_copy(&ad->u.net.v6info.saddr, &ip6->saddr); + ipv6_addr_copy(&ad->u.net.v6info.daddr, &ip6->daddr); + ret = 0; + + nexthdr = ip6->nexthdr; + offset += sizeof(_ipv6h); + offset = ipv6_skip_exthdr(skb, offset, &nexthdr); + if (offset < 0) + goto out; + + if (proto) + *proto = nexthdr; + + switch (nexthdr) { + case IPPROTO_TCP: { + struct tcphdr _tcph, *th; + + th = skb_header_pointer(skb, offset, sizeof(_tcph), &_tcph); + if (th == NULL) + break; + + ad->u.net.sport = th->source; + ad->u.net.dport = th->dest; + break; + } + + case IPPROTO_UDP: { + struct udphdr _udph, *uh; + + uh = skb_header_pointer(skb, offset, sizeof(_udph), &_udph); + if (uh == NULL) + break; + + ad->u.net.sport = uh->source; + ad->u.net.dport = uh->dest; + break; + } + + case IPPROTO_DCCP: { + struct dccp_hdr _dccph, *dh; + + dh = skb_header_pointer(skb, offset, sizeof(_dccph), &_dccph); + if (dh == NULL) + break; + + ad->u.net.sport = dh->dccph_sport; + ad->u.net.dport = dh->dccph_dport; + break; + } + + /* includes fragments */ + default: + break; + } +out: + return ret; +} + +#endif /* IPV6 */ + +static int selinux_parse_skb(struct sk_buff *skb, struct common_audit_data *ad, + char **_addrp, int src, u8 *proto) +{ + char *addrp; + int ret; + + switch (ad->u.net.family) { + case PF_INET: + ret = selinux_parse_skb_ipv4(skb, ad, proto); + if (ret) + goto parse_error; + addrp = (char *)(src ? &ad->u.net.v4info.saddr : + &ad->u.net.v4info.daddr); + goto okay; + +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + case PF_INET6: + ret = selinux_parse_skb_ipv6(skb, ad, proto); + if (ret) + goto parse_error; + addrp = (char *)(src ? &ad->u.net.v6info.saddr : + &ad->u.net.v6info.daddr); + goto okay; +#endif /* IPV6 */ + default: + addrp = NULL; + goto okay; + } + +parse_error: + printk(KERN_WARNING + "SELinux: failure in selinux_parse_skb()," + " unable to parse packet\n"); + return ret; + +okay: + if (_addrp) + *_addrp = addrp; + return 0; +} + +/** + * selinux_skb_peerlbl_sid - Determine the peer label of a packet + * @skb: the packet + * @family: protocol family + * @sid: the packet's peer label SID + * + * Description: + * Check the various different forms of network peer labeling and determine + * the peer label/SID for the packet; most of the magic actually occurs in + * the security server function security_net_peersid_cmp(). The function + * returns zero if the value in @sid is valid (although it may be SECSID_NULL) + * or -EACCES if @sid is invalid due to inconsistencies with the different + * peer labels. + * + */ +static int selinux_skb_peerlbl_sid(struct sk_buff *skb, u16 family, u32 *sid) +{ + int err; + u32 xfrm_sid; + u32 nlbl_sid; + u32 nlbl_type; + + selinux_skb_xfrm_sid(skb, &xfrm_sid); + selinux_netlbl_skbuff_getsid(skb, family, &nlbl_type, &nlbl_sid); + + err = security_net_peersid_resolve(nlbl_sid, nlbl_type, xfrm_sid, sid); + if (unlikely(err)) { + printk(KERN_WARNING + "SELinux: failure in selinux_skb_peerlbl_sid()," + " unable to determine packet's peer label\n"); + return -EACCES; + } + + return 0; +} + +/* socket security operations */ + +static int socket_sockcreate_sid(const struct task_security_struct *tsec, + u16 secclass, u32 *socksid) +{ + if (tsec->sockcreate_sid > SECSID_NULL) { + *socksid = tsec->sockcreate_sid; + return 0; + } + + return security_transition_sid(tsec->sid, tsec->sid, secclass, NULL, + socksid); +} + +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 tsid = task_sid(task); + + if (sksec->sid == SECINITSID_KERNEL) + return 0; + + COMMON_AUDIT_DATA_INIT(&ad, NET); + ad.u.net.sk = sk; + + 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 task_security_struct *tsec = current_security(); + u32 newsid; + u16 secclass; + int rc; + + if (kern) + return 0; + + secclass = socket_type_to_security_class(family, type, protocol); + rc = socket_sockcreate_sid(tsec, secclass, &newsid); + if (rc) + return rc; + + 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 task_security_struct *tsec = current_security(); + struct inode_security_struct *isec = SOCK_INODE(sock)->i_security; + struct sk_security_struct *sksec; + int err = 0; + + isec->sclass = socket_type_to_security_class(family, type, protocol); + + if (kern) + isec->sid = SECINITSID_KERNEL; + else { + err = socket_sockcreate_sid(tsec, isec->sclass, &(isec->sid)); + if (err) + return err; + } + + isec->initialized = 1; + + if (sock->sk) { + sksec = sock->sk->sk_security; + sksec->sid = isec->sid; + sksec->sclass = isec->sclass; + err = selinux_netlbl_socket_post_create(sock->sk, family); + } + + return err; +} + +/* Range of port numbers used to automatically bind. + Need to determine whether we should perform a name_bind + permission check between the socket and the port number. */ + +static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + u16 family; + int err; + + err = sock_has_perm(current, sk, SOCKET__BIND); + if (err) + goto out; + + /* + * If PF_INET or PF_INET6, check name_bind permission for the port. + * Multiple address binding for SCTP is not supported yet: we just + * check the first address now. + */ + family = sk->sk_family; + if (family == PF_INET || family == PF_INET6) { + char *addrp; + 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; + u32 sid, node_perm; + + if (family == PF_INET) { + addr4 = (struct sockaddr_in *)address; + snum = ntohs(addr4->sin_port); + addrp = (char *)&addr4->sin_addr.s_addr; + } else { + addr6 = (struct sockaddr_in6 *)address; + snum = ntohs(addr6->sin6_port); + addrp = (char *)&addr6->sin6_addr.s6_addr; + } + + if (snum) { + int low, high; + + inet_get_local_port_range(&low, &high); + + if (snum < max(PROT_SOCK, low) || snum > high) { + err = sel_netport_sid(sk->sk_protocol, + snum, &sid); + if (err) + goto out; + COMMON_AUDIT_DATA_INIT(&ad, NET); + ad.u.net.sport = htons(snum); + ad.u.net.family = family; + err = avc_has_perm(sksec->sid, sid, + sksec->sclass, + SOCKET__NAME_BIND, &ad); + if (err) + goto out; + } + } + + switch (sksec->sclass) { + case SECCLASS_TCP_SOCKET: + node_perm = TCP_SOCKET__NODE_BIND; + break; + + case SECCLASS_UDP_SOCKET: + node_perm = UDP_SOCKET__NODE_BIND; + break; + + case SECCLASS_DCCP_SOCKET: + node_perm = DCCP_SOCKET__NODE_BIND; + break; + + default: + node_perm = RAWIP_SOCKET__NODE_BIND; + break; + } + + err = sel_netnode_sid(addrp, family, &sid); + if (err) + goto out; + + COMMON_AUDIT_DATA_INIT(&ad, NET); + ad.u.net.sport = htons(snum); + ad.u.net.family = family; + + if (family == PF_INET) + ad.u.net.v4info.saddr = addr4->sin_addr.s_addr; + else + ipv6_addr_copy(&ad.u.net.v6info.saddr, &addr6->sin6_addr); + + err = avc_has_perm(sksec->sid, sid, + sksec->sclass, node_perm, &ad); + if (err) + goto out; + } +out: + return err; +} + +static int selinux_socket_connect(struct socket *sock, struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + struct sk_security_struct *sksec = sk->sk_security; + int err; + + 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. + */ + 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; + unsigned short snum; + u32 sid, perm; + + if (sk->sk_family == PF_INET) { + addr4 = (struct sockaddr_in *)address; + if (addrlen < sizeof(struct sockaddr_in)) + return -EINVAL; + snum = ntohs(addr4->sin_port); + } else { + addr6 = (struct sockaddr_in6 *)address; + if (addrlen < SIN6_LEN_RFC2133) + return -EINVAL; + snum = ntohs(addr6->sin6_port); + } + + err = sel_netport_sid(sk->sk_protocol, snum, &sid); + if (err) + goto out; + + 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(sksec->sid, sid, sksec->sclass, perm, &ad); + if (err) + goto out; + } + + err = selinux_netlbl_socket_connect(sk, address); + +out: + return err; +} + +static int selinux_socket_listen(struct socket *sock, int backlog) +{ + return sock_has_perm(current, sock->sk, SOCKET__LISTEN); +} + +static int selinux_socket_accept(struct socket *sock, struct socket *newsock) +{ + int err; + struct inode_security_struct *isec; + struct inode_security_struct *newisec; + + err = sock_has_perm(current, sock->sk, SOCKET__ACCEPT); + if (err) + return err; + + newisec = SOCK_INODE(newsock)->i_security; + + isec = SOCK_INODE(sock)->i_security; + newisec->sclass = isec->sclass; + newisec->sid = isec->sid; + newisec->initialized = 1; + + return 0; +} + +static int selinux_socket_sendmsg(struct socket *sock, struct msghdr *msg, + int size) +{ + 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 sock_has_perm(current, sock->sk, SOCKET__READ); +} + +static int selinux_socket_getsockname(struct socket *sock) +{ + return sock_has_perm(current, sock->sk, SOCKET__GETATTR); +} + +static int selinux_socket_getpeername(struct socket *sock) +{ + return sock_has_perm(current, sock->sk, SOCKET__GETATTR); +} + +static int selinux_socket_setsockopt(struct socket *sock, int level, int optname) +{ + int err; + + err = sock_has_perm(current, sock->sk, SOCKET__SETOPT); + if (err) + return err; + + return selinux_netlbl_socket_setsockopt(sock, level, optname); +} + +static int selinux_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + return sock_has_perm(current, sock->sk, SOCKET__GETOPT); +} + +static int selinux_socket_shutdown(struct socket *sock, int how) +{ + return sock_has_perm(current, sock->sk, SOCKET__SHUTDOWN); +} + +static int selinux_socket_unix_stream_connect(struct sock *sock, + struct sock *other, + struct sock *newsk) +{ + struct sk_security_struct *sksec_sock = sock->sk_security; + struct sk_security_struct *sksec_other = other->sk_security; + struct sk_security_struct *sksec_new = newsk->sk_security; + struct common_audit_data ad; + int err; + + COMMON_AUDIT_DATA_INIT(&ad, NET); + ad.u.net.sk = other; + + err = avc_has_perm(sksec_sock->sid, sksec_other->sid, + sksec_other->sclass, + UNIX_STREAM_SOCKET__CONNECTTO, &ad); + if (err) + return err; + + /* server child socket */ + 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; + + /* 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 sk_security_struct *ssec = sock->sk->sk_security; + struct sk_security_struct *osec = other->sk->sk_security; + struct common_audit_data ad; + + COMMON_AUDIT_DATA_INIT(&ad, NET); + ad.u.net.sk = other->sk; + + 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, + u32 peer_sid, + struct common_audit_data *ad) +{ + int err; + u32 if_sid; + u32 node_sid; + + err = sel_netif_sid(ifindex, &if_sid); + if (err) + return err; + err = avc_has_perm(peer_sid, if_sid, + SECCLASS_NETIF, NETIF__INGRESS, ad); + if (err) + return err; + + err = sel_netnode_sid(addrp, family, &node_sid); + if (err) + return err; + return avc_has_perm(peer_sid, node_sid, + SECCLASS_NODE, NODE__RECVFROM, ad); +} + +static int selinux_sock_rcv_skb_compat(struct sock *sk, struct sk_buff *skb, + u16 family) +{ + int err = 0; + struct sk_security_struct *sksec = sk->sk_security; + u32 sk_sid = sksec->sid; + struct common_audit_data ad; + char *addrp; + + COMMON_AUDIT_DATA_INIT(&ad, NET); + ad.u.net.netif = skb->skb_iif; + ad.u.net.family = family; + err = selinux_parse_skb(skb, &ad, &addrp, 1, NULL); + if (err) + return err; + + if (selinux_secmark_enabled()) { + err = avc_has_perm(sk_sid, skb->secmark, SECCLASS_PACKET, + PACKET__RECV, &ad); + if (err) + return err; + } + + err = selinux_netlbl_sock_rcv_skb(sksec, skb, family, &ad); + if (err) + return err; + err = selinux_xfrm_sock_rcv_skb(sksec->sid, skb, &ad); + + return err; +} + +static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) +{ + int err; + struct sk_security_struct *sksec = sk->sk_security; + u16 family = sk->sk_family; + u32 sk_sid = sksec->sid; + struct common_audit_data ad; + char *addrp; + u8 secmark_active; + u8 peerlbl_active; + + if (family != PF_INET && family != PF_INET6) + return 0; + + /* Handle mapped IPv4 packets arriving via IPv6 sockets */ + if (family == PF_INET6 && skb->protocol == htons(ETH_P_IP)) + family = PF_INET; + + /* If any sort of compatibility mode is enabled then handoff processing + * to the selinux_sock_rcv_skb_compat() function to deal with the + * special handling. We do this in an attempt to keep this function + * as fast and as clean as possible. */ + if (!selinux_policycap_netpeer) + return selinux_sock_rcv_skb_compat(sk, skb, family); + + secmark_active = selinux_secmark_enabled(); + peerlbl_active = netlbl_enabled() || selinux_xfrm_enabled(); + if (!secmark_active && !peerlbl_active) + return 0; + + COMMON_AUDIT_DATA_INIT(&ad, NET); + ad.u.net.netif = skb->skb_iif; + ad.u.net.family = family; + err = selinux_parse_skb(skb, &ad, &addrp, 1, NULL); + if (err) + return err; + + if (peerlbl_active) { + u32 peer_sid; + + err = selinux_skb_peerlbl_sid(skb, family, &peer_sid); + if (err) + return err; + err = selinux_inet_sys_rcv_skb(skb->skb_iif, addrp, family, + peer_sid, &ad); + if (err) { + selinux_netlbl_err(skb, err, 0); + return err; + } + err = avc_has_perm(sk_sid, peer_sid, SECCLASS_PEER, + PEER__RECV, &ad); + if (err) + selinux_netlbl_err(skb, err, 0); + } + + if (secmark_active) { + err = avc_has_perm(sk_sid, skb->secmark, SECCLASS_PACKET, + PACKET__RECV, &ad); + if (err) + return err; + } + + return err; +} + +static int selinux_socket_getpeersec_stream(struct socket *sock, char __user *optval, + int __user *optlen, unsigned len) +{ + int err = 0; + char *scontext; + u32 scontext_len; + struct sk_security_struct *sksec = sock->sk->sk_security; + u32 peer_sid = SECSID_NULL; + + if (sksec->sclass == SECCLASS_UNIX_STREAM_SOCKET || + sksec->sclass == SECCLASS_TCP_SOCKET) + peer_sid = sksec->peer_sid; + if (peer_sid == SECSID_NULL) + return -ENOPROTOOPT; + + err = security_sid_to_context(peer_sid, &scontext, &scontext_len); + if (err) + return err; + + if (scontext_len > len) { + err = -ERANGE; + goto out_len; + } + + if (copy_to_user(optval, scontext, scontext_len)) + err = -EFAULT; + +out_len: + if (put_user(scontext_len, optlen)) + err = -EFAULT; + kfree(scontext); + return err; +} + +static int selinux_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb, u32 *secid) +{ + u32 peer_secid = SECSID_NULL; + u16 family; + + if (skb && skb->protocol == htons(ETH_P_IP)) + family = PF_INET; + else if (skb && skb->protocol == htons(ETH_P_IPV6)) + family = PF_INET6; + else if (sock) + family = sock->sk->sk_family; + else + goto out; + + if (sock && family == PF_UNIX) + selinux_inode_getsecid(SOCK_INODE(sock), &peer_secid); + else if (skb) + selinux_skb_peerlbl_sid(skb, family, &peer_secid); + +out: + *secid = peer_secid; + if (peer_secid == SECSID_NULL) + return -EINVAL; + return 0; +} + +static int selinux_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; + selinux_netlbl_sk_security_reset(sksec); + sk->sk_security = sksec; + + return 0; +} + +static void selinux_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); +} + +static void selinux_sk_clone_security(const struct sock *sk, struct sock *newsk) +{ + struct sk_security_struct *sksec = sk->sk_security; + struct sk_security_struct *newsksec = newsk->sk_security; + + newsksec->sid = sksec->sid; + newsksec->peer_sid = sksec->peer_sid; + newsksec->sclass = sksec->sclass; + + selinux_netlbl_sk_security_reset(newsksec); +} + +static void selinux_sk_getsecid(struct sock *sk, u32 *secid) +{ + if (!sk) + *secid = SECINITSID_ANY_SOCKET; + else { + struct sk_security_struct *sksec = sk->sk_security; + + *secid = sksec->sid; + } +} + +static void selinux_sock_graft(struct sock *sk, struct socket *parent) +{ + struct inode_security_struct *isec = SOCK_INODE(parent)->i_security; + struct sk_security_struct *sksec = sk->sk_security; + + if (sk->sk_family == PF_INET || sk->sk_family == PF_INET6 || + sk->sk_family == PF_UNIX) + isec->sid = sksec->sid; + sksec->sclass = isec->sclass; +} + +static int selinux_inet_conn_request(struct sock *sk, struct sk_buff *skb, + struct request_sock *req) +{ + struct sk_security_struct *sksec = sk->sk_security; + int err; + u16 family = sk->sk_family; + u32 newsid; + u32 peersid; + + /* handle mapped IPv4 packets arriving via IPv6 sockets */ + if (family == PF_INET6 && skb->protocol == htons(ETH_P_IP)) + family = PF_INET; + + err = selinux_skb_peerlbl_sid(skb, family, &peersid); + if (err) + return err; + if (peersid == SECSID_NULL) { + req->secid = sksec->sid; + req->peer_secid = SECSID_NULL; + } else { + err = security_sid_mls_copy(sksec->sid, peersid, &newsid); + if (err) + return err; + req->secid = newsid; + req->peer_secid = peersid; + } + + return selinux_netlbl_inet_conn_request(req, family); +} + +static void selinux_inet_csk_clone(struct sock *newsk, + const struct request_sock *req) +{ + struct sk_security_struct *newsksec = newsk->sk_security; + + newsksec->sid = req->secid; + newsksec->peer_sid = req->peer_secid; + /* NOTE: Ideally, we should also get the isec->sid for the + new socket in sync, but we don't have the isec available yet. + So we will wait until sock_graft to do it, by which + time it will have been created and available. */ + + /* We don't need to take any sort of lock here as we are the only + * thread with access to newsksec */ + selinux_netlbl_inet_csk_clone(newsk, req->rsk_ops->family); +} + +static void selinux_inet_conn_established(struct sock *sk, struct sk_buff *skb) +{ + u16 family = sk->sk_family; + struct sk_security_struct *sksec = sk->sk_security; + + /* handle mapped IPv4 packets arriving via IPv6 sockets */ + if (family == PF_INET6 && skb->protocol == htons(ETH_P_IP)) + family = PF_INET; + + selinux_skb_peerlbl_sid(skb, family, &sksec->peer_sid); +} + +static int selinux_secmark_relabel_packet(u32 sid) +{ + const struct task_security_struct *__tsec; + u32 tsid; + + __tsec = current_security(); + tsid = __tsec->sid; + + return avc_has_perm(tsid, sid, SECCLASS_PACKET, PACKET__RELABELTO, NULL); +} + +static void selinux_secmark_refcount_inc(void) +{ + atomic_inc(&selinux_secmark_refcount); +} + +static void selinux_secmark_refcount_dec(void) +{ + atomic_dec(&selinux_secmark_refcount); +} + +static void selinux_req_classify_flow(const struct request_sock *req, + struct flowi *fl) +{ + fl->flowi_secid = req->secid; +} + +static int selinux_tun_dev_create(void) +{ + u32 sid = current_sid(); + + /* we aren't taking into account the "sockcreate" SID since the socket + * that is being created here is not a socket in the traditional sense, + * instead it is a private sock, accessible only to the kernel, and + * representing a wide range of network traffic spanning multiple + * connections unlike traditional sockets - check the TUN driver to + * get a better understanding of why this socket is special */ + + return avc_has_perm(sid, sid, SECCLASS_TUN_SOCKET, TUN_SOCKET__CREATE, + NULL); +} + +static void selinux_tun_dev_post_create(struct sock *sk) +{ + struct sk_security_struct *sksec = sk->sk_security; + + /* we don't currently perform any NetLabel based labeling here and it + * isn't clear that we would want to do so anyway; while we could apply + * labeling without the support of the TUN user the resulting labeled + * traffic from the other end of the connection would almost certainly + * cause confusion to the TUN user that had no idea network labeling + * protocols were being used */ + + /* see the comments in selinux_tun_dev_create() about why we don't use + * the sockcreate SID here */ + + sksec->sid = current_sid(); + sksec->sclass = SECCLASS_TUN_SOCKET; +} + +static int selinux_tun_dev_attach(struct sock *sk) +{ + struct sk_security_struct *sksec = sk->sk_security; + u32 sid = current_sid(); + int err; + + err = avc_has_perm(sid, sksec->sid, SECCLASS_TUN_SOCKET, + TUN_SOCKET__RELABELFROM, NULL); + if (err) + return err; + err = avc_has_perm(sid, sid, SECCLASS_TUN_SOCKET, + TUN_SOCKET__RELABELTO, NULL); + if (err) + return err; + + sksec->sid = sid; + + return 0; +} + +static int selinux_nlmsg_perm(struct sock *sk, struct sk_buff *skb) +{ + int err = 0; + u32 perm; + struct nlmsghdr *nlh; + struct sk_security_struct *sksec = sk->sk_security; + + if (skb->len < NLMSG_SPACE(0)) { + err = -EINVAL; + goto out; + } + nlh = nlmsg_hdr(skb); + + 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, sksec->sclass); + if (!selinux_enforcing || security_get_allow_unknown()) + err = 0; + } + + /* Ignore */ + if (err == -ENOENT) + err = 0; + goto out; + } + + err = sock_has_perm(current, sk, perm); +out: + return err; +} + +#ifdef CONFIG_NETFILTER + +static unsigned int selinux_ip_forward(struct sk_buff *skb, int ifindex, + u16 family) +{ + int err; + char *addrp; + u32 peer_sid; + struct common_audit_data ad; + u8 secmark_active; + u8 netlbl_active; + u8 peerlbl_active; + + if (!selinux_policycap_netpeer) + return NF_ACCEPT; + + secmark_active = selinux_secmark_enabled(); + netlbl_active = netlbl_enabled(); + peerlbl_active = netlbl_active || selinux_xfrm_enabled(); + if (!secmark_active && !peerlbl_active) + return NF_ACCEPT; + + if (selinux_skb_peerlbl_sid(skb, family, &peer_sid) != 0) + return NF_DROP; + + COMMON_AUDIT_DATA_INIT(&ad, NET); + ad.u.net.netif = ifindex; + ad.u.net.family = family; + if (selinux_parse_skb(skb, &ad, &addrp, 1, NULL) != 0) + return NF_DROP; + + if (peerlbl_active) { + err = selinux_inet_sys_rcv_skb(ifindex, addrp, family, + peer_sid, &ad); + if (err) { + selinux_netlbl_err(skb, err, 1); + return NF_DROP; + } + } + + if (secmark_active) + if (avc_has_perm(peer_sid, skb->secmark, + SECCLASS_PACKET, PACKET__FORWARD_IN, &ad)) + return NF_DROP; + + if (netlbl_active) + /* we do this in the FORWARD path and not the POST_ROUTING + * path because we want to make sure we apply the necessary + * labeling before IPsec is applied so we can leverage AH + * protection */ + if (selinux_netlbl_skbuff_setsid(skb, family, peer_sid) != 0) + return NF_DROP; + + return NF_ACCEPT; +} + +static unsigned int selinux_ipv4_forward(unsigned int hooknum, + struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) +{ + return selinux_ip_forward(skb, in->ifindex, PF_INET); +} + +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) +static unsigned int selinux_ipv6_forward(unsigned int hooknum, + struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) +{ + return selinux_ip_forward(skb, in->ifindex, PF_INET6); +} +#endif /* IPV6 */ + +static unsigned int selinux_ip_output(struct sk_buff *skb, + u16 family) +{ + u32 sid; + + if (!netlbl_enabled()) + return NF_ACCEPT; + + /* we do this in the LOCAL_OUT path and not the POST_ROUTING path + * because we want to make sure we apply the necessary labeling + * before IPsec is applied so we can leverage AH protection */ + if (skb->sk) { + struct sk_security_struct *sksec = skb->sk->sk_security; + sid = sksec->sid; + } else + sid = SECINITSID_KERNEL; + if (selinux_netlbl_skbuff_setsid(skb, family, sid) != 0) + return NF_DROP; + + return NF_ACCEPT; +} + +static unsigned int selinux_ipv4_output(unsigned int hooknum, + struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) +{ + return selinux_ip_output(skb, PF_INET); +} + +static unsigned int selinux_ip_postroute_compat(struct sk_buff *skb, + int ifindex, + u16 family) +{ + struct sock *sk = skb->sk; + struct sk_security_struct *sksec; + struct common_audit_data ad; + char *addrp; + u8 proto; + + if (sk == NULL) + return NF_ACCEPT; + sksec = sk->sk_security; + + COMMON_AUDIT_DATA_INIT(&ad, NET); + ad.u.net.netif = ifindex; + ad.u.net.family = family; + if (selinux_parse_skb(skb, &ad, &addrp, 0, &proto)) + return NF_DROP; + + if (selinux_secmark_enabled()) + if (avc_has_perm(sksec->sid, skb->secmark, + SECCLASS_PACKET, PACKET__SEND, &ad)) + return NF_DROP_ERR(-ECONNREFUSED); + + if (selinux_xfrm_postroute_last(sksec->sid, skb, &ad, proto)) + return NF_DROP_ERR(-ECONNREFUSED); + + return NF_ACCEPT; +} + +static unsigned int selinux_ip_postroute(struct sk_buff *skb, int ifindex, + u16 family) +{ + u32 secmark_perm; + u32 peer_sid; + struct sock *sk; + struct common_audit_data ad; + char *addrp; + u8 secmark_active; + u8 peerlbl_active; + + /* If any sort of compatibility mode is enabled then handoff processing + * to the selinux_ip_postroute_compat() function to deal with the + * special handling. We do this in an attempt to keep this function + * as fast and as clean as possible. */ + if (!selinux_policycap_netpeer) + return selinux_ip_postroute_compat(skb, ifindex, family); +#ifdef CONFIG_XFRM + /* If skb->dst->xfrm is non-NULL then the packet is undergoing an IPsec + * packet transformation so allow the packet to pass without any checks + * since we'll have another chance to perform access control checks + * when the packet is on it's final way out. + * NOTE: there appear to be some IPv6 multicast cases where skb->dst + * is NULL, in this case go ahead and apply access control. */ + if (skb_dst(skb) != NULL && skb_dst(skb)->xfrm != NULL) + return NF_ACCEPT; +#endif + secmark_active = selinux_secmark_enabled(); + peerlbl_active = netlbl_enabled() || selinux_xfrm_enabled(); + if (!secmark_active && !peerlbl_active) + return NF_ACCEPT; + + /* if the packet is being forwarded then get the peer label from the + * packet itself; otherwise check to see if it is from a local + * application or the kernel, if from an application get the peer label + * from the sending socket, otherwise use the kernel's sid */ + sk = skb->sk; + if (sk == NULL) { + if (skb->skb_iif) { + secmark_perm = PACKET__FORWARD_OUT; + if (selinux_skb_peerlbl_sid(skb, family, &peer_sid)) + return NF_DROP; + } else { + secmark_perm = PACKET__SEND; + peer_sid = SECINITSID_KERNEL; + } + } else { + struct sk_security_struct *sksec = sk->sk_security; + peer_sid = sksec->sid; + secmark_perm = PACKET__SEND; + } + + COMMON_AUDIT_DATA_INIT(&ad, NET); + ad.u.net.netif = ifindex; + ad.u.net.family = family; + if (selinux_parse_skb(skb, &ad, &addrp, 0, NULL)) + return NF_DROP; + + if (secmark_active) + if (avc_has_perm(peer_sid, skb->secmark, + SECCLASS_PACKET, secmark_perm, &ad)) + return NF_DROP_ERR(-ECONNREFUSED); + + if (peerlbl_active) { + u32 if_sid; + u32 node_sid; + + if (sel_netif_sid(ifindex, &if_sid)) + return NF_DROP; + if (avc_has_perm(peer_sid, if_sid, + SECCLASS_NETIF, NETIF__EGRESS, &ad)) + return NF_DROP_ERR(-ECONNREFUSED); + + if (sel_netnode_sid(addrp, family, &node_sid)) + return NF_DROP; + if (avc_has_perm(peer_sid, node_sid, + SECCLASS_NODE, NODE__SENDTO, &ad)) + return NF_DROP_ERR(-ECONNREFUSED); + } + + return NF_ACCEPT; +} + +static unsigned int selinux_ipv4_postroute(unsigned int hooknum, + struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) +{ + return selinux_ip_postroute(skb, out->ifindex, PF_INET); +} + +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) +static unsigned int selinux_ipv6_postroute(unsigned int hooknum, + struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)) +{ + return selinux_ip_postroute(skb, out->ifindex, PF_INET6); +} +#endif /* IPV6 */ + +#endif /* CONFIG_NETFILTER */ + +static int selinux_netlink_send(struct sock *sk, struct sk_buff *skb) +{ + int err; + + err = cap_netlink_send(sk, skb); + if (err) + return err; + + return selinux_nlmsg_perm(sk, skb); +} + +static int selinux_netlink_recv(struct sk_buff *skb, int capability) +{ + int err; + struct common_audit_data ad; + u32 sid; + + err = cap_netlink_recv(skb, capability); + if (err) + return err; + + COMMON_AUDIT_DATA_INIT(&ad, CAP); + ad.u.cap = capability; + + security_task_getsecid(current, &sid); + return avc_has_perm(sid, sid, SECCLASS_CAPABILITY, + CAP_TO_MASK(capability), &ad); +} + +static int ipc_alloc_security(struct task_struct *task, + struct kern_ipc_perm *perm, + u16 sclass) +{ + struct ipc_security_struct *isec; + u32 sid; + + isec = kzalloc(sizeof(struct ipc_security_struct), GFP_KERNEL); + if (!isec) + return -ENOMEM; + + sid = task_sid(task); + isec->sclass = sclass; + isec->sid = sid; + perm->security = isec; + + return 0; +} + +static void ipc_free_security(struct kern_ipc_perm *perm) +{ + struct ipc_security_struct *isec = perm->security; + perm->security = NULL; + kfree(isec); +} + +static int msg_msg_alloc_security(struct msg_msg *msg) +{ + struct msg_security_struct *msec; + + msec = kzalloc(sizeof(struct msg_security_struct), GFP_KERNEL); + if (!msec) + return -ENOMEM; + + msec->sid = SECINITSID_UNLABELED; + msg->security = msec; + + return 0; +} + +static void msg_msg_free_security(struct msg_msg *msg) +{ + struct msg_security_struct *msec = msg->security; + + msg->security = NULL; + kfree(msec); +} + +static int ipc_has_perm(struct kern_ipc_perm *ipc_perms, + u32 perms) +{ + struct ipc_security_struct *isec; + struct common_audit_data ad; + u32 sid = current_sid(); + + isec = ipc_perms->security; + + COMMON_AUDIT_DATA_INIT(&ad, IPC); + ad.u.ipc_id = ipc_perms->key; + + return avc_has_perm(sid, isec->sid, isec->sclass, perms, &ad); +} + +static int selinux_msg_msg_alloc_security(struct msg_msg *msg) +{ + return msg_msg_alloc_security(msg); +} + +static void selinux_msg_msg_free_security(struct msg_msg *msg) +{ + msg_msg_free_security(msg); +} + +/* message queue security operations */ +static int selinux_msg_queue_alloc_security(struct msg_queue *msq) +{ + struct ipc_security_struct *isec; + struct common_audit_data ad; + u32 sid = current_sid(); + int rc; + + rc = ipc_alloc_security(current, &msq->q_perm, SECCLASS_MSGQ); + if (rc) + return rc; + + isec = msq->q_perm.security; + + COMMON_AUDIT_DATA_INIT(&ad, IPC); + ad.u.ipc_id = msq->q_perm.key; + + rc = avc_has_perm(sid, isec->sid, SECCLASS_MSGQ, + MSGQ__CREATE, &ad); + if (rc) { + ipc_free_security(&msq->q_perm); + return rc; + } + return 0; +} + +static void selinux_msg_queue_free_security(struct msg_queue *msq) +{ + ipc_free_security(&msq->q_perm); +} + +static int selinux_msg_queue_associate(struct msg_queue *msq, int msqflg) +{ + struct ipc_security_struct *isec; + struct common_audit_data ad; + u32 sid = current_sid(); + + isec = msq->q_perm.security; + + COMMON_AUDIT_DATA_INIT(&ad, IPC); + ad.u.ipc_id = msq->q_perm.key; + + return avc_has_perm(sid, isec->sid, SECCLASS_MSGQ, + MSGQ__ASSOCIATE, &ad); +} + +static int selinux_msg_queue_msgctl(struct msg_queue *msq, int cmd) +{ + int err; + int perms; + + switch (cmd) { + case IPC_INFO: + case MSG_INFO: + /* No specific object, just general system-wide information. */ + return task_has_system(current, SYSTEM__IPC_INFO); + case IPC_STAT: + case MSG_STAT: + perms = MSGQ__GETATTR | MSGQ__ASSOCIATE; + break; + case IPC_SET: + perms = MSGQ__SETATTR; + break; + case IPC_RMID: + perms = MSGQ__DESTROY; + break; + default: + return 0; + } + + err = ipc_has_perm(&msq->q_perm, perms); + return err; +} + +static int selinux_msg_queue_msgsnd(struct msg_queue *msq, struct msg_msg *msg, int msqflg) +{ + struct ipc_security_struct *isec; + struct msg_security_struct *msec; + struct common_audit_data ad; + u32 sid = current_sid(); + int rc; + + isec = msq->q_perm.security; + msec = msg->security; + + /* + * First time through, need to assign label to the message + */ + if (msec->sid == SECINITSID_UNLABELED) { + /* + * Compute new sid based on current process and + * message queue this message will be stored in + */ + rc = security_transition_sid(sid, isec->sid, SECCLASS_MSG, + NULL, &msec->sid); + if (rc) + return rc; + } + + COMMON_AUDIT_DATA_INIT(&ad, IPC); + ad.u.ipc_id = msq->q_perm.key; + + /* Can this process write to the queue? */ + rc = avc_has_perm(sid, isec->sid, SECCLASS_MSGQ, + MSGQ__WRITE, &ad); + if (!rc) + /* Can this process send the message */ + rc = avc_has_perm(sid, msec->sid, SECCLASS_MSG, + MSG__SEND, &ad); + if (!rc) + /* Can the message be put in the queue? */ + rc = avc_has_perm(msec->sid, isec->sid, SECCLASS_MSGQ, + MSGQ__ENQUEUE, &ad); + + return rc; +} + +static int selinux_msg_queue_msgrcv(struct msg_queue *msq, struct msg_msg *msg, + struct task_struct *target, + long type, int mode) +{ + struct ipc_security_struct *isec; + struct msg_security_struct *msec; + struct common_audit_data ad; + u32 sid = task_sid(target); + int rc; + + isec = msq->q_perm.security; + msec = msg->security; + + COMMON_AUDIT_DATA_INIT(&ad, IPC); + ad.u.ipc_id = msq->q_perm.key; + + rc = avc_has_perm(sid, isec->sid, + SECCLASS_MSGQ, MSGQ__READ, &ad); + if (!rc) + rc = avc_has_perm(sid, msec->sid, + SECCLASS_MSG, MSG__RECEIVE, &ad); + return rc; +} + +/* Shared Memory security operations */ +static int selinux_shm_alloc_security(struct shmid_kernel *shp) +{ + struct ipc_security_struct *isec; + struct common_audit_data ad; + u32 sid = current_sid(); + int rc; + + rc = ipc_alloc_security(current, &shp->shm_perm, SECCLASS_SHM); + if (rc) + return rc; + + isec = shp->shm_perm.security; + + COMMON_AUDIT_DATA_INIT(&ad, IPC); + ad.u.ipc_id = shp->shm_perm.key; + + rc = avc_has_perm(sid, isec->sid, SECCLASS_SHM, + SHM__CREATE, &ad); + if (rc) { + ipc_free_security(&shp->shm_perm); + return rc; + } + return 0; +} + +static void selinux_shm_free_security(struct shmid_kernel *shp) +{ + ipc_free_security(&shp->shm_perm); +} + +static int selinux_shm_associate(struct shmid_kernel *shp, int shmflg) +{ + struct ipc_security_struct *isec; + struct common_audit_data ad; + u32 sid = current_sid(); + + isec = shp->shm_perm.security; + + COMMON_AUDIT_DATA_INIT(&ad, IPC); + ad.u.ipc_id = shp->shm_perm.key; + + return avc_has_perm(sid, isec->sid, SECCLASS_SHM, + SHM__ASSOCIATE, &ad); +} + +/* Note, at this point, shp is locked down */ +static int selinux_shm_shmctl(struct shmid_kernel *shp, int cmd) +{ + int perms; + int err; + + switch (cmd) { + case IPC_INFO: + case SHM_INFO: + /* No specific object, just general system-wide information. */ + return task_has_system(current, SYSTEM__IPC_INFO); + case IPC_STAT: + case SHM_STAT: + perms = SHM__GETATTR | SHM__ASSOCIATE; + break; + case IPC_SET: + perms = SHM__SETATTR; + break; + case SHM_LOCK: + case SHM_UNLOCK: + perms = SHM__LOCK; + break; + case IPC_RMID: + perms = SHM__DESTROY; + break; + default: + return 0; + } + + err = ipc_has_perm(&shp->shm_perm, perms); + return err; +} + +static int selinux_shm_shmat(struct shmid_kernel *shp, + char __user *shmaddr, int shmflg) +{ + u32 perms; + + if (shmflg & SHM_RDONLY) + perms = SHM__READ; + else + perms = SHM__READ | SHM__WRITE; + + return ipc_has_perm(&shp->shm_perm, perms); +} + +/* Semaphore security operations */ +static int selinux_sem_alloc_security(struct sem_array *sma) +{ + struct ipc_security_struct *isec; + struct common_audit_data ad; + u32 sid = current_sid(); + int rc; + + rc = ipc_alloc_security(current, &sma->sem_perm, SECCLASS_SEM); + if (rc) + return rc; + + isec = sma->sem_perm.security; + + COMMON_AUDIT_DATA_INIT(&ad, IPC); + ad.u.ipc_id = sma->sem_perm.key; + + rc = avc_has_perm(sid, isec->sid, SECCLASS_SEM, + SEM__CREATE, &ad); + if (rc) { + ipc_free_security(&sma->sem_perm); + return rc; + } + return 0; +} + +static void selinux_sem_free_security(struct sem_array *sma) +{ + ipc_free_security(&sma->sem_perm); +} + +static int selinux_sem_associate(struct sem_array *sma, int semflg) +{ + struct ipc_security_struct *isec; + struct common_audit_data ad; + u32 sid = current_sid(); + + isec = sma->sem_perm.security; + + COMMON_AUDIT_DATA_INIT(&ad, IPC); + ad.u.ipc_id = sma->sem_perm.key; + + return avc_has_perm(sid, isec->sid, SECCLASS_SEM, + SEM__ASSOCIATE, &ad); +} + +/* Note, at this point, sma is locked down */ +static int selinux_sem_semctl(struct sem_array *sma, int cmd) +{ + int err; + u32 perms; + + switch (cmd) { + case IPC_INFO: + case SEM_INFO: + /* No specific object, just general system-wide information. */ + return task_has_system(current, SYSTEM__IPC_INFO); + case GETPID: + case GETNCNT: + case GETZCNT: + perms = SEM__GETATTR; + break; + case GETVAL: + case GETALL: + perms = SEM__READ; + break; + case SETVAL: + case SETALL: + perms = SEM__WRITE; + break; + case IPC_RMID: + perms = SEM__DESTROY; + break; + case IPC_SET: + perms = SEM__SETATTR; + break; + case IPC_STAT: + case SEM_STAT: + perms = SEM__GETATTR | SEM__ASSOCIATE; + break; + default: + return 0; + } + + err = ipc_has_perm(&sma->sem_perm, perms); + return err; +} + +static int selinux_sem_semop(struct sem_array *sma, + struct sembuf *sops, unsigned nsops, int alter) +{ + u32 perms; + + if (alter) + perms = SEM__READ | SEM__WRITE; + else + perms = SEM__READ; + + return ipc_has_perm(&sma->sem_perm, perms); +} + +static int selinux_ipc_permission(struct kern_ipc_perm *ipcp, short flag) +{ + u32 av = 0; + + av = 0; + if (flag & S_IRUGO) + av |= IPC__UNIX_READ; + if (flag & S_IWUGO) + av |= IPC__UNIX_WRITE; + + if (av == 0) + return 0; + + return ipc_has_perm(ipcp, av); +} + +static void selinux_ipc_getsecid(struct kern_ipc_perm *ipcp, u32 *secid) +{ + struct ipc_security_struct *isec = ipcp->security; + *secid = isec->sid; +} + +static void selinux_d_instantiate(struct dentry *dentry, struct inode *inode) +{ + if (inode) + inode_doinit_with_dentry(inode, dentry); +} + +static int selinux_getprocattr(struct task_struct *p, + char *name, char **value) +{ + const struct task_security_struct *__tsec; + u32 sid; + int error; + unsigned len; + + if (current != p) { + error = current_has_perm(p, PROCESS__GETATTR); + if (error) + return error; + } + + rcu_read_lock(); + __tsec = __task_cred(p)->security; + + if (!strcmp(name, "current")) + sid = __tsec->sid; + else if (!strcmp(name, "prev")) + sid = __tsec->osid; + else if (!strcmp(name, "exec")) + sid = __tsec->exec_sid; + else if (!strcmp(name, "fscreate")) + sid = __tsec->create_sid; + else if (!strcmp(name, "keycreate")) + sid = __tsec->keycreate_sid; + else if (!strcmp(name, "sockcreate")) + sid = __tsec->sockcreate_sid; + else + goto invalid; + rcu_read_unlock(); + + if (!sid) + return 0; + + error = security_sid_to_context(sid, value, &len); + if (error) + return error; + return len; + +invalid: + rcu_read_unlock(); + return -EINVAL; +} + +static int selinux_setprocattr(struct task_struct *p, + char *name, void *value, size_t size) +{ + struct task_security_struct *tsec; + struct task_struct *tracer; + struct cred *new; + u32 sid = 0, ptsid; + int error; + char *str = value; + + if (current != p) { + /* SELinux only allows a process to change its own + security attributes. */ + return -EACCES; + } + + /* + * Basic control over ability to set these attributes at all. + * current == p, but we'll pass them separately in case the + * above restriction is ever removed. + */ + if (!strcmp(name, "exec")) + error = current_has_perm(p, PROCESS__SETEXEC); + else if (!strcmp(name, "fscreate")) + error = current_has_perm(p, PROCESS__SETFSCREATE); + else if (!strcmp(name, "keycreate")) + error = current_has_perm(p, PROCESS__SETKEYCREATE); + else if (!strcmp(name, "sockcreate")) + error = current_has_perm(p, PROCESS__SETSOCKCREATE); + else if (!strcmp(name, "current")) + error = current_has_perm(p, PROCESS__SETCURRENT); + else + error = -EINVAL; + if (error) + return error; + + /* Obtain a SID for the context, if one was specified. */ + if (size && str[1] && str[1] != '\n') { + if (str[size-1] == '\n') { + str[size-1] = 0; + size--; + } + error = security_context_to_sid(value, size, &sid); + if (error == -EINVAL && !strcmp(name, "fscreate")) { + if (!capable(CAP_MAC_ADMIN)) + return error; + error = security_context_to_sid_force(value, size, + &sid); + } + if (error) + return error; + } + + new = prepare_creds(); + if (!new) + return -ENOMEM; + + /* Permission checking based on the specified context is + performed during the actual operation (execve, + open/mkdir/...), when we know the full context of the + operation. See selinux_bprm_set_creds for the execve + checks and may_create for the file creation checks. The + operation will then fail if the context is not permitted. */ + tsec = new->security; + if (!strcmp(name, "exec")) { + tsec->exec_sid = sid; + } else if (!strcmp(name, "fscreate")) { + tsec->create_sid = sid; + } else if (!strcmp(name, "keycreate")) { + error = may_create_key(sid, p); + if (error) + goto abort_change; + tsec->keycreate_sid = sid; + } else if (!strcmp(name, "sockcreate")) { + tsec->sockcreate_sid = sid; + } else if (!strcmp(name, "current")) { + error = -EINVAL; + if (sid == 0) + goto abort_change; + + /* Only allow single threaded processes to change context */ + error = -EPERM; + if (!current_is_single_threaded()) { + error = security_bounded_transition(tsec->sid, sid); + if (error) + goto abort_change; + } + + /* Check permissions for the transition. */ + error = avc_has_perm(tsec->sid, sid, SECCLASS_PROCESS, + PROCESS__DYNTRANSITION, NULL); + if (error) + goto abort_change; + + /* Check for ptracing, and update the task SID if ok. + Otherwise, leave SID unchanged and fail. */ + ptsid = 0; + task_lock(p); + tracer = tracehook_tracer_task(p); + if (tracer) + ptsid = task_sid(tracer); + task_unlock(p); + + if (tracer) { + error = avc_has_perm(ptsid, sid, SECCLASS_PROCESS, + PROCESS__PTRACE, NULL); + if (error) + goto abort_change; + } + + tsec->sid = sid; + } else { + error = -EINVAL; + goto abort_change; + } + + commit_creds(new); + return size; + +abort_change: + abort_creds(new); + return error; +} + +static int selinux_secid_to_secctx(u32 secid, char **secdata, u32 *seclen) +{ + return security_sid_to_context(secid, secdata, seclen); +} + +static int selinux_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid) +{ + return security_context_to_sid(secdata, seclen, secid); +} + +static void selinux_release_secctx(char *secdata, u32 seclen) +{ + kfree(secdata); +} + +/* + * called with inode->i_mutex locked + */ +static int selinux_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen) +{ + return selinux_inode_setsecurity(inode, XATTR_SELINUX_SUFFIX, ctx, ctxlen, 0); +} + +/* + * called with inode->i_mutex locked + */ +static int selinux_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen) +{ + return __vfs_setxattr_noperm(dentry, XATTR_NAME_SELINUX, ctx, ctxlen, 0); +} + +static int selinux_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen) +{ + int len = 0; + len = selinux_inode_getsecurity(inode, XATTR_SELINUX_SUFFIX, + ctx, true); + if (len < 0) + return len; + *ctxlen = len; + return 0; +} +#ifdef CONFIG_KEYS + +static int selinux_key_alloc(struct key *k, const struct cred *cred, + unsigned long flags) +{ + const struct task_security_struct *tsec; + struct key_security_struct *ksec; + + ksec = kzalloc(sizeof(struct key_security_struct), GFP_KERNEL); + if (!ksec) + return -ENOMEM; + + tsec = cred->security; + if (tsec->keycreate_sid) + ksec->sid = tsec->keycreate_sid; + else + ksec->sid = tsec->sid; + + k->security = ksec; + return 0; +} + +static void selinux_key_free(struct key *k) +{ + struct key_security_struct *ksec = k->security; + + k->security = NULL; + kfree(ksec); +} + +static int selinux_key_permission(key_ref_t key_ref, + const struct cred *cred, + key_perm_t perm) +{ + struct key *key; + struct key_security_struct *ksec; + u32 sid; + + /* if no specific permissions are requested, we skip the + permission check. No serious, additional covert channels + appear to be created. */ + if (perm == 0) + return 0; + + sid = cred_sid(cred); + + key = key_ref_to_ptr(key_ref); + ksec = key->security; + + return avc_has_perm(sid, ksec->sid, SECCLASS_KEY, perm, NULL); +} + +static int selinux_key_getsecurity(struct key *key, char **_buffer) +{ + struct key_security_struct *ksec = key->security; + char *context = NULL; + unsigned len; + int rc; + + rc = security_sid_to_context(ksec->sid, &context, &len); + if (!rc) + rc = len; + *_buffer = context; + return rc; +} + +#endif + +static struct security_operations selinux_ops = { + .name = "selinux", + + .ptrace_access_check = selinux_ptrace_access_check, + .ptrace_traceme = selinux_ptrace_traceme, + .capget = selinux_capget, + .capset = selinux_capset, + .capable = selinux_capable, + .quotactl = selinux_quotactl, + .quota_on = selinux_quota_on, + .syslog = selinux_syslog, + .vm_enough_memory = selinux_vm_enough_memory, + + .netlink_send = selinux_netlink_send, + .netlink_recv = selinux_netlink_recv, + + .bprm_set_creds = selinux_bprm_set_creds, + .bprm_committing_creds = selinux_bprm_committing_creds, + .bprm_committed_creds = selinux_bprm_committed_creds, + .bprm_secureexec = selinux_bprm_secureexec, + + .sb_alloc_security = selinux_sb_alloc_security, + .sb_free_security = selinux_sb_free_security, + .sb_copy_data = selinux_sb_copy_data, + .sb_remount = selinux_sb_remount, + .sb_kern_mount = selinux_sb_kern_mount, + .sb_show_options = selinux_sb_show_options, + .sb_statfs = selinux_sb_statfs, + .sb_mount = selinux_mount, + .sb_umount = selinux_umount, + .sb_set_mnt_opts = selinux_set_mnt_opts, + .sb_clone_mnt_opts = selinux_sb_clone_mnt_opts, + .sb_parse_opts_str = selinux_parse_opts_str, + + + .inode_alloc_security = selinux_inode_alloc_security, + .inode_free_security = selinux_inode_free_security, + .inode_init_security = selinux_inode_init_security, + .inode_create = selinux_inode_create, + .inode_link = selinux_inode_link, + .inode_unlink = selinux_inode_unlink, + .inode_symlink = selinux_inode_symlink, + .inode_mkdir = selinux_inode_mkdir, + .inode_rmdir = selinux_inode_rmdir, + .inode_mknod = selinux_inode_mknod, + .inode_rename = selinux_inode_rename, + .inode_readlink = selinux_inode_readlink, + .inode_follow_link = selinux_inode_follow_link, + .inode_permission = selinux_inode_permission, + .inode_setattr = selinux_inode_setattr, + .inode_getattr = selinux_inode_getattr, + .inode_setxattr = selinux_inode_setxattr, + .inode_post_setxattr = selinux_inode_post_setxattr, + .inode_getxattr = selinux_inode_getxattr, + .inode_listxattr = selinux_inode_listxattr, + .inode_removexattr = selinux_inode_removexattr, + .inode_getsecurity = selinux_inode_getsecurity, + .inode_setsecurity = selinux_inode_setsecurity, + .inode_listsecurity = selinux_inode_listsecurity, + .inode_getsecid = selinux_inode_getsecid, + + .file_permission = selinux_file_permission, + .file_alloc_security = selinux_file_alloc_security, + .file_free_security = selinux_file_free_security, + .file_ioctl = selinux_file_ioctl, + .file_mmap = selinux_file_mmap, + .file_mprotect = selinux_file_mprotect, + .file_lock = selinux_file_lock, + .file_fcntl = selinux_file_fcntl, + .file_set_fowner = selinux_file_set_fowner, + .file_send_sigiotask = selinux_file_send_sigiotask, + .file_receive = selinux_file_receive, + + .dentry_open = selinux_dentry_open, + + .task_create = selinux_task_create, + .cred_alloc_blank = selinux_cred_alloc_blank, + .cred_free = selinux_cred_free, + .cred_prepare = selinux_cred_prepare, + .cred_transfer = selinux_cred_transfer, + .kernel_act_as = selinux_kernel_act_as, + .kernel_create_files_as = selinux_kernel_create_files_as, + .kernel_module_request = selinux_kernel_module_request, + .task_setpgid = selinux_task_setpgid, + .task_getpgid = selinux_task_getpgid, + .task_getsid = selinux_task_getsid, + .task_getsecid = selinux_task_getsecid, + .task_setnice = selinux_task_setnice, + .task_setioprio = selinux_task_setioprio, + .task_getioprio = selinux_task_getioprio, + .task_setrlimit = selinux_task_setrlimit, + .task_setscheduler = selinux_task_setscheduler, + .task_getscheduler = selinux_task_getscheduler, + .task_movememory = selinux_task_movememory, + .task_kill = selinux_task_kill, + .task_wait = selinux_task_wait, + .task_to_inode = selinux_task_to_inode, + + .ipc_permission = selinux_ipc_permission, + .ipc_getsecid = selinux_ipc_getsecid, + + .msg_msg_alloc_security = selinux_msg_msg_alloc_security, + .msg_msg_free_security = selinux_msg_msg_free_security, + + .msg_queue_alloc_security = selinux_msg_queue_alloc_security, + .msg_queue_free_security = selinux_msg_queue_free_security, + .msg_queue_associate = selinux_msg_queue_associate, + .msg_queue_msgctl = selinux_msg_queue_msgctl, + .msg_queue_msgsnd = selinux_msg_queue_msgsnd, + .msg_queue_msgrcv = selinux_msg_queue_msgrcv, + + .shm_alloc_security = selinux_shm_alloc_security, + .shm_free_security = selinux_shm_free_security, + .shm_associate = selinux_shm_associate, + .shm_shmctl = selinux_shm_shmctl, + .shm_shmat = selinux_shm_shmat, + + .sem_alloc_security = selinux_sem_alloc_security, + .sem_free_security = selinux_sem_free_security, + .sem_associate = selinux_sem_associate, + .sem_semctl = selinux_sem_semctl, + .sem_semop = selinux_sem_semop, + + .d_instantiate = selinux_d_instantiate, + + .getprocattr = selinux_getprocattr, + .setprocattr = selinux_setprocattr, + + .secid_to_secctx = selinux_secid_to_secctx, + .secctx_to_secid = selinux_secctx_to_secid, + .release_secctx = selinux_release_secctx, + .inode_notifysecctx = selinux_inode_notifysecctx, + .inode_setsecctx = selinux_inode_setsecctx, + .inode_getsecctx = selinux_inode_getsecctx, + + .unix_stream_connect = selinux_socket_unix_stream_connect, + .unix_may_send = selinux_socket_unix_may_send, + + .socket_create = selinux_socket_create, + .socket_post_create = selinux_socket_post_create, + .socket_bind = selinux_socket_bind, + .socket_connect = selinux_socket_connect, + .socket_listen = selinux_socket_listen, + .socket_accept = selinux_socket_accept, + .socket_sendmsg = selinux_socket_sendmsg, + .socket_recvmsg = selinux_socket_recvmsg, + .socket_getsockname = selinux_socket_getsockname, + .socket_getpeername = selinux_socket_getpeername, + .socket_getsockopt = selinux_socket_getsockopt, + .socket_setsockopt = selinux_socket_setsockopt, + .socket_shutdown = selinux_socket_shutdown, + .socket_sock_rcv_skb = selinux_socket_sock_rcv_skb, + .socket_getpeersec_stream = selinux_socket_getpeersec_stream, + .socket_getpeersec_dgram = selinux_socket_getpeersec_dgram, + .sk_alloc_security = selinux_sk_alloc_security, + .sk_free_security = selinux_sk_free_security, + .sk_clone_security = selinux_sk_clone_security, + .sk_getsecid = selinux_sk_getsecid, + .sock_graft = selinux_sock_graft, + .inet_conn_request = selinux_inet_conn_request, + .inet_csk_clone = selinux_inet_csk_clone, + .inet_conn_established = selinux_inet_conn_established, + .secmark_relabel_packet = selinux_secmark_relabel_packet, + .secmark_refcount_inc = selinux_secmark_refcount_inc, + .secmark_refcount_dec = selinux_secmark_refcount_dec, + .req_classify_flow = selinux_req_classify_flow, + .tun_dev_create = selinux_tun_dev_create, + .tun_dev_post_create = selinux_tun_dev_post_create, + .tun_dev_attach = selinux_tun_dev_attach, + +#ifdef CONFIG_SECURITY_NETWORK_XFRM + .xfrm_policy_alloc_security = selinux_xfrm_policy_alloc, + .xfrm_policy_clone_security = selinux_xfrm_policy_clone, + .xfrm_policy_free_security = selinux_xfrm_policy_free, + .xfrm_policy_delete_security = selinux_xfrm_policy_delete, + .xfrm_state_alloc_security = selinux_xfrm_state_alloc, + .xfrm_state_free_security = selinux_xfrm_state_free, + .xfrm_state_delete_security = selinux_xfrm_state_delete, + .xfrm_policy_lookup = selinux_xfrm_policy_lookup, + .xfrm_state_pol_flow_match = selinux_xfrm_state_pol_flow_match, + .xfrm_decode_session = selinux_xfrm_decode_session, +#endif + +#ifdef CONFIG_KEYS + .key_alloc = selinux_key_alloc, + .key_free = selinux_key_free, + .key_permission = selinux_key_permission, + .key_getsecurity = selinux_key_getsecurity, +#endif + +#ifdef CONFIG_AUDIT + .audit_rule_init = selinux_audit_rule_init, + .audit_rule_known = selinux_audit_rule_known, + .audit_rule_match = selinux_audit_rule_match, + .audit_rule_free = selinux_audit_rule_free, +#endif +}; + +static __init int selinux_init(void) +{ + if (!security_module_enable(&selinux_ops)) { + selinux_enabled = 0; + return 0; + } + + if (!selinux_enabled) { + printk(KERN_INFO "SELinux: Disabled at boot.\n"); + return 0; + } + + printk(KERN_INFO "SELinux: Initializing.\n"); + + /* Set the security state for the initial task. */ + cred_init_security(); + + default_noexec = !(VM_DATA_DEFAULT_FLAGS & VM_EXEC); + + sel_inode_cache = kmem_cache_create("selinux_inode_security", + sizeof(struct inode_security_struct), + 0, SLAB_PANIC, NULL); + avc_init(); + + if (register_security(&selinux_ops)) + panic("SELinux: Unable to register with kernel.\n"); + + if (selinux_enforcing) + printk(KERN_DEBUG "SELinux: Starting in enforcing mode\n"); + else + printk(KERN_DEBUG "SELinux: Starting in permissive mode\n"); + + return 0; +} + +static void delayed_superblock_init(struct super_block *sb, void *unused) +{ + superblock_doinit(sb, NULL); +} + +void selinux_complete_init(void) +{ + printk(KERN_DEBUG "SELinux: Completing initialization.\n"); + + /* Set up any superblocks initialized prior to the policy load. */ + printk(KERN_DEBUG "SELinux: Setting up existing superblocks.\n"); + iterate_supers(delayed_superblock_init, NULL); +} + +/* SELinux requires early initialization in order to label + all processes and objects when they are created. */ +security_initcall(selinux_init); + +#if defined(CONFIG_NETFILTER) + +static struct nf_hook_ops selinux_ipv4_ops[] = { + { + .hook = selinux_ipv4_postroute, + .owner = THIS_MODULE, + .pf = PF_INET, + .hooknum = NF_INET_POST_ROUTING, + .priority = NF_IP_PRI_SELINUX_LAST, + }, + { + .hook = selinux_ipv4_forward, + .owner = THIS_MODULE, + .pf = PF_INET, + .hooknum = NF_INET_FORWARD, + .priority = NF_IP_PRI_SELINUX_FIRST, + }, + { + .hook = selinux_ipv4_output, + .owner = THIS_MODULE, + .pf = PF_INET, + .hooknum = NF_INET_LOCAL_OUT, + .priority = NF_IP_PRI_SELINUX_FIRST, + } +}; + +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + +static struct nf_hook_ops selinux_ipv6_ops[] = { + { + .hook = selinux_ipv6_postroute, + .owner = THIS_MODULE, + .pf = PF_INET6, + .hooknum = NF_INET_POST_ROUTING, + .priority = NF_IP6_PRI_SELINUX_LAST, + }, + { + .hook = selinux_ipv6_forward, + .owner = THIS_MODULE, + .pf = PF_INET6, + .hooknum = NF_INET_FORWARD, + .priority = NF_IP6_PRI_SELINUX_FIRST, + } +}; + +#endif /* IPV6 */ + +static int __init selinux_nf_ip_init(void) +{ + int err = 0; + + if (!selinux_enabled) + goto out; + + printk(KERN_DEBUG "SELinux: Registering netfilter hooks\n"); + + err = nf_register_hooks(selinux_ipv4_ops, ARRAY_SIZE(selinux_ipv4_ops)); + if (err) + panic("SELinux: nf_register_hooks for IPv4: error %d\n", err); + +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + err = nf_register_hooks(selinux_ipv6_ops, ARRAY_SIZE(selinux_ipv6_ops)); + if (err) + panic("SELinux: nf_register_hooks for IPv6: error %d\n", err); +#endif /* IPV6 */ + +out: + return err; +} + +__initcall(selinux_nf_ip_init); + +#ifdef CONFIG_SECURITY_SELINUX_DISABLE +static void selinux_nf_ip_exit(void) +{ + printk(KERN_DEBUG "SELinux: Unregistering netfilter hooks\n"); + + nf_unregister_hooks(selinux_ipv4_ops, ARRAY_SIZE(selinux_ipv4_ops)); +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + nf_unregister_hooks(selinux_ipv6_ops, ARRAY_SIZE(selinux_ipv6_ops)); +#endif /* IPV6 */ +} +#endif + +#else /* CONFIG_NETFILTER */ + +#ifdef CONFIG_SECURITY_SELINUX_DISABLE +#define selinux_nf_ip_exit() +#endif + +#endif /* CONFIG_NETFILTER */ + +#ifdef CONFIG_SECURITY_SELINUX_DISABLE +static int selinux_disabled; + +int selinux_disable(void) +{ + extern void exit_sel_fs(void); + + if (ss_initialized) { + /* Not permitted after initial policy load. */ + return -EINVAL; + } + + if (selinux_disabled) { + /* Only do this once. */ + return -EINVAL; + } + + printk(KERN_INFO "SELinux: Disabled at runtime.\n"); + + selinux_disabled = 1; + selinux_enabled = 0; + + reset_security_ops(); + + /* Try to destroy the avc node cache */ + avc_disable(); + + /* Unregister netfilter hooks. */ + selinux_nf_ip_exit(); + + /* Unregister selinuxfs. */ + exit_sel_fs(); + + return 0; +} +#endif diff --git a/security/selinux/include/audit.h b/security/selinux/include/audit.h new file mode 100644 index 00000000..1bdf9734 --- /dev/null +++ b/security/selinux/include/audit.h @@ -0,0 +1,65 @@ +/* + * SELinux support for the Audit LSM hooks + * + * Most of below header was moved from include/linux/selinux.h which + * is released under below copyrights: + * + * Author: James Morris <jmorris@redhat.com> + * + * Copyright (C) 2005 Red Hat, Inc., James Morris <jmorris@redhat.com> + * Copyright (C) 2006 Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * Copyright (C) 2006 IBM Corporation, Timothy R. Chavez <tinytim@us.ibm.com> + * + * 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. + */ + +#ifndef _SELINUX_AUDIT_H +#define _SELINUX_AUDIT_H + +/** + * selinux_audit_rule_init - alloc/init an selinux audit rule structure. + * @field: the field this rule refers to + * @op: the operater the rule uses + * @rulestr: the text "target" of the rule + * @rule: pointer to the new rule structure returned via this + * + * Returns 0 if successful, -errno if not. On success, the rule structure + * will be allocated internally. The caller must free this structure with + * selinux_audit_rule_free() after use. + */ +int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **rule); + +/** + * selinux_audit_rule_free - free an selinux audit rule structure. + * @rule: pointer to the audit rule to be freed + * + * This will free all memory associated with the given rule. + * If @rule is NULL, no operation is performed. + */ +void selinux_audit_rule_free(void *rule); + +/** + * selinux_audit_rule_match - determine if a context ID matches a rule. + * @sid: the context ID to check + * @field: the field this rule refers to + * @op: the operater the rule uses + * @rule: pointer to the audit rule to check against + * @actx: the audit context (can be NULL) associated with the check + * + * Returns 1 if the context id matches the rule, 0 if it does not, and + * -errno on failure. + */ +int selinux_audit_rule_match(u32 sid, u32 field, u32 op, void *rule, + struct audit_context *actx); + +/** + * selinux_audit_rule_known - check to see if rule contains selinux fields. + * @rule: rule to be checked + * Returns 1 if there are selinux fields specified in the rule, 0 otherwise. + */ +int selinux_audit_rule_known(struct audit_krule *krule); + +#endif /* _SELINUX_AUDIT_H */ + diff --git a/security/selinux/include/avc.h b/security/selinux/include/avc.h new file mode 100644 index 00000000..47fda963 --- /dev/null +++ b/security/selinux/include/avc.h @@ -0,0 +1,109 @@ +/* + * Access vector cache interface for object managers. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SELINUX_AVC_H_ +#define _SELINUX_AVC_H_ + +#include <linux/stddef.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/kdev_t.h> +#include <linux/spinlock.h> +#include <linux/init.h> +#include <linux/audit.h> +#include <linux/lsm_audit.h> +#include <linux/in6.h> +#include <asm/system.h> +#include "flask.h" +#include "av_permissions.h" +#include "security.h" + +#ifdef CONFIG_SECURITY_SELINUX_DEVELOP +extern int selinux_enforcing; +#else +#define selinux_enforcing 1 +#endif + +/* + * An entry in the AVC. + */ +struct avc_entry; + +struct task_struct; +struct inode; +struct sock; +struct sk_buff; + +/* + * AVC statistics + */ +struct avc_cache_stats { + unsigned int lookups; + unsigned int misses; + unsigned int allocations; + unsigned int reclaims; + unsigned int frees; +}; + +/* + * AVC operations + */ + +void __init avc_init(void); + +int avc_audit(u32 ssid, u32 tsid, + u16 tclass, u32 requested, + struct av_decision *avd, + int result, + struct common_audit_data *a, unsigned flags); + +#define AVC_STRICT 1 /* Ignore permissive mode. */ +int avc_has_perm_noaudit(u32 ssid, u32 tsid, + u16 tclass, u32 requested, + unsigned flags, + struct av_decision *avd); + +int avc_has_perm_flags(u32 ssid, u32 tsid, + u16 tclass, u32 requested, + struct common_audit_data *auditdata, + unsigned); + +static inline int avc_has_perm(u32 ssid, u32 tsid, + u16 tclass, u32 requested, + struct common_audit_data *auditdata) +{ + return avc_has_perm_flags(ssid, tsid, tclass, requested, auditdata, 0); +} + +u32 avc_policy_seqno(void); + +#define AVC_CALLBACK_GRANT 1 +#define AVC_CALLBACK_TRY_REVOKE 2 +#define AVC_CALLBACK_REVOKE 4 +#define AVC_CALLBACK_RESET 8 +#define AVC_CALLBACK_AUDITALLOW_ENABLE 16 +#define AVC_CALLBACK_AUDITALLOW_DISABLE 32 +#define AVC_CALLBACK_AUDITDENY_ENABLE 64 +#define AVC_CALLBACK_AUDITDENY_DISABLE 128 + +int avc_add_callback(int (*callback)(u32 event, u32 ssid, u32 tsid, + u16 tclass, u32 perms, + u32 *out_retained), + u32 events, u32 ssid, u32 tsid, + u16 tclass, u32 perms); + +/* Exported to selinuxfs */ +int avc_get_hash_stats(char *page); +extern unsigned int avc_cache_threshold; + +/* Attempt to free avc node cache */ +void avc_disable(void); + +#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS +DECLARE_PER_CPU(struct avc_cache_stats, avc_cache_stats); +#endif + +#endif /* _SELINUX_AVC_H_ */ + diff --git a/security/selinux/include/avc_ss.h b/security/selinux/include/avc_ss.h new file mode 100644 index 00000000..4677aa51 --- /dev/null +++ b/security/selinux/include/avc_ss.h @@ -0,0 +1,22 @@ +/* + * Access vector cache interface for the security server. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SELINUX_AVC_SS_H_ +#define _SELINUX_AVC_SS_H_ + +#include "flask.h" + +int avc_ss_reset(u32 seqno); + +/* Class/perm mapping support */ +struct security_class_mapping { + const char *name; + const char *perms[sizeof(u32) * 8 + 1]; +}; + +extern struct security_class_mapping secclass_map[]; + +#endif /* _SELINUX_AVC_SS_H_ */ + diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h new file mode 100644 index 00000000..b8c53723 --- /dev/null +++ b/security/selinux/include/classmap.h @@ -0,0 +1,153 @@ +#define COMMON_FILE_SOCK_PERMS "ioctl", "read", "write", "create", \ + "getattr", "setattr", "lock", "relabelfrom", "relabelto", "append" + +#define COMMON_FILE_PERMS COMMON_FILE_SOCK_PERMS, "unlink", "link", \ + "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", \ + "sendto", "recv_msg", "send_msg", "name_bind" + +#define COMMON_IPC_PERMS "create", "destroy", "getattr", "setattr", "read", \ + "write", "associate", "unix_read", "unix_write" + +/* + * Note: The name for any socket class should be suffixed by "socket", + * and doesn't contain more than one substr of "socket". + */ +struct security_class_mapping secclass_map[] = { + { "security", + { "compute_av", "compute_create", "compute_member", + "check_context", "load_policy", "compute_relabel", + "compute_user", "setenforce", "setbool", "setsecparam", + "setcheckreqprot", "read_policy", NULL } }, + { "process", + { "fork", "transition", "sigchld", "sigkill", + "sigstop", "signull", "signal", "ptrace", "getsched", "setsched", + "getsession", "getpgid", "setpgid", "getcap", "setcap", "share", + "getattr", "setexec", "setfscreate", "noatsecure", "siginh", + "setrlimit", "rlimitinh", "dyntransition", "setcurrent", + "execmem", "execstack", "execheap", "setkeycreate", + "setsockcreate", NULL } }, + { "system", + { "ipc_info", "syslog_read", "syslog_mod", + "syslog_console", "module_request", NULL } }, + { "capability", + { "chown", "dac_override", "dac_read_search", + "fowner", "fsetid", "kill", "setgid", "setuid", "setpcap", + "linux_immutable", "net_bind_service", "net_broadcast", + "net_admin", "net_raw", "ipc_lock", "ipc_owner", "sys_module", + "sys_rawio", "sys_chroot", "sys_ptrace", "sys_pacct", "sys_admin", + "sys_boot", "sys_nice", "sys_resource", "sys_time", + "sys_tty_config", "mknod", "lease", "audit_write", + "audit_control", "setfcap", NULL } }, + { "filesystem", + { "mount", "remount", "unmount", "getattr", + "relabelfrom", "relabelto", "transition", "associate", "quotamod", + "quotaget", NULL } }, + { "file", + { COMMON_FILE_PERMS, + "execute_no_trans", "entrypoint", NULL } }, + { "dir", + { COMMON_FILE_PERMS, "add_name", "remove_name", + "reparent", "search", "rmdir", NULL } }, + { "fd", { "use", NULL } }, + { "lnk_file", + { COMMON_FILE_PERMS, NULL } }, + { "chr_file", + { COMMON_FILE_PERMS, NULL } }, + { "blk_file", + { COMMON_FILE_PERMS, NULL } }, + { "sock_file", + { COMMON_FILE_PERMS, NULL } }, + { "fifo_file", + { COMMON_FILE_PERMS, NULL } }, + { "socket", + { COMMON_SOCK_PERMS, NULL } }, + { "tcp_socket", + { COMMON_SOCK_PERMS, + "connectto", "newconn", "acceptfrom", "node_bind", "name_connect", + NULL } }, + { "udp_socket", + { COMMON_SOCK_PERMS, + "node_bind", NULL } }, + { "rawip_socket", + { COMMON_SOCK_PERMS, + "node_bind", NULL } }, + { "node", + { "tcp_recv", "tcp_send", "udp_recv", "udp_send", + "rawip_recv", "rawip_send", "enforce_dest", + "dccp_recv", "dccp_send", "recvfrom", "sendto", NULL } }, + { "netif", + { "tcp_recv", "tcp_send", "udp_recv", "udp_send", + "rawip_recv", "rawip_send", "dccp_recv", "dccp_send", + "ingress", "egress", NULL } }, + { "netlink_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "packet_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "key_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "unix_stream_socket", + { COMMON_SOCK_PERMS, "connectto", "newconn", "acceptfrom", NULL + } }, + { "unix_dgram_socket", + { COMMON_SOCK_PERMS, NULL + } }, + { "sem", + { COMMON_IPC_PERMS, NULL } }, + { "msg", { "send", "receive", NULL } }, + { "msgq", + { COMMON_IPC_PERMS, "enqueue", NULL } }, + { "shm", + { COMMON_IPC_PERMS, "lock", NULL } }, + { "ipc", + { COMMON_IPC_PERMS, NULL } }, + { "netlink_route_socket", + { COMMON_SOCK_PERMS, + "nlmsg_read", "nlmsg_write", NULL } }, + { "netlink_firewall_socket", + { COMMON_SOCK_PERMS, + "nlmsg_read", "nlmsg_write", NULL } }, + { "netlink_tcpdiag_socket", + { COMMON_SOCK_PERMS, + "nlmsg_read", "nlmsg_write", NULL } }, + { "netlink_nflog_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "netlink_xfrm_socket", + { COMMON_SOCK_PERMS, + "nlmsg_read", "nlmsg_write", NULL } }, + { "netlink_selinux_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "netlink_audit_socket", + { COMMON_SOCK_PERMS, + "nlmsg_read", "nlmsg_write", "nlmsg_relay", "nlmsg_readpriv", + "nlmsg_tty_audit", NULL } }, + { "netlink_ip6fw_socket", + { COMMON_SOCK_PERMS, + "nlmsg_read", "nlmsg_write", NULL } }, + { "netlink_dnrt_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "association", + { "sendto", "recvfrom", "setcontext", "polmatch", NULL } }, + { "netlink_kobject_uevent_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "appletalk_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "packet", + { "send", "recv", "relabelto", "forward_in", "forward_out", NULL } }, + { "key", + { "view", "read", "write", "search", "link", "setattr", "create", + NULL } }, + { "dccp_socket", + { COMMON_SOCK_PERMS, + "node_bind", "name_connect", NULL } }, + { "memprotect", { "mmap_zero", NULL } }, + { "peer", { "recv", NULL } }, + { "capability2", { "mac_override", "mac_admin", "syslog", NULL } }, + { "kernel_service", { "use_as_override", "create_files_as", NULL } }, + { "tun_socket", + { COMMON_SOCK_PERMS, NULL } }, + { NULL } + }; diff --git a/security/selinux/include/conditional.h b/security/selinux/include/conditional.h new file mode 100644 index 00000000..67ce7a8d --- /dev/null +++ b/security/selinux/include/conditional.h @@ -0,0 +1,22 @@ +/* + * Interface to booleans in the security server. This is exported + * for the selinuxfs. + * + * Author: Karl MacMillan <kmacmillan@tresys.com> + * + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + * 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. + */ + +#ifndef _SELINUX_CONDITIONAL_H_ +#define _SELINUX_CONDITIONAL_H_ + +int security_get_bools(int *len, char ***names, int **values); + +int security_set_bools(int len, int *values); + +int security_get_bool_value(int bool); + +#endif diff --git a/security/selinux/include/initial_sid_to_string.h b/security/selinux/include/initial_sid_to_string.h new file mode 100644 index 00000000..a59b64e3 --- /dev/null +++ b/security/selinux/include/initial_sid_to_string.h @@ -0,0 +1,33 @@ +/* This file is automatically generated. Do not edit. */ +static const char *initial_sid_to_string[] = +{ + "null", + "kernel", + "security", + "unlabeled", + "fs", + "file", + "file_labels", + "init", + "any_socket", + "port", + "netif", + "netmsg", + "node", + "igmp_packet", + "icmp_socket", + "tcp_socket", + "sysctl_modprobe", + "sysctl", + "sysctl_fs", + "sysctl_kernel", + "sysctl_net", + "sysctl_net_unix", + "sysctl_vm", + "sysctl_dev", + "kmod", + "policy", + "scmp_packet", + "devnull", +}; + diff --git a/security/selinux/include/netif.h b/security/selinux/include/netif.h new file mode 100644 index 00000000..ce23edd1 --- /dev/null +++ b/security/selinux/include/netif.h @@ -0,0 +1,23 @@ +/* + * Network interface table. + * + * Network interfaces (devices) do not have a security field, so we + * maintain a table associating each interface with a SID. + * + * Author: James Morris <jmorris@redhat.com> + * + * Copyright (C) 2003 Red Hat, Inc., James Morris <jmorris@redhat.com> + * Copyright (C) 2007 Hewlett-Packard Development Company, L.P. + * Paul Moore, <paul.moore@hp.com> + * + * 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. + */ +#ifndef _SELINUX_NETIF_H_ +#define _SELINUX_NETIF_H_ + +int sel_netif_sid(int ifindex, u32 *sid); + +#endif /* _SELINUX_NETIF_H_ */ + diff --git a/security/selinux/include/netlabel.h b/security/selinux/include/netlabel.h new file mode 100644 index 00000000..cf2f628e --- /dev/null +++ b/security/selinux/include/netlabel.h @@ -0,0 +1,149 @@ +/* + * SELinux interface to the NetLabel subsystem + * + * Author : Paul Moore <paul.moore@hp.com> + * + */ + +/* + * (c) Copyright Hewlett-Packard Development Company, L.P., 2006 + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _SELINUX_NETLABEL_H_ +#define _SELINUX_NETLABEL_H_ + +#include <linux/types.h> +#include <linux/fs.h> +#include <linux/net.h> +#include <linux/skbuff.h> +#include <net/sock.h> +#include <net/request_sock.h> + +#include "avc.h" +#include "objsec.h" + +#ifdef CONFIG_NETLABEL +void selinux_netlbl_cache_invalidate(void); + +void selinux_netlbl_err(struct sk_buff *skb, int error, int gateway); + +void selinux_netlbl_sk_security_free(struct sk_security_struct *sksec); +void selinux_netlbl_sk_security_reset(struct sk_security_struct *sksec); + +int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, + u16 family, + u32 *type, + u32 *sid); +int selinux_netlbl_skbuff_setsid(struct sk_buff *skb, + u16 family, + u32 sid); + +int selinux_netlbl_inet_conn_request(struct request_sock *req, u16 family); +void selinux_netlbl_inet_csk_clone(struct sock *sk, u16 family); +int selinux_netlbl_socket_post_create(struct sock *sk, u16 family); +int selinux_netlbl_sock_rcv_skb(struct sk_security_struct *sksec, + struct sk_buff *skb, + u16 family, + struct common_audit_data *ad); +int selinux_netlbl_socket_setsockopt(struct socket *sock, + int level, + int optname); +int selinux_netlbl_socket_connect(struct sock *sk, struct sockaddr *addr); + +#else +static inline void selinux_netlbl_cache_invalidate(void) +{ + return; +} + +static inline void selinux_netlbl_err(struct sk_buff *skb, + int error, + int gateway) +{ + return; +} + +static inline void selinux_netlbl_sk_security_free( + struct sk_security_struct *sksec) +{ + return; +} + +static inline void selinux_netlbl_sk_security_reset( + struct sk_security_struct *sksec) +{ + return; +} + +static inline int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, + u16 family, + u32 *type, + u32 *sid) +{ + *type = NETLBL_NLTYPE_NONE; + *sid = SECSID_NULL; + return 0; +} +static inline int selinux_netlbl_skbuff_setsid(struct sk_buff *skb, + u16 family, + u32 sid) +{ + return 0; +} + +static inline int selinux_netlbl_conn_setsid(struct sock *sk, + struct sockaddr *addr) +{ + return 0; +} + +static inline int selinux_netlbl_inet_conn_request(struct request_sock *req, + u16 family) +{ + return 0; +} +static inline void selinux_netlbl_inet_csk_clone(struct sock *sk, u16 family) +{ + return; +} +static inline int selinux_netlbl_socket_post_create(struct sock *sk, + u16 family) +{ + return 0; +} +static inline int selinux_netlbl_sock_rcv_skb(struct sk_security_struct *sksec, + struct sk_buff *skb, + u16 family, + struct common_audit_data *ad) +{ + return 0; +} +static inline int selinux_netlbl_socket_setsockopt(struct socket *sock, + int level, + int optname) +{ + return 0; +} +static inline int selinux_netlbl_socket_connect(struct sock *sk, + struct sockaddr *addr) +{ + return 0; +} +#endif /* CONFIG_NETLABEL */ + +#endif diff --git a/security/selinux/include/netnode.h b/security/selinux/include/netnode.h new file mode 100644 index 00000000..1b94450d --- /dev/null +++ b/security/selinux/include/netnode.h @@ -0,0 +1,32 @@ +/* + * Network node table + * + * SELinux must keep a mapping of network nodes to labels/SIDs. This + * mapping is maintained as part of the normal policy but a fast cache is + * needed to reduce the lookup overhead since most of these queries happen on + * a per-packet basis. + * + * Author: Paul Moore <paul.moore@hp.com> + * + */ + +/* + * (c) Copyright Hewlett-Packard Development Company, L.P., 2007 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _SELINUX_NETNODE_H +#define _SELINUX_NETNODE_H + +int sel_netnode_sid(void *addr, u16 family, u32 *sid); + +#endif diff --git a/security/selinux/include/netport.h b/security/selinux/include/netport.h new file mode 100644 index 00000000..8991752e --- /dev/null +++ b/security/selinux/include/netport.h @@ -0,0 +1,31 @@ +/* + * Network port table + * + * SELinux must keep a mapping of network ports to labels/SIDs. This + * mapping is maintained as part of the normal policy but a fast cache is + * needed to reduce the lookup overhead. + * + * Author: Paul Moore <paul.moore@hp.com> + * + */ + +/* + * (c) Copyright Hewlett-Packard Development Company, L.P., 2008 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _SELINUX_NETPORT_H +#define _SELINUX_NETPORT_H + +int sel_netport_sid(u8 protocol, u16 pnum, u32 *sid); + +#endif diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h new file mode 100644 index 00000000..26c7eee1 --- /dev/null +++ b/security/selinux/include/objsec.h @@ -0,0 +1,119 @@ +/* + * NSA Security-Enhanced Linux (SELinux) security module + * + * This file contains the SELinux security data structures for kernel objects. + * + * Author(s): Stephen Smalley, <sds@epoch.ncsc.mil> + * Chris Vance, <cvance@nai.com> + * Wayne Salamon, <wsalamon@nai.com> + * James Morris <jmorris@redhat.com> + * + * Copyright (C) 2001,2002 Networks Associates Technology, Inc. + * Copyright (C) 2003 Red Hat, Inc., James Morris <jmorris@redhat.com> + * + * 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. + */ +#ifndef _SELINUX_OBJSEC_H_ +#define _SELINUX_OBJSEC_H_ + +#include <linux/list.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/binfmts.h> +#include <linux/in.h> +#include <linux/spinlock.h> +#include "flask.h" +#include "avc.h" + +struct task_security_struct { + u32 osid; /* SID prior to last execve */ + u32 sid; /* current SID */ + u32 exec_sid; /* exec SID */ + u32 create_sid; /* fscreate SID */ + u32 keycreate_sid; /* keycreate SID */ + u32 sockcreate_sid; /* fscreate SID */ +}; + +struct inode_security_struct { + struct inode *inode; /* back pointer to inode object */ + struct list_head list; /* list of inode_security_struct */ + u32 task_sid; /* SID of creating task */ + u32 sid; /* SID of this object */ + u16 sclass; /* security class of this object */ + unsigned char initialized; /* initialization flag */ + struct mutex lock; +}; + +struct file_security_struct { + u32 sid; /* SID of open file description */ + u32 fown_sid; /* SID of file owner (for SIGIO) */ + u32 isid; /* SID of inode at the time of file open */ + u32 pseqno; /* Policy seqno at the time of file open */ +}; + +struct superblock_security_struct { + struct super_block *sb; /* back pointer to sb object */ + u32 sid; /* SID of file system superblock */ + u32 def_sid; /* default SID for labeling */ + u32 mntpoint_sid; /* SECURITY_FS_USE_MNTPOINT context for files */ + unsigned int behavior; /* labeling behavior */ + unsigned char flags; /* which mount options were specified */ + struct mutex lock; + struct list_head isec_head; + spinlock_t isec_lock; +}; + +struct msg_security_struct { + u32 sid; /* SID of message */ +}; + +struct ipc_security_struct { + u16 sclass; /* security class of this object */ + u32 sid; /* SID of IPC resource */ +}; + +struct netif_security_struct { + int ifindex; /* device index */ + u32 sid; /* SID for this interface */ +}; + +struct netnode_security_struct { + union { + __be32 ipv4; /* IPv4 node address */ + struct in6_addr ipv6; /* IPv6 node address */ + } addr; + u32 sid; /* SID for this node */ + u16 family; /* address family */ +}; + +struct netport_security_struct { + u32 sid; /* SID for this node */ + u16 port; /* port number */ + u8 protocol; /* transport protocol */ +}; + +struct sk_security_struct { +#ifdef CONFIG_NETLABEL + enum { /* NetLabel state */ + NLBL_UNSET = 0, + NLBL_REQUIRE, + NLBL_LABELED, + NLBL_REQSKB, + NLBL_CONNLABELED, + } nlbl_state; + struct netlbl_lsm_secattr *nlbl_secattr; /* NetLabel sec attributes */ +#endif + u32 sid; /* SID of this object */ + u32 peer_sid; /* SID of peer */ + u16 sclass; /* sock security class */ +}; + +struct key_security_struct { + u32 sid; /* SID of key */ +}; + +extern unsigned int selinux_checkreqprot; + +#endif /* _SELINUX_OBJSEC_H_ */ diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h new file mode 100644 index 00000000..3ba4feba --- /dev/null +++ b/security/selinux/include/security.h @@ -0,0 +1,221 @@ +/* + * Security server interface. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + * + */ + +#ifndef _SELINUX_SECURITY_H_ +#define _SELINUX_SECURITY_H_ + +#include <linux/dcache.h> +#include <linux/magic.h> +#include <linux/types.h> +#include "flask.h" + +#define SECSID_NULL 0x00000000 /* unspecified SID */ +#define SECSID_WILD 0xffffffff /* wildcard SID */ +#define SECCLASS_NULL 0x0000 /* no class */ + +/* Identify specific policy version changes */ +#define POLICYDB_VERSION_BASE 15 +#define POLICYDB_VERSION_BOOL 16 +#define POLICYDB_VERSION_IPV6 17 +#define POLICYDB_VERSION_NLCLASS 18 +#define POLICYDB_VERSION_VALIDATETRANS 19 +#define POLICYDB_VERSION_MLS 19 +#define POLICYDB_VERSION_AVTAB 20 +#define POLICYDB_VERSION_RANGETRANS 21 +#define POLICYDB_VERSION_POLCAP 22 +#define POLICYDB_VERSION_PERMISSIVE 23 +#define POLICYDB_VERSION_BOUNDARY 24 +#define POLICYDB_VERSION_FILENAME_TRANS 25 +#define POLICYDB_VERSION_ROLETRANS 26 + +/* Range of policy versions we understand*/ +#define POLICYDB_VERSION_MIN POLICYDB_VERSION_BASE +#ifdef CONFIG_SECURITY_SELINUX_POLICYDB_VERSION_MAX +#define POLICYDB_VERSION_MAX CONFIG_SECURITY_SELINUX_POLICYDB_VERSION_MAX_VALUE +#else +#define POLICYDB_VERSION_MAX POLICYDB_VERSION_ROLETRANS +#endif + +/* Mask for just the mount related flags */ +#define SE_MNTMASK 0x0f +/* Super block security struct flags for mount options */ +#define CONTEXT_MNT 0x01 +#define FSCONTEXT_MNT 0x02 +#define ROOTCONTEXT_MNT 0x04 +#define DEFCONTEXT_MNT 0x08 +/* Non-mount related flags */ +#define SE_SBINITIALIZED 0x10 +#define SE_SBPROC 0x20 +#define SE_SBLABELSUPP 0x40 + +#define CONTEXT_STR "context=" +#define FSCONTEXT_STR "fscontext=" +#define ROOTCONTEXT_STR "rootcontext=" +#define DEFCONTEXT_STR "defcontext=" +#define LABELSUPP_STR "seclabel" + +struct netlbl_lsm_secattr; + +extern int selinux_enabled; + +/* Policy capabilities */ +enum { + POLICYDB_CAPABILITY_NETPEER, + POLICYDB_CAPABILITY_OPENPERM, + __POLICYDB_CAPABILITY_MAX +}; +#define POLICYDB_CAPABILITY_MAX (__POLICYDB_CAPABILITY_MAX - 1) + +extern int selinux_policycap_netpeer; +extern int selinux_policycap_openperm; + +/* + * type_datum properties + * available at the kernel policy version >= POLICYDB_VERSION_BOUNDARY + */ +#define TYPEDATUM_PROPERTY_PRIMARY 0x0001 +#define TYPEDATUM_PROPERTY_ATTRIBUTE 0x0002 + +/* limitation of boundary depth */ +#define POLICYDB_BOUNDS_MAXDEPTH 4 + +int security_mls_enabled(void); + +int security_load_policy(void *data, size_t len); +int security_read_policy(void **data, size_t *len); +size_t security_policydb_len(void); + +int security_policycap_supported(unsigned int req_cap); + +#define SEL_VEC_MAX 32 +struct av_decision { + u32 allowed; + u32 auditallow; + u32 auditdeny; + u32 seqno; + u32 flags; +}; + +/* definitions of av_decision.flags */ +#define AVD_FLAGS_PERMISSIVE 0x0001 + +void security_compute_av(u32 ssid, u32 tsid, + u16 tclass, struct av_decision *avd); + +void security_compute_av_user(u32 ssid, u32 tsid, + u16 tclass, struct av_decision *avd); + +int security_transition_sid(u32 ssid, u32 tsid, u16 tclass, + const struct qstr *qstr, u32 *out_sid); + +int security_transition_sid_user(u32 ssid, u32 tsid, u16 tclass, + const char *objname, u32 *out_sid); + +int security_member_sid(u32 ssid, u32 tsid, + u16 tclass, u32 *out_sid); + +int security_change_sid(u32 ssid, u32 tsid, + u16 tclass, u32 *out_sid); + +int security_sid_to_context(u32 sid, char **scontext, + u32 *scontext_len); + +int security_sid_to_context_force(u32 sid, char **scontext, u32 *scontext_len); + +int security_context_to_sid(const char *scontext, u32 scontext_len, + u32 *out_sid); + +int security_context_to_sid_default(const char *scontext, u32 scontext_len, + u32 *out_sid, u32 def_sid, gfp_t gfp_flags); + +int security_context_to_sid_force(const char *scontext, u32 scontext_len, + u32 *sid); + +int security_get_user_sids(u32 callsid, char *username, + u32 **sids, u32 *nel); + +int security_port_sid(u8 protocol, u16 port, u32 *out_sid); + +int security_netif_sid(char *name, u32 *if_sid); + +int security_node_sid(u16 domain, void *addr, u32 addrlen, + u32 *out_sid); + +int security_validate_transition(u32 oldsid, u32 newsid, u32 tasksid, + u16 tclass); + +int security_bounded_transition(u32 oldsid, u32 newsid); + +int security_sid_mls_copy(u32 sid, u32 mls_sid, u32 *new_sid); + +int security_net_peersid_resolve(u32 nlbl_sid, u32 nlbl_type, + u32 xfrm_sid, + u32 *peer_sid); + +int security_get_classes(char ***classes, int *nclasses); +int security_get_permissions(char *class, char ***perms, int *nperms); +int security_get_reject_unknown(void); +int security_get_allow_unknown(void); + +#define SECURITY_FS_USE_XATTR 1 /* use xattr */ +#define SECURITY_FS_USE_TRANS 2 /* use transition SIDs, e.g. devpts/tmpfs */ +#define SECURITY_FS_USE_TASK 3 /* use task SIDs, e.g. pipefs/sockfs */ +#define SECURITY_FS_USE_GENFS 4 /* use the genfs support */ +#define SECURITY_FS_USE_NONE 5 /* no labeling support */ +#define SECURITY_FS_USE_MNTPOINT 6 /* use mountpoint labeling */ + +int security_fs_use(const char *fstype, unsigned int *behavior, + u32 *sid); + +int security_genfs_sid(const char *fstype, char *name, u16 sclass, + u32 *sid); + +#ifdef CONFIG_NETLABEL +int security_netlbl_secattr_to_sid(struct netlbl_lsm_secattr *secattr, + u32 *sid); + +int security_netlbl_sid_to_secattr(u32 sid, + struct netlbl_lsm_secattr *secattr); +#else +static inline int security_netlbl_secattr_to_sid( + struct netlbl_lsm_secattr *secattr, + u32 *sid) +{ + return -EIDRM; +} + +static inline int security_netlbl_sid_to_secattr(u32 sid, + struct netlbl_lsm_secattr *secattr) +{ + return -ENOENT; +} +#endif /* CONFIG_NETLABEL */ + +const char *security_get_initial_sid_context(u32 sid); + +/* + * status notifier using mmap interface + */ +extern struct page *selinux_kernel_status_page(void); + +#define SELINUX_KERNEL_STATUS_VERSION 1 +struct selinux_kernel_status { + u32 version; /* version number of thie structure */ + u32 sequence; /* sequence number of seqlock logic */ + u32 enforcing; /* current setting of enforcing mode */ + u32 policyload; /* times of policy reloaded */ + u32 deny_unknown; /* current setting of deny_unknown */ + /* + * The version > 0 supports above members. + */ +} __attribute__((packed)); + +extern void selinux_status_update_setenforce(int enforcing); +extern void selinux_status_update_policyload(int seqno); + +#endif /* _SELINUX_SECURITY_H_ */ + diff --git a/security/selinux/include/xfrm.h b/security/selinux/include/xfrm.h new file mode 100644 index 00000000..b43813c9 --- /dev/null +++ b/security/selinux/include/xfrm.h @@ -0,0 +1,88 @@ +/* + * SELinux support for the XFRM LSM hooks + * + * Author : Trent Jaeger, <jaegert@us.ibm.com> + * Updated : Venkat Yekkirala, <vyekkirala@TrustedCS.com> + */ +#ifndef _SELINUX_XFRM_H_ +#define _SELINUX_XFRM_H_ + +int selinux_xfrm_policy_alloc(struct xfrm_sec_ctx **ctxp, + struct xfrm_user_sec_ctx *sec_ctx); +int selinux_xfrm_policy_clone(struct xfrm_sec_ctx *old_ctx, + struct xfrm_sec_ctx **new_ctxp); +void selinux_xfrm_policy_free(struct xfrm_sec_ctx *ctx); +int selinux_xfrm_policy_delete(struct xfrm_sec_ctx *ctx); +int selinux_xfrm_state_alloc(struct xfrm_state *x, + struct xfrm_user_sec_ctx *sec_ctx, u32 secid); +void selinux_xfrm_state_free(struct xfrm_state *x); +int selinux_xfrm_state_delete(struct xfrm_state *x); +int selinux_xfrm_policy_lookup(struct xfrm_sec_ctx *ctx, u32 fl_secid, u8 dir); +int selinux_xfrm_state_pol_flow_match(struct xfrm_state *x, + struct xfrm_policy *xp, const struct flowi *fl); + +/* + * Extract the security blob from the sock (it's actually on the socket) + */ +static inline struct inode_security_struct *get_sock_isec(struct sock *sk) +{ + if (!sk->sk_socket) + return NULL; + + return SOCK_INODE(sk->sk_socket)->i_security; +} + +#ifdef CONFIG_SECURITY_NETWORK_XFRM +extern atomic_t selinux_xfrm_refcount; + +static inline int selinux_xfrm_enabled(void) +{ + return (atomic_read(&selinux_xfrm_refcount) > 0); +} + +int selinux_xfrm_sock_rcv_skb(u32 sid, struct sk_buff *skb, + struct common_audit_data *ad); +int selinux_xfrm_postroute_last(u32 isec_sid, struct sk_buff *skb, + struct common_audit_data *ad, u8 proto); +int selinux_xfrm_decode_session(struct sk_buff *skb, u32 *sid, int ckall); + +static inline void selinux_xfrm_notify_policyload(void) +{ + atomic_inc(&flow_cache_genid); +} +#else +static inline int selinux_xfrm_enabled(void) +{ + return 0; +} + +static inline int selinux_xfrm_sock_rcv_skb(u32 isec_sid, struct sk_buff *skb, + struct common_audit_data *ad) +{ + return 0; +} + +static inline int selinux_xfrm_postroute_last(u32 isec_sid, struct sk_buff *skb, + struct common_audit_data *ad, u8 proto) +{ + return 0; +} + +static inline int selinux_xfrm_decode_session(struct sk_buff *skb, u32 *sid, int ckall) +{ + *sid = SECSID_NULL; + return 0; +} + +static inline void selinux_xfrm_notify_policyload(void) +{ +} +#endif + +static inline void selinux_skb_xfrm_sid(struct sk_buff *skb, u32 *sid) +{ + int err = selinux_xfrm_decode_session(skb, sid, 0); + BUG_ON(err); +} + +#endif /* _SELINUX_XFRM_H_ */ diff --git a/security/selinux/netif.c b/security/selinux/netif.c new file mode 100644 index 00000000..58cc481c --- /dev/null +++ b/security/selinux/netif.c @@ -0,0 +1,304 @@ +/* + * Network interface table. + * + * Network interfaces (devices) do not have a security field, so we + * maintain a table associating each interface with a SID. + * + * Author: James Morris <jmorris@redhat.com> + * + * Copyright (C) 2003 Red Hat, Inc., James Morris <jmorris@redhat.com> + * Copyright (C) 2007 Hewlett-Packard Development Company, L.P. + * Paul Moore <paul.moore@hp.com> + * + * 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/init.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/stddef.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/notifier.h> +#include <linux/netdevice.h> +#include <linux/rcupdate.h> +#include <net/net_namespace.h> + +#include "security.h" +#include "objsec.h" +#include "netif.h" + +#define SEL_NETIF_HASH_SIZE 64 +#define SEL_NETIF_HASH_MAX 1024 + +struct sel_netif { + struct list_head list; + struct netif_security_struct nsec; + struct rcu_head rcu_head; +}; + +static u32 sel_netif_total; +static LIST_HEAD(sel_netif_list); +static DEFINE_SPINLOCK(sel_netif_lock); +static struct list_head sel_netif_hash[SEL_NETIF_HASH_SIZE]; + +/** + * sel_netif_hashfn - Hashing function for the interface table + * @ifindex: the network interface + * + * Description: + * This is the hashing function for the network interface table, it returns the + * bucket number for the given interface. + * + */ +static inline u32 sel_netif_hashfn(int ifindex) +{ + return (ifindex & (SEL_NETIF_HASH_SIZE - 1)); +} + +/** + * sel_netif_find - Search for an interface record + * @ifindex: the network interface + * + * Description: + * Search the network interface table and return the record matching @ifindex. + * If an entry can not be found in the table return NULL. + * + */ +static inline struct sel_netif *sel_netif_find(int ifindex) +{ + int idx = sel_netif_hashfn(ifindex); + struct sel_netif *netif; + + list_for_each_entry_rcu(netif, &sel_netif_hash[idx], list) + /* all of the devices should normally fit in the hash, so we + * optimize for that case */ + if (likely(netif->nsec.ifindex == ifindex)) + return netif; + + return NULL; +} + +/** + * sel_netif_insert - Insert a new interface into the table + * @netif: the new interface record + * + * Description: + * Add a new interface record to the network interface hash table. Returns + * zero on success, negative values on failure. + * + */ +static int sel_netif_insert(struct sel_netif *netif) +{ + int idx; + + if (sel_netif_total >= SEL_NETIF_HASH_MAX) + return -ENOSPC; + + idx = sel_netif_hashfn(netif->nsec.ifindex); + list_add_rcu(&netif->list, &sel_netif_hash[idx]); + sel_netif_total++; + + return 0; +} + +/** + * sel_netif_destroy - Remove an interface record from the table + * @netif: the existing interface record + * + * Description: + * Remove an existing interface record from the network interface table. + * + */ +static void sel_netif_destroy(struct sel_netif *netif) +{ + list_del_rcu(&netif->list); + sel_netif_total--; + kfree_rcu(netif, rcu_head); +} + +/** + * sel_netif_sid_slow - Lookup the SID of a network interface using the policy + * @ifindex: the network interface + * @sid: interface SID + * + * Description: + * This function determines the SID of a network interface by quering the + * security policy. The result is added to the network interface table to + * speedup future queries. Returns zero on success, negative values on + * failure. + * + */ +static int sel_netif_sid_slow(int ifindex, u32 *sid) +{ + int ret; + struct sel_netif *netif; + struct sel_netif *new = NULL; + struct net_device *dev; + + /* NOTE: we always use init's network namespace since we don't + * currently support containers */ + + dev = dev_get_by_index(&init_net, ifindex); + if (unlikely(dev == NULL)) { + printk(KERN_WARNING + "SELinux: failure in sel_netif_sid_slow()," + " invalid network interface (%d)\n", ifindex); + return -ENOENT; + } + + spin_lock_bh(&sel_netif_lock); + netif = sel_netif_find(ifindex); + if (netif != NULL) { + *sid = netif->nsec.sid; + ret = 0; + goto out; + } + new = kzalloc(sizeof(*new), GFP_ATOMIC); + if (new == NULL) { + ret = -ENOMEM; + goto out; + } + ret = security_netif_sid(dev->name, &new->nsec.sid); + if (ret != 0) + goto out; + new->nsec.ifindex = ifindex; + ret = sel_netif_insert(new); + if (ret != 0) + goto out; + *sid = new->nsec.sid; + +out: + spin_unlock_bh(&sel_netif_lock); + dev_put(dev); + if (unlikely(ret)) { + printk(KERN_WARNING + "SELinux: failure in sel_netif_sid_slow()," + " unable to determine network interface label (%d)\n", + ifindex); + kfree(new); + } + return ret; +} + +/** + * sel_netif_sid - Lookup the SID of a network interface + * @ifindex: the network interface + * @sid: interface SID + * + * Description: + * This function determines the SID of a network interface using the fastest + * method possible. First the interface table is queried, but if an entry + * can't be found then the policy is queried and the result is added to the + * table to speedup future queries. Returns zero on success, negative values + * on failure. + * + */ +int sel_netif_sid(int ifindex, u32 *sid) +{ + struct sel_netif *netif; + + rcu_read_lock(); + netif = sel_netif_find(ifindex); + if (likely(netif != NULL)) { + *sid = netif->nsec.sid; + rcu_read_unlock(); + return 0; + } + rcu_read_unlock(); + + return sel_netif_sid_slow(ifindex, sid); +} + +/** + * sel_netif_kill - Remove an entry from the network interface table + * @ifindex: the network interface + * + * Description: + * This function removes the entry matching @ifindex from the network interface + * table if it exists. + * + */ +static void sel_netif_kill(int ifindex) +{ + struct sel_netif *netif; + + rcu_read_lock(); + spin_lock_bh(&sel_netif_lock); + netif = sel_netif_find(ifindex); + if (netif) + sel_netif_destroy(netif); + spin_unlock_bh(&sel_netif_lock); + rcu_read_unlock(); +} + +/** + * sel_netif_flush - Flush the entire network interface table + * + * Description: + * Remove all entries from the network interface table. + * + */ +static void sel_netif_flush(void) +{ + int idx; + struct sel_netif *netif; + + spin_lock_bh(&sel_netif_lock); + for (idx = 0; idx < SEL_NETIF_HASH_SIZE; idx++) + list_for_each_entry(netif, &sel_netif_hash[idx], list) + sel_netif_destroy(netif); + spin_unlock_bh(&sel_netif_lock); +} + +static int sel_netif_avc_callback(u32 event, u32 ssid, u32 tsid, + u16 class, u32 perms, u32 *retained) +{ + if (event == AVC_CALLBACK_RESET) { + sel_netif_flush(); + synchronize_net(); + } + return 0; +} + +static int sel_netif_netdev_notifier_handler(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct net_device *dev = ptr; + + if (dev_net(dev) != &init_net) + return NOTIFY_DONE; + + if (event == NETDEV_DOWN) + sel_netif_kill(dev->ifindex); + + return NOTIFY_DONE; +} + +static struct notifier_block sel_netif_netdev_notifier = { + .notifier_call = sel_netif_netdev_notifier_handler, +}; + +static __init int sel_netif_init(void) +{ + int i, err; + + if (!selinux_enabled) + return 0; + + for (i = 0; i < SEL_NETIF_HASH_SIZE; i++) + INIT_LIST_HEAD(&sel_netif_hash[i]); + + register_netdevice_notifier(&sel_netif_netdev_notifier); + + err = avc_add_callback(sel_netif_avc_callback, AVC_CALLBACK_RESET, + SECSID_NULL, SECSID_NULL, SECCLASS_NULL, 0); + if (err) + panic("avc_add_callback() failed, error %d\n", err); + + return err; +} + +__initcall(sel_netif_init); + diff --git a/security/selinux/netlabel.c b/security/selinux/netlabel.c new file mode 100644 index 00000000..c3bf3ed0 --- /dev/null +++ b/security/selinux/netlabel.c @@ -0,0 +1,470 @@ +/* + * SELinux NetLabel Support + * + * This file provides the necessary glue to tie NetLabel into the SELinux + * subsystem. + * + * Author: Paul Moore <paul.moore@hp.com> + * + */ + +/* + * (c) Copyright Hewlett-Packard Development Company, L.P., 2007, 2008 + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/spinlock.h> +#include <linux/rcupdate.h> +#include <linux/gfp.h> +#include <linux/ip.h> +#include <linux/ipv6.h> +#include <net/sock.h> +#include <net/netlabel.h> +#include <net/ip.h> +#include <net/ipv6.h> + +#include "objsec.h" +#include "security.h" +#include "netlabel.h" + +/** + * selinux_netlbl_sidlookup_cached - Cache a SID lookup + * @skb: the packet + * @secattr: the NetLabel security attributes + * @sid: the SID + * + * Description: + * Query the SELinux security server to lookup the correct SID for the given + * security attributes. If the query is successful, cache the result to speed + * up future lookups. Returns zero on success, negative values on failure. + * + */ +static int selinux_netlbl_sidlookup_cached(struct sk_buff *skb, + struct netlbl_lsm_secattr *secattr, + u32 *sid) +{ + int rc; + + rc = security_netlbl_secattr_to_sid(secattr, sid); + if (rc == 0 && + (secattr->flags & NETLBL_SECATTR_CACHEABLE) && + (secattr->flags & NETLBL_SECATTR_CACHE)) + netlbl_cache_add(skb, secattr); + + return rc; +} + +/** + * selinux_netlbl_sock_genattr - Generate the NetLabel socket secattr + * @sk: the socket + * + * Description: + * Generate the NetLabel security attributes for a socket, making full use of + * the socket's attribute cache. Returns a pointer to the security attributes + * on success, NULL on failure. + * + */ +static struct netlbl_lsm_secattr *selinux_netlbl_sock_genattr(struct sock *sk) +{ + int rc; + struct sk_security_struct *sksec = sk->sk_security; + struct netlbl_lsm_secattr *secattr; + + if (sksec->nlbl_secattr != NULL) + return sksec->nlbl_secattr; + + secattr = netlbl_secattr_alloc(GFP_ATOMIC); + if (secattr == NULL) + return NULL; + rc = security_netlbl_sid_to_secattr(sksec->sid, secattr); + if (rc != 0) { + netlbl_secattr_free(secattr); + return NULL; + } + sksec->nlbl_secattr = secattr; + + return secattr; +} + +/** + * selinux_netlbl_cache_invalidate - Invalidate the NetLabel cache + * + * Description: + * Invalidate the NetLabel security attribute mapping cache. + * + */ +void selinux_netlbl_cache_invalidate(void) +{ + netlbl_cache_invalidate(); +} + +/** + * selinux_netlbl_err - Handle a NetLabel packet error + * @skb: the packet + * @error: the error code + * @gateway: true if host is acting as a gateway, false otherwise + * + * Description: + * When a packet is dropped due to a call to avc_has_perm() pass the error + * code to the NetLabel subsystem so any protocol specific processing can be + * done. This is safe to call even if you are unsure if NetLabel labeling is + * present on the packet, NetLabel is smart enough to only act when it should. + * + */ +void selinux_netlbl_err(struct sk_buff *skb, int error, int gateway) +{ + netlbl_skbuff_err(skb, error, gateway); +} + +/** + * selinux_netlbl_sk_security_free - Free the NetLabel fields + * @sksec: the sk_security_struct + * + * Description: + * Free all of the memory in the NetLabel fields of a sk_security_struct. + * + */ +void selinux_netlbl_sk_security_free(struct sk_security_struct *sksec) +{ + if (sksec->nlbl_secattr != NULL) + netlbl_secattr_free(sksec->nlbl_secattr); +} + +/** + * selinux_netlbl_sk_security_reset - Reset the NetLabel fields + * @sksec: the sk_security_struct + * @family: the socket family + * + * Description: + * Called when the NetLabel state of a sk_security_struct needs to be reset. + * The caller is responsible for all the NetLabel sk_security_struct locking. + * + */ +void selinux_netlbl_sk_security_reset(struct sk_security_struct *sksec) +{ + sksec->nlbl_state = NLBL_UNSET; +} + +/** + * selinux_netlbl_skbuff_getsid - Get the sid of a packet using NetLabel + * @skb: the packet + * @family: protocol family + * @type: NetLabel labeling protocol type + * @sid: the SID + * + * Description: + * Call the NetLabel mechanism to get the security attributes of the given + * packet and use those attributes to determine the correct context/SID to + * assign to the packet. Returns zero on success, negative values on failure. + * + */ +int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, + u16 family, + u32 *type, + u32 *sid) +{ + int rc; + struct netlbl_lsm_secattr secattr; + + if (!netlbl_enabled()) { + *sid = SECSID_NULL; + return 0; + } + + netlbl_secattr_init(&secattr); + rc = netlbl_skbuff_getattr(skb, family, &secattr); + if (rc == 0 && secattr.flags != NETLBL_SECATTR_NONE) + rc = selinux_netlbl_sidlookup_cached(skb, &secattr, sid); + else + *sid = SECSID_NULL; + *type = secattr.type; + netlbl_secattr_destroy(&secattr); + + return rc; +} + +/** + * selinux_netlbl_skbuff_setsid - Set the NetLabel on a packet given a sid + * @skb: the packet + * @family: protocol family + * @sid: the SID + * + * Description + * Call the NetLabel mechanism to set the label of a packet using @sid. + * Returns zero on success, negative values on failure. + * + */ +int selinux_netlbl_skbuff_setsid(struct sk_buff *skb, + u16 family, + u32 sid) +{ + int rc; + struct netlbl_lsm_secattr secattr_storage; + struct netlbl_lsm_secattr *secattr = NULL; + struct sock *sk; + + /* if this is a locally generated packet check to see if it is already + * being labeled by it's parent socket, if it is just exit */ + sk = skb->sk; + if (sk != NULL) { + struct sk_security_struct *sksec = sk->sk_security; + if (sksec->nlbl_state != NLBL_REQSKB) + return 0; + secattr = sksec->nlbl_secattr; + } + if (secattr == NULL) { + secattr = &secattr_storage; + netlbl_secattr_init(secattr); + rc = security_netlbl_sid_to_secattr(sid, secattr); + if (rc != 0) + goto skbuff_setsid_return; + } + + rc = netlbl_skbuff_setattr(skb, family, secattr); + +skbuff_setsid_return: + if (secattr == &secattr_storage) + netlbl_secattr_destroy(secattr); + return rc; +} + +/** + * selinux_netlbl_inet_conn_request - Label an incoming stream connection + * @req: incoming connection request socket + * + * Description: + * A new incoming connection request is represented by @req, we need to label + * the new request_sock here and the stack will ensure the on-the-wire label + * will get preserved when a full sock is created once the connection handshake + * is complete. Returns zero on success, negative values on failure. + * + */ +int selinux_netlbl_inet_conn_request(struct request_sock *req, u16 family) +{ + int rc; + struct netlbl_lsm_secattr secattr; + + if (family != PF_INET) + return 0; + + netlbl_secattr_init(&secattr); + rc = security_netlbl_sid_to_secattr(req->secid, &secattr); + if (rc != 0) + goto inet_conn_request_return; + rc = netlbl_req_setattr(req, &secattr); +inet_conn_request_return: + netlbl_secattr_destroy(&secattr); + return rc; +} + +/** + * selinux_netlbl_inet_csk_clone - Initialize the newly created sock + * @sk: the new sock + * + * Description: + * A new connection has been established using @sk, we've already labeled the + * socket via the request_sock struct in selinux_netlbl_inet_conn_request() but + * we need to set the NetLabel state here since we now have a sock structure. + * + */ +void selinux_netlbl_inet_csk_clone(struct sock *sk, u16 family) +{ + struct sk_security_struct *sksec = sk->sk_security; + + if (family == PF_INET) + sksec->nlbl_state = NLBL_LABELED; + else + sksec->nlbl_state = NLBL_UNSET; +} + +/** + * selinux_netlbl_socket_post_create - Label a socket using NetLabel + * @sock: the socket to label + * @family: protocol family + * + * Description: + * Attempt to label a socket using the NetLabel mechanism using the given + * SID. Returns zero values on success, negative values on failure. + * + */ +int selinux_netlbl_socket_post_create(struct sock *sk, u16 family) +{ + int rc; + struct sk_security_struct *sksec = sk->sk_security; + struct netlbl_lsm_secattr *secattr; + + if (family != PF_INET) + return 0; + + secattr = selinux_netlbl_sock_genattr(sk); + if (secattr == NULL) + return -ENOMEM; + rc = netlbl_sock_setattr(sk, family, secattr); + switch (rc) { + case 0: + sksec->nlbl_state = NLBL_LABELED; + break; + case -EDESTADDRREQ: + sksec->nlbl_state = NLBL_REQSKB; + rc = 0; + break; + } + + return rc; +} + +/** + * selinux_netlbl_sock_rcv_skb - Do an inbound access check using NetLabel + * @sksec: the sock's sk_security_struct + * @skb: the packet + * @family: protocol family + * @ad: the audit data + * + * Description: + * Fetch the NetLabel security attributes from @skb and perform an access check + * against the receiving socket. Returns zero on success, negative values on + * error. + * + */ +int selinux_netlbl_sock_rcv_skb(struct sk_security_struct *sksec, + struct sk_buff *skb, + u16 family, + struct common_audit_data *ad) +{ + int rc; + u32 nlbl_sid; + u32 perm; + struct netlbl_lsm_secattr secattr; + + if (!netlbl_enabled()) + return 0; + + netlbl_secattr_init(&secattr); + rc = netlbl_skbuff_getattr(skb, family, &secattr); + if (rc == 0 && secattr.flags != NETLBL_SECATTR_NONE) + rc = selinux_netlbl_sidlookup_cached(skb, &secattr, &nlbl_sid); + else + nlbl_sid = SECINITSID_UNLABELED; + netlbl_secattr_destroy(&secattr); + if (rc != 0) + return rc; + + switch (sksec->sclass) { + case SECCLASS_UDP_SOCKET: + perm = UDP_SOCKET__RECVFROM; + break; + case SECCLASS_TCP_SOCKET: + perm = TCP_SOCKET__RECVFROM; + break; + default: + perm = RAWIP_SOCKET__RECVFROM; + } + + rc = avc_has_perm(sksec->sid, nlbl_sid, sksec->sclass, perm, ad); + if (rc == 0) + return 0; + + if (nlbl_sid != SECINITSID_UNLABELED) + netlbl_skbuff_err(skb, rc, 0); + return rc; +} + +/** + * selinux_netlbl_socket_setsockopt - Do not allow users to remove a NetLabel + * @sock: the socket + * @level: the socket level or protocol + * @optname: the socket option name + * + * Description: + * Check the setsockopt() call and if the user is trying to replace the IP + * options on a socket and a NetLabel is in place for the socket deny the + * access; otherwise allow the access. Returns zero when the access is + * allowed, -EACCES when denied, and other negative values on error. + * + */ +int selinux_netlbl_socket_setsockopt(struct socket *sock, + int level, + int optname) +{ + int rc = 0; + struct sock *sk = sock->sk; + struct sk_security_struct *sksec = sk->sk_security; + struct netlbl_lsm_secattr secattr; + + if (level == IPPROTO_IP && optname == IP_OPTIONS && + (sksec->nlbl_state == NLBL_LABELED || + sksec->nlbl_state == NLBL_CONNLABELED)) { + netlbl_secattr_init(&secattr); + lock_sock(sk); + rc = netlbl_sock_getattr(sk, &secattr); + release_sock(sk); + if (rc == 0) + rc = -EACCES; + else if (rc == -ENOMSG) + rc = 0; + netlbl_secattr_destroy(&secattr); + } + + return rc; +} + +/** + * selinux_netlbl_socket_connect - Label a client-side socket on connect + * @sk: the socket to label + * @addr: the destination address + * + * Description: + * Attempt to label a connected socket with NetLabel using the given address. + * Returns zero values on success, negative values on failure. + * + */ +int selinux_netlbl_socket_connect(struct sock *sk, struct sockaddr *addr) +{ + int rc; + struct sk_security_struct *sksec = sk->sk_security; + struct netlbl_lsm_secattr *secattr; + + if (sksec->nlbl_state != NLBL_REQSKB && + sksec->nlbl_state != NLBL_CONNLABELED) + return 0; + + local_bh_disable(); + bh_lock_sock_nested(sk); + + /* connected sockets are allowed to disconnect when the address family + * is set to AF_UNSPEC, if that is what is happening we want to reset + * the socket */ + if (addr->sa_family == AF_UNSPEC) { + netlbl_sock_delattr(sk); + sksec->nlbl_state = NLBL_REQSKB; + rc = 0; + goto socket_connect_return; + } + secattr = selinux_netlbl_sock_genattr(sk); + if (secattr == NULL) { + rc = -ENOMEM; + goto socket_connect_return; + } + rc = netlbl_conn_setattr(sk, addr, secattr); + if (rc == 0) + sksec->nlbl_state = NLBL_CONNLABELED; + +socket_connect_return: + bh_unlock_sock(sk); + local_bh_enable(); + return rc; +} diff --git a/security/selinux/netlink.c b/security/selinux/netlink.c new file mode 100644 index 00000000..36ac257c --- /dev/null +++ b/security/selinux/netlink.c @@ -0,0 +1,116 @@ +/* + * Netlink event notifications for SELinux. + * + * Author: James Morris <jmorris@redhat.com> + * + * Copyright (C) 2004 Red Hat, Inc., James Morris <jmorris@redhat.com> + * + * 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/init.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/stddef.h> +#include <linux/kernel.h> +#include <linux/skbuff.h> +#include <linux/netlink.h> +#include <linux/selinux_netlink.h> +#include <net/net_namespace.h> + +static struct sock *selnl; + +static int selnl_msglen(int msgtype) +{ + int ret = 0; + + switch (msgtype) { + case SELNL_MSG_SETENFORCE: + ret = sizeof(struct selnl_msg_setenforce); + break; + + case SELNL_MSG_POLICYLOAD: + ret = sizeof(struct selnl_msg_policyload); + break; + + default: + BUG(); + } + return ret; +} + +static void selnl_add_payload(struct nlmsghdr *nlh, int len, int msgtype, void *data) +{ + switch (msgtype) { + case SELNL_MSG_SETENFORCE: { + struct selnl_msg_setenforce *msg = NLMSG_DATA(nlh); + + memset(msg, 0, len); + msg->val = *((int *)data); + break; + } + + case SELNL_MSG_POLICYLOAD: { + struct selnl_msg_policyload *msg = NLMSG_DATA(nlh); + + memset(msg, 0, len); + msg->seqno = *((u32 *)data); + break; + } + + default: + BUG(); + } +} + +static void selnl_notify(int msgtype, void *data) +{ + int len; + sk_buff_data_t tmp; + struct sk_buff *skb; + struct nlmsghdr *nlh; + + len = selnl_msglen(msgtype); + + skb = alloc_skb(NLMSG_SPACE(len), GFP_USER); + if (!skb) + goto oom; + + tmp = skb->tail; + nlh = NLMSG_PUT(skb, 0, 0, msgtype, len); + selnl_add_payload(nlh, len, msgtype, data); + nlh->nlmsg_len = skb->tail - tmp; + NETLINK_CB(skb).dst_group = SELNLGRP_AVC; + netlink_broadcast(selnl, skb, 0, SELNLGRP_AVC, GFP_USER); +out: + return; + +nlmsg_failure: + kfree_skb(skb); +oom: + printk(KERN_ERR "SELinux: OOM in %s\n", __func__); + goto out; +} + +void selnl_notify_setenforce(int val) +{ + selnl_notify(SELNL_MSG_SETENFORCE, &val); +} + +void selnl_notify_policyload(u32 seqno) +{ + selnl_notify(SELNL_MSG_POLICYLOAD, &seqno); +} + +static int __init selnl_init(void) +{ + selnl = netlink_kernel_create(&init_net, NETLINK_SELINUX, + SELNLGRP_MAX, NULL, NULL, THIS_MODULE); + if (selnl == NULL) + panic("SELinux: Cannot create netlink socket."); + netlink_set_nonroot(NETLINK_SELINUX, NL_NONROOT_RECV); + return 0; +} + +__initcall(selnl_init); diff --git a/security/selinux/netnode.c b/security/selinux/netnode.c new file mode 100644 index 00000000..3618251d --- /dev/null +++ b/security/selinux/netnode.c @@ -0,0 +1,347 @@ +/* + * Network node table + * + * SELinux must keep a mapping of network nodes to labels/SIDs. This + * mapping is maintained as part of the normal policy but a fast cache is + * needed to reduce the lookup overhead since most of these queries happen on + * a per-packet basis. + * + * Author: Paul Moore <paul.moore@hp.com> + * + * This code is heavily based on the "netif" concept originally developed by + * James Morris <jmorris@redhat.com> + * (see security/selinux/netif.c for more information) + * + */ + +/* + * (c) Copyright Hewlett-Packard Development Company, L.P., 2007 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/types.h> +#include <linux/rcupdate.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/in.h> +#include <linux/in6.h> +#include <linux/ip.h> +#include <linux/ipv6.h> +#include <net/ip.h> +#include <net/ipv6.h> + +#include "netnode.h" +#include "objsec.h" + +#define SEL_NETNODE_HASH_SIZE 256 +#define SEL_NETNODE_HASH_BKT_LIMIT 16 + +struct sel_netnode_bkt { + unsigned int size; + struct list_head list; +}; + +struct sel_netnode { + struct netnode_security_struct nsec; + + struct list_head list; + struct rcu_head rcu; +}; + +/* NOTE: we are using a combined hash table for both IPv4 and IPv6, the reason + * for this is that I suspect most users will not make heavy use of both + * address families at the same time so one table will usually end up wasted, + * if this becomes a problem we can always add a hash table for each address + * family later */ + +static LIST_HEAD(sel_netnode_list); +static DEFINE_SPINLOCK(sel_netnode_lock); +static struct sel_netnode_bkt sel_netnode_hash[SEL_NETNODE_HASH_SIZE]; + +/** + * sel_netnode_free - Frees a node entry + * @p: the entry's RCU field + * + * Description: + * This function is designed to be used as a callback to the call_rcu() + * function so that memory allocated to a hash table node entry can be + * released safely. + * + */ +static void sel_netnode_free(struct rcu_head *p) +{ + struct sel_netnode *node = container_of(p, struct sel_netnode, rcu); + kfree(node); +} + +/** + * sel_netnode_hashfn_ipv4 - IPv4 hashing function for the node table + * @addr: IPv4 address + * + * Description: + * This is the IPv4 hashing function for the node interface table, it returns + * the bucket number for the given IP address. + * + */ +static unsigned int sel_netnode_hashfn_ipv4(__be32 addr) +{ + /* at some point we should determine if the mismatch in byte order + * affects the hash function dramatically */ + return (addr & (SEL_NETNODE_HASH_SIZE - 1)); +} + +/** + * sel_netnode_hashfn_ipv6 - IPv6 hashing function for the node table + * @addr: IPv6 address + * + * Description: + * This is the IPv6 hashing function for the node interface table, it returns + * the bucket number for the given IP address. + * + */ +static unsigned int sel_netnode_hashfn_ipv6(const struct in6_addr *addr) +{ + /* just hash the least significant 32 bits to keep things fast (they + * are the most likely to be different anyway), we can revisit this + * later if needed */ + return (addr->s6_addr32[3] & (SEL_NETNODE_HASH_SIZE - 1)); +} + +/** + * sel_netnode_find - Search for a node record + * @addr: IP address + * @family: address family + * + * Description: + * Search the network node table and return the record matching @addr. If an + * entry can not be found in the table return NULL. + * + */ +static struct sel_netnode *sel_netnode_find(const void *addr, u16 family) +{ + unsigned int idx; + struct sel_netnode *node; + + switch (family) { + case PF_INET: + idx = sel_netnode_hashfn_ipv4(*(__be32 *)addr); + break; + case PF_INET6: + idx = sel_netnode_hashfn_ipv6(addr); + break; + default: + BUG(); + return NULL; + } + + list_for_each_entry_rcu(node, &sel_netnode_hash[idx].list, list) + if (node->nsec.family == family) + switch (family) { + case PF_INET: + if (node->nsec.addr.ipv4 == *(__be32 *)addr) + return node; + break; + case PF_INET6: + if (ipv6_addr_equal(&node->nsec.addr.ipv6, + addr)) + return node; + break; + } + + return NULL; +} + +/** + * sel_netnode_insert - Insert a new node into the table + * @node: the new node record + * + * Description: + * Add a new node record to the network address hash table. + * + */ +static void sel_netnode_insert(struct sel_netnode *node) +{ + unsigned int idx; + + switch (node->nsec.family) { + case PF_INET: + idx = sel_netnode_hashfn_ipv4(node->nsec.addr.ipv4); + break; + case PF_INET6: + idx = sel_netnode_hashfn_ipv6(&node->nsec.addr.ipv6); + break; + default: + BUG(); + } + + /* 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); + if (sel_netnode_hash[idx].size == SEL_NETNODE_HASH_BKT_LIMIT) { + struct sel_netnode *tail; + tail = list_entry( + rcu_dereference(sel_netnode_hash[idx].list.prev), + struct sel_netnode, list); + list_del_rcu(&tail->list); + call_rcu(&tail->rcu, sel_netnode_free); + } else + sel_netnode_hash[idx].size++; +} + +/** + * sel_netnode_sid_slow - Lookup the SID of a network address using the policy + * @addr: the IP address + * @family: the address family + * @sid: node SID + * + * Description: + * This function determines the SID of a network address by quering the + * security policy. The result is added to the network address table to + * speedup future queries. Returns zero on success, negative values on + * failure. + * + */ +static int sel_netnode_sid_slow(void *addr, u16 family, u32 *sid) +{ + int ret = -ENOMEM; + struct sel_netnode *node; + struct sel_netnode *new = NULL; + + spin_lock_bh(&sel_netnode_lock); + node = sel_netnode_find(addr, family); + if (node != NULL) { + *sid = node->nsec.sid; + spin_unlock_bh(&sel_netnode_lock); + return 0; + } + new = kzalloc(sizeof(*new), GFP_ATOMIC); + if (new == NULL) + goto out; + switch (family) { + case PF_INET: + ret = security_node_sid(PF_INET, + addr, sizeof(struct in_addr), sid); + new->nsec.addr.ipv4 = *(__be32 *)addr; + break; + case PF_INET6: + ret = security_node_sid(PF_INET6, + addr, sizeof(struct in6_addr), sid); + ipv6_addr_copy(&new->nsec.addr.ipv6, addr); + break; + default: + BUG(); + } + if (ret != 0) + goto out; + + new->nsec.family = family; + new->nsec.sid = *sid; + sel_netnode_insert(new); + +out: + spin_unlock_bh(&sel_netnode_lock); + if (unlikely(ret)) { + printk(KERN_WARNING + "SELinux: failure in sel_netnode_sid_slow()," + " unable to determine network node label\n"); + kfree(new); + } + return ret; +} + +/** + * sel_netnode_sid - Lookup the SID of a network address + * @addr: the IP address + * @family: the address family + * @sid: node SID + * + * Description: + * This function determines the SID of a network address using the fastest + * method possible. First the address table is queried, but if an entry + * can't be found then the policy is queried and the result is added to the + * table to speedup future queries. Returns zero on success, negative values + * on failure. + * + */ +int sel_netnode_sid(void *addr, u16 family, u32 *sid) +{ + struct sel_netnode *node; + + rcu_read_lock(); + node = sel_netnode_find(addr, family); + if (node != NULL) { + *sid = node->nsec.sid; + rcu_read_unlock(); + return 0; + } + rcu_read_unlock(); + + return sel_netnode_sid_slow(addr, family, sid); +} + +/** + * sel_netnode_flush - Flush the entire network address table + * + * Description: + * Remove all entries from the network address table. + * + */ +static void sel_netnode_flush(void) +{ + unsigned int idx; + struct sel_netnode *node, *node_tmp; + + spin_lock_bh(&sel_netnode_lock); + for (idx = 0; idx < SEL_NETNODE_HASH_SIZE; idx++) { + list_for_each_entry_safe(node, node_tmp, + &sel_netnode_hash[idx].list, list) { + list_del_rcu(&node->list); + call_rcu(&node->rcu, sel_netnode_free); + } + sel_netnode_hash[idx].size = 0; + } + spin_unlock_bh(&sel_netnode_lock); +} + +static int sel_netnode_avc_callback(u32 event, u32 ssid, u32 tsid, + u16 class, u32 perms, u32 *retained) +{ + if (event == AVC_CALLBACK_RESET) { + sel_netnode_flush(); + synchronize_net(); + } + return 0; +} + +static __init int sel_netnode_init(void) +{ + int iter; + int ret; + + if (!selinux_enabled) + return 0; + + for (iter = 0; iter < SEL_NETNODE_HASH_SIZE; iter++) { + INIT_LIST_HEAD(&sel_netnode_hash[iter].list); + sel_netnode_hash[iter].size = 0; + } + + ret = avc_add_callback(sel_netnode_avc_callback, AVC_CALLBACK_RESET, + SECSID_NULL, SECSID_NULL, SECCLASS_NULL, 0); + if (ret != 0) + panic("avc_add_callback() failed, error %d\n", ret); + + return ret; +} + +__initcall(sel_netnode_init); diff --git a/security/selinux/netport.c b/security/selinux/netport.c new file mode 100644 index 00000000..e2b74ebd --- /dev/null +++ b/security/selinux/netport.c @@ -0,0 +1,284 @@ +/* + * Network port table + * + * SELinux must keep a mapping of network ports to labels/SIDs. This + * mapping is maintained as part of the normal policy but a fast cache is + * needed to reduce the lookup overhead. + * + * Author: Paul Moore <paul.moore@hp.com> + * + * This code is heavily based on the "netif" concept originally developed by + * James Morris <jmorris@redhat.com> + * (see security/selinux/netif.c for more information) + * + */ + +/* + * (c) Copyright Hewlett-Packard Development Company, L.P., 2008 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/types.h> +#include <linux/rcupdate.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/in.h> +#include <linux/in6.h> +#include <linux/ip.h> +#include <linux/ipv6.h> +#include <net/ip.h> +#include <net/ipv6.h> + +#include "netport.h" +#include "objsec.h" + +#define SEL_NETPORT_HASH_SIZE 256 +#define SEL_NETPORT_HASH_BKT_LIMIT 16 + +struct sel_netport_bkt { + int size; + struct list_head list; +}; + +struct sel_netport { + struct netport_security_struct psec; + + struct list_head list; + struct rcu_head rcu; +}; + +/* NOTE: we are using a combined hash table for both IPv4 and IPv6, the reason + * for this is that I suspect most users will not make heavy use of both + * address families at the same time so one table will usually end up wasted, + * if this becomes a problem we can always add a hash table for each address + * family later */ + +static LIST_HEAD(sel_netport_list); +static DEFINE_SPINLOCK(sel_netport_lock); +static struct sel_netport_bkt sel_netport_hash[SEL_NETPORT_HASH_SIZE]; + +/** + * sel_netport_free - Frees a port entry + * @p: the entry's RCU field + * + * Description: + * This function is designed to be used as a callback to the call_rcu() + * function so that memory allocated to a hash table port entry can be + * released safely. + * + */ +static void sel_netport_free(struct rcu_head *p) +{ + struct sel_netport *port = container_of(p, struct sel_netport, rcu); + kfree(port); +} + +/** + * sel_netport_hashfn - Hashing function for the port table + * @pnum: port number + * + * Description: + * This is the hashing function for the port table, it returns the bucket + * number for the given port. + * + */ +static unsigned int sel_netport_hashfn(u16 pnum) +{ + return (pnum & (SEL_NETPORT_HASH_SIZE - 1)); +} + +/** + * sel_netport_find - Search for a port record + * @protocol: protocol + * @port: pnum + * + * Description: + * Search the network port table and return the matching record. If an entry + * can not be found in the table return NULL. + * + */ +static struct sel_netport *sel_netport_find(u8 protocol, u16 pnum) +{ + unsigned int idx; + struct sel_netport *port; + + idx = sel_netport_hashfn(pnum); + list_for_each_entry_rcu(port, &sel_netport_hash[idx].list, list) + if (port->psec.port == pnum && port->psec.protocol == protocol) + return port; + + return NULL; +} + +/** + * sel_netport_insert - Insert a new port into the table + * @port: the new port record + * + * Description: + * Add a new port record to the network address hash table. + * + */ +static void sel_netport_insert(struct sel_netport *port) +{ + unsigned int idx; + + /* 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 */ + idx = sel_netport_hashfn(port->psec.port); + list_add_rcu(&port->list, &sel_netport_hash[idx].list); + if (sel_netport_hash[idx].size == SEL_NETPORT_HASH_BKT_LIMIT) { + struct sel_netport *tail; + tail = list_entry( + rcu_dereference_protected( + sel_netport_hash[idx].list.prev, + lockdep_is_held(&sel_netport_lock)), + struct sel_netport, list); + list_del_rcu(&tail->list); + call_rcu(&tail->rcu, sel_netport_free); + } else + sel_netport_hash[idx].size++; +} + +/** + * sel_netport_sid_slow - Lookup the SID of a network address using the policy + * @protocol: protocol + * @pnum: port + * @sid: port SID + * + * Description: + * This function determines the SID of a network port by quering the security + * policy. The result is added to the network port table to speedup future + * queries. Returns zero on success, negative values on failure. + * + */ +static int sel_netport_sid_slow(u8 protocol, u16 pnum, u32 *sid) +{ + int ret = -ENOMEM; + struct sel_netport *port; + struct sel_netport *new = NULL; + + spin_lock_bh(&sel_netport_lock); + port = sel_netport_find(protocol, pnum); + if (port != NULL) { + *sid = port->psec.sid; + spin_unlock_bh(&sel_netport_lock); + return 0; + } + new = kzalloc(sizeof(*new), GFP_ATOMIC); + if (new == NULL) + goto out; + ret = security_port_sid(protocol, pnum, sid); + if (ret != 0) + goto out; + + new->psec.port = pnum; + new->psec.protocol = protocol; + new->psec.sid = *sid; + sel_netport_insert(new); + +out: + spin_unlock_bh(&sel_netport_lock); + if (unlikely(ret)) { + printk(KERN_WARNING + "SELinux: failure in sel_netport_sid_slow()," + " unable to determine network port label\n"); + kfree(new); + } + return ret; +} + +/** + * sel_netport_sid - Lookup the SID of a network port + * @protocol: protocol + * @pnum: port + * @sid: port SID + * + * Description: + * This function determines the SID of a network port using the fastest method + * possible. First the port table is queried, but if an entry can't be found + * then the policy is queried and the result is added to the table to speedup + * future queries. Returns zero on success, negative values on failure. + * + */ +int sel_netport_sid(u8 protocol, u16 pnum, u32 *sid) +{ + struct sel_netport *port; + + rcu_read_lock(); + port = sel_netport_find(protocol, pnum); + if (port != NULL) { + *sid = port->psec.sid; + rcu_read_unlock(); + return 0; + } + rcu_read_unlock(); + + return sel_netport_sid_slow(protocol, pnum, sid); +} + +/** + * sel_netport_flush - Flush the entire network port table + * + * Description: + * Remove all entries from the network address table. + * + */ +static void sel_netport_flush(void) +{ + unsigned int idx; + struct sel_netport *port, *port_tmp; + + spin_lock_bh(&sel_netport_lock); + for (idx = 0; idx < SEL_NETPORT_HASH_SIZE; idx++) { + list_for_each_entry_safe(port, port_tmp, + &sel_netport_hash[idx].list, list) { + list_del_rcu(&port->list); + call_rcu(&port->rcu, sel_netport_free); + } + sel_netport_hash[idx].size = 0; + } + spin_unlock_bh(&sel_netport_lock); +} + +static int sel_netport_avc_callback(u32 event, u32 ssid, u32 tsid, + u16 class, u32 perms, u32 *retained) +{ + if (event == AVC_CALLBACK_RESET) { + sel_netport_flush(); + synchronize_net(); + } + return 0; +} + +static __init int sel_netport_init(void) +{ + int iter; + int ret; + + if (!selinux_enabled) + return 0; + + for (iter = 0; iter < SEL_NETPORT_HASH_SIZE; iter++) { + INIT_LIST_HEAD(&sel_netport_hash[iter].list); + sel_netport_hash[iter].size = 0; + } + + ret = avc_add_callback(sel_netport_avc_callback, AVC_CALLBACK_RESET, + SECSID_NULL, SECSID_NULL, SECCLASS_NULL, 0); + if (ret != 0) + panic("avc_add_callback() failed, error %d\n", ret); + + return ret; +} + +__initcall(sel_netport_init); diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c new file mode 100644 index 00000000..8b02b213 --- /dev/null +++ b/security/selinux/nlmsgtab.c @@ -0,0 +1,182 @@ +/* + * Netlink message type permission tables, for user generated messages. + * + * Author: James Morris <jmorris@redhat.com> + * + * Copyright (C) 2004 Red Hat, Inc., James Morris <jmorris@redhat.com> + * + * 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/types.h> +#include <linux/kernel.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#include <linux/if.h> +#include <linux/netfilter_ipv4/ip_queue.h> +#include <linux/inet_diag.h> +#include <linux/xfrm.h> +#include <linux/audit.h> + +#include "flask.h" +#include "av_permissions.h" + +struct nlmsg_perm { + u16 nlmsg_type; + u32 perm; +}; + +static struct nlmsg_perm nlmsg_route_perms[] = +{ + { RTM_NEWLINK, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELLINK, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETLINK, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_SETLINK, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_NEWADDR, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELADDR, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETADDR, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWROUTE, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELROUTE, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETROUTE, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWNEIGH, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELNEIGH, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETNEIGH, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWRULE, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELRULE, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETRULE, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWQDISC, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELQDISC, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETQDISC, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWTCLASS, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELTCLASS, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETTCLASS, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWTFILTER, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELTFILTER, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETTFILTER, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWACTION, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELACTION, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETACTION, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWPREFIX, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETMULTICAST, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_GETANYCAST, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_GETNEIGHTBL, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_SETNEIGHTBL, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_NEWADDRLABEL, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELADDRLABEL, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETADDRLABEL, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_GETDCB, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_SETDCB, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, +}; + +static struct nlmsg_perm nlmsg_firewall_perms[] = +{ + { IPQM_MODE, NETLINK_FIREWALL_SOCKET__NLMSG_WRITE }, + { IPQM_VERDICT, NETLINK_FIREWALL_SOCKET__NLMSG_WRITE }, +}; + +static struct nlmsg_perm nlmsg_tcpdiag_perms[] = +{ + { TCPDIAG_GETSOCK, NETLINK_TCPDIAG_SOCKET__NLMSG_READ }, + { DCCPDIAG_GETSOCK, NETLINK_TCPDIAG_SOCKET__NLMSG_READ }, +}; + +static struct nlmsg_perm nlmsg_xfrm_perms[] = +{ + { XFRM_MSG_NEWSA, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_DELSA, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_GETSA, NETLINK_XFRM_SOCKET__NLMSG_READ }, + { XFRM_MSG_NEWPOLICY, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_DELPOLICY, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_GETPOLICY, NETLINK_XFRM_SOCKET__NLMSG_READ }, + { XFRM_MSG_ALLOCSPI, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_ACQUIRE, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_EXPIRE, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_UPDPOLICY, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_UPDSA, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_POLEXPIRE, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_FLUSHSA, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_FLUSHPOLICY, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_NEWAE, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_GETAE, NETLINK_XFRM_SOCKET__NLMSG_READ }, +}; + +static struct nlmsg_perm nlmsg_audit_perms[] = +{ + { AUDIT_GET, NETLINK_AUDIT_SOCKET__NLMSG_READ }, + { AUDIT_SET, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_LIST, NETLINK_AUDIT_SOCKET__NLMSG_READPRIV }, + { AUDIT_ADD, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_DEL, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_LIST_RULES, NETLINK_AUDIT_SOCKET__NLMSG_READPRIV }, + { AUDIT_ADD_RULE, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_DEL_RULE, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_USER, NETLINK_AUDIT_SOCKET__NLMSG_RELAY }, + { AUDIT_SIGNAL_INFO, NETLINK_AUDIT_SOCKET__NLMSG_READ }, + { AUDIT_TRIM, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_MAKE_EQUIV, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_TTY_GET, NETLINK_AUDIT_SOCKET__NLMSG_READ }, + { AUDIT_TTY_SET, NETLINK_AUDIT_SOCKET__NLMSG_TTY_AUDIT }, +}; + + +static int nlmsg_perm(u16 nlmsg_type, u32 *perm, struct nlmsg_perm *tab, size_t tabsize) +{ + int i, err = -EINVAL; + + for (i = 0; i < tabsize/sizeof(struct nlmsg_perm); i++) + if (nlmsg_type == tab[i].nlmsg_type) { + *perm = tab[i].perm; + err = 0; + break; + } + + return err; +} + +int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm) +{ + int err = 0; + + switch (sclass) { + case SECCLASS_NETLINK_ROUTE_SOCKET: + err = nlmsg_perm(nlmsg_type, perm, nlmsg_route_perms, + sizeof(nlmsg_route_perms)); + break; + + case SECCLASS_NETLINK_FIREWALL_SOCKET: + case SECCLASS_NETLINK_IP6FW_SOCKET: + err = nlmsg_perm(nlmsg_type, perm, nlmsg_firewall_perms, + sizeof(nlmsg_firewall_perms)); + break; + + case SECCLASS_NETLINK_TCPDIAG_SOCKET: + err = nlmsg_perm(nlmsg_type, perm, nlmsg_tcpdiag_perms, + sizeof(nlmsg_tcpdiag_perms)); + break; + + case SECCLASS_NETLINK_XFRM_SOCKET: + err = nlmsg_perm(nlmsg_type, perm, nlmsg_xfrm_perms, + sizeof(nlmsg_xfrm_perms)); + break; + + case SECCLASS_NETLINK_AUDIT_SOCKET: + if ((nlmsg_type >= AUDIT_FIRST_USER_MSG && + nlmsg_type <= AUDIT_LAST_USER_MSG) || + (nlmsg_type >= AUDIT_FIRST_USER_MSG2 && + nlmsg_type <= AUDIT_LAST_USER_MSG2)) { + *perm = NETLINK_AUDIT_SOCKET__NLMSG_RELAY; + } else { + err = nlmsg_perm(nlmsg_type, perm, nlmsg_audit_perms, + sizeof(nlmsg_audit_perms)); + } + break; + + /* No messaging from userspace, or class unknown/unhandled */ + default: + err = -ENOENT; + break; + } + + return err; +} diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c new file mode 100644 index 00000000..27a96732 --- /dev/null +++ b/security/selinux/selinuxfs.c @@ -0,0 +1,1990 @@ +/* Updated: Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Updated: Hewlett-Packard <paul.moore@hp.com> + * + * Added support for the policy capability bitmap + * + * Copyright (C) 2007 Hewlett-Packard Development Company, L.P. + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + * Copyright (C) 2004 Red Hat, Inc., James Morris <jmorris@redhat.com> + * 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. + */ + +#include <linux/kernel.h> +#include <linux/pagemap.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/fs.h> +#include <linux/mutex.h> +#include <linux/init.h> +#include <linux/string.h> +#include <linux/security.h> +#include <linux/major.h> +#include <linux/seq_file.h> +#include <linux/percpu.h> +#include <linux/audit.h> +#include <linux/uaccess.h> +#include <linux/kobject.h> +#include <linux/ctype.h> + +/* selinuxfs pseudo filesystem for exporting the security policy API. + Based on the proc code and the fs/nfsd/nfsctl.c code. */ + +#include "flask.h" +#include "avc.h" +#include "avc_ss.h" +#include "security.h" +#include "objsec.h" +#include "conditional.h" + +/* Policy capability filenames */ +static char *policycap_names[] = { + "network_peer_controls", + "open_perms" +}; + +unsigned int selinux_checkreqprot = CONFIG_SECURITY_SELINUX_CHECKREQPROT_VALUE; + +static int __init checkreqprot_setup(char *str) +{ + unsigned long checkreqprot; + if (!strict_strtoul(str, 0, &checkreqprot)) + selinux_checkreqprot = checkreqprot ? 1 : 0; + return 1; +} +__setup("checkreqprot=", checkreqprot_setup); + +static DEFINE_MUTEX(sel_mutex); + +/* global data for booleans */ +static struct dentry *bool_dir; +static int bool_num; +static char **bool_pending_names; +static int *bool_pending_values; + +/* global data for classes */ +static struct dentry *class_dir; +static unsigned long last_class_ino; + +static char policy_opened; + +/* global data for policy capabilities */ +static struct dentry *policycap_dir; + +extern void selnl_notify_setenforce(int val); + +/* Check whether a task is allowed to use a security operation. */ +static int task_has_security(struct task_struct *tsk, + u32 perms) +{ + const struct task_security_struct *tsec; + u32 sid = 0; + + rcu_read_lock(); + tsec = __task_cred(tsk)->security; + if (tsec) + sid = tsec->sid; + rcu_read_unlock(); + if (!tsec) + return -EACCES; + + return avc_has_perm(sid, SECINITSID_SECURITY, + SECCLASS_SECURITY, perms, NULL); +} + +enum sel_inos { + SEL_ROOT_INO = 2, + SEL_LOAD, /* load policy */ + SEL_ENFORCE, /* get or set enforcing status */ + SEL_CONTEXT, /* validate context */ + SEL_ACCESS, /* compute access decision */ + SEL_CREATE, /* compute create labeling decision */ + SEL_RELABEL, /* compute relabeling decision */ + SEL_USER, /* compute reachable user contexts */ + SEL_POLICYVERS, /* return policy version for this kernel */ + SEL_COMMIT_BOOLS, /* commit new boolean values */ + SEL_MLS, /* return if MLS policy is enabled */ + SEL_DISABLE, /* disable SELinux until next reboot */ + SEL_MEMBER, /* compute polyinstantiation membership decision */ + SEL_CHECKREQPROT, /* check requested protection, not kernel-applied one */ + SEL_COMPAT_NET, /* whether to use old compat network packet controls */ + SEL_REJECT_UNKNOWN, /* export unknown reject handling to userspace */ + SEL_DENY_UNKNOWN, /* export unknown deny handling to userspace */ + SEL_STATUS, /* export current status using mmap() */ + SEL_POLICY, /* allow userspace to read the in kernel policy */ + SEL_INO_NEXT, /* The next inode number to use */ +}; + +static unsigned long sel_last_ino = SEL_INO_NEXT - 1; + +#define SEL_INITCON_INO_OFFSET 0x01000000 +#define SEL_BOOL_INO_OFFSET 0x02000000 +#define SEL_CLASS_INO_OFFSET 0x04000000 +#define SEL_POLICYCAP_INO_OFFSET 0x08000000 +#define SEL_INO_MASK 0x00ffffff + +#define TMPBUFLEN 12 +static ssize_t sel_read_enforce(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char tmpbuf[TMPBUFLEN]; + ssize_t length; + + length = scnprintf(tmpbuf, TMPBUFLEN, "%d", selinux_enforcing); + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +#ifdef CONFIG_SECURITY_SELINUX_DEVELOP +static ssize_t sel_write_enforce(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) + +{ + char *page = NULL; + ssize_t length; + int new_value; + + length = -ENOMEM; + if (count >= PAGE_SIZE) + goto out; + + /* No partial writes. */ + length = EINVAL; + if (*ppos != 0) + goto out; + + length = -ENOMEM; + page = (char *)get_zeroed_page(GFP_KERNEL); + if (!page) + goto out; + + length = -EFAULT; + if (copy_from_user(page, buf, count)) + goto out; + + length = -EINVAL; + if (sscanf(page, "%d", &new_value) != 1) + goto out; + + if (new_value != selinux_enforcing) { + length = task_has_security(current, SECURITY__SETENFORCE); + if (length) + goto out; + audit_log(current->audit_context, GFP_KERNEL, AUDIT_MAC_STATUS, + "enforcing=%d old_enforcing=%d auid=%u ses=%u", + new_value, selinux_enforcing, + audit_get_loginuid(current), + audit_get_sessionid(current)); + selinux_enforcing = new_value; + if (selinux_enforcing) + avc_ss_reset(0); + selnl_notify_setenforce(selinux_enforcing); + selinux_status_update_setenforce(selinux_enforcing); + } + length = count; +out: + free_page((unsigned long) page); + return length; +} +#else +#define sel_write_enforce NULL +#endif + +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, + size_t count, loff_t *ppos) +{ + char tmpbuf[TMPBUFLEN]; + ssize_t length; + ino_t ino = filp->f_path.dentry->d_inode->i_ino; + int handle_unknown = (ino == SEL_REJECT_UNKNOWN) ? + security_get_reject_unknown() : !security_get_allow_unknown(); + + length = scnprintf(tmpbuf, TMPBUFLEN, "%d", handle_unknown); + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +static const struct file_operations sel_handle_unknown_ops = { + .read = sel_read_handle_unknown, + .llseek = generic_file_llseek, +}; + +static int sel_open_handle_status(struct inode *inode, struct file *filp) +{ + struct page *status = selinux_kernel_status_page(); + + if (!status) + return -ENOMEM; + + filp->private_data = status; + + return 0; +} + +static ssize_t sel_read_handle_status(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct page *status = filp->private_data; + + BUG_ON(!status); + + return simple_read_from_buffer(buf, count, ppos, + page_address(status), + sizeof(struct selinux_kernel_status)); +} + +static int sel_mmap_handle_status(struct file *filp, + struct vm_area_struct *vma) +{ + struct page *status = filp->private_data; + unsigned long size = vma->vm_end - vma->vm_start; + + BUG_ON(!status); + + /* only allows one page from the head */ + if (vma->vm_pgoff > 0 || size != PAGE_SIZE) + return -EIO; + /* disallow writable mapping */ + if (vma->vm_flags & VM_WRITE) + return -EPERM; + /* disallow mprotect() turns it into writable */ + vma->vm_flags &= ~VM_MAYWRITE; + + return remap_pfn_range(vma, vma->vm_start, + page_to_pfn(status), + size, vma->vm_page_prot); +} + +static const struct file_operations sel_handle_status_ops = { + .open = sel_open_handle_status, + .read = sel_read_handle_status, + .mmap = sel_mmap_handle_status, + .llseek = generic_file_llseek, +}; + +#ifdef CONFIG_SECURITY_SELINUX_DISABLE +static ssize_t sel_write_disable(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) + +{ + char *page = NULL; + ssize_t length; + int new_value; + extern int selinux_disable(void); + + length = -ENOMEM; + if (count >= PAGE_SIZE) + goto out; + + /* No partial writes. */ + length = -EINVAL; + if (*ppos != 0) + goto out; + + length = -ENOMEM; + page = (char *)get_zeroed_page(GFP_KERNEL); + if (!page) + goto out; + + length = -EFAULT; + if (copy_from_user(page, buf, count)) + goto out; + + length = -EINVAL; + if (sscanf(page, "%d", &new_value) != 1) + goto out; + + if (new_value) { + length = selinux_disable(); + if (length) + goto out; + audit_log(current->audit_context, GFP_KERNEL, AUDIT_MAC_STATUS, + "selinux=0 auid=%u ses=%u", + audit_get_loginuid(current), + audit_get_sessionid(current)); + } + + length = count; +out: + free_page((unsigned long) page); + return length; +} +#else +#define sel_write_disable NULL +#endif + +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, + size_t count, loff_t *ppos) +{ + char tmpbuf[TMPBUFLEN]; + ssize_t length; + + length = scnprintf(tmpbuf, TMPBUFLEN, "%u", POLICYDB_VERSION_MAX); + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +static const struct file_operations sel_policyvers_ops = { + .read = sel_read_policyvers, + .llseek = generic_file_llseek, +}; + +/* declaration for sel_write_load */ +static int sel_make_bools(void); +static int sel_make_classes(void); +static int sel_make_policycap(void); + +/* declaration for sel_make_class_dirs */ +static int sel_make_dir(struct inode *dir, struct dentry *dentry, + unsigned long *ino); + +static ssize_t sel_read_mls(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char tmpbuf[TMPBUFLEN]; + ssize_t length; + + length = scnprintf(tmpbuf, TMPBUFLEN, "%d", + security_mls_enabled()); + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +static const struct file_operations sel_mls_ops = { + .read = sel_read_mls, + .llseek = generic_file_llseek, +}; + +struct policy_load_memory { + size_t len; + void *data; +}; + +static int sel_open_policy(struct inode *inode, struct file *filp) +{ + struct policy_load_memory *plm = NULL; + int rc; + + BUG_ON(filp->private_data); + + mutex_lock(&sel_mutex); + + rc = task_has_security(current, SECURITY__READ_POLICY); + if (rc) + goto err; + + rc = -EBUSY; + if (policy_opened) + goto err; + + rc = -ENOMEM; + plm = kzalloc(sizeof(*plm), GFP_KERNEL); + if (!plm) + goto err; + + if (i_size_read(inode) != security_policydb_len()) { + mutex_lock(&inode->i_mutex); + i_size_write(inode, security_policydb_len()); + mutex_unlock(&inode->i_mutex); + } + + rc = security_read_policy(&plm->data, &plm->len); + if (rc) + goto err; + + policy_opened = 1; + + filp->private_data = plm; + + mutex_unlock(&sel_mutex); + + return 0; +err: + mutex_unlock(&sel_mutex); + + if (plm) + vfree(plm->data); + kfree(plm); + return rc; +} + +static int sel_release_policy(struct inode *inode, struct file *filp) +{ + struct policy_load_memory *plm = filp->private_data; + + BUG_ON(!plm); + + policy_opened = 0; + + vfree(plm->data); + kfree(plm); + + return 0; +} + +static ssize_t sel_read_policy(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct policy_load_memory *plm = filp->private_data; + int ret; + + mutex_lock(&sel_mutex); + + ret = task_has_security(current, SECURITY__READ_POLICY); + if (ret) + goto out; + + ret = simple_read_from_buffer(buf, count, ppos, plm->data, plm->len); +out: + mutex_unlock(&sel_mutex); + return ret; +} + +static int sel_mmap_policy_fault(struct vm_area_struct *vma, + struct vm_fault *vmf) +{ + struct policy_load_memory *plm = vma->vm_file->private_data; + unsigned long offset; + struct page *page; + + if (vmf->flags & (FAULT_FLAG_MKWRITE | FAULT_FLAG_WRITE)) + return VM_FAULT_SIGBUS; + + offset = vmf->pgoff << PAGE_SHIFT; + if (offset >= roundup(plm->len, PAGE_SIZE)) + return VM_FAULT_SIGBUS; + + page = vmalloc_to_page(plm->data + offset); + get_page(page); + + vmf->page = page; + + return 0; +} + +static struct vm_operations_struct sel_mmap_policy_ops = { + .fault = sel_mmap_policy_fault, + .page_mkwrite = sel_mmap_policy_fault, +}; + +int sel_mmap_policy(struct file *filp, struct vm_area_struct *vma) +{ + if (vma->vm_flags & VM_SHARED) { + /* do not allow mprotect to make mapping writable */ + vma->vm_flags &= ~VM_MAYWRITE; + + if (vma->vm_flags & VM_WRITE) + return -EACCES; + } + + vma->vm_flags |= VM_RESERVED; + vma->vm_ops = &sel_mmap_policy_ops; + + return 0; +} + +static const struct file_operations sel_policy_ops = { + .open = sel_open_policy, + .read = sel_read_policy, + .mmap = sel_mmap_policy, + .release = sel_release_policy, +}; + +static ssize_t sel_write_load(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) + +{ + ssize_t length; + void *data = NULL; + + mutex_lock(&sel_mutex); + + length = task_has_security(current, SECURITY__LOAD_POLICY); + if (length) + goto out; + + /* No partial writes. */ + length = -EINVAL; + if (*ppos != 0) + goto out; + + length = -EFBIG; + if (count > 64 * 1024 * 1024) + goto out; + + length = -ENOMEM; + data = vmalloc(count); + if (!data) + goto out; + + length = -EFAULT; + if (copy_from_user(data, buf, count) != 0) + goto out; + + length = security_load_policy(data, count); + if (length) + goto out; + + length = sel_make_bools(); + if (length) + goto out1; + + length = sel_make_classes(); + if (length) + goto out1; + + length = sel_make_policycap(); + if (length) + goto out1; + + length = count; + +out1: + audit_log(current->audit_context, GFP_KERNEL, AUDIT_MAC_POLICY_LOAD, + "policy loaded auid=%u ses=%u", + audit_get_loginuid(current), + audit_get_sessionid(current)); +out: + mutex_unlock(&sel_mutex); + vfree(data); + return length; +} + +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) +{ + char *canon = NULL; + u32 sid, len; + ssize_t length; + + length = task_has_security(current, SECURITY__CHECK_CONTEXT); + if (length) + goto out; + + length = security_context_to_sid(buf, size, &sid); + if (length) + goto out; + + length = security_sid_to_context(sid, &canon, &len); + if (length) + goto out; + + length = -ERANGE; + if (len > SIMPLE_TRANSACTION_LIMIT) { + printk(KERN_ERR "SELinux: %s: context size (%u) exceeds " + "payload max\n", __func__, len); + goto out; + } + + memcpy(buf, canon, len); + length = len; +out: + kfree(canon); + return length; +} + +static ssize_t sel_read_checkreqprot(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char tmpbuf[TMPBUFLEN]; + ssize_t length; + + length = scnprintf(tmpbuf, TMPBUFLEN, "%u", selinux_checkreqprot); + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +static ssize_t sel_write_checkreqprot(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + char *page = NULL; + ssize_t length; + unsigned int new_value; + + length = task_has_security(current, SECURITY__SETCHECKREQPROT); + if (length) + goto out; + + length = -ENOMEM; + if (count >= PAGE_SIZE) + goto out; + + /* No partial writes. */ + length = -EINVAL; + if (*ppos != 0) + goto out; + + length = -ENOMEM; + page = (char *)get_zeroed_page(GFP_KERNEL); + if (!page) + goto out; + + length = -EFAULT; + if (copy_from_user(page, buf, count)) + goto out; + + length = -EINVAL; + if (sscanf(page, "%u", &new_value) != 1) + goto out; + + selinux_checkreqprot = new_value ? 1 : 0; + length = count; +out: + free_page((unsigned long) page); + return length; +} +static const struct file_operations sel_checkreqprot_ops = { + .read = sel_read_checkreqprot, + .write = sel_write_checkreqprot, + .llseek = generic_file_llseek, +}; + +/* + * Remaining nodes use transaction based IO methods like nfsd/nfsctl.c + */ +static ssize_t sel_write_access(struct file *file, char *buf, size_t size); +static ssize_t sel_write_create(struct file *file, char *buf, size_t size); +static ssize_t sel_write_relabel(struct file *file, char *buf, size_t size); +static ssize_t sel_write_user(struct file *file, char *buf, size_t size); +static ssize_t sel_write_member(struct file *file, char *buf, size_t size); + +static ssize_t (*write_op[])(struct file *, char *, size_t) = { + [SEL_ACCESS] = sel_write_access, + [SEL_CREATE] = sel_write_create, + [SEL_RELABEL] = sel_write_relabel, + [SEL_USER] = sel_write_user, + [SEL_MEMBER] = sel_write_member, + [SEL_CONTEXT] = sel_write_context, +}; + +static ssize_t selinux_transaction_write(struct file *file, const char __user *buf, size_t size, loff_t *pos) +{ + ino_t ino = file->f_path.dentry->d_inode->i_ino; + char *data; + ssize_t rv; + + if (ino >= ARRAY_SIZE(write_op) || !write_op[ino]) + return -EINVAL; + + data = simple_transaction_get(file, buf, size); + if (IS_ERR(data)) + return PTR_ERR(data); + + rv = write_op[ino](file, data, size); + if (rv > 0) { + simple_transaction_set(file, rv); + rv = size; + } + return rv; +} + +static const struct file_operations transaction_ops = { + .write = selinux_transaction_write, + .read = simple_transaction_read, + .release = simple_transaction_release, + .llseek = generic_file_llseek, +}; + +/* + * payload - write methods + * If the method has a response, the response should be put in buf, + * and the length returned. Otherwise return 0 or and -error. + */ + +static ssize_t sel_write_access(struct file *file, char *buf, size_t size) +{ + char *scon = NULL, *tcon = NULL; + u32 ssid, tsid; + u16 tclass; + struct av_decision avd; + ssize_t length; + + length = task_has_security(current, SECURITY__COMPUTE_AV); + if (length) + goto out; + + length = -ENOMEM; + scon = kzalloc(size + 1, GFP_KERNEL); + if (!scon) + goto out; + + length = -ENOMEM; + tcon = kzalloc(size + 1, GFP_KERNEL); + if (!tcon) + goto out; + + length = -EINVAL; + if (sscanf(buf, "%s %s %hu", scon, tcon, &tclass) != 3) + goto out; + + length = security_context_to_sid(scon, strlen(scon) + 1, &ssid); + if (length) + goto out; + + length = security_context_to_sid(tcon, strlen(tcon) + 1, &tsid); + if (length) + goto out; + + security_compute_av_user(ssid, tsid, tclass, &avd); + + length = scnprintf(buf, SIMPLE_TRANSACTION_LIMIT, + "%x %x %x %x %u %x", + avd.allowed, 0xffffffff, + avd.auditallow, avd.auditdeny, + avd.seqno, avd.flags); +out: + kfree(tcon); + kfree(scon); + return length; +} + +static inline int hexcode_to_int(int code) { + if (code == '\0' || !isxdigit(code)) + return -1; + if (isdigit(code)) + return code - '0'; + return tolower(code) - 'a' + 10; +} + +static ssize_t sel_write_create(struct file *file, char *buf, size_t size) +{ + char *scon = NULL, *tcon = NULL; + char *namebuf = NULL, *objname = NULL; + u32 ssid, tsid, newsid; + u16 tclass; + ssize_t length; + char *newcon = NULL; + u32 len; + int nargs; + + length = task_has_security(current, SECURITY__COMPUTE_CREATE); + if (length) + goto out; + + length = -ENOMEM; + scon = kzalloc(size + 1, GFP_KERNEL); + if (!scon) + goto out; + + length = -ENOMEM; + tcon = kzalloc(size + 1, GFP_KERNEL); + if (!tcon) + goto out; + + length = -ENOMEM; + namebuf = kzalloc(size + 1, GFP_KERNEL); + if (!namebuf) + goto out; + + length = -EINVAL; + nargs = sscanf(buf, "%s %s %hu %s", scon, tcon, &tclass, namebuf); + if (nargs < 3 || nargs > 4) + goto out; + if (nargs == 4) { + /* + * If and when the name of new object to be queried contains + * either whitespace or multibyte characters, they shall be + * encoded based on the percentage-encoding rule. + * If not encoded, the sscanf logic picks up only left-half + * of the supplied name; splitted by a whitespace unexpectedly. + */ + char *r, *w; + int c1, c2; + + r = w = namebuf; + do { + c1 = *r++; + if (c1 == '+') + c1 = ' '; + else if (c1 == '%') { + if ((c1 = hexcode_to_int(*r++)) < 0) + goto out; + if ((c2 = hexcode_to_int(*r++)) < 0) + goto out; + c1 = (c1 << 4) | c2; + } + *w++ = c1; + } while (c1 != '\0'); + + objname = namebuf; + } + + length = security_context_to_sid(scon, strlen(scon) + 1, &ssid); + if (length) + goto out; + + length = security_context_to_sid(tcon, strlen(tcon) + 1, &tsid); + if (length) + goto out; + + length = security_transition_sid_user(ssid, tsid, tclass, + objname, &newsid); + if (length) + goto out; + + length = security_sid_to_context(newsid, &newcon, &len); + if (length) + goto out; + + length = -ERANGE; + if (len > SIMPLE_TRANSACTION_LIMIT) { + printk(KERN_ERR "SELinux: %s: context size (%u) exceeds " + "payload max\n", __func__, len); + goto out; + } + + memcpy(buf, newcon, len); + length = len; +out: + kfree(newcon); + kfree(namebuf); + kfree(tcon); + kfree(scon); + return length; +} + +static ssize_t sel_write_relabel(struct file *file, char *buf, size_t size) +{ + char *scon = NULL, *tcon = NULL; + u32 ssid, tsid, newsid; + u16 tclass; + ssize_t length; + char *newcon = NULL; + u32 len; + + length = task_has_security(current, SECURITY__COMPUTE_RELABEL); + if (length) + goto out; + + length = -ENOMEM; + scon = kzalloc(size + 1, GFP_KERNEL); + if (!scon) + goto out; + + length = -ENOMEM; + tcon = kzalloc(size + 1, GFP_KERNEL); + if (!tcon) + goto out; + + length = -EINVAL; + if (sscanf(buf, "%s %s %hu", scon, tcon, &tclass) != 3) + goto out; + + length = security_context_to_sid(scon, strlen(scon) + 1, &ssid); + if (length) + goto out; + + length = security_context_to_sid(tcon, strlen(tcon) + 1, &tsid); + if (length) + goto out; + + length = security_change_sid(ssid, tsid, tclass, &newsid); + if (length) + goto out; + + length = security_sid_to_context(newsid, &newcon, &len); + if (length) + goto out; + + length = -ERANGE; + if (len > SIMPLE_TRANSACTION_LIMIT) + goto out; + + memcpy(buf, newcon, len); + length = len; +out: + kfree(newcon); + kfree(tcon); + kfree(scon); + return length; +} + +static ssize_t sel_write_user(struct file *file, char *buf, size_t size) +{ + char *con = NULL, *user = NULL, *ptr; + u32 sid, *sids = NULL; + ssize_t length; + char *newcon; + int i, rc; + u32 len, nsids; + + length = task_has_security(current, SECURITY__COMPUTE_USER); + if (length) + goto out; + + length = -ENOMEM; + con = kzalloc(size + 1, GFP_KERNEL); + if (!con) + goto out; + + length = -ENOMEM; + user = kzalloc(size + 1, GFP_KERNEL); + if (!user) + goto out; + + length = -EINVAL; + if (sscanf(buf, "%s %s", con, user) != 2) + goto out; + + length = security_context_to_sid(con, strlen(con) + 1, &sid); + if (length) + goto out; + + length = security_get_user_sids(sid, user, &sids, &nsids); + if (length) + goto out; + + length = sprintf(buf, "%u", nsids) + 1; + ptr = buf + length; + for (i = 0; i < nsids; i++) { + rc = security_sid_to_context(sids[i], &newcon, &len); + if (rc) { + length = rc; + goto out; + } + if ((length + len) >= SIMPLE_TRANSACTION_LIMIT) { + kfree(newcon); + length = -ERANGE; + goto out; + } + memcpy(ptr, newcon, len); + kfree(newcon); + ptr += len; + length += len; + } +out: + kfree(sids); + kfree(user); + kfree(con); + return length; +} + +static ssize_t sel_write_member(struct file *file, char *buf, size_t size) +{ + char *scon = NULL, *tcon = NULL; + u32 ssid, tsid, newsid; + u16 tclass; + ssize_t length; + char *newcon = NULL; + u32 len; + + length = task_has_security(current, SECURITY__COMPUTE_MEMBER); + if (length) + goto out; + + length = -ENOMEM; + scon = kzalloc(size + 1, GFP_KERNEL); + if (!scon) + goto out; + + length = -ENOMEM; + tcon = kzalloc(size + 1, GFP_KERNEL); + if (!tcon) + goto out; + + length = -EINVAL; + if (sscanf(buf, "%s %s %hu", scon, tcon, &tclass) != 3) + goto out; + + length = security_context_to_sid(scon, strlen(scon) + 1, &ssid); + if (length) + goto out; + + length = security_context_to_sid(tcon, strlen(tcon) + 1, &tsid); + if (length) + goto out; + + length = security_member_sid(ssid, tsid, tclass, &newsid); + if (length) + goto out; + + length = security_sid_to_context(newsid, &newcon, &len); + if (length) + goto out; + + length = -ERANGE; + if (len > SIMPLE_TRANSACTION_LIMIT) { + printk(KERN_ERR "SELinux: %s: context size (%u) exceeds " + "payload max\n", __func__, len); + goto out; + } + + memcpy(buf, newcon, len); + length = len; +out: + kfree(newcon); + kfree(tcon); + kfree(scon); + return length; +} + +static struct inode *sel_make_inode(struct super_block *sb, int mode) +{ + struct inode *ret = new_inode(sb); + + if (ret) { + ret->i_mode = mode; + ret->i_atime = ret->i_mtime = ret->i_ctime = CURRENT_TIME; + } + return ret; +} + +static ssize_t sel_read_bool(struct file *filep, char __user *buf, + size_t count, loff_t *ppos) +{ + char *page = NULL; + ssize_t length; + ssize_t ret; + int cur_enforcing; + struct inode *inode = filep->f_path.dentry->d_inode; + unsigned index = inode->i_ino & SEL_INO_MASK; + const char *name = filep->f_path.dentry->d_name.name; + + mutex_lock(&sel_mutex); + + ret = -EINVAL; + if (index >= bool_num || strcmp(name, bool_pending_names[index])) + goto out; + + ret = -ENOMEM; + page = (char *)get_zeroed_page(GFP_KERNEL); + if (!page) + goto out; + + cur_enforcing = security_get_bool_value(index); + if (cur_enforcing < 0) { + ret = cur_enforcing; + goto out; + } + length = scnprintf(page, PAGE_SIZE, "%d %d", cur_enforcing, + bool_pending_values[index]); + ret = simple_read_from_buffer(buf, count, ppos, page, length); +out: + mutex_unlock(&sel_mutex); + free_page((unsigned long)page); + return ret; +} + +static ssize_t sel_write_bool(struct file *filep, const char __user *buf, + size_t count, loff_t *ppos) +{ + char *page = NULL; + ssize_t length; + int new_value; + struct inode *inode = filep->f_path.dentry->d_inode; + unsigned index = inode->i_ino & SEL_INO_MASK; + const char *name = filep->f_path.dentry->d_name.name; + + mutex_lock(&sel_mutex); + + length = task_has_security(current, SECURITY__SETBOOL); + if (length) + goto out; + + length = -EINVAL; + if (index >= bool_num || strcmp(name, bool_pending_names[index])) + goto out; + + length = -ENOMEM; + if (count >= PAGE_SIZE) + goto out; + + /* No partial writes. */ + length = -EINVAL; + if (*ppos != 0) + goto out; + + length = -ENOMEM; + page = (char *)get_zeroed_page(GFP_KERNEL); + if (!page) + goto out; + + length = -EFAULT; + if (copy_from_user(page, buf, count)) + goto out; + + length = -EINVAL; + if (sscanf(page, "%d", &new_value) != 1) + goto out; + + if (new_value) + new_value = 1; + + bool_pending_values[index] = new_value; + length = count; + +out: + mutex_unlock(&sel_mutex); + free_page((unsigned long) page); + return length; +} + +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, + const char __user *buf, + size_t count, loff_t *ppos) +{ + char *page = NULL; + ssize_t length; + int new_value; + + mutex_lock(&sel_mutex); + + length = task_has_security(current, SECURITY__SETBOOL); + if (length) + goto out; + + length = -ENOMEM; + if (count >= PAGE_SIZE) + goto out; + + /* No partial writes. */ + length = -EINVAL; + if (*ppos != 0) + goto out; + + length = -ENOMEM; + page = (char *)get_zeroed_page(GFP_KERNEL); + if (!page) + goto out; + + length = -EFAULT; + if (copy_from_user(page, buf, count)) + goto out; + + length = -EINVAL; + if (sscanf(page, "%d", &new_value) != 1) + goto out; + + length = 0; + if (new_value && bool_pending_values) + length = security_set_bools(bool_num, bool_pending_values); + + if (!length) + length = count; + +out: + mutex_unlock(&sel_mutex); + free_page((unsigned long) page); + return length; +} + +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) +{ + struct list_head *node; + + spin_lock(&de->d_lock); + node = de->d_subdirs.next; + while (node != &de->d_subdirs) { + struct dentry *d = list_entry(node, struct dentry, d_u.d_child); + + spin_lock_nested(&d->d_lock, DENTRY_D_LOCK_NESTED); + list_del_init(node); + + if (d->d_inode) { + dget_dlock(d); + spin_unlock(&de->d_lock); + spin_unlock(&d->d_lock); + d_delete(d); + simple_unlink(de->d_inode, d); + dput(d); + spin_lock(&de->d_lock); + } else + spin_unlock(&d->d_lock); + node = de->d_subdirs.next; + } + + spin_unlock(&de->d_lock); +} + +#define BOOL_DIR_NAME "booleans" + +static int sel_make_bools(void) +{ + int i, ret; + ssize_t len; + struct dentry *dentry = NULL; + struct dentry *dir = bool_dir; + struct inode *inode = NULL; + struct inode_security_struct *isec; + char **names = NULL, *page; + int num; + int *values = NULL; + u32 sid; + + /* remove any existing files */ + for (i = 0; i < bool_num; i++) + kfree(bool_pending_names[i]); + kfree(bool_pending_names); + kfree(bool_pending_values); + bool_num = 0; + bool_pending_names = NULL; + bool_pending_values = NULL; + + sel_remove_entries(dir); + + ret = -ENOMEM; + page = (char *)get_zeroed_page(GFP_KERNEL); + if (!page) + goto out; + + ret = security_get_bools(&num, &names, &values); + if (ret) + goto out; + + for (i = 0; i < num; i++) { + ret = -ENOMEM; + dentry = d_alloc_name(dir, names[i]); + if (!dentry) + goto out; + + ret = -ENOMEM; + inode = sel_make_inode(dir->d_sb, S_IFREG | S_IRUGO | S_IWUSR); + if (!inode) + goto out; + + ret = -EINVAL; + len = snprintf(page, PAGE_SIZE, "/%s/%s", BOOL_DIR_NAME, names[i]); + if (len < 0) + goto out; + + ret = -ENAMETOOLONG; + if (len >= PAGE_SIZE) + goto out; + + isec = (struct inode_security_struct *)inode->i_security; + ret = security_genfs_sid("selinuxfs", page, SECCLASS_FILE, &sid); + if (ret) + goto out; + + isec->sid = sid; + isec->initialized = 1; + inode->i_fop = &sel_bool_ops; + inode->i_ino = i|SEL_BOOL_INO_OFFSET; + d_add(dentry, inode); + } + bool_num = num; + bool_pending_names = names; + bool_pending_values = values; + + free_page((unsigned long)page); + return 0; +out: + free_page((unsigned long)page); + + if (names) { + for (i = 0; i < num; i++) + kfree(names[i]); + kfree(names); + } + kfree(values); + sel_remove_entries(dir); + + return ret; +} + +#define NULL_FILE_NAME "null" + +struct dentry *selinux_null; + +static ssize_t sel_read_avc_cache_threshold(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char tmpbuf[TMPBUFLEN]; + ssize_t length; + + length = scnprintf(tmpbuf, TMPBUFLEN, "%u", avc_cache_threshold); + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +static ssize_t sel_write_avc_cache_threshold(struct file *file, + const char __user *buf, + size_t count, loff_t *ppos) + +{ + char *page = NULL; + ssize_t ret; + int new_value; + + ret = task_has_security(current, SECURITY__SETSECPARAM); + if (ret) + goto out; + + ret = -ENOMEM; + if (count >= PAGE_SIZE) + goto out; + + /* No partial writes. */ + ret = -EINVAL; + if (*ppos != 0) + goto out; + + ret = -ENOMEM; + page = (char *)get_zeroed_page(GFP_KERNEL); + if (!page) + goto out; + + ret = -EFAULT; + if (copy_from_user(page, buf, count)) + goto out; + + ret = -EINVAL; + if (sscanf(page, "%u", &new_value) != 1) + goto out; + + avc_cache_threshold = new_value; + + ret = count; +out: + free_page((unsigned long)page); + return ret; +} + +static ssize_t sel_read_avc_hash_stats(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char *page; + ssize_t length; + + page = (char *)__get_free_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + + length = avc_get_hash_stats(page); + if (length >= 0) + length = simple_read_from_buffer(buf, count, ppos, page, length); + free_page((unsigned long)page); + + return length; +} + +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 +static struct avc_cache_stats *sel_avc_get_stat_idx(loff_t *idx) +{ + int cpu; + + for (cpu = *idx; cpu < nr_cpu_ids; ++cpu) { + if (!cpu_possible(cpu)) + continue; + *idx = cpu + 1; + return &per_cpu(avc_cache_stats, cpu); + } + return NULL; +} + +static void *sel_avc_stats_seq_start(struct seq_file *seq, loff_t *pos) +{ + loff_t n = *pos - 1; + + if (*pos == 0) + return SEQ_START_TOKEN; + + return sel_avc_get_stat_idx(&n); +} + +static void *sel_avc_stats_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ + return sel_avc_get_stat_idx(pos); +} + +static int sel_avc_stats_seq_show(struct seq_file *seq, void *v) +{ + struct avc_cache_stats *st = v; + + if (v == SEQ_START_TOKEN) + seq_printf(seq, "lookups hits misses allocations reclaims " + "frees\n"); + else { + unsigned int lookups = st->lookups; + unsigned int misses = st->misses; + unsigned int hits = lookups - misses; + seq_printf(seq, "%u %u %u %u %u %u\n", lookups, + hits, misses, st->allocations, + st->reclaims, st->frees); + } + return 0; +} + +static void sel_avc_stats_seq_stop(struct seq_file *seq, void *v) +{ } + +static const struct seq_operations sel_avc_cache_stats_seq_ops = { + .start = sel_avc_stats_seq_start, + .next = sel_avc_stats_seq_next, + .show = sel_avc_stats_seq_show, + .stop = sel_avc_stats_seq_stop, +}; + +static int sel_open_avc_cache_stats(struct inode *inode, struct file *file) +{ + return seq_open(file, &sel_avc_cache_stats_seq_ops); +} + +static const struct file_operations sel_avc_cache_stats_ops = { + .open = sel_open_avc_cache_stats, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; +#endif + +static int sel_make_avc_files(struct dentry *dir) +{ + int i; + static struct tree_descr files[] = { + { "cache_threshold", + &sel_avc_cache_threshold_ops, S_IRUGO|S_IWUSR }, + { "hash_stats", &sel_avc_hash_stats_ops, S_IRUGO }, +#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS + { "cache_stats", &sel_avc_cache_stats_ops, S_IRUGO }, +#endif + }; + + for (i = 0; i < ARRAY_SIZE(files); i++) { + struct inode *inode; + struct dentry *dentry; + + dentry = d_alloc_name(dir, files[i].name); + if (!dentry) + return -ENOMEM; + + inode = sel_make_inode(dir->d_sb, S_IFREG|files[i].mode); + if (!inode) + return -ENOMEM; + + inode->i_fop = files[i].ops; + inode->i_ino = ++sel_last_ino; + d_add(dentry, inode); + } + + return 0; +} + +static ssize_t sel_read_initcon(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct inode *inode; + char *con; + u32 sid, len; + ssize_t ret; + + inode = file->f_path.dentry->d_inode; + sid = inode->i_ino&SEL_INO_MASK; + ret = security_sid_to_context(sid, &con, &len); + if (ret) + return ret; + + ret = simple_read_from_buffer(buf, count, ppos, con, len); + kfree(con); + return ret; +} + +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) +{ + int i; + + for (i = 1; i <= SECINITSID_NUM; i++) { + struct inode *inode; + struct dentry *dentry; + dentry = d_alloc_name(dir, security_get_initial_sid_context(i)); + if (!dentry) + return -ENOMEM; + + inode = sel_make_inode(dir->d_sb, S_IFREG|S_IRUGO); + if (!inode) + return -ENOMEM; + + inode->i_fop = &sel_initcon_ops; + inode->i_ino = i|SEL_INITCON_INO_OFFSET; + d_add(dentry, inode); + } + + return 0; +} + +static inline unsigned int sel_div(unsigned long a, unsigned long b) +{ + return a / b - (a % b < 0); +} + +static inline unsigned long sel_class_to_ino(u16 class) +{ + return (class * (SEL_VEC_MAX + 1)) | SEL_CLASS_INO_OFFSET; +} + +static inline u16 sel_ino_to_class(unsigned long ino) +{ + return sel_div(ino & SEL_INO_MASK, SEL_VEC_MAX + 1); +} + +static inline unsigned long sel_perm_to_ino(u16 class, u32 perm) +{ + return (class * (SEL_VEC_MAX + 1) + perm) | SEL_CLASS_INO_OFFSET; +} + +static inline u32 sel_ino_to_perm(unsigned long ino) +{ + return (ino & SEL_INO_MASK) % (SEL_VEC_MAX + 1); +} + +static ssize_t sel_read_class(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + ssize_t rc, len; + char *page; + unsigned long ino = file->f_path.dentry->d_inode->i_ino; + + page = (char *)__get_free_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + + len = snprintf(page, PAGE_SIZE, "%d", sel_ino_to_class(ino)); + rc = simple_read_from_buffer(buf, count, ppos, page, len); + free_page((unsigned long)page); + + return rc; +} + +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, + size_t count, loff_t *ppos) +{ + ssize_t rc, len; + char *page; + unsigned long ino = file->f_path.dentry->d_inode->i_ino; + + page = (char *)__get_free_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + + len = snprintf(page, PAGE_SIZE, "%d", sel_ino_to_perm(ino)); + rc = simple_read_from_buffer(buf, count, ppos, page, len); + free_page((unsigned long)page); + + return rc; +} + +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, + size_t count, loff_t *ppos) +{ + int value; + char tmpbuf[TMPBUFLEN]; + ssize_t length; + unsigned long i_ino = file->f_path.dentry->d_inode->i_ino; + + value = security_policycap_supported(i_ino & SEL_INO_MASK); + length = scnprintf(tmpbuf, TMPBUFLEN, "%d", value); + + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +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, + struct dentry *dir) +{ + int i, rc, nperms; + char **perms; + + rc = security_get_permissions(objclass, &perms, &nperms); + if (rc) + return rc; + + for (i = 0; i < nperms; i++) { + struct inode *inode; + struct dentry *dentry; + + rc = -ENOMEM; + dentry = d_alloc_name(dir, perms[i]); + if (!dentry) + goto out; + + rc = -ENOMEM; + inode = sel_make_inode(dir->d_sb, S_IFREG|S_IRUGO); + if (!inode) + goto out; + + inode->i_fop = &sel_perm_ops; + /* i+1 since perm values are 1-indexed */ + inode->i_ino = sel_perm_to_ino(classvalue, i + 1); + d_add(dentry, inode); + } + rc = 0; +out: + for (i = 0; i < nperms; i++) + kfree(perms[i]); + kfree(perms); + return rc; +} + +static int sel_make_class_dir_entries(char *classname, int index, + struct dentry *dir) +{ + struct dentry *dentry = NULL; + struct inode *inode = NULL; + int rc; + + dentry = d_alloc_name(dir, "index"); + if (!dentry) + return -ENOMEM; + + inode = sel_make_inode(dir->d_sb, S_IFREG|S_IRUGO); + if (!inode) + return -ENOMEM; + + inode->i_fop = &sel_class_ops; + inode->i_ino = sel_class_to_ino(index); + d_add(dentry, inode); + + dentry = d_alloc_name(dir, "perms"); + if (!dentry) + return -ENOMEM; + + rc = sel_make_dir(dir->d_inode, dentry, &last_class_ino); + if (rc) + return rc; + + rc = sel_make_perm_files(classname, index, dentry); + + return rc; +} + +static void sel_remove_classes(void) +{ + struct list_head *class_node; + + list_for_each(class_node, &class_dir->d_subdirs) { + struct dentry *class_subdir = list_entry(class_node, + struct dentry, d_u.d_child); + struct list_head *class_subdir_node; + + list_for_each(class_subdir_node, &class_subdir->d_subdirs) { + struct dentry *d = list_entry(class_subdir_node, + struct dentry, d_u.d_child); + + if (d->d_inode) + if (d->d_inode->i_mode & S_IFDIR) + sel_remove_entries(d); + } + + sel_remove_entries(class_subdir); + } + + sel_remove_entries(class_dir); +} + +static int sel_make_classes(void) +{ + int rc, nclasses, i; + char **classes; + + /* delete any existing entries */ + sel_remove_classes(); + + rc = security_get_classes(&classes, &nclasses); + if (rc) + return rc; + + /* +2 since classes are 1-indexed */ + last_class_ino = sel_class_to_ino(nclasses + 2); + + for (i = 0; i < nclasses; i++) { + struct dentry *class_name_dir; + + rc = -ENOMEM; + class_name_dir = d_alloc_name(class_dir, classes[i]); + if (!class_name_dir) + goto out; + + rc = sel_make_dir(class_dir->d_inode, class_name_dir, + &last_class_ino); + if (rc) + goto out; + + /* i+1 since class values are 1-indexed */ + rc = sel_make_class_dir_entries(classes[i], i + 1, + class_name_dir); + if (rc) + goto out; + } + rc = 0; +out: + for (i = 0; i < nclasses; i++) + kfree(classes[i]); + kfree(classes); + return rc; +} + +static int sel_make_policycap(void) +{ + unsigned int iter; + struct dentry *dentry = NULL; + struct inode *inode = NULL; + + sel_remove_entries(policycap_dir); + + for (iter = 0; iter <= POLICYDB_CAPABILITY_MAX; iter++) { + if (iter < ARRAY_SIZE(policycap_names)) + dentry = d_alloc_name(policycap_dir, + policycap_names[iter]); + else + dentry = d_alloc_name(policycap_dir, "unknown"); + + if (dentry == NULL) + return -ENOMEM; + + inode = sel_make_inode(policycap_dir->d_sb, S_IFREG | S_IRUGO); + if (inode == NULL) + return -ENOMEM; + + inode->i_fop = &sel_policycap_ops; + inode->i_ino = iter | SEL_POLICYCAP_INO_OFFSET; + d_add(dentry, inode); + } + + return 0; +} + +static int sel_make_dir(struct inode *dir, struct dentry *dentry, + unsigned long *ino) +{ + struct inode *inode; + + inode = sel_make_inode(dir->i_sb, S_IFDIR | S_IRUGO | S_IXUGO); + if (!inode) + return -ENOMEM; + + inode->i_op = &simple_dir_inode_operations; + inode->i_fop = &simple_dir_operations; + inode->i_ino = ++(*ino); + /* directory inodes start off with i_nlink == 2 (for "." entry) */ + inc_nlink(inode); + d_add(dentry, inode); + /* bump link count on parent directory, too */ + inc_nlink(dir); + + return 0; +} + +static int sel_fill_super(struct super_block *sb, void *data, int silent) +{ + int ret; + struct dentry *dentry; + struct inode *inode, *root_inode; + struct inode_security_struct *isec; + + static struct tree_descr selinux_files[] = { + [SEL_LOAD] = {"load", &sel_load_ops, S_IRUSR|S_IWUSR}, + [SEL_ENFORCE] = {"enforce", &sel_enforce_ops, S_IRUGO|S_IWUSR}, + [SEL_CONTEXT] = {"context", &transaction_ops, S_IRUGO|S_IWUGO}, + [SEL_ACCESS] = {"access", &transaction_ops, S_IRUGO|S_IWUGO}, + [SEL_CREATE] = {"create", &transaction_ops, S_IRUGO|S_IWUGO}, + [SEL_RELABEL] = {"relabel", &transaction_ops, S_IRUGO|S_IWUGO}, + [SEL_USER] = {"user", &transaction_ops, S_IRUGO|S_IWUGO}, + [SEL_POLICYVERS] = {"policyvers", &sel_policyvers_ops, S_IRUGO}, + [SEL_COMMIT_BOOLS] = {"commit_pending_bools", &sel_commit_bools_ops, S_IWUSR}, + [SEL_MLS] = {"mls", &sel_mls_ops, S_IRUGO}, + [SEL_DISABLE] = {"disable", &sel_disable_ops, S_IWUSR}, + [SEL_MEMBER] = {"member", &transaction_ops, S_IRUGO|S_IWUGO}, + [SEL_CHECKREQPROT] = {"checkreqprot", &sel_checkreqprot_ops, S_IRUGO|S_IWUSR}, + [SEL_REJECT_UNKNOWN] = {"reject_unknown", &sel_handle_unknown_ops, S_IRUGO}, + [SEL_DENY_UNKNOWN] = {"deny_unknown", &sel_handle_unknown_ops, S_IRUGO}, + [SEL_STATUS] = {"status", &sel_handle_status_ops, S_IRUGO}, + [SEL_POLICY] = {"policy", &sel_policy_ops, S_IRUSR}, + /* last one */ {""} + }; + ret = simple_fill_super(sb, SELINUX_MAGIC, selinux_files); + if (ret) + goto err; + + root_inode = sb->s_root->d_inode; + + ret = -ENOMEM; + dentry = d_alloc_name(sb->s_root, BOOL_DIR_NAME); + if (!dentry) + goto err; + + ret = sel_make_dir(root_inode, dentry, &sel_last_ino); + if (ret) + goto err; + + bool_dir = dentry; + + ret = -ENOMEM; + dentry = d_alloc_name(sb->s_root, NULL_FILE_NAME); + if (!dentry) + goto err; + + ret = -ENOMEM; + inode = sel_make_inode(sb, S_IFCHR | S_IRUGO | S_IWUGO); + if (!inode) + goto err; + + inode->i_ino = ++sel_last_ino; + isec = (struct inode_security_struct *)inode->i_security; + isec->sid = SECINITSID_DEVNULL; + isec->sclass = SECCLASS_CHR_FILE; + isec->initialized = 1; + + init_special_inode(inode, S_IFCHR | S_IRUGO | S_IWUGO, MKDEV(MEM_MAJOR, 3)); + d_add(dentry, inode); + selinux_null = dentry; + + ret = -ENOMEM; + dentry = d_alloc_name(sb->s_root, "avc"); + if (!dentry) + goto err; + + ret = sel_make_dir(root_inode, dentry, &sel_last_ino); + if (ret) + goto err; + + ret = sel_make_avc_files(dentry); + if (ret) + goto err; + + ret = -ENOMEM; + dentry = d_alloc_name(sb->s_root, "initial_contexts"); + if (!dentry) + goto err; + + ret = sel_make_dir(root_inode, dentry, &sel_last_ino); + if (ret) + goto err; + + ret = sel_make_initcon_files(dentry); + if (ret) + goto err; + + ret = -ENOMEM; + dentry = d_alloc_name(sb->s_root, "class"); + if (!dentry) + goto err; + + ret = sel_make_dir(root_inode, dentry, &sel_last_ino); + if (ret) + goto err; + + class_dir = dentry; + + ret = -ENOMEM; + dentry = d_alloc_name(sb->s_root, "policy_capabilities"); + if (!dentry) + goto err; + + ret = sel_make_dir(root_inode, dentry, &sel_last_ino); + if (ret) + goto err; + + policycap_dir = dentry; + + return 0; +err: + printk(KERN_ERR "SELinux: %s: failed while creating inodes\n", + __func__); + return ret; +} + +static struct dentry *sel_mount(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) +{ + return mount_single(fs_type, flags, data, sel_fill_super); +} + +static struct file_system_type sel_fs_type = { + .name = "selinuxfs", + .mount = sel_mount, + .kill_sb = kill_litter_super, +}; + +struct vfsmount *selinuxfs_mount; +static struct kobject *selinuxfs_kobj; + +static int __init init_sel_fs(void) +{ + int err; + + if (!selinux_enabled) + return 0; + + selinuxfs_kobj = kobject_create_and_add("selinux", fs_kobj); + if (!selinuxfs_kobj) + return -ENOMEM; + + err = register_filesystem(&sel_fs_type); + if (err) { + kobject_put(selinuxfs_kobj); + return err; + } + + selinuxfs_mount = kern_mount(&sel_fs_type); + if (IS_ERR(selinuxfs_mount)) { + printk(KERN_ERR "selinuxfs: could not mount!\n"); + err = PTR_ERR(selinuxfs_mount); + selinuxfs_mount = NULL; + } + + return err; +} + +__initcall(init_sel_fs); + +#ifdef CONFIG_SECURITY_SELINUX_DISABLE +void exit_sel_fs(void) +{ + kobject_put(selinuxfs_kobj); + unregister_filesystem(&sel_fs_type); +} +#endif diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c new file mode 100644 index 00000000..a3dd9faa --- /dev/null +++ b/security/selinux/ss/avtab.c @@ -0,0 +1,556 @@ +/* + * Implementation of the access vector table type. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ + +/* Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Copyright (C) 2003 Tresys Technology, LLC + * 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. + * + * Updated: Yuichi Nakamura <ynakam@hitachisoft.jp> + * Tuned number of hash slots for avtab to reduce memory usage + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include "avtab.h" +#include "policydb.h" + +static struct kmem_cache *avtab_node_cachep; + +static inline int avtab_hash(struct avtab_key *keyp, u16 mask) +{ + return ((keyp->target_class + (keyp->target_type << 2) + + (keyp->source_type << 9)) & mask); +} + +static struct avtab_node* +avtab_insert_node(struct avtab *h, int hvalue, + struct avtab_node *prev, struct avtab_node *cur, + struct avtab_key *key, struct avtab_datum *datum) +{ + struct avtab_node *newnode; + newnode = kmem_cache_zalloc(avtab_node_cachep, GFP_KERNEL); + if (newnode == NULL) + return NULL; + newnode->key = *key; + newnode->datum = *datum; + if (prev) { + newnode->next = prev->next; + prev->next = newnode; + } else { + newnode->next = h->htable[hvalue]; + h->htable[hvalue] = newnode; + } + + h->nel++; + return newnode; +} + +static int avtab_insert(struct avtab *h, struct avtab_key *key, struct avtab_datum *datum) +{ + int hvalue; + struct avtab_node *prev, *cur, *newnode; + u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD); + + if (!h || !h->htable) + return -EINVAL; + + hvalue = avtab_hash(key, h->mask); + for (prev = NULL, cur = h->htable[hvalue]; + cur; + prev = cur, cur = cur->next) { + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class == cur->key.target_class && + (specified & cur->key.specified)) + return -EEXIST; + if (key->source_type < cur->key.source_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type < cur->key.target_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class < cur->key.target_class) + break; + } + + newnode = avtab_insert_node(h, hvalue, prev, cur, key, datum); + if (!newnode) + return -ENOMEM; + + return 0; +} + +/* Unlike avtab_insert(), this function allow multiple insertions of the same + * key/specified mask into the table, as needed by the conditional avtab. + * It also returns a pointer to the node inserted. + */ +struct avtab_node * +avtab_insert_nonunique(struct avtab *h, struct avtab_key *key, struct avtab_datum *datum) +{ + int hvalue; + struct avtab_node *prev, *cur; + u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD); + + if (!h || !h->htable) + return NULL; + hvalue = avtab_hash(key, h->mask); + for (prev = NULL, cur = h->htable[hvalue]; + cur; + prev = cur, cur = cur->next) { + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class == cur->key.target_class && + (specified & cur->key.specified)) + break; + if (key->source_type < cur->key.source_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type < cur->key.target_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class < cur->key.target_class) + break; + } + return avtab_insert_node(h, hvalue, prev, cur, key, datum); +} + +struct avtab_datum *avtab_search(struct avtab *h, struct avtab_key *key) +{ + int hvalue; + struct avtab_node *cur; + u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD); + + if (!h || !h->htable) + return NULL; + + hvalue = avtab_hash(key, h->mask); + for (cur = h->htable[hvalue]; cur; cur = cur->next) { + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class == cur->key.target_class && + (specified & cur->key.specified)) + return &cur->datum; + + if (key->source_type < cur->key.source_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type < cur->key.target_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class < cur->key.target_class) + break; + } + + return NULL; +} + +/* This search function returns a node pointer, and can be used in + * conjunction with avtab_search_next_node() + */ +struct avtab_node* +avtab_search_node(struct avtab *h, struct avtab_key *key) +{ + int hvalue; + struct avtab_node *cur; + u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD); + + if (!h || !h->htable) + return NULL; + + hvalue = avtab_hash(key, h->mask); + for (cur = h->htable[hvalue]; cur; cur = cur->next) { + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class == cur->key.target_class && + (specified & cur->key.specified)) + return cur; + + if (key->source_type < cur->key.source_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type < cur->key.target_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class < cur->key.target_class) + break; + } + return NULL; +} + +struct avtab_node* +avtab_search_node_next(struct avtab_node *node, int specified) +{ + struct avtab_node *cur; + + if (!node) + return NULL; + + specified &= ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD); + for (cur = node->next; cur; cur = cur->next) { + if (node->key.source_type == cur->key.source_type && + node->key.target_type == cur->key.target_type && + node->key.target_class == cur->key.target_class && + (specified & cur->key.specified)) + return cur; + + if (node->key.source_type < cur->key.source_type) + break; + if (node->key.source_type == cur->key.source_type && + node->key.target_type < cur->key.target_type) + break; + if (node->key.source_type == cur->key.source_type && + node->key.target_type == cur->key.target_type && + node->key.target_class < cur->key.target_class) + break; + } + return NULL; +} + +void avtab_destroy(struct avtab *h) +{ + int i; + struct avtab_node *cur, *temp; + + if (!h || !h->htable) + return; + + for (i = 0; i < h->nslot; i++) { + cur = h->htable[i]; + while (cur) { + temp = cur; + cur = cur->next; + kmem_cache_free(avtab_node_cachep, temp); + } + h->htable[i] = NULL; + } + kfree(h->htable); + h->htable = NULL; + h->nslot = 0; + h->mask = 0; +} + +int avtab_init(struct avtab *h) +{ + h->htable = NULL; + h->nel = 0; + return 0; +} + +int avtab_alloc(struct avtab *h, u32 nrules) +{ + u16 mask = 0; + u32 shift = 0; + u32 work = nrules; + u32 nslot = 0; + + if (nrules == 0) + goto avtab_alloc_out; + + while (work) { + work = work >> 1; + shift++; + } + if (shift > 2) + shift = shift - 2; + nslot = 1 << shift; + if (nslot > MAX_AVTAB_HASH_BUCKETS) + nslot = MAX_AVTAB_HASH_BUCKETS; + mask = nslot - 1; + + h->htable = kcalloc(nslot, sizeof(*(h->htable)), GFP_KERNEL); + if (!h->htable) + return -ENOMEM; + + avtab_alloc_out: + h->nel = 0; + h->nslot = nslot; + h->mask = mask; + printk(KERN_DEBUG "SELinux: %d avtab hash slots, %d rules.\n", + h->nslot, nrules); + return 0; +} + +void avtab_hash_eval(struct avtab *h, char *tag) +{ + int i, chain_len, slots_used, max_chain_len; + unsigned long long chain2_len_sum; + struct avtab_node *cur; + + slots_used = 0; + max_chain_len = 0; + chain2_len_sum = 0; + for (i = 0; i < h->nslot; i++) { + cur = h->htable[i]; + if (cur) { + slots_used++; + chain_len = 0; + while (cur) { + chain_len++; + cur = cur->next; + } + + if (chain_len > max_chain_len) + max_chain_len = chain_len; + chain2_len_sum += chain_len * chain_len; + } + } + + printk(KERN_DEBUG "SELinux: %s: %d entries and %d/%d buckets used, " + "longest chain length %d sum of chain length^2 %llu\n", + tag, h->nel, slots_used, h->nslot, max_chain_len, + chain2_len_sum); +} + +static uint16_t spec_order[] = { + AVTAB_ALLOWED, + AVTAB_AUDITDENY, + AVTAB_AUDITALLOW, + AVTAB_TRANSITION, + AVTAB_CHANGE, + AVTAB_MEMBER +}; + +int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol, + int (*insertf)(struct avtab *a, struct avtab_key *k, + struct avtab_datum *d, void *p), + void *p) +{ + __le16 buf16[4]; + u16 enabled; + __le32 buf32[7]; + u32 items, items2, val, vers = pol->policyvers; + struct avtab_key key; + struct avtab_datum datum; + int i, rc; + unsigned set; + + memset(&key, 0, sizeof(struct avtab_key)); + memset(&datum, 0, sizeof(struct avtab_datum)); + + if (vers < POLICYDB_VERSION_AVTAB) { + rc = next_entry(buf32, fp, sizeof(u32)); + if (rc) { + printk(KERN_ERR "SELinux: avtab: truncated entry\n"); + return rc; + } + items2 = le32_to_cpu(buf32[0]); + if (items2 > ARRAY_SIZE(buf32)) { + printk(KERN_ERR "SELinux: avtab: entry overflow\n"); + return -EINVAL; + + } + rc = next_entry(buf32, fp, sizeof(u32)*items2); + if (rc) { + printk(KERN_ERR "SELinux: avtab: truncated entry\n"); + return rc; + } + items = 0; + + val = le32_to_cpu(buf32[items++]); + key.source_type = (u16)val; + if (key.source_type != val) { + printk(KERN_ERR "SELinux: avtab: truncated source type\n"); + 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 -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 -EINVAL; + } + + val = le32_to_cpu(buf32[items++]); + enabled = (val & AVTAB_ENABLED_OLD) ? AVTAB_ENABLED : 0; + + if (!(val & (AVTAB_AV | AVTAB_TYPE))) { + printk(KERN_ERR "SELinux: avtab: null entry\n"); + return -EINVAL; + } + if ((val & AVTAB_AV) && + (val & AVTAB_TYPE)) { + printk(KERN_ERR "SELinux: avtab: entry has both access vectors and types\n"); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(spec_order); i++) { + if (val & spec_order[i]) { + key.specified = spec_order[i] | enabled; + datum.data = le32_to_cpu(buf32[items++]); + rc = insertf(a, &key, &datum, p); + if (rc) + return rc; + } + } + + if (items != items2) { + printk(KERN_ERR "SELinux: avtab: entry only had %d items, expected %d\n", items2, items); + return -EINVAL; + } + return 0; + } + + rc = next_entry(buf16, fp, sizeof(u16)*4); + if (rc) { + printk(KERN_ERR "SELinux: avtab: truncated entry\n"); + return rc; + } + + items = 0; + key.source_type = le16_to_cpu(buf16[items++]); + key.target_type = le16_to_cpu(buf16[items++]); + key.target_class = le16_to_cpu(buf16[items++]); + key.specified = le16_to_cpu(buf16[items++]); + + if (!policydb_type_isvalid(pol, key.source_type) || + !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 -EINVAL; + } + + set = 0; + for (i = 0; i < ARRAY_SIZE(spec_order); i++) { + if (key.specified & spec_order[i]) + set++; + } + if (!set || set > 1) { + printk(KERN_ERR "SELinux: avtab: more than one specifier\n"); + return -EINVAL; + } + + rc = next_entry(buf32, fp, sizeof(u32)); + if (rc) { + printk(KERN_ERR "SELinux: avtab: truncated entry\n"); + 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 -EINVAL; + } + return insertf(a, &key, &datum, p); +} + +static int avtab_insertf(struct avtab *a, struct avtab_key *k, + struct avtab_datum *d, void *p) +{ + return avtab_insert(a, k, d); +} + +int avtab_read(struct avtab *a, void *fp, struct policydb *pol) +{ + int rc; + __le32 buf[1]; + u32 nel, i; + + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) { + printk(KERN_ERR "SELinux: avtab: truncated table\n"); + goto bad; + } + nel = le32_to_cpu(buf[0]); + if (!nel) { + printk(KERN_ERR "SELinux: avtab: table is empty\n"); + rc = -EINVAL; + goto bad; + } + + rc = avtab_alloc(a, nel); + if (rc) + goto bad; + + for (i = 0; i < nel; i++) { + rc = avtab_read_item(a, fp, pol, avtab_insertf, NULL); + if (rc) { + if (rc == -ENOMEM) + printk(KERN_ERR "SELinux: avtab: out of memory\n"); + else if (rc == -EEXIST) + printk(KERN_ERR "SELinux: avtab: duplicate entry\n"); + + goto bad; + } + } + + rc = 0; +out: + return rc; + +bad: + avtab_destroy(a); + goto out; +} + +int avtab_write_item(struct policydb *p, struct avtab_node *cur, void *fp) +{ + __le16 buf16[4]; + __le32 buf32[1]; + int rc; + + buf16[0] = cpu_to_le16(cur->key.source_type); + buf16[1] = cpu_to_le16(cur->key.target_type); + buf16[2] = cpu_to_le16(cur->key.target_class); + buf16[3] = cpu_to_le16(cur->key.specified); + rc = put_entry(buf16, sizeof(u16), 4, fp); + if (rc) + return rc; + buf32[0] = cpu_to_le32(cur->datum.data); + rc = put_entry(buf32, sizeof(u32), 1, fp); + if (rc) + return rc; + return 0; +} + +int avtab_write(struct policydb *p, struct avtab *a, void *fp) +{ + unsigned int i; + int rc = 0; + struct avtab_node *cur; + __le32 buf[1]; + + buf[0] = cpu_to_le32(a->nel); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + for (i = 0; i < a->nslot; i++) { + for (cur = a->htable[i]; cur; cur = cur->next) { + rc = avtab_write_item(p, cur, fp); + if (rc) + return rc; + } + } + + return rc; +} +void avtab_cache_init(void) +{ + avtab_node_cachep = kmem_cache_create("avtab_node", + sizeof(struct avtab_node), + 0, SLAB_PANIC, NULL); +} + +void avtab_cache_destroy(void) +{ + kmem_cache_destroy(avtab_node_cachep); +} diff --git a/security/selinux/ss/avtab.h b/security/selinux/ss/avtab.h new file mode 100644 index 00000000..63ce2f9e --- /dev/null +++ b/security/selinux/ss/avtab.h @@ -0,0 +1,91 @@ +/* + * An access vector table (avtab) is a hash table + * of access vectors and transition types indexed + * by a type pair and a class. An access vector + * table is used to represent the type enforcement + * tables. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ + +/* Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Copyright (C) 2003 Tresys Technology, LLC + * 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. + * + * Updated: Yuichi Nakamura <ynakam@hitachisoft.jp> + * Tuned number of hash slots for avtab to reduce memory usage + */ +#ifndef _SS_AVTAB_H_ +#define _SS_AVTAB_H_ + +struct avtab_key { + u16 source_type; /* source type */ + u16 target_type; /* target type */ + u16 target_class; /* target object class */ +#define AVTAB_ALLOWED 0x0001 +#define AVTAB_AUDITALLOW 0x0002 +#define AVTAB_AUDITDENY 0x0004 +#define AVTAB_AV (AVTAB_ALLOWED | AVTAB_AUDITALLOW | AVTAB_AUDITDENY) +#define AVTAB_TRANSITION 0x0010 +#define AVTAB_MEMBER 0x0020 +#define AVTAB_CHANGE 0x0040 +#define AVTAB_TYPE (AVTAB_TRANSITION | AVTAB_MEMBER | AVTAB_CHANGE) +#define AVTAB_ENABLED_OLD 0x80000000 /* reserved for used in cond_avtab */ +#define AVTAB_ENABLED 0x8000 /* reserved for used in cond_avtab */ + u16 specified; /* what field is specified */ +}; + +struct avtab_datum { + u32 data; /* access vector or type value */ +}; + +struct avtab_node { + struct avtab_key key; + struct avtab_datum datum; + struct avtab_node *next; +}; + +struct avtab { + struct avtab_node **htable; + u32 nel; /* number of elements */ + u32 nslot; /* number of hash slots */ + u16 mask; /* mask to compute hash func */ + +}; + +int avtab_init(struct avtab *); +int avtab_alloc(struct avtab *, u32); +struct avtab_datum *avtab_search(struct avtab *h, struct avtab_key *k); +void avtab_destroy(struct avtab *h); +void avtab_hash_eval(struct avtab *h, char *tag); + +struct policydb; +int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol, + int (*insert)(struct avtab *a, struct avtab_key *k, + struct avtab_datum *d, void *p), + void *p); + +int avtab_read(struct avtab *a, void *fp, struct policydb *pol); +int avtab_write_item(struct policydb *p, struct avtab_node *cur, void *fp); +int avtab_write(struct policydb *p, struct avtab *a, void *fp); + +struct avtab_node *avtab_insert_nonunique(struct avtab *h, struct avtab_key *key, + struct avtab_datum *datum); + +struct avtab_node *avtab_search_node(struct avtab *h, struct avtab_key *key); + +struct avtab_node *avtab_search_node_next(struct avtab_node *node, int specified); + +void avtab_cache_init(void); +void avtab_cache_destroy(void); + +#define MAX_AVTAB_HASH_BITS 11 +#define MAX_AVTAB_HASH_BUCKETS (1 << MAX_AVTAB_HASH_BITS) + +#endif /* _SS_AVTAB_H_ */ + diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c new file mode 100644 index 00000000..a5337320 --- /dev/null +++ b/security/selinux/ss/conditional.c @@ -0,0 +1,648 @@ +/* Authors: Karl MacMillan <kmacmillan@tresys.com> + * Frank Mayer <mayerf@tresys.com> + * + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + * 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. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/spinlock.h> +#include <linux/slab.h> + +#include "security.h" +#include "conditional.h" + +/* + * cond_evaluate_expr evaluates a conditional expr + * in reverse polish notation. It returns true (1), false (0), + * or undefined (-1). Undefined occurs when the expression + * exceeds the stack depth of COND_EXPR_MAXDEPTH. + */ +static int cond_evaluate_expr(struct policydb *p, struct cond_expr *expr) +{ + + struct cond_expr *cur; + int s[COND_EXPR_MAXDEPTH]; + int sp = -1; + + for (cur = expr; cur; cur = cur->next) { + switch (cur->expr_type) { + case COND_BOOL: + if (sp == (COND_EXPR_MAXDEPTH - 1)) + return -1; + sp++; + s[sp] = p->bool_val_to_struct[cur->bool - 1]->state; + break; + case COND_NOT: + if (sp < 0) + return -1; + s[sp] = !s[sp]; + break; + case COND_OR: + if (sp < 1) + return -1; + sp--; + s[sp] |= s[sp + 1]; + break; + case COND_AND: + if (sp < 1) + return -1; + sp--; + s[sp] &= s[sp + 1]; + break; + case COND_XOR: + if (sp < 1) + return -1; + sp--; + s[sp] ^= s[sp + 1]; + break; + case COND_EQ: + if (sp < 1) + return -1; + sp--; + s[sp] = (s[sp] == s[sp + 1]); + break; + case COND_NEQ: + if (sp < 1) + return -1; + sp--; + s[sp] = (s[sp] != s[sp + 1]); + break; + default: + return -1; + } + } + return s[0]; +} + +/* + * evaluate_cond_node evaluates the conditional stored in + * a struct cond_node and if the result is different than the + * current state of the node it sets the rules in the true/false + * list appropriately. If the result of the expression is undefined + * all of the rules are disabled for safety. + */ +int evaluate_cond_node(struct policydb *p, struct cond_node *node) +{ + int new_state; + struct cond_av_list *cur; + + new_state = cond_evaluate_expr(p, node->expr); + if (new_state != node->cur_state) { + node->cur_state = new_state; + if (new_state == -1) + printk(KERN_ERR "SELinux: expression result was undefined - disabling all rules.\n"); + /* turn the rules on or off */ + for (cur = node->true_list; cur; cur = cur->next) { + if (new_state <= 0) + cur->node->key.specified &= ~AVTAB_ENABLED; + else + cur->node->key.specified |= AVTAB_ENABLED; + } + + for (cur = node->false_list; cur; cur = cur->next) { + /* -1 or 1 */ + if (new_state) + cur->node->key.specified &= ~AVTAB_ENABLED; + else + cur->node->key.specified |= AVTAB_ENABLED; + } + } + return 0; +} + +int cond_policydb_init(struct policydb *p) +{ + int rc; + + p->bool_val_to_struct = NULL; + p->cond_list = NULL; + + rc = avtab_init(&p->te_cond_avtab); + if (rc) + return rc; + + return 0; +} + +static void cond_av_list_destroy(struct cond_av_list *list) +{ + struct cond_av_list *cur, *next; + for (cur = list; cur; cur = next) { + next = cur->next; + /* the avtab_ptr_t node is destroy by the avtab */ + kfree(cur); + } +} + +static void cond_node_destroy(struct cond_node *node) +{ + struct cond_expr *cur_expr, *next_expr; + + for (cur_expr = node->expr; cur_expr; cur_expr = next_expr) { + next_expr = cur_expr->next; + kfree(cur_expr); + } + cond_av_list_destroy(node->true_list); + cond_av_list_destroy(node->false_list); + kfree(node); +} + +static void cond_list_destroy(struct cond_node *list) +{ + struct cond_node *next, *cur; + + if (list == NULL) + return; + + for (cur = list; cur; cur = next) { + next = cur->next; + cond_node_destroy(cur); + } +} + +void cond_policydb_destroy(struct policydb *p) +{ + kfree(p->bool_val_to_struct); + avtab_destroy(&p->te_cond_avtab); + cond_list_destroy(p->cond_list); +} + +int cond_init_bool_indexes(struct policydb *p) +{ + kfree(p->bool_val_to_struct); + p->bool_val_to_struct = (struct cond_bool_datum **) + kmalloc(p->p_bools.nprim * sizeof(struct cond_bool_datum *), GFP_KERNEL); + if (!p->bool_val_to_struct) + return -ENOMEM; + return 0; +} + +int cond_destroy_bool(void *key, void *datum, void *p) +{ + kfree(key); + kfree(datum); + return 0; +} + +int cond_index_bool(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct cond_bool_datum *booldatum; + struct flex_array *fa; + + booldatum = datum; + p = datap; + + if (!booldatum->value || booldatum->value > p->p_bools.nprim) + return -EINVAL; + + fa = p->sym_val_to_name[SYM_BOOLS]; + if (flex_array_put_ptr(fa, booldatum->value - 1, key, + GFP_KERNEL | __GFP_ZERO)) + BUG(); + p->bool_val_to_struct[booldatum->value - 1] = booldatum; + + return 0; +} + +static int bool_isvalid(struct cond_bool_datum *b) +{ + if (!(b->state == 0 || b->state == 1)) + return 0; + return 1; +} + +int cond_read_bool(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct cond_bool_datum *booldatum; + __le32 buf[3]; + u32 len; + int rc; + + booldatum = kzalloc(sizeof(struct cond_bool_datum), GFP_KERNEL); + if (!booldatum) + return -ENOMEM; + + rc = next_entry(buf, fp, sizeof buf); + 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) + goto err; + key[len] = '\0'; + rc = hashtab_insert(h, key, booldatum); + if (rc) + goto err; + + return 0; +err: + cond_destroy_bool(key, booldatum, NULL); + return rc; +} + +struct cond_insertf_data { + struct policydb *p; + struct cond_av_list *other; + struct cond_av_list *head; + struct cond_av_list *tail; +}; + +static int cond_insertf(struct avtab *a, struct avtab_key *k, struct avtab_datum *d, void *ptr) +{ + struct cond_insertf_data *data = ptr; + struct policydb *p = data->p; + 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 + * conflicting rules by searching the te_avtab and the + * cond_te_avtab. + */ + if (k->specified & AVTAB_TYPE) { + if (avtab_search(&p->te_avtab, k)) { + printk(KERN_ERR "SELinux: type rule already exists outside of a conditional.\n"); + goto err; + } + /* + * If we are reading the false list other will be a pointer to + * the true list. We can have duplicate entries if there is only + * 1 other entry and it is in our true list. + * + * If we are reading the true list (other == NULL) there shouldn't + * be any other entries. + */ + if (other) { + node_ptr = avtab_search_node(&p->te_cond_avtab, k); + if (node_ptr) { + if (avtab_search_node_next(node_ptr, k->specified)) { + printk(KERN_ERR "SELinux: too many conflicting type rules.\n"); + goto err; + } + found = 0; + for (cur = other; cur; cur = cur->next) { + if (cur->node == node_ptr) { + found = 1; + break; + } + } + if (!found) { + printk(KERN_ERR "SELinux: conflicting type rules.\n"); + goto err; + } + } + } else { + if (avtab_search(&p->te_cond_avtab, k)) { + printk(KERN_ERR "SELinux: conflicting type rules when adding type rule for true.\n"); + goto err; + } + } + } + + 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) { + rc = -ENOMEM; + goto err; + } + + list->node = node_ptr; + if (!data->head) + data->head = list; + else + data->tail->next = list; + data->tail = list; + return 0; + +err: + cond_av_list_destroy(data->head); + data->head = NULL; + return rc; +} + +static int cond_read_av_list(struct policydb *p, void *fp, struct cond_av_list **ret_list, struct cond_av_list *other) +{ + int i, rc; + __le32 buf[1]; + u32 len; + struct cond_insertf_data data; + + *ret_list = NULL; + + len = 0; + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + return rc; + + len = le32_to_cpu(buf[0]); + if (len == 0) + return 0; + + data.p = p; + data.other = other; + data.head = NULL; + data.tail = NULL; + for (i = 0; i < len; i++) { + rc = avtab_read_item(&p->te_cond_avtab, fp, p, cond_insertf, + &data); + if (rc) + return rc; + } + + *ret_list = data.head; + return 0; +} + +static int expr_isvalid(struct policydb *p, struct cond_expr *expr) +{ + if (expr->expr_type <= 0 || expr->expr_type > COND_LAST) { + printk(KERN_ERR "SELinux: conditional expressions uses unknown operator.\n"); + return 0; + } + + if (expr->bool > p->p_bools.nprim) { + printk(KERN_ERR "SELinux: conditional expressions uses unknown bool.\n"); + return 0; + } + return 1; +} + +static int cond_read_node(struct policydb *p, struct cond_node *node, void *fp) +{ + __le32 buf[2]; + u32 len, i; + int rc; + struct cond_expr *expr = NULL, *last = NULL; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + return rc; + + node->cur_state = le32_to_cpu(buf[0]); + + len = 0; + rc = next_entry(buf, fp, sizeof(u32)); + 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) + goto err; + + rc = -ENOMEM; + expr = kzalloc(sizeof(struct cond_expr), GFP_KERNEL); + if (!expr) + goto err; + + expr->expr_type = le32_to_cpu(buf[0]); + expr->bool = le32_to_cpu(buf[1]); + + if (!expr_isvalid(p, expr)) { + rc = -EINVAL; + kfree(expr); + goto err; + } + + if (i == 0) + node->expr = expr; + else + last->next = expr; + last = expr; + } + + rc = cond_read_av_list(p, fp, &node->true_list, NULL); + if (rc) + goto err; + 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 rc; +} + +int cond_read_list(struct policydb *p, void *fp) +{ + struct cond_node *node, *last = NULL; + __le32 buf[1]; + u32 i, len; + int rc; + + rc = next_entry(buf, fp, sizeof buf); + if (rc) + return rc; + + len = le32_to_cpu(buf[0]); + + rc = avtab_alloc(&(p->te_cond_avtab), p->te_avtab.nel); + if (rc) + goto err; + + for (i = 0; i < len; i++) { + rc = -ENOMEM; + node = kzalloc(sizeof(struct cond_node), GFP_KERNEL); + if (!node) + goto err; + + rc = cond_read_node(p, node, fp); + if (rc) + goto err; + + if (i == 0) + p->cond_list = node; + else + last->next = node; + last = node; + } + return 0; +err: + cond_list_destroy(p->cond_list); + p->cond_list = NULL; + return rc; +} + +int cond_write_bool(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct cond_bool_datum *booldatum = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + __le32 buf[3]; + u32 len; + int rc; + + len = strlen(key); + buf[0] = cpu_to_le32(booldatum->value); + buf[1] = cpu_to_le32(booldatum->state); + buf[2] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + return 0; +} + +/* + * cond_write_cond_av_list doesn't write out the av_list nodes. + * Instead it writes out the key/value pairs from the avtab. This + * is necessary because there is no way to uniquely identifying rules + * in the avtab so it is not possible to associate individual rules + * in the avtab with a conditional without saving them as part of + * the conditional. This means that the avtab with the conditional + * rules will not be saved but will be rebuilt on policy load. + */ +static int cond_write_av_list(struct policydb *p, + struct cond_av_list *list, struct policy_file *fp) +{ + __le32 buf[1]; + struct cond_av_list *cur_list; + u32 len; + int rc; + + len = 0; + for (cur_list = list; cur_list != NULL; cur_list = cur_list->next) + len++; + + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + if (len == 0) + return 0; + + for (cur_list = list; cur_list != NULL; cur_list = cur_list->next) { + rc = avtab_write_item(p, cur_list->node, fp); + if (rc) + return rc; + } + + return 0; +} + +int cond_write_node(struct policydb *p, struct cond_node *node, + struct policy_file *fp) +{ + struct cond_expr *cur_expr; + __le32 buf[2]; + int rc; + u32 len = 0; + + buf[0] = cpu_to_le32(node->cur_state); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + for (cur_expr = node->expr; cur_expr != NULL; cur_expr = cur_expr->next) + len++; + + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + for (cur_expr = node->expr; cur_expr != NULL; cur_expr = cur_expr->next) { + buf[0] = cpu_to_le32(cur_expr->expr_type); + buf[1] = cpu_to_le32(cur_expr->bool); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + } + + rc = cond_write_av_list(p, node->true_list, fp); + if (rc) + return rc; + rc = cond_write_av_list(p, node->false_list, fp); + if (rc) + return rc; + + return 0; +} + +int cond_write_list(struct policydb *p, struct cond_node *list, void *fp) +{ + struct cond_node *cur; + u32 len; + __le32 buf[1]; + int rc; + + len = 0; + for (cur = list; cur != NULL; cur = cur->next) + len++; + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + for (cur = list; cur != NULL; cur = cur->next) { + rc = cond_write_node(p, cur, fp); + if (rc) + return rc; + } + + return 0; +} +/* Determine whether additional permissions are granted by the conditional + * av table, and if so, add them to the result + */ +void cond_compute_av(struct avtab *ctab, struct avtab_key *key, struct av_decision *avd) +{ + struct avtab_node *node; + + if (!ctab || !key || !avd) + return; + + for (node = avtab_search_node(ctab, key); node; + node = avtab_search_node_next(node, key->specified)) { + if ((u16)(AVTAB_ALLOWED|AVTAB_ENABLED) == + (node->key.specified & (AVTAB_ALLOWED|AVTAB_ENABLED))) + avd->allowed |= node->datum.data; + if ((u16)(AVTAB_AUDITDENY|AVTAB_ENABLED) == + (node->key.specified & (AVTAB_AUDITDENY|AVTAB_ENABLED))) + /* Since a '0' in an auditdeny mask represents a + * permission we do NOT want to audit (dontaudit), we use + * the '&' operand to ensure that all '0's in the mask + * are retained (much unlike the allow and auditallow cases). + */ + avd->auditdeny &= node->datum.data; + if ((u16)(AVTAB_AUDITALLOW|AVTAB_ENABLED) == + (node->key.specified & (AVTAB_AUDITALLOW|AVTAB_ENABLED))) + avd->auditallow |= node->datum.data; + } + return; +} diff --git a/security/selinux/ss/conditional.h b/security/selinux/ss/conditional.h new file mode 100644 index 00000000..3f209c63 --- /dev/null +++ b/security/selinux/ss/conditional.h @@ -0,0 +1,79 @@ +/* Authors: Karl MacMillan <kmacmillan@tresys.com> + * Frank Mayer <mayerf@tresys.com> + * + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + * 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. + */ + +#ifndef _CONDITIONAL_H_ +#define _CONDITIONAL_H_ + +#include "avtab.h" +#include "symtab.h" +#include "policydb.h" + +#define COND_EXPR_MAXDEPTH 10 + +/* + * A conditional expression is a list of operators and operands + * in reverse polish notation. + */ +struct cond_expr { +#define COND_BOOL 1 /* plain bool */ +#define COND_NOT 2 /* !bool */ +#define COND_OR 3 /* bool || bool */ +#define COND_AND 4 /* bool && bool */ +#define COND_XOR 5 /* bool ^ bool */ +#define COND_EQ 6 /* bool == bool */ +#define COND_NEQ 7 /* bool != bool */ +#define COND_LAST COND_NEQ + __u32 expr_type; + __u32 bool; + struct cond_expr *next; +}; + +/* + * Each cond_node contains a list of rules to be enabled/disabled + * depending on the current value of the conditional expression. This + * struct is for that list. + */ +struct cond_av_list { + struct avtab_node *node; + struct cond_av_list *next; +}; + +/* + * A cond node represents a conditional block in a policy. It + * contains a conditional expression, the current state of the expression, + * two lists of rules to enable/disable depending on the value of the + * expression (the true list corresponds to if and the false list corresponds + * to else).. + */ +struct cond_node { + int cur_state; + struct cond_expr *expr; + struct cond_av_list *true_list; + struct cond_av_list *false_list; + struct cond_node *next; +}; + +int cond_policydb_init(struct policydb *p); +void cond_policydb_destroy(struct policydb *p); + +int cond_init_bool_indexes(struct policydb *p); +int cond_destroy_bool(void *key, void *datum, void *p); + +int cond_index_bool(void *key, void *datum, void *datap); + +int cond_read_bool(struct policydb *p, struct hashtab *h, void *fp); +int cond_read_list(struct policydb *p, void *fp); +int cond_write_bool(void *key, void *datum, void *ptr); +int cond_write_list(struct policydb *p, struct cond_node *list, void *fp); + +void cond_compute_av(struct avtab *ctab, struct avtab_key *key, struct av_decision *avd); + +int evaluate_cond_node(struct policydb *p, struct cond_node *node); + +#endif /* _CONDITIONAL_H_ */ diff --git a/security/selinux/ss/constraint.h b/security/selinux/ss/constraint.h new file mode 100644 index 00000000..149dda73 --- /dev/null +++ b/security/selinux/ss/constraint.h @@ -0,0 +1,61 @@ +/* + * A constraint is a condition that must be satisfied in + * order for one or more permissions to be granted. + * Constraints are used to impose additional restrictions + * beyond the type-based rules in `te' or the role-based + * transition rules in `rbac'. Constraints are typically + * used to prevent a process from transitioning to a new user + * identity or role unless it is in a privileged type. + * Constraints are likewise typically used to prevent a + * process from labeling an object with a different user + * identity. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SS_CONSTRAINT_H_ +#define _SS_CONSTRAINT_H_ + +#include "ebitmap.h" + +#define CEXPR_MAXDEPTH 5 + +struct constraint_expr { +#define CEXPR_NOT 1 /* not expr */ +#define CEXPR_AND 2 /* expr and expr */ +#define CEXPR_OR 3 /* expr or expr */ +#define CEXPR_ATTR 4 /* attr op attr */ +#define CEXPR_NAMES 5 /* attr op names */ + u32 expr_type; /* expression type */ + +#define CEXPR_USER 1 /* user */ +#define CEXPR_ROLE 2 /* role */ +#define CEXPR_TYPE 4 /* type */ +#define CEXPR_TARGET 8 /* target if set, source otherwise */ +#define CEXPR_XTARGET 16 /* special 3rd target for validatetrans rule */ +#define CEXPR_L1L2 32 /* low level 1 vs. low level 2 */ +#define CEXPR_L1H2 64 /* low level 1 vs. high level 2 */ +#define CEXPR_H1L2 128 /* high level 1 vs. low level 2 */ +#define CEXPR_H1H2 256 /* high level 1 vs. high level 2 */ +#define CEXPR_L1H1 512 /* low level 1 vs. high level 1 */ +#define CEXPR_L2H2 1024 /* low level 2 vs. high level 2 */ + u32 attr; /* attribute */ + +#define CEXPR_EQ 1 /* == or eq */ +#define CEXPR_NEQ 2 /* != */ +#define CEXPR_DOM 3 /* dom */ +#define CEXPR_DOMBY 4 /* domby */ +#define CEXPR_INCOMP 5 /* incomp */ + u32 op; /* operator */ + + struct ebitmap names; /* names */ + + struct constraint_expr *next; /* next expression */ +}; + +struct constraint_node { + u32 permissions; /* constrained permissions */ + struct constraint_expr *expr; /* constraint on permissions */ + struct constraint_node *next; /* next constraint */ +}; + +#endif /* _SS_CONSTRAINT_H_ */ diff --git a/security/selinux/ss/context.h b/security/selinux/ss/context.h new file mode 100644 index 00000000..45e8fb05 --- /dev/null +++ b/security/selinux/ss/context.h @@ -0,0 +1,143 @@ +/* + * A security context is a set of security attributes + * associated with each subject and object controlled + * by the security policy. Security contexts are + * externally represented as variable-length strings + * that can be interpreted by a user or application + * with an understanding of the security policy. + * Internally, the security server uses a simple + * structure. This structure is private to the + * security server and can be changed without affecting + * clients of the security server. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SS_CONTEXT_H_ +#define _SS_CONTEXT_H_ + +#include "ebitmap.h" +#include "mls_types.h" +#include "security.h" + +/* + * A security context consists of an authenticated user + * identity, a role, a type and a MLS range. + */ +struct context { + u32 user; + u32 role; + u32 type; + u32 len; /* length of string in bytes */ + struct mls_range range; + char *str; /* string representation if context cannot be mapped. */ +}; + +static inline void mls_context_init(struct context *c) +{ + memset(&c->range, 0, sizeof(c->range)); +} + +static inline int mls_context_cpy(struct context *dst, struct context *src) +{ + int rc; + + dst->range.level[0].sens = src->range.level[0].sens; + rc = ebitmap_cpy(&dst->range.level[0].cat, &src->range.level[0].cat); + if (rc) + goto out; + + dst->range.level[1].sens = src->range.level[1].sens; + rc = ebitmap_cpy(&dst->range.level[1].cat, &src->range.level[1].cat); + if (rc) + ebitmap_destroy(&dst->range.level[0].cat); +out: + return rc; +} + +/* + * Sets both levels in the MLS range of 'dst' to the low level of 'src'. + */ +static inline int mls_context_cpy_low(struct context *dst, struct context *src) +{ + int rc; + + dst->range.level[0].sens = src->range.level[0].sens; + rc = ebitmap_cpy(&dst->range.level[0].cat, &src->range.level[0].cat); + if (rc) + goto out; + + dst->range.level[1].sens = src->range.level[0].sens; + rc = ebitmap_cpy(&dst->range.level[1].cat, &src->range.level[0].cat); + if (rc) + ebitmap_destroy(&dst->range.level[0].cat); +out: + return rc; +} + +static inline int mls_context_cmp(struct context *c1, struct context *c2) +{ + return ((c1->range.level[0].sens == c2->range.level[0].sens) && + ebitmap_cmp(&c1->range.level[0].cat, &c2->range.level[0].cat) && + (c1->range.level[1].sens == c2->range.level[1].sens) && + ebitmap_cmp(&c1->range.level[1].cat, &c2->range.level[1].cat)); +} + +static inline void mls_context_destroy(struct context *c) +{ + ebitmap_destroy(&c->range.level[0].cat); + ebitmap_destroy(&c->range.level[1].cat); + mls_context_init(c); +} + +static inline void context_init(struct context *c) +{ + memset(c, 0, sizeof(*c)); +} + +static inline int context_cpy(struct context *dst, struct context *src) +{ + int rc; + + dst->user = src->user; + dst->role = src->role; + dst->type = src->type; + if (src->str) { + dst->str = kstrdup(src->str, GFP_ATOMIC); + if (!dst->str) + return -ENOMEM; + dst->len = src->len; + } else { + dst->str = NULL; + dst->len = 0; + } + rc = mls_context_cpy(dst, src); + if (rc) { + kfree(dst->str); + return rc; + } + return 0; +} + +static inline void context_destroy(struct context *c) +{ + c->user = c->role = c->type = 0; + kfree(c->str); + c->str = NULL; + c->len = 0; + mls_context_destroy(c); +} + +static inline int context_cmp(struct context *c1, struct context *c2) +{ + if (c1->len && c2->len) + return (c1->len == c2->len && !strcmp(c1->str, c2->str)); + if (c1->len || c2->len) + return 0; + return ((c1->user == c2->user) && + (c1->role == c2->role) && + (c1->type == c2->type) && + mls_context_cmp(c1, c2)); +} + +#endif /* _SS_CONTEXT_H_ */ + diff --git a/security/selinux/ss/ebitmap.c b/security/selinux/ss/ebitmap.c new file mode 100644 index 00000000..d42951fc --- /dev/null +++ b/security/selinux/ss/ebitmap.c @@ -0,0 +1,525 @@ +/* + * Implementation of the extensible bitmap type. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +/* + * Updated: Hewlett-Packard <paul.moore@hp.com> + * + * Added support to import/export the NetLabel category bitmap + * + * (c) Copyright Hewlett-Packard Development Company, L.P., 2006 + */ +/* + * Updated: KaiGai Kohei <kaigai@ak.jp.nec.com> + * Applied standard bit operations to improve bitmap scanning. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <net/netlabel.h> +#include "ebitmap.h" +#include "policydb.h" + +#define BITS_PER_U64 (sizeof(u64) * 8) + +int ebitmap_cmp(struct ebitmap *e1, struct ebitmap *e2) +{ + struct ebitmap_node *n1, *n2; + + if (e1->highbit != e2->highbit) + return 0; + + n1 = e1->node; + n2 = e2->node; + while (n1 && n2 && + (n1->startbit == n2->startbit) && + !memcmp(n1->maps, n2->maps, EBITMAP_SIZE / 8)) { + n1 = n1->next; + n2 = n2->next; + } + + if (n1 || n2) + return 0; + + return 1; +} + +int ebitmap_cpy(struct ebitmap *dst, struct ebitmap *src) +{ + struct ebitmap_node *n, *new, *prev; + + ebitmap_init(dst); + n = src->node; + prev = NULL; + while (n) { + new = kzalloc(sizeof(*new), GFP_ATOMIC); + if (!new) { + ebitmap_destroy(dst); + return -ENOMEM; + } + new->startbit = n->startbit; + memcpy(new->maps, n->maps, EBITMAP_SIZE / 8); + new->next = NULL; + if (prev) + prev->next = new; + else + dst->node = new; + prev = new; + n = n->next; + } + + dst->highbit = src->highbit; + return 0; +} + +#ifdef CONFIG_NETLABEL +/** + * ebitmap_netlbl_export - Export an ebitmap into a NetLabel category bitmap + * @ebmap: the ebitmap to export + * @catmap: the NetLabel category bitmap + * + * Description: + * Export a SELinux extensibile bitmap into a NetLabel category bitmap. + * Returns zero on success, negative values on error. + * + */ +int ebitmap_netlbl_export(struct ebitmap *ebmap, + struct netlbl_lsm_secattr_catmap **catmap) +{ + struct ebitmap_node *e_iter = ebmap->node; + struct netlbl_lsm_secattr_catmap *c_iter; + u32 cmap_idx, cmap_sft; + int i; + + /* NetLabel's NETLBL_CATMAP_MAPTYPE is defined as an array of u64, + * however, it is not always compatible with an array of unsigned long + * in ebitmap_node. + * In addition, you should pay attention the following implementation + * assumes unsigned long has a width equal with or less than 64-bit. + */ + + if (e_iter == NULL) { + *catmap = NULL; + return 0; + } + + c_iter = netlbl_secattr_catmap_alloc(GFP_ATOMIC); + if (c_iter == NULL) + return -ENOMEM; + *catmap = c_iter; + c_iter->startbit = e_iter->startbit & ~(NETLBL_CATMAP_SIZE - 1); + + while (e_iter) { + for (i = 0; i < EBITMAP_UNIT_NUMS; i++) { + unsigned int delta, e_startbit, c_endbit; + + e_startbit = e_iter->startbit + i * EBITMAP_UNIT_SIZE; + c_endbit = c_iter->startbit + NETLBL_CATMAP_SIZE; + if (e_startbit >= c_endbit) { + c_iter->next + = netlbl_secattr_catmap_alloc(GFP_ATOMIC); + if (c_iter->next == NULL) + goto netlbl_export_failure; + c_iter = c_iter->next; + c_iter->startbit + = e_startbit & ~(NETLBL_CATMAP_SIZE - 1); + } + delta = e_startbit - c_iter->startbit; + cmap_idx = delta / NETLBL_CATMAP_MAPSIZE; + cmap_sft = delta % NETLBL_CATMAP_MAPSIZE; + c_iter->bitmap[cmap_idx] + |= e_iter->maps[i] << cmap_sft; + } + e_iter = e_iter->next; + } + + return 0; + +netlbl_export_failure: + netlbl_secattr_catmap_free(*catmap); + return -ENOMEM; +} + +/** + * ebitmap_netlbl_import - Import a NetLabel category bitmap into an ebitmap + * @ebmap: the ebitmap to import + * @catmap: the NetLabel category bitmap + * + * Description: + * Import a NetLabel category bitmap into a SELinux extensibile bitmap. + * Returns zero on success, negative values on error. + * + */ +int ebitmap_netlbl_import(struct ebitmap *ebmap, + struct netlbl_lsm_secattr_catmap *catmap) +{ + struct ebitmap_node *e_iter = NULL; + struct ebitmap_node *emap_prev = NULL; + struct netlbl_lsm_secattr_catmap *c_iter = catmap; + u32 c_idx, c_pos, e_idx, e_sft; + + /* NetLabel's NETLBL_CATMAP_MAPTYPE is defined as an array of u64, + * however, it is not always compatible with an array of unsigned long + * in ebitmap_node. + * In addition, you should pay attention the following implementation + * assumes unsigned long has a width equal with or less than 64-bit. + */ + + do { + for (c_idx = 0; c_idx < NETLBL_CATMAP_MAPCNT; c_idx++) { + unsigned int delta; + u64 map = c_iter->bitmap[c_idx]; + + if (!map) + continue; + + c_pos = c_iter->startbit + + c_idx * NETLBL_CATMAP_MAPSIZE; + if (!e_iter + || c_pos >= e_iter->startbit + EBITMAP_SIZE) { + e_iter = kzalloc(sizeof(*e_iter), GFP_ATOMIC); + if (!e_iter) + goto netlbl_import_failure; + e_iter->startbit + = c_pos - (c_pos % EBITMAP_SIZE); + if (emap_prev == NULL) + ebmap->node = e_iter; + else + emap_prev->next = e_iter; + emap_prev = e_iter; + } + delta = c_pos - e_iter->startbit; + e_idx = delta / EBITMAP_UNIT_SIZE; + e_sft = delta % EBITMAP_UNIT_SIZE; + while (map) { + e_iter->maps[e_idx++] |= map & (-1UL); + map = EBITMAP_SHIFT_UNIT_SIZE(map); + } + } + c_iter = c_iter->next; + } while (c_iter); + if (e_iter != NULL) + ebmap->highbit = e_iter->startbit + EBITMAP_SIZE; + else + ebitmap_destroy(ebmap); + + return 0; + +netlbl_import_failure: + ebitmap_destroy(ebmap); + return -ENOMEM; +} +#endif /* CONFIG_NETLABEL */ + +int ebitmap_contains(struct ebitmap *e1, struct ebitmap *e2) +{ + struct ebitmap_node *n1, *n2; + int i; + + if (e1->highbit < e2->highbit) + return 0; + + n1 = e1->node; + n2 = e2->node; + while (n1 && n2 && (n1->startbit <= n2->startbit)) { + if (n1->startbit < n2->startbit) { + n1 = n1->next; + continue; + } + for (i = 0; i < EBITMAP_UNIT_NUMS; i++) { + if ((n1->maps[i] & n2->maps[i]) != n2->maps[i]) + return 0; + } + + n1 = n1->next; + n2 = n2->next; + } + + if (n2) + return 0; + + return 1; +} + +int ebitmap_get_bit(struct ebitmap *e, unsigned long bit) +{ + struct ebitmap_node *n; + + if (e->highbit < bit) + return 0; + + n = e->node; + while (n && (n->startbit <= bit)) { + if ((n->startbit + EBITMAP_SIZE) > bit) + return ebitmap_node_get_bit(n, bit); + n = n->next; + } + + return 0; +} + +int ebitmap_set_bit(struct ebitmap *e, unsigned long bit, int value) +{ + struct ebitmap_node *n, *prev, *new; + + prev = NULL; + n = e->node; + while (n && n->startbit <= bit) { + if ((n->startbit + EBITMAP_SIZE) > bit) { + if (value) { + ebitmap_node_set_bit(n, bit); + } else { + unsigned int s; + + ebitmap_node_clr_bit(n, bit); + + s = find_first_bit(n->maps, EBITMAP_SIZE); + if (s < EBITMAP_SIZE) + return 0; + + /* drop this node from the bitmap */ + if (!n->next) { + /* + * this was the highest map + * within the bitmap + */ + if (prev) + e->highbit = prev->startbit + + EBITMAP_SIZE; + else + e->highbit = 0; + } + if (prev) + prev->next = n->next; + else + e->node = n->next; + kfree(n); + } + return 0; + } + prev = n; + n = n->next; + } + + if (!value) + return 0; + + new = kzalloc(sizeof(*new), GFP_ATOMIC); + if (!new) + return -ENOMEM; + + new->startbit = bit - (bit % EBITMAP_SIZE); + ebitmap_node_set_bit(new, bit); + + if (!n) + /* this node will be the highest map within the bitmap */ + e->highbit = new->startbit + EBITMAP_SIZE; + + if (prev) { + new->next = prev->next; + prev->next = new; + } else { + new->next = e->node; + e->node = new; + } + + return 0; +} + +void ebitmap_destroy(struct ebitmap *e) +{ + struct ebitmap_node *n, *temp; + + if (!e) + return; + + n = e->node; + while (n) { + temp = n; + n = n->next; + kfree(temp); + } + + e->highbit = 0; + e->node = NULL; + return; +} + +int ebitmap_read(struct ebitmap *e, void *fp) +{ + struct ebitmap_node *n = NULL; + u32 mapunit, count, startbit, index; + u64 map; + __le32 buf[3]; + int rc, i; + + ebitmap_init(e); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + goto out; + + mapunit = le32_to_cpu(buf[0]); + e->highbit = le32_to_cpu(buf[1]); + count = le32_to_cpu(buf[2]); + + if (mapunit != BITS_PER_U64) { + printk(KERN_ERR "SELinux: ebitmap: map size %u does not " + "match my size %Zd (high bit was %d)\n", + mapunit, BITS_PER_U64, e->highbit); + goto bad; + } + + /* round up e->highbit */ + e->highbit += EBITMAP_SIZE - 1; + e->highbit -= (e->highbit % EBITMAP_SIZE); + + if (!e->highbit) { + e->node = NULL; + goto ok; + } + + for (i = 0; i < count; i++) { + rc = next_entry(&startbit, fp, sizeof(u32)); + if (rc < 0) { + printk(KERN_ERR "SELinux: ebitmap: truncated map\n"); + goto bad; + } + startbit = le32_to_cpu(startbit); + + if (startbit & (mapunit - 1)) { + printk(KERN_ERR "SELinux: ebitmap start bit (%d) is " + "not a multiple of the map unit size (%u)\n", + startbit, mapunit); + goto bad; + } + if (startbit > e->highbit - mapunit) { + printk(KERN_ERR "SELinux: ebitmap start bit (%d) is " + "beyond the end of the bitmap (%u)\n", + startbit, (e->highbit - mapunit)); + goto bad; + } + + if (!n || startbit >= n->startbit + EBITMAP_SIZE) { + struct ebitmap_node *tmp; + tmp = kzalloc(sizeof(*tmp), GFP_KERNEL); + if (!tmp) { + printk(KERN_ERR + "SELinux: ebitmap: out of memory\n"); + rc = -ENOMEM; + goto bad; + } + /* round down */ + tmp->startbit = startbit - (startbit % EBITMAP_SIZE); + if (n) + n->next = tmp; + else + e->node = tmp; + n = tmp; + } else if (startbit <= n->startbit) { + printk(KERN_ERR "SELinux: ebitmap: start bit %d" + " comes after start bit %d\n", + startbit, n->startbit); + goto bad; + } + + rc = next_entry(&map, fp, sizeof(u64)); + if (rc < 0) { + printk(KERN_ERR "SELinux: ebitmap: truncated map\n"); + goto bad; + } + map = le64_to_cpu(map); + + index = (startbit - n->startbit) / EBITMAP_UNIT_SIZE; + while (map) { + n->maps[index++] = map & (-1UL); + map = EBITMAP_SHIFT_UNIT_SIZE(map); + } + } +ok: + rc = 0; +out: + return rc; +bad: + if (!rc) + rc = -EINVAL; + ebitmap_destroy(e); + goto out; +} + +int ebitmap_write(struct ebitmap *e, void *fp) +{ + struct ebitmap_node *n; + u32 count; + __le32 buf[3]; + u64 map; + int bit, last_bit, last_startbit, rc; + + buf[0] = cpu_to_le32(BITS_PER_U64); + + count = 0; + last_bit = 0; + last_startbit = -1; + ebitmap_for_each_positive_bit(e, n, bit) { + if (rounddown(bit, (int)BITS_PER_U64) > last_startbit) { + count++; + last_startbit = rounddown(bit, BITS_PER_U64); + } + last_bit = roundup(bit + 1, BITS_PER_U64); + } + buf[1] = cpu_to_le32(last_bit); + buf[2] = cpu_to_le32(count); + + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + + map = 0; + last_startbit = INT_MIN; + ebitmap_for_each_positive_bit(e, n, bit) { + if (rounddown(bit, (int)BITS_PER_U64) > last_startbit) { + __le64 buf64[1]; + + /* this is the very first bit */ + if (!map) { + last_startbit = rounddown(bit, BITS_PER_U64); + map = (u64)1 << (bit - last_startbit); + continue; + } + + /* write the last node */ + buf[0] = cpu_to_le32(last_startbit); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + buf64[0] = cpu_to_le64(map); + rc = put_entry(buf64, sizeof(u64), 1, fp); + if (rc) + return rc; + + /* set up for the next node */ + map = 0; + last_startbit = rounddown(bit, BITS_PER_U64); + } + map |= (u64)1 << (bit - last_startbit); + } + /* write the last node */ + if (map) { + __le64 buf64[1]; + + /* write the last node */ + buf[0] = cpu_to_le32(last_startbit); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + buf64[0] = cpu_to_le64(map); + rc = put_entry(buf64, sizeof(u64), 1, fp); + if (rc) + return rc; + } + return 0; +} diff --git a/security/selinux/ss/ebitmap.h b/security/selinux/ss/ebitmap.h new file mode 100644 index 00000000..922f8afa --- /dev/null +++ b/security/selinux/ss/ebitmap.h @@ -0,0 +1,145 @@ +/* + * An extensible bitmap is a bitmap that supports an + * arbitrary number of bits. Extensible bitmaps are + * used to represent sets of values, such as types, + * roles, categories, and classes. + * + * Each extensible bitmap is implemented as a linked + * list of bitmap nodes, where each bitmap node has + * an explicitly specified starting bit position within + * the total bitmap. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SS_EBITMAP_H_ +#define _SS_EBITMAP_H_ + +#include <net/netlabel.h> + +#define EBITMAP_UNIT_NUMS ((32 - sizeof(void *) - sizeof(u32)) \ + / sizeof(unsigned long)) +#define EBITMAP_UNIT_SIZE BITS_PER_LONG +#define EBITMAP_SIZE (EBITMAP_UNIT_NUMS * EBITMAP_UNIT_SIZE) +#define EBITMAP_BIT 1ULL +#define EBITMAP_SHIFT_UNIT_SIZE(x) \ + (((x) >> EBITMAP_UNIT_SIZE / 2) >> EBITMAP_UNIT_SIZE / 2) + +struct ebitmap_node { + struct ebitmap_node *next; + unsigned long maps[EBITMAP_UNIT_NUMS]; + u32 startbit; +}; + +struct ebitmap { + struct ebitmap_node *node; /* first node in the bitmap */ + u32 highbit; /* highest position in the total bitmap */ +}; + +#define ebitmap_length(e) ((e)->highbit) + +static inline unsigned int ebitmap_start_positive(struct ebitmap *e, + struct ebitmap_node **n) +{ + unsigned int ofs; + + for (*n = e->node; *n; *n = (*n)->next) { + ofs = find_first_bit((*n)->maps, EBITMAP_SIZE); + if (ofs < EBITMAP_SIZE) + return (*n)->startbit + ofs; + } + return ebitmap_length(e); +} + +static inline void ebitmap_init(struct ebitmap *e) +{ + memset(e, 0, sizeof(*e)); +} + +static inline unsigned int ebitmap_next_positive(struct ebitmap *e, + struct ebitmap_node **n, + unsigned int bit) +{ + unsigned int ofs; + + ofs = find_next_bit((*n)->maps, EBITMAP_SIZE, bit - (*n)->startbit + 1); + if (ofs < EBITMAP_SIZE) + return ofs + (*n)->startbit; + + for (*n = (*n)->next; *n; *n = (*n)->next) { + ofs = find_first_bit((*n)->maps, EBITMAP_SIZE); + if (ofs < EBITMAP_SIZE) + return ofs + (*n)->startbit; + } + return ebitmap_length(e); +} + +#define EBITMAP_NODE_INDEX(node, bit) \ + (((bit) - (node)->startbit) / EBITMAP_UNIT_SIZE) +#define EBITMAP_NODE_OFFSET(node, bit) \ + (((bit) - (node)->startbit) % EBITMAP_UNIT_SIZE) + +static inline int ebitmap_node_get_bit(struct ebitmap_node *n, + unsigned int bit) +{ + unsigned int index = EBITMAP_NODE_INDEX(n, bit); + unsigned int ofs = EBITMAP_NODE_OFFSET(n, bit); + + BUG_ON(index >= EBITMAP_UNIT_NUMS); + if ((n->maps[index] & (EBITMAP_BIT << ofs))) + return 1; + return 0; +} + +static inline void ebitmap_node_set_bit(struct ebitmap_node *n, + unsigned int bit) +{ + unsigned int index = EBITMAP_NODE_INDEX(n, bit); + unsigned int ofs = EBITMAP_NODE_OFFSET(n, bit); + + BUG_ON(index >= EBITMAP_UNIT_NUMS); + n->maps[index] |= (EBITMAP_BIT << ofs); +} + +static inline void ebitmap_node_clr_bit(struct ebitmap_node *n, + unsigned int bit) +{ + unsigned int index = EBITMAP_NODE_INDEX(n, bit); + unsigned int ofs = EBITMAP_NODE_OFFSET(n, bit); + + BUG_ON(index >= EBITMAP_UNIT_NUMS); + n->maps[index] &= ~(EBITMAP_BIT << ofs); +} + +#define ebitmap_for_each_positive_bit(e, n, bit) \ + for (bit = ebitmap_start_positive(e, &n); \ + bit < ebitmap_length(e); \ + bit = ebitmap_next_positive(e, &n, bit)) \ + +int ebitmap_cmp(struct ebitmap *e1, struct ebitmap *e2); +int ebitmap_cpy(struct ebitmap *dst, struct ebitmap *src); +int ebitmap_contains(struct ebitmap *e1, struct ebitmap *e2); +int ebitmap_get_bit(struct ebitmap *e, unsigned long bit); +int ebitmap_set_bit(struct ebitmap *e, unsigned long bit, int value); +void ebitmap_destroy(struct ebitmap *e); +int ebitmap_read(struct ebitmap *e, void *fp); +int ebitmap_write(struct ebitmap *e, void *fp); + +#ifdef CONFIG_NETLABEL +int ebitmap_netlbl_export(struct ebitmap *ebmap, + struct netlbl_lsm_secattr_catmap **catmap); +int ebitmap_netlbl_import(struct ebitmap *ebmap, + struct netlbl_lsm_secattr_catmap *catmap); +#else +static inline int ebitmap_netlbl_export(struct ebitmap *ebmap, + struct netlbl_lsm_secattr_catmap **catmap) +{ + return -ENOMEM; +} +static inline int ebitmap_netlbl_import(struct ebitmap *ebmap, + struct netlbl_lsm_secattr_catmap *catmap) +{ + return -ENOMEM; +} +#endif + +#endif /* _SS_EBITMAP_H_ */ diff --git a/security/selinux/ss/hashtab.c b/security/selinux/ss/hashtab.c new file mode 100644 index 00000000..933e735b --- /dev/null +++ b/security/selinux/ss/hashtab.c @@ -0,0 +1,165 @@ +/* + * Implementation of the hash table type. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include "hashtab.h" + +struct hashtab *hashtab_create(u32 (*hash_value)(struct hashtab *h, const void *key), + int (*keycmp)(struct hashtab *h, const void *key1, const void *key2), + u32 size) +{ + struct hashtab *p; + u32 i; + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (p == NULL) + return p; + + p->size = size; + p->nel = 0; + p->hash_value = hash_value; + p->keycmp = keycmp; + p->htable = kmalloc(sizeof(*(p->htable)) * size, GFP_KERNEL); + if (p->htable == NULL) { + kfree(p); + return NULL; + } + + for (i = 0; i < size; i++) + p->htable[i] = NULL; + + return p; +} + +int hashtab_insert(struct hashtab *h, void *key, void *datum) +{ + u32 hvalue; + struct hashtab_node *prev, *cur, *newnode; + + if (!h || h->nel == HASHTAB_MAX_NODES) + return -EINVAL; + + hvalue = h->hash_value(h, key); + prev = NULL; + cur = h->htable[hvalue]; + while (cur && h->keycmp(h, key, cur->key) > 0) { + prev = cur; + cur = cur->next; + } + + if (cur && (h->keycmp(h, key, cur->key) == 0)) + return -EEXIST; + + newnode = kzalloc(sizeof(*newnode), GFP_KERNEL); + if (newnode == NULL) + return -ENOMEM; + newnode->key = key; + newnode->datum = datum; + if (prev) { + newnode->next = prev->next; + prev->next = newnode; + } else { + newnode->next = h->htable[hvalue]; + h->htable[hvalue] = newnode; + } + + h->nel++; + return 0; +} + +void *hashtab_search(struct hashtab *h, const void *key) +{ + u32 hvalue; + struct hashtab_node *cur; + + if (!h) + return NULL; + + hvalue = h->hash_value(h, key); + cur = h->htable[hvalue]; + while (cur && h->keycmp(h, key, cur->key) > 0) + cur = cur->next; + + if (cur == NULL || (h->keycmp(h, key, cur->key) != 0)) + return NULL; + + return cur->datum; +} + +void hashtab_destroy(struct hashtab *h) +{ + u32 i; + struct hashtab_node *cur, *temp; + + if (!h) + return; + + for (i = 0; i < h->size; i++) { + cur = h->htable[i]; + while (cur) { + temp = cur; + cur = cur->next; + kfree(temp); + } + h->htable[i] = NULL; + } + + kfree(h->htable); + h->htable = NULL; + + kfree(h); +} + +int hashtab_map(struct hashtab *h, + int (*apply)(void *k, void *d, void *args), + void *args) +{ + u32 i; + int ret; + struct hashtab_node *cur; + + if (!h) + return 0; + + for (i = 0; i < h->size; i++) { + cur = h->htable[i]; + while (cur) { + ret = apply(cur->key, cur->datum, args); + if (ret) + return ret; + cur = cur->next; + } + } + return 0; +} + + +void hashtab_stat(struct hashtab *h, struct hashtab_info *info) +{ + u32 i, chain_len, slots_used, max_chain_len; + struct hashtab_node *cur; + + slots_used = 0; + max_chain_len = 0; + for (slots_used = max_chain_len = i = 0; i < h->size; i++) { + cur = h->htable[i]; + if (cur) { + slots_used++; + chain_len = 0; + while (cur) { + chain_len++; + cur = cur->next; + } + + if (chain_len > max_chain_len) + max_chain_len = chain_len; + } + } + + info->slots_used = slots_used; + info->max_chain_len = max_chain_len; +} diff --git a/security/selinux/ss/hashtab.h b/security/selinux/ss/hashtab.h new file mode 100644 index 00000000..953872cd --- /dev/null +++ b/security/selinux/ss/hashtab.h @@ -0,0 +1,87 @@ +/* + * A hash table (hashtab) maintains associations between + * key values and datum values. The type of the key values + * and the type of the datum values is arbitrary. The + * functions for hash computation and key comparison are + * provided by the creator of the table. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SS_HASHTAB_H_ +#define _SS_HASHTAB_H_ + +#define HASHTAB_MAX_NODES 0xffffffff + +struct hashtab_node { + void *key; + void *datum; + struct hashtab_node *next; +}; + +struct hashtab { + struct hashtab_node **htable; /* hash table */ + u32 size; /* number of slots in hash table */ + u32 nel; /* number of elements in hash table */ + u32 (*hash_value)(struct hashtab *h, const void *key); + /* hash function */ + int (*keycmp)(struct hashtab *h, const void *key1, const void *key2); + /* key comparison function */ +}; + +struct hashtab_info { + u32 slots_used; + u32 max_chain_len; +}; + +/* + * Creates a new hash table with the specified characteristics. + * + * Returns NULL if insufficent space is available or + * the new hash table otherwise. + */ +struct hashtab *hashtab_create(u32 (*hash_value)(struct hashtab *h, const void *key), + int (*keycmp)(struct hashtab *h, const void *key1, const void *key2), + u32 size); + +/* + * Inserts the specified (key, datum) pair into the specified hash table. + * + * Returns -ENOMEM on memory allocation error, + * -EEXIST if there is already an entry with the same key, + * -EINVAL for general errors or + 0 otherwise. + */ +int hashtab_insert(struct hashtab *h, void *k, void *d); + +/* + * Searches for the entry with the specified key in the hash table. + * + * Returns NULL if no entry has the specified key or + * the datum of the entry otherwise. + */ +void *hashtab_search(struct hashtab *h, const void *k); + +/* + * Destroys the specified hash table. + */ +void hashtab_destroy(struct hashtab *h); + +/* + * Applies the specified apply function to (key,datum,args) + * for each entry in the specified hash table. + * + * The order in which the function is applied to the entries + * is dependent upon the internal structure of the hash table. + * + * If apply returns a non-zero status, then hashtab_map will cease + * iterating through the hash table and will propagate the error + * return to its caller. + */ +int hashtab_map(struct hashtab *h, + int (*apply)(void *k, void *d, void *args), + void *args); + +/* Fill info with some hash table statistics */ +void hashtab_stat(struct hashtab *h, struct hashtab_info *info); + +#endif /* _SS_HASHTAB_H */ diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c new file mode 100644 index 00000000..e9617421 --- /dev/null +++ b/security/selinux/ss/mls.c @@ -0,0 +1,654 @@ +/* + * Implementation of the multi-level security (MLS) policy. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Copyright (C) 2004-2006 Trusted Computer Solutions, Inc. + */ +/* + * Updated: Hewlett-Packard <paul.moore@hp.com> + * + * Added support to import/export the MLS label from NetLabel + * + * (c) Copyright Hewlett-Packard Development Company, L.P., 2006 + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <net/netlabel.h> +#include "sidtab.h" +#include "mls.h" +#include "policydb.h" +#include "services.h" + +/* + * Return the length in bytes for the MLS fields of the + * security context string representation of `context'. + */ +int mls_compute_context_len(struct context *context) +{ + int i, l, len, head, prev; + char *nm; + struct ebitmap *e; + struct ebitmap_node *node; + + if (!policydb.mls_enabled) + return 0; + + len = 1; /* for the beginning ":" */ + for (l = 0; l < 2; l++) { + int index_sens = context->range.level[l].sens; + len += strlen(sym_name(&policydb, SYM_LEVELS, index_sens - 1)); + + /* categories */ + head = -2; + prev = -2; + e = &context->range.level[l].cat; + ebitmap_for_each_positive_bit(e, node, i) { + if (i - prev > 1) { + /* one or more negative bits are skipped */ + if (head != prev) { + nm = sym_name(&policydb, SYM_CATS, prev); + len += strlen(nm) + 1; + } + nm = sym_name(&policydb, SYM_CATS, i); + len += strlen(nm) + 1; + head = i; + } + prev = i; + } + if (prev != head) { + nm = sym_name(&policydb, SYM_CATS, prev); + len += strlen(nm) + 1; + } + if (l == 0) { + if (mls_level_eq(&context->range.level[0], + &context->range.level[1])) + break; + else + len++; + } + } + + return len; +} + +/* + * Write the security context string representation of + * the MLS fields of `context' into the string `*scontext'. + * Update `*scontext' to point to the end of the MLS fields. + */ +void mls_sid_to_context(struct context *context, + char **scontext) +{ + char *scontextp, *nm; + int i, l, head, prev; + struct ebitmap *e; + struct ebitmap_node *node; + + if (!policydb.mls_enabled) + return; + + scontextp = *scontext; + + *scontextp = ':'; + scontextp++; + + for (l = 0; l < 2; l++) { + strcpy(scontextp, sym_name(&policydb, SYM_LEVELS, + context->range.level[l].sens - 1)); + scontextp += strlen(scontextp); + + /* categories */ + head = -2; + prev = -2; + e = &context->range.level[l].cat; + ebitmap_for_each_positive_bit(e, node, i) { + if (i - prev > 1) { + /* one or more negative bits are skipped */ + if (prev != head) { + if (prev - head > 1) + *scontextp++ = '.'; + else + *scontextp++ = ','; + nm = sym_name(&policydb, SYM_CATS, prev); + strcpy(scontextp, nm); + scontextp += strlen(nm); + } + if (prev < 0) + *scontextp++ = ':'; + else + *scontextp++ = ','; + nm = sym_name(&policydb, SYM_CATS, i); + strcpy(scontextp, nm); + scontextp += strlen(nm); + head = i; + } + prev = i; + } + + if (prev != head) { + if (prev - head > 1) + *scontextp++ = '.'; + else + *scontextp++ = ','; + nm = sym_name(&policydb, SYM_CATS, prev); + strcpy(scontextp, nm); + scontextp += strlen(nm); + } + + if (l == 0) { + if (mls_level_eq(&context->range.level[0], + &context->range.level[1])) + break; + else + *scontextp++ = '-'; + } + } + + *scontext = scontextp; + return; +} + +int mls_level_isvalid(struct policydb *p, struct mls_level *l) +{ + struct level_datum *levdatum; + struct ebitmap_node *node; + int i; + + if (!l->sens || l->sens > p->p_levels.nprim) + return 0; + levdatum = hashtab_search(p->p_levels.table, + sym_name(p, SYM_LEVELS, l->sens - 1)); + if (!levdatum) + return 0; + + ebitmap_for_each_positive_bit(&l->cat, node, i) { + if (i > p->p_cats.nprim) + return 0; + if (!ebitmap_get_bit(&levdatum->level->cat, i)) { + /* + * Category may not be associated with + * sensitivity. + */ + return 0; + } + } + + return 1; +} + +int mls_range_isvalid(struct policydb *p, struct mls_range *r) +{ + return (mls_level_isvalid(p, &r->level[0]) && + mls_level_isvalid(p, &r->level[1]) && + mls_level_dom(&r->level[1], &r->level[0])); +} + +/* + * Return 1 if the MLS fields in the security context + * structure `c' are valid. Return 0 otherwise. + */ +int mls_context_isvalid(struct policydb *p, struct context *c) +{ + struct user_datum *usrdatum; + + if (!p->mls_enabled) + return 1; + + if (!mls_range_isvalid(p, &c->range)) + return 0; + + if (c->role == OBJECT_R_VAL) + return 1; + + /* + * User must be authorized for the MLS range. + */ + if (!c->user || c->user > p->p_users.nprim) + return 0; + usrdatum = p->user_val_to_struct[c->user - 1]; + if (!mls_range_contains(usrdatum->range, c->range)) + return 0; /* user may not be associated with range */ + + return 1; +} + +/* + * Set the MLS fields in the security context structure + * `context' based on the string representation in + * the string `*scontext'. Update `*scontext' to + * point to the end of the string representation of + * the MLS fields. + * + * This function modifies the string in place, inserting + * NULL characters to terminate the MLS fields. + * + * If a def_sid is provided and no MLS field is present, + * copy the MLS field of the associated default context. + * Used for upgraded to MLS systems where objects may lack + * MLS fields. + * + * Policy read-lock must be held for sidtab lookup. + * + */ +int mls_context_to_sid(struct policydb *pol, + char oldc, + char **scontext, + struct context *context, + struct sidtab *s, + u32 def_sid) +{ + + char delim; + char *scontextp, *p, *rngptr; + struct level_datum *levdatum; + struct cat_datum *catdatum, *rngdatum; + int l, rc = -EINVAL; + + if (!pol->mls_enabled) { + if (def_sid != SECSID_NULL && oldc) + *scontext += strlen(*scontext) + 1; + return 0; + } + + /* + * No MLS component to the security context, try and map to + * default if provided. + */ + if (!oldc) { + struct context *defcon; + + if (def_sid == SECSID_NULL) + goto out; + + defcon = sidtab_search(s, def_sid); + if (!defcon) + goto out; + + rc = mls_context_cpy(context, defcon); + goto out; + } + + /* Extract low sensitivity. */ + scontextp = p = *scontext; + while (*p && *p != ':' && *p != '-') + p++; + + delim = *p; + if (delim != '\0') + *p++ = '\0'; + + for (l = 0; l < 2; l++) { + levdatum = hashtab_search(pol->p_levels.table, scontextp); + if (!levdatum) { + rc = -EINVAL; + goto out; + } + + context->range.level[l].sens = levdatum->level->sens; + + if (delim == ':') { + /* Extract category set. */ + while (1) { + scontextp = p; + while (*p && *p != ',' && *p != '-') + p++; + delim = *p; + if (delim != '\0') + *p++ = '\0'; + + /* Separate into range if exists */ + rngptr = strchr(scontextp, '.'); + if (rngptr != NULL) { + /* Remove '.' */ + *rngptr++ = '\0'; + } + + catdatum = hashtab_search(pol->p_cats.table, + scontextp); + if (!catdatum) { + rc = -EINVAL; + goto out; + } + + rc = ebitmap_set_bit(&context->range.level[l].cat, + catdatum->value - 1, 1); + if (rc) + goto out; + + /* If range, set all categories in range */ + if (rngptr) { + int i; + + rngdatum = hashtab_search(pol->p_cats.table, rngptr); + if (!rngdatum) { + rc = -EINVAL; + goto out; + } + + if (catdatum->value >= rngdatum->value) { + rc = -EINVAL; + goto out; + } + + for (i = catdatum->value; i < rngdatum->value; i++) { + rc = ebitmap_set_bit(&context->range.level[l].cat, i, 1); + if (rc) + goto out; + } + } + + if (delim != ',') + break; + } + } + if (delim == '-') { + /* Extract high sensitivity. */ + scontextp = p; + while (*p && *p != ':') + p++; + + delim = *p; + if (delim != '\0') + *p++ = '\0'; + } else + break; + } + + if (l == 0) { + context->range.level[1].sens = context->range.level[0].sens; + rc = ebitmap_cpy(&context->range.level[1].cat, + &context->range.level[0].cat); + if (rc) + goto out; + } + *scontext = ++p; + rc = 0; +out: + return rc; +} + +/* + * Set the MLS fields in the security context structure + * `context' based on the string representation in + * the string `str'. This function will allocate temporary memory with the + * given constraints of gfp_mask. + */ +int mls_from_string(char *str, struct context *context, gfp_t gfp_mask) +{ + char *tmpstr, *freestr; + int rc; + + if (!policydb.mls_enabled) + return -EINVAL; + + /* we need freestr because mls_context_to_sid will change + the value of tmpstr */ + tmpstr = freestr = kstrdup(str, gfp_mask); + if (!tmpstr) { + rc = -ENOMEM; + } else { + rc = mls_context_to_sid(&policydb, ':', &tmpstr, context, + NULL, SECSID_NULL); + kfree(freestr); + } + + return rc; +} + +/* + * Copies the MLS range `range' into `context'. + */ +int mls_range_set(struct context *context, + struct mls_range *range) +{ + int l, rc = 0; + + /* Copy the MLS range into the context */ + for (l = 0; l < 2; l++) { + context->range.level[l].sens = range->level[l].sens; + rc = ebitmap_cpy(&context->range.level[l].cat, + &range->level[l].cat); + if (rc) + break; + } + + return rc; +} + +int mls_setup_user_range(struct context *fromcon, struct user_datum *user, + struct context *usercon) +{ + if (policydb.mls_enabled) { + struct mls_level *fromcon_sen = &(fromcon->range.level[0]); + struct mls_level *fromcon_clr = &(fromcon->range.level[1]); + struct mls_level *user_low = &(user->range.level[0]); + struct mls_level *user_clr = &(user->range.level[1]); + struct mls_level *user_def = &(user->dfltlevel); + struct mls_level *usercon_sen = &(usercon->range.level[0]); + struct mls_level *usercon_clr = &(usercon->range.level[1]); + + /* Honor the user's default level if we can */ + if (mls_level_between(user_def, fromcon_sen, fromcon_clr)) + *usercon_sen = *user_def; + else if (mls_level_between(fromcon_sen, user_def, user_clr)) + *usercon_sen = *fromcon_sen; + else if (mls_level_between(fromcon_clr, user_low, user_def)) + *usercon_sen = *user_low; + else + return -EINVAL; + + /* Lower the clearance of available contexts + if the clearance of "fromcon" is lower than + that of the user's default clearance (but + only if the "fromcon" clearance dominates + the user's computed sensitivity level) */ + if (mls_level_dom(user_clr, fromcon_clr)) + *usercon_clr = *fromcon_clr; + else if (mls_level_dom(fromcon_clr, user_clr)) + *usercon_clr = *user_clr; + else + return -EINVAL; + } + + return 0; +} + +/* + * Convert the MLS fields in the security context + * structure `c' from the values specified in the + * policy `oldp' to the values specified in the policy `newp'. + */ +int mls_convert_context(struct policydb *oldp, + struct policydb *newp, + struct context *c) +{ + struct level_datum *levdatum; + struct cat_datum *catdatum; + struct ebitmap bitmap; + struct ebitmap_node *node; + int l, i; + + if (!policydb.mls_enabled) + return 0; + + for (l = 0; l < 2; l++) { + levdatum = hashtab_search(newp->p_levels.table, + sym_name(oldp, SYM_LEVELS, + c->range.level[l].sens - 1)); + + if (!levdatum) + return -EINVAL; + c->range.level[l].sens = levdatum->level->sens; + + ebitmap_init(&bitmap); + ebitmap_for_each_positive_bit(&c->range.level[l].cat, node, i) { + int rc; + + catdatum = hashtab_search(newp->p_cats.table, + sym_name(oldp, SYM_CATS, i)); + if (!catdatum) + return -EINVAL; + rc = ebitmap_set_bit(&bitmap, catdatum->value - 1, 1); + if (rc) + return rc; + } + ebitmap_destroy(&c->range.level[l].cat); + c->range.level[l].cat = bitmap; + } + + return 0; +} + +int mls_compute_sid(struct context *scontext, + struct context *tcontext, + u16 tclass, + u32 specified, + struct context *newcontext, + bool sock) +{ + struct range_trans rtr; + struct mls_range *r; + + if (!policydb.mls_enabled) + return 0; + + switch (specified) { + case AVTAB_TRANSITION: + /* Look for a range transition rule. */ + rtr.source_type = scontext->type; + rtr.target_type = tcontext->type; + rtr.target_class = tclass; + r = hashtab_search(policydb.range_tr, &rtr); + if (r) + return mls_range_set(newcontext, r); + /* Fallthrough */ + case AVTAB_CHANGE: + if ((tclass == policydb.process_class) || (sock == true)) + /* Use the process MLS attributes. */ + return mls_context_cpy(newcontext, scontext); + else + /* Use the process effective MLS attributes. */ + return mls_context_cpy_low(newcontext, scontext); + case AVTAB_MEMBER: + /* Use the process effective MLS attributes. */ + return mls_context_cpy_low(newcontext, scontext); + + /* fall through */ + } + return -EINVAL; +} + +#ifdef CONFIG_NETLABEL +/** + * mls_export_netlbl_lvl - Export the MLS sensitivity levels to NetLabel + * @context: the security context + * @secattr: the NetLabel security attributes + * + * Description: + * Given the security context copy the low MLS sensitivity level into the + * NetLabel MLS sensitivity level field. + * + */ +void mls_export_netlbl_lvl(struct context *context, + struct netlbl_lsm_secattr *secattr) +{ + if (!policydb.mls_enabled) + return; + + secattr->attr.mls.lvl = context->range.level[0].sens - 1; + secattr->flags |= NETLBL_SECATTR_MLS_LVL; +} + +/** + * mls_import_netlbl_lvl - Import the NetLabel MLS sensitivity levels + * @context: the security context + * @secattr: the NetLabel security attributes + * + * Description: + * Given the security context and the NetLabel security attributes, copy the + * NetLabel MLS sensitivity level into the context. + * + */ +void mls_import_netlbl_lvl(struct context *context, + struct netlbl_lsm_secattr *secattr) +{ + if (!policydb.mls_enabled) + return; + + context->range.level[0].sens = secattr->attr.mls.lvl + 1; + context->range.level[1].sens = context->range.level[0].sens; +} + +/** + * mls_export_netlbl_cat - Export the MLS categories to NetLabel + * @context: the security context + * @secattr: the NetLabel security attributes + * + * Description: + * Given the security context copy the low MLS categories into the NetLabel + * MLS category field. Returns zero on success, negative values on failure. + * + */ +int mls_export_netlbl_cat(struct context *context, + struct netlbl_lsm_secattr *secattr) +{ + int rc; + + if (!policydb.mls_enabled) + return 0; + + rc = ebitmap_netlbl_export(&context->range.level[0].cat, + &secattr->attr.mls.cat); + if (rc == 0 && secattr->attr.mls.cat != NULL) + secattr->flags |= NETLBL_SECATTR_MLS_CAT; + + return rc; +} + +/** + * mls_import_netlbl_cat - Import the MLS categories from NetLabel + * @context: the security context + * @secattr: the NetLabel security attributes + * + * Description: + * Copy the NetLabel security attributes into the SELinux context; since the + * NetLabel security attribute only contains a single MLS category use it for + * both the low and high categories of the context. Returns zero on success, + * negative values on failure. + * + */ +int mls_import_netlbl_cat(struct context *context, + struct netlbl_lsm_secattr *secattr) +{ + int rc; + + if (!policydb.mls_enabled) + return 0; + + rc = ebitmap_netlbl_import(&context->range.level[0].cat, + secattr->attr.mls.cat); + if (rc != 0) + goto import_netlbl_cat_failure; + + rc = ebitmap_cpy(&context->range.level[1].cat, + &context->range.level[0].cat); + if (rc != 0) + goto import_netlbl_cat_failure; + + return 0; + +import_netlbl_cat_failure: + ebitmap_destroy(&context->range.level[0].cat); + ebitmap_destroy(&context->range.level[1].cat); + return rc; +} +#endif /* CONFIG_NETLABEL */ diff --git a/security/selinux/ss/mls.h b/security/selinux/ss/mls.h new file mode 100644 index 00000000..037bf9d8 --- /dev/null +++ b/security/selinux/ss/mls.h @@ -0,0 +1,91 @@ +/* + * Multi-level security (MLS) policy operations. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Copyright (C) 2004-2006 Trusted Computer Solutions, Inc. + */ +/* + * Updated: Hewlett-Packard <paul.moore@hp.com> + * + * Added support to import/export the MLS label from NetLabel + * + * (c) Copyright Hewlett-Packard Development Company, L.P., 2006 + */ + +#ifndef _SS_MLS_H_ +#define _SS_MLS_H_ + +#include "context.h" +#include "policydb.h" + +int mls_compute_context_len(struct context *context); +void mls_sid_to_context(struct context *context, char **scontext); +int mls_context_isvalid(struct policydb *p, struct context *c); +int mls_range_isvalid(struct policydb *p, struct mls_range *r); +int mls_level_isvalid(struct policydb *p, struct mls_level *l); + +int mls_context_to_sid(struct policydb *p, + char oldc, + char **scontext, + struct context *context, + struct sidtab *s, + u32 def_sid); + +int mls_from_string(char *str, struct context *context, gfp_t gfp_mask); + +int mls_range_set(struct context *context, struct mls_range *range); + +int mls_convert_context(struct policydb *oldp, + struct policydb *newp, + struct context *context); + +int mls_compute_sid(struct context *scontext, + struct context *tcontext, + u16 tclass, + u32 specified, + struct context *newcontext, + bool sock); + +int mls_setup_user_range(struct context *fromcon, struct user_datum *user, + struct context *usercon); + +#ifdef CONFIG_NETLABEL +void mls_export_netlbl_lvl(struct context *context, + struct netlbl_lsm_secattr *secattr); +void mls_import_netlbl_lvl(struct context *context, + struct netlbl_lsm_secattr *secattr); +int mls_export_netlbl_cat(struct context *context, + struct netlbl_lsm_secattr *secattr); +int mls_import_netlbl_cat(struct context *context, + struct netlbl_lsm_secattr *secattr); +#else +static inline void mls_export_netlbl_lvl(struct context *context, + struct netlbl_lsm_secattr *secattr) +{ + return; +} +static inline void mls_import_netlbl_lvl(struct context *context, + struct netlbl_lsm_secattr *secattr) +{ + return; +} +static inline int mls_export_netlbl_cat(struct context *context, + struct netlbl_lsm_secattr *secattr) +{ + return -ENOMEM; +} +static inline int mls_import_netlbl_cat(struct context *context, + struct netlbl_lsm_secattr *secattr) +{ + return -ENOMEM; +} +#endif + +#endif /* _SS_MLS_H */ + diff --git a/security/selinux/ss/mls_types.h b/security/selinux/ss/mls_types.h new file mode 100644 index 00000000..03bed52a --- /dev/null +++ b/security/selinux/ss/mls_types.h @@ -0,0 +1,51 @@ +/* + * Type definitions for the multi-level security (MLS) policy. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. + */ + +#ifndef _SS_MLS_TYPES_H_ +#define _SS_MLS_TYPES_H_ + +#include "security.h" +#include "ebitmap.h" + +struct mls_level { + u32 sens; /* sensitivity */ + struct ebitmap cat; /* category set */ +}; + +struct mls_range { + struct mls_level level[2]; /* low == level[0], high == level[1] */ +}; + +static inline int mls_level_eq(struct mls_level *l1, struct mls_level *l2) +{ + return ((l1->sens == l2->sens) && + ebitmap_cmp(&l1->cat, &l2->cat)); +} + +static inline int mls_level_dom(struct mls_level *l1, struct mls_level *l2) +{ + return ((l1->sens >= l2->sens) && + ebitmap_contains(&l1->cat, &l2->cat)); +} + +#define mls_level_incomp(l1, l2) \ +(!mls_level_dom((l1), (l2)) && !mls_level_dom((l2), (l1))) + +#define mls_level_between(l1, l2, l3) \ +(mls_level_dom((l1), (l2)) && mls_level_dom((l3), (l1))) + +#define mls_range_contains(r1, r2) \ +(mls_level_dom(&(r2).level[0], &(r1).level[0]) && \ + mls_level_dom(&(r1).level[1], &(r2).level[1])) + +#endif /* _SS_MLS_TYPES_H_ */ diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c new file mode 100644 index 00000000..d246aca3 --- /dev/null +++ b/security/selinux/ss/policydb.c @@ -0,0 +1,3381 @@ +/* + * Implementation of the policy database. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ + +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Updated: Hewlett-Packard <paul.moore@hp.com> + * + * Added support for the policy capability bitmap + * + * Copyright (C) 2007 Hewlett-Packard Development Company, L.P. + * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + * 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. + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/audit.h> +#include <linux/flex_array.h> +#include "security.h" + +#include "policydb.h" +#include "conditional.h" +#include "mls.h" +#include "services.h" + +#define _DEBUG_HASHES + +#ifdef DEBUG_HASHES +static const char *symtab_name[SYM_NUM] = { + "common prefixes", + "classes", + "roles", + "types", + "users", + "bools", + "levels", + "categories", +}; +#endif + +static unsigned int symtab_sizes[SYM_NUM] = { + 2, + 32, + 16, + 512, + 128, + 16, + 16, + 16, +}; + +struct policydb_compat_info { + int version; + int sym_num; + int ocon_num; +}; + +/* These need to be updated if SYM_NUM or OCON_NUM changes */ +static struct policydb_compat_info policydb_compat[] = { + { + .version = POLICYDB_VERSION_BASE, + .sym_num = SYM_NUM - 3, + .ocon_num = OCON_NUM - 1, + }, + { + .version = POLICYDB_VERSION_BOOL, + .sym_num = SYM_NUM - 2, + .ocon_num = OCON_NUM - 1, + }, + { + .version = POLICYDB_VERSION_IPV6, + .sym_num = SYM_NUM - 2, + .ocon_num = OCON_NUM, + }, + { + .version = POLICYDB_VERSION_NLCLASS, + .sym_num = SYM_NUM - 2, + .ocon_num = OCON_NUM, + }, + { + .version = POLICYDB_VERSION_MLS, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM, + }, + { + .version = POLICYDB_VERSION_AVTAB, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM, + }, + { + .version = POLICYDB_VERSION_RANGETRANS, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM, + }, + { + .version = POLICYDB_VERSION_POLCAP, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM, + }, + { + .version = POLICYDB_VERSION_PERMISSIVE, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM, + }, + { + .version = POLICYDB_VERSION_BOUNDARY, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM, + }, + { + .version = POLICYDB_VERSION_FILENAME_TRANS, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM, + }, + { + .version = POLICYDB_VERSION_ROLETRANS, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM, + }, +}; + +static struct policydb_compat_info *policydb_lookup_compat(int version) +{ + int i; + struct policydb_compat_info *info = NULL; + + for (i = 0; i < ARRAY_SIZE(policydb_compat); i++) { + if (policydb_compat[i].version == version) { + info = &policydb_compat[i]; + break; + } + } + return info; +} + +/* + * Initialize the role table. + */ +static int roles_init(struct policydb *p) +{ + char *key = NULL; + int rc; + struct role_datum *role; + + rc = -ENOMEM; + role = kzalloc(sizeof(*role), GFP_KERNEL); + if (!role) + goto out; + + rc = -EINVAL; + role->value = ++p->p_roles.nprim; + if (role->value != OBJECT_R_VAL) + goto out; + + rc = -ENOMEM; + key = kstrdup(OBJECT_R, GFP_KERNEL); + if (!key) + goto out; + + rc = hashtab_insert(p->p_roles.table, key, role); + if (rc) + goto out; + + return 0; +out: + kfree(key); + kfree(role); + return rc; +} + +static u32 filenametr_hash(struct hashtab *h, const void *k) +{ + const struct filename_trans *ft = k; + unsigned long hash; + unsigned int byte_num; + unsigned char focus; + + hash = ft->stype ^ ft->ttype ^ ft->tclass; + + byte_num = 0; + while ((focus = ft->name[byte_num++])) + hash = partial_name_hash(focus, hash); + return hash & (h->size - 1); +} + +static int filenametr_cmp(struct hashtab *h, const void *k1, const void *k2) +{ + const struct filename_trans *ft1 = k1; + const struct filename_trans *ft2 = k2; + int v; + + v = ft1->stype - ft2->stype; + if (v) + return v; + + v = ft1->ttype - ft2->ttype; + if (v) + return v; + + v = ft1->tclass - ft2->tclass; + if (v) + return v; + + return strcmp(ft1->name, ft2->name); + +} + +static u32 rangetr_hash(struct hashtab *h, const void *k) +{ + const struct range_trans *key = k; + return (key->source_type + (key->target_type << 3) + + (key->target_class << 5)) & (h->size - 1); +} + +static int rangetr_cmp(struct hashtab *h, const void *k1, const void *k2) +{ + const struct range_trans *key1 = k1, *key2 = k2; + int v; + + v = key1->source_type - key2->source_type; + if (v) + return v; + + v = key1->target_type - key2->target_type; + if (v) + return v; + + v = key1->target_class - key2->target_class; + + return v; +} + +/* + * Initialize a policy database structure. + */ +static int policydb_init(struct policydb *p) +{ + int i, rc; + + memset(p, 0, sizeof(*p)); + + for (i = 0; i < SYM_NUM; i++) { + rc = symtab_init(&p->symtab[i], symtab_sizes[i]); + if (rc) + goto out; + } + + rc = avtab_init(&p->te_avtab); + if (rc) + goto out; + + rc = roles_init(p); + if (rc) + goto out; + + rc = cond_policydb_init(p); + if (rc) + goto out; + + p->filename_trans = hashtab_create(filenametr_hash, filenametr_cmp, (1 << 10)); + if (!p->filename_trans) + goto out; + + p->range_tr = hashtab_create(rangetr_hash, rangetr_cmp, 256); + if (!p->range_tr) + goto out; + + ebitmap_init(&p->filename_trans_ttypes); + ebitmap_init(&p->policycaps); + ebitmap_init(&p->permissive_map); + + return 0; +out: + hashtab_destroy(p->filename_trans); + hashtab_destroy(p->range_tr); + for (i = 0; i < SYM_NUM; i++) + hashtab_destroy(p->symtab[i].table); + return rc; +} + +/* + * The following *_index functions are used to + * define the val_to_name and val_to_struct arrays + * in a policy database structure. The val_to_name + * arrays are used when converting security context + * structures into string representations. The + * val_to_struct arrays are used when the attributes + * of a class, role, or user are needed. + */ + +static int common_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct common_datum *comdatum; + struct flex_array *fa; + + comdatum = datum; + p = datap; + if (!comdatum->value || comdatum->value > p->p_commons.nprim) + return -EINVAL; + + fa = p->sym_val_to_name[SYM_COMMONS]; + if (flex_array_put_ptr(fa, comdatum->value - 1, key, + GFP_KERNEL | __GFP_ZERO)) + BUG(); + return 0; +} + +static int class_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct class_datum *cladatum; + struct flex_array *fa; + + cladatum = datum; + p = datap; + if (!cladatum->value || cladatum->value > p->p_classes.nprim) + return -EINVAL; + fa = p->sym_val_to_name[SYM_CLASSES]; + if (flex_array_put_ptr(fa, cladatum->value - 1, key, + GFP_KERNEL | __GFP_ZERO)) + BUG(); + p->class_val_to_struct[cladatum->value - 1] = cladatum; + return 0; +} + +static int role_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct role_datum *role; + struct flex_array *fa; + + role = datum; + p = datap; + if (!role->value + || role->value > p->p_roles.nprim + || role->bounds > p->p_roles.nprim) + return -EINVAL; + + fa = p->sym_val_to_name[SYM_ROLES]; + if (flex_array_put_ptr(fa, role->value - 1, key, + GFP_KERNEL | __GFP_ZERO)) + BUG(); + p->role_val_to_struct[role->value - 1] = role; + return 0; +} + +static int type_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct type_datum *typdatum; + struct flex_array *fa; + + typdatum = datum; + p = datap; + + if (typdatum->primary) { + if (!typdatum->value + || typdatum->value > p->p_types.nprim + || typdatum->bounds > p->p_types.nprim) + return -EINVAL; + fa = p->sym_val_to_name[SYM_TYPES]; + if (flex_array_put_ptr(fa, typdatum->value - 1, key, + GFP_KERNEL | __GFP_ZERO)) + BUG(); + + fa = p->type_val_to_struct_array; + if (flex_array_put_ptr(fa, typdatum->value - 1, typdatum, + GFP_KERNEL | __GFP_ZERO)) + BUG(); + } + + return 0; +} + +static int user_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct user_datum *usrdatum; + struct flex_array *fa; + + usrdatum = datum; + p = datap; + if (!usrdatum->value + || usrdatum->value > p->p_users.nprim + || usrdatum->bounds > p->p_users.nprim) + return -EINVAL; + + fa = p->sym_val_to_name[SYM_USERS]; + if (flex_array_put_ptr(fa, usrdatum->value - 1, key, + GFP_KERNEL | __GFP_ZERO)) + BUG(); + p->user_val_to_struct[usrdatum->value - 1] = usrdatum; + return 0; +} + +static int sens_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct level_datum *levdatum; + struct flex_array *fa; + + levdatum = datum; + p = datap; + + if (!levdatum->isalias) { + if (!levdatum->level->sens || + levdatum->level->sens > p->p_levels.nprim) + return -EINVAL; + fa = p->sym_val_to_name[SYM_LEVELS]; + if (flex_array_put_ptr(fa, levdatum->level->sens - 1, key, + GFP_KERNEL | __GFP_ZERO)) + BUG(); + } + + return 0; +} + +static int cat_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct cat_datum *catdatum; + struct flex_array *fa; + + catdatum = datum; + p = datap; + + if (!catdatum->isalias) { + if (!catdatum->value || catdatum->value > p->p_cats.nprim) + return -EINVAL; + fa = p->sym_val_to_name[SYM_CATS]; + if (flex_array_put_ptr(fa, catdatum->value - 1, key, + GFP_KERNEL | __GFP_ZERO)) + BUG(); + } + + return 0; +} + +static int (*index_f[SYM_NUM]) (void *key, void *datum, void *datap) = +{ + common_index, + class_index, + role_index, + type_index, + user_index, + cond_index_bool, + sens_index, + cat_index, +}; + +#ifdef DEBUG_HASHES +static void hash_eval(struct hashtab *h, const char *hash_name) +{ + struct hashtab_info info; + + hashtab_stat(h, &info); + printk(KERN_DEBUG "SELinux: %s: %d entries and %d/%d buckets used, " + "longest chain length %d\n", hash_name, h->nel, + info.slots_used, h->size, info.max_chain_len); +} + +static void symtab_hash_eval(struct symtab *s) +{ + int i; + + for (i = 0; i < SYM_NUM; i++) + hash_eval(s[i].table, symtab_name[i]); +} + +#else +static inline void hash_eval(struct hashtab *h, char *hash_name) +{ +} +#endif + +/* + * Define the other val_to_name and val_to_struct arrays + * in a policy database structure. + * + * Caller must clean up on failure. + */ +static int policydb_index(struct policydb *p) +{ + int i, rc; + + printk(KERN_DEBUG "SELinux: %d users, %d roles, %d types, %d bools", + p->p_users.nprim, p->p_roles.nprim, p->p_types.nprim, p->p_bools.nprim); + if (p->mls_enabled) + printk(", %d sens, %d cats", p->p_levels.nprim, + p->p_cats.nprim); + printk("\n"); + + printk(KERN_DEBUG "SELinux: %d classes, %d rules\n", + p->p_classes.nprim, p->te_avtab.nel); + +#ifdef DEBUG_HASHES + avtab_hash_eval(&p->te_avtab, "rules"); + symtab_hash_eval(p->symtab); +#endif + + rc = -ENOMEM; + p->class_val_to_struct = + kmalloc(p->p_classes.nprim * sizeof(*(p->class_val_to_struct)), + GFP_KERNEL); + if (!p->class_val_to_struct) + goto out; + + rc = -ENOMEM; + p->role_val_to_struct = + kmalloc(p->p_roles.nprim * sizeof(*(p->role_val_to_struct)), + GFP_KERNEL); + if (!p->role_val_to_struct) + goto out; + + rc = -ENOMEM; + p->user_val_to_struct = + kmalloc(p->p_users.nprim * sizeof(*(p->user_val_to_struct)), + GFP_KERNEL); + if (!p->user_val_to_struct) + goto out; + + /* Yes, I want the sizeof the pointer, not the structure */ + rc = -ENOMEM; + p->type_val_to_struct_array = flex_array_alloc(sizeof(struct type_datum *), + p->p_types.nprim, + GFP_KERNEL | __GFP_ZERO); + if (!p->type_val_to_struct_array) + goto out; + + rc = flex_array_prealloc(p->type_val_to_struct_array, 0, + p->p_types.nprim, GFP_KERNEL | __GFP_ZERO); + if (rc) + goto out; + + rc = cond_init_bool_indexes(p); + if (rc) + goto out; + + for (i = 0; i < SYM_NUM; i++) { + rc = -ENOMEM; + p->sym_val_to_name[i] = flex_array_alloc(sizeof(char *), + p->symtab[i].nprim, + GFP_KERNEL | __GFP_ZERO); + if (!p->sym_val_to_name[i]) + goto out; + + rc = flex_array_prealloc(p->sym_val_to_name[i], + 0, p->symtab[i].nprim, + GFP_KERNEL | __GFP_ZERO); + if (rc) + goto out; + + rc = hashtab_map(p->symtab[i].table, index_f[i], p); + if (rc) + goto out; + } + rc = 0; +out: + return rc; +} + +/* + * The following *_destroy functions are used to + * free any memory allocated for each kind of + * symbol data in the policy database. + */ + +static int perm_destroy(void *key, void *datum, void *p) +{ + kfree(key); + kfree(datum); + return 0; +} + +static int common_destroy(void *key, void *datum, void *p) +{ + struct common_datum *comdatum; + + kfree(key); + if (datum) { + comdatum = datum; + hashtab_map(comdatum->permissions.table, perm_destroy, NULL); + hashtab_destroy(comdatum->permissions.table); + } + kfree(datum); + return 0; +} + +static int cls_destroy(void *key, void *datum, void *p) +{ + struct class_datum *cladatum; + struct constraint_node *constraint, *ctemp; + struct constraint_expr *e, *etmp; + + kfree(key); + if (datum) { + cladatum = datum; + hashtab_map(cladatum->permissions.table, perm_destroy, NULL); + hashtab_destroy(cladatum->permissions.table); + constraint = cladatum->constraints; + while (constraint) { + e = constraint->expr; + while (e) { + ebitmap_destroy(&e->names); + etmp = e; + e = e->next; + kfree(etmp); + } + ctemp = constraint; + constraint = constraint->next; + kfree(ctemp); + } + + constraint = cladatum->validatetrans; + while (constraint) { + e = constraint->expr; + while (e) { + ebitmap_destroy(&e->names); + etmp = e; + e = e->next; + kfree(etmp); + } + ctemp = constraint; + constraint = constraint->next; + kfree(ctemp); + } + + kfree(cladatum->comkey); + } + kfree(datum); + return 0; +} + +static int role_destroy(void *key, void *datum, void *p) +{ + struct role_datum *role; + + kfree(key); + if (datum) { + role = datum; + ebitmap_destroy(&role->dominates); + ebitmap_destroy(&role->types); + } + kfree(datum); + return 0; +} + +static int type_destroy(void *key, void *datum, void *p) +{ + kfree(key); + kfree(datum); + return 0; +} + +static int user_destroy(void *key, void *datum, void *p) +{ + struct user_datum *usrdatum; + + kfree(key); + if (datum) { + usrdatum = datum; + ebitmap_destroy(&usrdatum->roles); + ebitmap_destroy(&usrdatum->range.level[0].cat); + ebitmap_destroy(&usrdatum->range.level[1].cat); + ebitmap_destroy(&usrdatum->dfltlevel.cat); + } + kfree(datum); + return 0; +} + +static int sens_destroy(void *key, void *datum, void *p) +{ + struct level_datum *levdatum; + + kfree(key); + if (datum) { + levdatum = datum; + ebitmap_destroy(&levdatum->level->cat); + kfree(levdatum->level); + } + kfree(datum); + return 0; +} + +static int cat_destroy(void *key, void *datum, void *p) +{ + kfree(key); + kfree(datum); + return 0; +} + +static int (*destroy_f[SYM_NUM]) (void *key, void *datum, void *datap) = +{ + common_destroy, + cls_destroy, + role_destroy, + type_destroy, + user_destroy, + cond_destroy_bool, + sens_destroy, + cat_destroy, +}; + +static int filenametr_destroy(void *key, void *datum, void *p) +{ + struct filename_trans *ft = key; + kfree(ft->name); + kfree(key); + kfree(datum); + cond_resched(); + return 0; +} + +static int range_tr_destroy(void *key, void *datum, void *p) +{ + struct mls_range *rt = datum; + kfree(key); + ebitmap_destroy(&rt->level[0].cat); + ebitmap_destroy(&rt->level[1].cat); + kfree(datum); + cond_resched(); + return 0; +} + +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 || + i == OCON_NETIF || i == OCON_FSUSE) + kfree(c->u.name); + kfree(c); +} + +/* + * Free any memory allocated by a policy database structure. + */ +void policydb_destroy(struct policydb *p) +{ + struct ocontext *c, *ctmp; + struct genfs *g, *gtmp; + int i; + struct role_allow *ra, *lra = NULL; + struct role_trans *tr, *ltr = NULL; + + for (i = 0; i < SYM_NUM; i++) { + cond_resched(); + hashtab_map(p->symtab[i].table, destroy_f[i], NULL); + hashtab_destroy(p->symtab[i].table); + } + + for (i = 0; i < SYM_NUM; i++) { + if (p->sym_val_to_name[i]) + flex_array_free(p->sym_val_to_name[i]); + } + + kfree(p->class_val_to_struct); + kfree(p->role_val_to_struct); + kfree(p->user_val_to_struct); + if (p->type_val_to_struct_array) + flex_array_free(p->type_val_to_struct_array); + + avtab_destroy(&p->te_avtab); + + for (i = 0; i < OCON_NUM; i++) { + cond_resched(); + c = p->ocontexts[i]; + while (c) { + ctmp = c; + c = c->next; + ocontext_destroy(ctmp, i); + } + p->ocontexts[i] = NULL; + } + + g = p->genfs; + while (g) { + cond_resched(); + kfree(g->fstype); + c = g->head; + while (c) { + ctmp = c; + c = c->next; + ocontext_destroy(ctmp, OCON_FSUSE); + } + gtmp = g; + g = g->next; + kfree(gtmp); + } + p->genfs = NULL; + + cond_policydb_destroy(p); + + for (tr = p->role_tr; tr; tr = tr->next) { + cond_resched(); + kfree(ltr); + ltr = tr; + } + kfree(ltr); + + for (ra = p->role_allow; ra; ra = ra->next) { + cond_resched(); + kfree(lra); + lra = ra; + } + kfree(lra); + + hashtab_map(p->filename_trans, filenametr_destroy, NULL); + hashtab_destroy(p->filename_trans); + + hashtab_map(p->range_tr, range_tr_destroy, NULL); + hashtab_destroy(p->range_tr); + + 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); + } + + ebitmap_destroy(&p->filename_trans_ttypes); + ebitmap_destroy(&p->policycaps); + ebitmap_destroy(&p->permissive_map); + + return; +} + +/* + * Load the initial SIDs specified in a policy database + * structure into a SID table. + */ +int policydb_load_isids(struct policydb *p, struct sidtab *s) +{ + struct ocontext *head, *c; + int rc; + + rc = sidtab_init(s); + if (rc) { + printk(KERN_ERR "SELinux: out of memory on SID table init\n"); + goto out; + } + + head = p->ocontexts[OCON_ISID]; + for (c = head; c; c = c->next) { + rc = -EINVAL; + if (!c->context[0].user) { + printk(KERN_ERR "SELinux: SID %s was never defined.\n", + c->u.name); + goto out; + } + + rc = sidtab_insert(s, c->sid[0], &c->context[0]); + if (rc) { + printk(KERN_ERR "SELinux: unable to load initial SID %s.\n", + c->u.name); + goto out; + } + } + rc = 0; +out: + return rc; +} + +int policydb_class_isvalid(struct policydb *p, unsigned int class) +{ + if (!class || class > p->p_classes.nprim) + return 0; + return 1; +} + +int policydb_role_isvalid(struct policydb *p, unsigned int role) +{ + if (!role || role > p->p_roles.nprim) + return 0; + return 1; +} + +int policydb_type_isvalid(struct policydb *p, unsigned int type) +{ + if (!type || type > p->p_types.nprim) + return 0; + return 1; +} + +/* + * Return 1 if the fields in the security context + * structure `c' are valid. Return 0 otherwise. + */ +int policydb_context_isvalid(struct policydb *p, struct context *c) +{ + struct role_datum *role; + struct user_datum *usrdatum; + + if (!c->role || c->role > p->p_roles.nprim) + return 0; + + if (!c->user || c->user > p->p_users.nprim) + return 0; + + if (!c->type || c->type > p->p_types.nprim) + return 0; + + if (c->role != OBJECT_R_VAL) { + /* + * Role must be authorized for the type. + */ + role = p->role_val_to_struct[c->role - 1]; + if (!ebitmap_get_bit(&role->types, c->type - 1)) + /* role may not be associated with type */ + return 0; + + /* + * User must be authorized for the role. + */ + usrdatum = p->user_val_to_struct[c->user - 1]; + if (!usrdatum) + return 0; + + if (!ebitmap_get_bit(&usrdatum->roles, c->role - 1)) + /* user may not be associated with role */ + return 0; + } + + if (!mls_context_isvalid(p, c)) + return 0; + + return 1; +} + +/* + * Read a MLS range structure from a policydb binary + * representation file. + */ +static int mls_read_range_helper(struct mls_range *r, void *fp) +{ + __le32 buf[2]; + u32 items; + int rc; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + + rc = -EINVAL; + items = le32_to_cpu(buf[0]); + if (items > ARRAY_SIZE(buf)) { + printk(KERN_ERR "SELinux: mls: range overflow\n"); + goto out; + } + + rc = next_entry(buf, fp, sizeof(u32) * items); + if (rc) { + printk(KERN_ERR "SELinux: mls: truncated range\n"); + goto out; + } + + r->level[0].sens = le32_to_cpu(buf[0]); + if (items > 1) + r->level[1].sens = le32_to_cpu(buf[1]); + else + r->level[1].sens = r->level[0].sens; + + rc = ebitmap_read(&r->level[0].cat, fp); + if (rc) { + printk(KERN_ERR "SELinux: mls: error reading low categories\n"); + goto out; + } + if (items > 1) { + rc = ebitmap_read(&r->level[1].cat, fp); + if (rc) { + printk(KERN_ERR "SELinux: mls: error reading high categories\n"); + goto bad_high; + } + } else { + rc = ebitmap_cpy(&r->level[1].cat, &r->level[0].cat); + if (rc) { + printk(KERN_ERR "SELinux: mls: out of memory\n"); + goto bad_high; + } + } + + return 0; +bad_high: + ebitmap_destroy(&r->level[0].cat); +out: + return rc; +} + +/* + * Read and validate a security context structure + * from a policydb binary representation file. + */ +static int context_read_and_validate(struct context *c, + struct policydb *p, + void *fp) +{ + __le32 buf[3]; + int rc; + + rc = next_entry(buf, fp, sizeof buf); + if (rc) { + printk(KERN_ERR "SELinux: context truncated\n"); + goto out; + } + c->user = le32_to_cpu(buf[0]); + c->role = le32_to_cpu(buf[1]); + c->type = le32_to_cpu(buf[2]); + if (p->policyvers >= POLICYDB_VERSION_MLS) { + rc = mls_read_range_helper(&c->range, fp); + if (rc) { + printk(KERN_ERR "SELinux: error reading MLS range of context\n"); + goto out; + } + } + + rc = -EINVAL; + if (!policydb_context_isvalid(p, c)) { + printk(KERN_ERR "SELinux: invalid security context\n"); + context_destroy(c); + goto out; + } + rc = 0; +out: + return rc; +} + +/* + * The following *_read functions are used to + * read the symbol data from a policy database + * binary representation file. + */ + +static int perm_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct perm_datum *perdatum; + int rc; + __le32 buf[2]; + u32 len; + + rc = -ENOMEM; + perdatum = kzalloc(sizeof(*perdatum), GFP_KERNEL); + if (!perdatum) + goto bad; + + rc = next_entry(buf, fp, sizeof buf); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + perdatum->value = le32_to_cpu(buf[1]); + + rc = -ENOMEM; + key = kmalloc(len + 1, GFP_KERNEL); + if (!key) + goto bad; + + rc = next_entry(key, fp, len); + if (rc) + goto bad; + key[len] = '\0'; + + rc = hashtab_insert(h, key, perdatum); + if (rc) + goto bad; + + return 0; +bad: + perm_destroy(key, perdatum, NULL); + return rc; +} + +static int common_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct common_datum *comdatum; + __le32 buf[4]; + u32 len, nel; + int i, rc; + + rc = -ENOMEM; + comdatum = kzalloc(sizeof(*comdatum), GFP_KERNEL); + if (!comdatum) + goto bad; + + rc = next_entry(buf, fp, sizeof buf); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + comdatum->value = le32_to_cpu(buf[1]); + + rc = symtab_init(&comdatum->permissions, PERM_SYMTAB_SIZE); + if (rc) + goto bad; + comdatum->permissions.nprim = le32_to_cpu(buf[2]); + nel = le32_to_cpu(buf[3]); + + rc = -ENOMEM; + key = kmalloc(len + 1, GFP_KERNEL); + if (!key) + goto bad; + + rc = next_entry(key, fp, len); + if (rc) + goto bad; + key[len] = '\0'; + + for (i = 0; i < nel; i++) { + rc = perm_read(p, comdatum->permissions.table, fp); + if (rc) + goto bad; + } + + rc = hashtab_insert(h, key, comdatum); + if (rc) + goto bad; + return 0; +bad: + common_destroy(key, comdatum, NULL); + return rc; +} + +static int read_cons_helper(struct constraint_node **nodep, int ncons, + int allowxtarget, void *fp) +{ + struct constraint_node *c, *lc; + struct constraint_expr *e, *le; + __le32 buf[3]; + u32 nexpr; + int rc, i, j, depth; + + lc = NULL; + for (i = 0; i < ncons; i++) { + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) + return -ENOMEM; + + if (lc) + lc->next = c; + else + *nodep = c; + + rc = next_entry(buf, fp, (sizeof(u32) * 2)); + if (rc) + return rc; + c->permissions = le32_to_cpu(buf[0]); + nexpr = le32_to_cpu(buf[1]); + le = NULL; + depth = -1; + for (j = 0; j < nexpr; j++) { + e = kzalloc(sizeof(*e), GFP_KERNEL); + if (!e) + return -ENOMEM; + + if (le) + le->next = e; + else + c->expr = e; + + rc = next_entry(buf, fp, (sizeof(u32) * 3)); + if (rc) + return rc; + e->expr_type = le32_to_cpu(buf[0]); + e->attr = le32_to_cpu(buf[1]); + e->op = le32_to_cpu(buf[2]); + + switch (e->expr_type) { + case CEXPR_NOT: + if (depth < 0) + return -EINVAL; + break; + case CEXPR_AND: + case CEXPR_OR: + if (depth < 1) + return -EINVAL; + depth--; + break; + case CEXPR_ATTR: + if (depth == (CEXPR_MAXDEPTH - 1)) + return -EINVAL; + depth++; + break; + case CEXPR_NAMES: + if (!allowxtarget && (e->attr & CEXPR_XTARGET)) + return -EINVAL; + if (depth == (CEXPR_MAXDEPTH - 1)) + return -EINVAL; + depth++; + rc = ebitmap_read(&e->names, fp); + if (rc) + return rc; + break; + default: + return -EINVAL; + } + le = e; + } + if (depth != 0) + return -EINVAL; + lc = c; + } + + return 0; +} + +static int class_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct class_datum *cladatum; + __le32 buf[6]; + u32 len, len2, ncons, nel; + int i, rc; + + rc = -ENOMEM; + cladatum = kzalloc(sizeof(*cladatum), GFP_KERNEL); + if (!cladatum) + goto bad; + + rc = next_entry(buf, fp, sizeof(u32)*6); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + len2 = le32_to_cpu(buf[1]); + cladatum->value = le32_to_cpu(buf[2]); + + rc = symtab_init(&cladatum->permissions, PERM_SYMTAB_SIZE); + if (rc) + goto bad; + cladatum->permissions.nprim = le32_to_cpu(buf[3]); + nel = le32_to_cpu(buf[4]); + + ncons = le32_to_cpu(buf[5]); + + rc = -ENOMEM; + key = kmalloc(len + 1, GFP_KERNEL); + if (!key) + goto bad; + + rc = next_entry(key, fp, len); + if (rc) + goto bad; + key[len] = '\0'; + + if (len2) { + rc = -ENOMEM; + cladatum->comkey = kmalloc(len2 + 1, GFP_KERNEL); + if (!cladatum->comkey) + goto bad; + rc = next_entry(cladatum->comkey, fp, len2); + if (rc) + goto bad; + cladatum->comkey[len2] = '\0'; + + rc = -EINVAL; + cladatum->comdatum = hashtab_search(p->p_commons.table, cladatum->comkey); + if (!cladatum->comdatum) { + printk(KERN_ERR "SELinux: unknown common %s\n", cladatum->comkey); + goto bad; + } + } + for (i = 0; i < nel; i++) { + rc = perm_read(p, cladatum->permissions.table, fp); + if (rc) + goto bad; + } + + rc = read_cons_helper(&cladatum->constraints, ncons, 0, fp); + if (rc) + goto bad; + + if (p->policyvers >= POLICYDB_VERSION_VALIDATETRANS) { + /* grab the validatetrans rules */ + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto bad; + ncons = le32_to_cpu(buf[0]); + rc = read_cons_helper(&cladatum->validatetrans, ncons, 1, fp); + if (rc) + goto bad; + } + + rc = hashtab_insert(h, key, cladatum); + if (rc) + goto bad; + + return 0; +bad: + cls_destroy(key, cladatum, NULL); + return rc; +} + +static int role_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct role_datum *role; + int rc, to_read = 2; + __le32 buf[3]; + u32 len; + + rc = -ENOMEM; + role = kzalloc(sizeof(*role), GFP_KERNEL); + if (!role) + goto bad; + + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + to_read = 3; + + rc = next_entry(buf, fp, sizeof(buf[0]) * to_read); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + role->value = le32_to_cpu(buf[1]); + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + role->bounds = le32_to_cpu(buf[2]); + + rc = -ENOMEM; + key = kmalloc(len + 1, GFP_KERNEL); + if (!key) + goto bad; + + rc = next_entry(key, fp, len); + if (rc) + goto bad; + key[len] = '\0'; + + rc = ebitmap_read(&role->dominates, fp); + if (rc) + goto bad; + + rc = ebitmap_read(&role->types, fp); + if (rc) + goto bad; + + if (strcmp(key, OBJECT_R) == 0) { + rc = -EINVAL; + if (role->value != OBJECT_R_VAL) { + printk(KERN_ERR "SELinux: Role %s has wrong value %d\n", + OBJECT_R, role->value); + goto bad; + } + rc = 0; + goto bad; + } + + rc = hashtab_insert(h, key, role); + if (rc) + goto bad; + return 0; +bad: + role_destroy(key, role, NULL); + return rc; +} + +static int type_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct type_datum *typdatum; + int rc, to_read = 3; + __le32 buf[4]; + u32 len; + + rc = -ENOMEM; + typdatum = kzalloc(sizeof(*typdatum), GFP_KERNEL); + if (!typdatum) + goto bad; + + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + to_read = 4; + + rc = next_entry(buf, fp, sizeof(buf[0]) * to_read); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + typdatum->value = le32_to_cpu(buf[1]); + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) { + u32 prop = le32_to_cpu(buf[2]); + + if (prop & TYPEDATUM_PROPERTY_PRIMARY) + typdatum->primary = 1; + if (prop & TYPEDATUM_PROPERTY_ATTRIBUTE) + typdatum->attribute = 1; + + typdatum->bounds = le32_to_cpu(buf[3]); + } else { + typdatum->primary = le32_to_cpu(buf[2]); + } + + rc = -ENOMEM; + key = kmalloc(len + 1, GFP_KERNEL); + if (!key) + goto bad; + rc = next_entry(key, fp, len); + if (rc) + goto bad; + key[len] = '\0'; + + rc = hashtab_insert(h, key, typdatum); + if (rc) + goto bad; + return 0; +bad: + type_destroy(key, typdatum, NULL); + return rc; +} + + +/* + * Read a MLS level structure from a policydb binary + * representation file. + */ +static int mls_read_level(struct mls_level *lp, void *fp) +{ + __le32 buf[1]; + int rc; + + memset(lp, 0, sizeof(*lp)); + + rc = next_entry(buf, fp, sizeof buf); + if (rc) { + printk(KERN_ERR "SELinux: mls: truncated level\n"); + return rc; + } + lp->sens = le32_to_cpu(buf[0]); + + rc = ebitmap_read(&lp->cat, fp); + if (rc) { + printk(KERN_ERR "SELinux: mls: error reading level categories\n"); + return rc; + } + return 0; +} + +static int user_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct user_datum *usrdatum; + int rc, to_read = 2; + __le32 buf[3]; + u32 len; + + rc = -ENOMEM; + usrdatum = kzalloc(sizeof(*usrdatum), GFP_KERNEL); + if (!usrdatum) + goto bad; + + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + to_read = 3; + + rc = next_entry(buf, fp, sizeof(buf[0]) * to_read); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + usrdatum->value = le32_to_cpu(buf[1]); + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + usrdatum->bounds = le32_to_cpu(buf[2]); + + rc = -ENOMEM; + key = kmalloc(len + 1, GFP_KERNEL); + if (!key) + goto bad; + rc = next_entry(key, fp, len); + if (rc) + goto bad; + key[len] = '\0'; + + rc = ebitmap_read(&usrdatum->roles, fp); + if (rc) + goto bad; + + if (p->policyvers >= POLICYDB_VERSION_MLS) { + rc = mls_read_range_helper(&usrdatum->range, fp); + if (rc) + goto bad; + rc = mls_read_level(&usrdatum->dfltlevel, fp); + if (rc) + goto bad; + } + + rc = hashtab_insert(h, key, usrdatum); + if (rc) + goto bad; + return 0; +bad: + user_destroy(key, usrdatum, NULL); + return rc; +} + +static int sens_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct level_datum *levdatum; + int rc; + __le32 buf[2]; + u32 len; + + rc = -ENOMEM; + levdatum = kzalloc(sizeof(*levdatum), GFP_ATOMIC); + if (!levdatum) + goto bad; + + rc = next_entry(buf, fp, sizeof buf); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + levdatum->isalias = le32_to_cpu(buf[1]); + + rc = -ENOMEM; + key = kmalloc(len + 1, GFP_ATOMIC); + if (!key) + goto bad; + rc = next_entry(key, fp, len); + if (rc) + goto bad; + key[len] = '\0'; + + rc = -ENOMEM; + levdatum->level = kmalloc(sizeof(struct mls_level), GFP_ATOMIC); + if (!levdatum->level) + goto bad; + + rc = mls_read_level(levdatum->level, fp); + if (rc) + goto bad; + + rc = hashtab_insert(h, key, levdatum); + if (rc) + goto bad; + return 0; +bad: + sens_destroy(key, levdatum, NULL); + return rc; +} + +static int cat_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct cat_datum *catdatum; + int rc; + __le32 buf[3]; + u32 len; + + rc = -ENOMEM; + catdatum = kzalloc(sizeof(*catdatum), GFP_ATOMIC); + if (!catdatum) + goto bad; + + rc = next_entry(buf, fp, sizeof buf); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + catdatum->value = le32_to_cpu(buf[1]); + catdatum->isalias = le32_to_cpu(buf[2]); + + rc = -ENOMEM; + key = kmalloc(len + 1, GFP_ATOMIC); + if (!key) + goto bad; + rc = next_entry(key, fp, len); + if (rc) + goto bad; + key[len] = '\0'; + + rc = hashtab_insert(h, key, catdatum); + if (rc) + goto bad; + return 0; +bad: + cat_destroy(key, catdatum, NULL); + return rc; +} + +static int (*read_f[SYM_NUM]) (struct policydb *p, struct hashtab *h, void *fp) = +{ + common_read, + class_read, + role_read, + type_read, + user_read, + cond_read_bool, + sens_read, + cat_read, +}; + +static int user_bounds_sanity_check(void *key, void *datum, void *datap) +{ + struct user_datum *upper, *user; + struct policydb *p = datap; + int depth = 0; + + upper = user = datum; + while (upper->bounds) { + struct ebitmap_node *node; + unsigned long bit; + + if (++depth == POLICYDB_BOUNDS_MAXDEPTH) { + printk(KERN_ERR "SELinux: user %s: " + "too deep or looped boundary", + (char *) key); + return -EINVAL; + } + + upper = p->user_val_to_struct[upper->bounds - 1]; + ebitmap_for_each_positive_bit(&user->roles, node, bit) { + if (ebitmap_get_bit(&upper->roles, bit)) + continue; + + printk(KERN_ERR + "SELinux: boundary violated policy: " + "user=%s role=%s bounds=%s\n", + sym_name(p, SYM_USERS, user->value - 1), + sym_name(p, SYM_ROLES, bit), + sym_name(p, SYM_USERS, upper->value - 1)); + + return -EINVAL; + } + } + + return 0; +} + +static int role_bounds_sanity_check(void *key, void *datum, void *datap) +{ + struct role_datum *upper, *role; + struct policydb *p = datap; + int depth = 0; + + upper = role = datum; + while (upper->bounds) { + struct ebitmap_node *node; + unsigned long bit; + + if (++depth == POLICYDB_BOUNDS_MAXDEPTH) { + printk(KERN_ERR "SELinux: role %s: " + "too deep or looped bounds\n", + (char *) key); + return -EINVAL; + } + + upper = p->role_val_to_struct[upper->bounds - 1]; + ebitmap_for_each_positive_bit(&role->types, node, bit) { + if (ebitmap_get_bit(&upper->types, bit)) + continue; + + printk(KERN_ERR + "SELinux: boundary violated policy: " + "role=%s type=%s bounds=%s\n", + sym_name(p, SYM_ROLES, role->value - 1), + sym_name(p, SYM_TYPES, bit), + sym_name(p, SYM_ROLES, upper->value - 1)); + + return -EINVAL; + } + } + + return 0; +} + +static int type_bounds_sanity_check(void *key, void *datum, void *datap) +{ + struct type_datum *upper; + struct policydb *p = datap; + int depth = 0; + + upper = datum; + while (upper->bounds) { + if (++depth == POLICYDB_BOUNDS_MAXDEPTH) { + printk(KERN_ERR "SELinux: type %s: " + "too deep or looped boundary\n", + (char *) key); + return -EINVAL; + } + + upper = flex_array_get_ptr(p->type_val_to_struct_array, + upper->bounds - 1); + BUG_ON(!upper); + + if (upper->attribute) { + printk(KERN_ERR "SELinux: type %s: " + "bounded by attribute %s", + (char *) key, + sym_name(p, SYM_TYPES, upper->value - 1)); + return -EINVAL; + } + } + + return 0; +} + +static int policydb_bounds_sanity_check(struct policydb *p) +{ + int rc; + + if (p->policyvers < POLICYDB_VERSION_BOUNDARY) + return 0; + + rc = hashtab_map(p->p_users.table, + user_bounds_sanity_check, p); + if (rc) + return rc; + + rc = hashtab_map(p->p_roles.table, + role_bounds_sanity_check, p); + if (rc) + return rc; + + rc = hashtab_map(p->p_types.table, + type_bounds_sanity_check, p); + if (rc) + return rc; + + return 0; +} + +extern int ss_initialized; + +u16 string_to_security_class(struct policydb *p, const char *name) +{ + struct class_datum *cladatum; + + cladatum = hashtab_search(p->p_classes.table, name); + if (!cladatum) + return 0; + + return cladatum->value; +} + +u32 string_to_av_perm(struct policydb *p, u16 tclass, const char *name) +{ + struct class_datum *cladatum; + struct perm_datum *perdatum = NULL; + struct common_datum *comdatum; + + if (!tclass || tclass > p->p_classes.nprim) + return 0; + + cladatum = p->class_val_to_struct[tclass-1]; + comdatum = cladatum->comdatum; + if (comdatum) + perdatum = hashtab_search(comdatum->permissions.table, + name); + if (!perdatum) + perdatum = hashtab_search(cladatum->permissions.table, + name); + if (!perdatum) + return 0; + + 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; + } + hash_eval(p->range_tr, "rangetr"); + rc = 0; +out: + kfree(rt); + kfree(r); + return rc; +} + +static int filename_trans_read(struct policydb *p, void *fp) +{ + struct filename_trans *ft; + struct filename_trans_datum *otype; + char *name; + u32 nel, len; + __le32 buf[4]; + int rc, i; + + if (p->policyvers < POLICYDB_VERSION_FILENAME_TRANS) + return 0; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + return rc; + nel = le32_to_cpu(buf[0]); + + for (i = 0; i < nel; i++) { + ft = NULL; + otype = NULL; + name = NULL; + + rc = -ENOMEM; + ft = kzalloc(sizeof(*ft), GFP_KERNEL); + if (!ft) + goto out; + + rc = -ENOMEM; + otype = kmalloc(sizeof(*otype), GFP_KERNEL); + if (!otype) + goto out; + + /* length of the path component string */ + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + len = le32_to_cpu(buf[0]); + + rc = -ENOMEM; + name = kmalloc(len + 1, GFP_KERNEL); + if (!name) + goto out; + + ft->name = name; + + /* path component string */ + rc = next_entry(name, fp, len); + if (rc) + goto out; + name[len] = 0; + + rc = next_entry(buf, fp, sizeof(u32) * 4); + if (rc) + goto out; + + ft->stype = le32_to_cpu(buf[0]); + ft->ttype = le32_to_cpu(buf[1]); + ft->tclass = le32_to_cpu(buf[2]); + + otype->otype = le32_to_cpu(buf[3]); + + rc = ebitmap_set_bit(&p->filename_trans_ttypes, ft->ttype, 1); + if (rc) + goto out; + + hashtab_insert(p->filename_trans, ft, otype); + } + hash_eval(p->filename_trans, "filenametr"); + return 0; +out: + kfree(ft); + kfree(name); + kfree(otype); + + 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. + */ +int policydb_read(struct policydb *p, void *fp) +{ + struct role_allow *ra, *lra; + struct role_trans *tr, *ltr; + int i, j, rc; + __le32 buf[4]; + u32 len, nprim, nel; + + char *policydb_str; + struct policydb_compat_info *info; + + rc = policydb_init(p); + if (rc) + return rc; + + /* Read the magic number and string length. */ + rc = next_entry(buf, fp, sizeof(u32) * 2); + if (rc) + goto bad; + + rc = -EINVAL; + if (le32_to_cpu(buf[0]) != POLICYDB_MAGIC) { + printk(KERN_ERR "SELinux: policydb magic number 0x%x does " + "not match expected magic number 0x%x\n", + le32_to_cpu(buf[0]), POLICYDB_MAGIC); + goto bad; + } + + rc = -EINVAL; + len = le32_to_cpu(buf[1]); + if (len != strlen(POLICYDB_STRING)) { + printk(KERN_ERR "SELinux: policydb string length %d does not " + "match expected length %Zu\n", + len, strlen(POLICYDB_STRING)); + goto bad; + } + + rc = -ENOMEM; + policydb_str = kmalloc(len + 1, GFP_KERNEL); + if (!policydb_str) { + printk(KERN_ERR "SELinux: unable to allocate memory for policydb " + "string of length %d\n", len); + goto bad; + } + + rc = next_entry(policydb_str, fp, len); + if (rc) { + printk(KERN_ERR "SELinux: truncated policydb string identifier\n"); + kfree(policydb_str); + goto bad; + } + + rc = -EINVAL; + policydb_str[len] = '\0'; + if (strcmp(policydb_str, POLICYDB_STRING)) { + printk(KERN_ERR "SELinux: policydb string %s does not match " + "my string %s\n", policydb_str, POLICYDB_STRING); + kfree(policydb_str); + goto bad; + } + /* Done with policydb_str. */ + kfree(policydb_str); + policydb_str = NULL; + + /* Read the version and table sizes. */ + rc = next_entry(buf, fp, sizeof(u32)*4); + if (rc) + goto bad; + + rc = -EINVAL; + p->policyvers = le32_to_cpu(buf[0]); + if (p->policyvers < POLICYDB_VERSION_MIN || + p->policyvers > POLICYDB_VERSION_MAX) { + printk(KERN_ERR "SELinux: policydb version %d does not match " + "my version range %d-%d\n", + le32_to_cpu(buf[0]), POLICYDB_VERSION_MIN, POLICYDB_VERSION_MAX); + goto bad; + } + + if ((le32_to_cpu(buf[1]) & POLICYDB_CONFIG_MLS)) { + p->mls_enabled = 1; + + rc = -EINVAL; + if (p->policyvers < POLICYDB_VERSION_MLS) { + printk(KERN_ERR "SELinux: security policydb version %d " + "(MLS) not backwards compatible\n", + p->policyvers); + goto bad; + } + } + p->reject_unknown = !!(le32_to_cpu(buf[1]) & REJECT_UNKNOWN); + p->allow_unknown = !!(le32_to_cpu(buf[1]) & ALLOW_UNKNOWN); + + if (p->policyvers >= POLICYDB_VERSION_POLCAP) { + rc = ebitmap_read(&p->policycaps, fp); + if (rc) + goto bad; + } + + if (p->policyvers >= POLICYDB_VERSION_PERMISSIVE) { + rc = ebitmap_read(&p->permissive_map, fp); + if (rc) + goto bad; + } + + rc = -EINVAL; + info = policydb_lookup_compat(p->policyvers); + if (!info) { + printk(KERN_ERR "SELinux: unable to find policy compat info " + "for version %d\n", p->policyvers); + goto bad; + } + + rc = -EINVAL; + if (le32_to_cpu(buf[2]) != info->sym_num || + le32_to_cpu(buf[3]) != info->ocon_num) { + printk(KERN_ERR "SELinux: policydb table sizes (%d,%d) do " + "not match mine (%d,%d)\n", le32_to_cpu(buf[2]), + le32_to_cpu(buf[3]), + info->sym_num, info->ocon_num); + goto bad; + } + + for (i = 0; i < info->sym_num; i++) { + rc = next_entry(buf, fp, sizeof(u32)*2); + if (rc) + goto bad; + nprim = le32_to_cpu(buf[0]); + nel = le32_to_cpu(buf[1]); + for (j = 0; j < nel; j++) { + rc = read_f[i](p, p->symtab[i].table, fp); + if (rc) + goto bad; + } + + p->symtab[i].nprim = nprim; + } + + rc = -EINVAL; + p->process_class = string_to_security_class(p, "process"); + if (!p->process_class) + goto bad; + + rc = avtab_read(&p->te_avtab, fp, p); + if (rc) + goto bad; + + if (p->policyvers >= POLICYDB_VERSION_BOOL) { + rc = cond_read_list(p, fp); + if (rc) + goto bad; + } + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto bad; + nel = le32_to_cpu(buf[0]); + ltr = NULL; + for (i = 0; i < nel; i++) { + rc = -ENOMEM; + tr = kzalloc(sizeof(*tr), GFP_KERNEL); + if (!tr) + goto bad; + if (ltr) + ltr->next = tr; + else + p->role_tr = tr; + rc = next_entry(buf, fp, sizeof(u32)*3); + if (rc) + goto bad; + + rc = -EINVAL; + tr->role = le32_to_cpu(buf[0]); + tr->type = le32_to_cpu(buf[1]); + tr->new_role = le32_to_cpu(buf[2]); + if (p->policyvers >= POLICYDB_VERSION_ROLETRANS) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto bad; + tr->tclass = le32_to_cpu(buf[0]); + } else + tr->tclass = p->process_class; + + if (!policydb_role_isvalid(p, tr->role) || + !policydb_type_isvalid(p, tr->type) || + !policydb_class_isvalid(p, tr->tclass) || + !policydb_role_isvalid(p, tr->new_role)) + goto bad; + ltr = tr; + } + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto bad; + nel = le32_to_cpu(buf[0]); + lra = NULL; + for (i = 0; i < nel; i++) { + rc = -ENOMEM; + ra = kzalloc(sizeof(*ra), GFP_KERNEL); + if (!ra) + goto bad; + if (lra) + lra->next = ra; + else + p->role_allow = ra; + rc = next_entry(buf, fp, sizeof(u32)*2); + if (rc) + goto bad; + + rc = -EINVAL; + ra->role = le32_to_cpu(buf[0]); + ra->new_role = le32_to_cpu(buf[1]); + if (!policydb_role_isvalid(p, ra->role) || + !policydb_role_isvalid(p, ra->new_role)) + goto bad; + lra = ra; + } + + rc = filename_trans_read(p, fp); + if (rc) + goto bad; + + rc = policydb_index(p); + if (rc) + goto bad; + + rc = -EINVAL; + p->process_trans_perms = string_to_av_perm(p, p->process_class, "transition"); + p->process_trans_perms |= string_to_av_perm(p, p->process_class, "dyntransition"); + if (!p->process_trans_perms) + goto bad; + + rc = ocontext_read(p, info, fp); + if (rc) + goto bad; + + rc = genfs_read(p, fp); + if (rc) + goto bad; + + rc = range_read(p, fp); + if (rc) + goto bad; + + 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; + + /* 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, + GFP_KERNEL | __GFP_ZERO); + if (rc) + goto bad; + + for (i = 0; i < p->p_types.nprim; 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) { + rc = ebitmap_read(e, fp); + if (rc) + goto bad; + } + /* add the type itself as the degenerate case */ + rc = ebitmap_set_bit(e, i, 1); + if (rc) + goto bad; + } + + rc = policydb_bounds_sanity_check(p); + if (rc) + goto bad; + + rc = 0; +out: + return rc; +bad: + policydb_destroy(p); + goto out; +} + +/* + * Write a MLS level structure to a policydb binary + * representation file. + */ +static int mls_write_level(struct mls_level *l, void *fp) +{ + __le32 buf[1]; + int rc; + + buf[0] = cpu_to_le32(l->sens); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + rc = ebitmap_write(&l->cat, fp); + if (rc) + return rc; + + return 0; +} + +/* + * Write a MLS range structure to a policydb binary + * representation file. + */ +static int mls_write_range_helper(struct mls_range *r, void *fp) +{ + __le32 buf[3]; + size_t items; + int rc, eq; + + eq = mls_level_eq(&r->level[1], &r->level[0]); + + if (eq) + items = 2; + else + items = 3; + buf[0] = cpu_to_le32(items-1); + buf[1] = cpu_to_le32(r->level[0].sens); + if (!eq) + buf[2] = cpu_to_le32(r->level[1].sens); + + BUG_ON(items > (sizeof(buf)/sizeof(buf[0]))); + + rc = put_entry(buf, sizeof(u32), items, fp); + if (rc) + return rc; + + rc = ebitmap_write(&r->level[0].cat, fp); + if (rc) + return rc; + if (!eq) { + rc = ebitmap_write(&r->level[1].cat, fp); + if (rc) + return rc; + } + + return 0; +} + +static int sens_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct level_datum *levdatum = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + __le32 buf[2]; + size_t len; + int rc; + + len = strlen(key); + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(levdatum->isalias); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + rc = mls_write_level(levdatum->level, fp); + if (rc) + return rc; + + return 0; +} + +static int cat_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct cat_datum *catdatum = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + __le32 buf[3]; + size_t len; + int rc; + + len = strlen(key); + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(catdatum->value); + buf[2] = cpu_to_le32(catdatum->isalias); + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + return 0; +} + +static int role_trans_write(struct policydb *p, void *fp) +{ + struct role_trans *r = p->role_tr; + struct role_trans *tr; + u32 buf[3]; + size_t nel; + int rc; + + nel = 0; + for (tr = r; tr; tr = tr->next) + nel++; + buf[0] = cpu_to_le32(nel); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + for (tr = r; tr; tr = tr->next) { + buf[0] = cpu_to_le32(tr->role); + buf[1] = cpu_to_le32(tr->type); + buf[2] = cpu_to_le32(tr->new_role); + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + if (p->policyvers >= POLICYDB_VERSION_ROLETRANS) { + buf[0] = cpu_to_le32(tr->tclass); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + } + } + + return 0; +} + +static int role_allow_write(struct role_allow *r, void *fp) +{ + struct role_allow *ra; + u32 buf[2]; + size_t nel; + int rc; + + nel = 0; + for (ra = r; ra; ra = ra->next) + nel++; + buf[0] = cpu_to_le32(nel); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + for (ra = r; ra; ra = ra->next) { + buf[0] = cpu_to_le32(ra->role); + buf[1] = cpu_to_le32(ra->new_role); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + } + return 0; +} + +/* + * Write a security context structure + * to a policydb binary representation file. + */ +static int context_write(struct policydb *p, struct context *c, + void *fp) +{ + int rc; + __le32 buf[3]; + + buf[0] = cpu_to_le32(c->user); + buf[1] = cpu_to_le32(c->role); + buf[2] = cpu_to_le32(c->type); + + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + + rc = mls_write_range_helper(&c->range, fp); + if (rc) + return rc; + + return 0; +} + +/* + * The following *_write functions are used to + * write the symbol data to a policy database + * binary representation file. + */ + +static int perm_write(void *vkey, void *datum, void *fp) +{ + char *key = vkey; + struct perm_datum *perdatum = datum; + __le32 buf[2]; + size_t len; + int rc; + + len = strlen(key); + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(perdatum->value); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + return 0; +} + +static int common_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct common_datum *comdatum = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + __le32 buf[4]; + size_t len; + int rc; + + len = strlen(key); + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(comdatum->value); + buf[2] = cpu_to_le32(comdatum->permissions.nprim); + buf[3] = cpu_to_le32(comdatum->permissions.table->nel); + rc = put_entry(buf, sizeof(u32), 4, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + rc = hashtab_map(comdatum->permissions.table, perm_write, fp); + if (rc) + return rc; + + return 0; +} + +static int write_cons_helper(struct policydb *p, struct constraint_node *node, + void *fp) +{ + struct constraint_node *c; + struct constraint_expr *e; + __le32 buf[3]; + u32 nel; + int rc; + + for (c = node; c; c = c->next) { + nel = 0; + for (e = c->expr; e; e = e->next) + nel++; + buf[0] = cpu_to_le32(c->permissions); + buf[1] = cpu_to_le32(nel); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + for (e = c->expr; e; e = e->next) { + buf[0] = cpu_to_le32(e->expr_type); + buf[1] = cpu_to_le32(e->attr); + buf[2] = cpu_to_le32(e->op); + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + + switch (e->expr_type) { + case CEXPR_NAMES: + rc = ebitmap_write(&e->names, fp); + if (rc) + return rc; + break; + default: + break; + } + } + } + + return 0; +} + +static int class_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct class_datum *cladatum = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + struct policydb *p = pd->p; + struct constraint_node *c; + __le32 buf[6]; + u32 ncons; + size_t len, len2; + int rc; + + len = strlen(key); + if (cladatum->comkey) + len2 = strlen(cladatum->comkey); + else + len2 = 0; + + ncons = 0; + for (c = cladatum->constraints; c; c = c->next) + ncons++; + + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(len2); + buf[2] = cpu_to_le32(cladatum->value); + buf[3] = cpu_to_le32(cladatum->permissions.nprim); + if (cladatum->permissions.table) + buf[4] = cpu_to_le32(cladatum->permissions.table->nel); + else + buf[4] = 0; + buf[5] = cpu_to_le32(ncons); + rc = put_entry(buf, sizeof(u32), 6, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + if (cladatum->comkey) { + rc = put_entry(cladatum->comkey, 1, len2, fp); + if (rc) + return rc; + } + + rc = hashtab_map(cladatum->permissions.table, perm_write, fp); + if (rc) + return rc; + + rc = write_cons_helper(p, cladatum->constraints, fp); + if (rc) + return rc; + + /* write out the validatetrans rule */ + ncons = 0; + for (c = cladatum->validatetrans; c; c = c->next) + ncons++; + + buf[0] = cpu_to_le32(ncons); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + rc = write_cons_helper(p, cladatum->validatetrans, fp); + if (rc) + return rc; + + return 0; +} + +static int role_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct role_datum *role = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + struct policydb *p = pd->p; + __le32 buf[3]; + size_t items, len; + int rc; + + len = strlen(key); + items = 0; + buf[items++] = cpu_to_le32(len); + buf[items++] = cpu_to_le32(role->value); + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + buf[items++] = cpu_to_le32(role->bounds); + + BUG_ON(items > (sizeof(buf)/sizeof(buf[0]))); + + rc = put_entry(buf, sizeof(u32), items, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + rc = ebitmap_write(&role->dominates, fp); + if (rc) + return rc; + + rc = ebitmap_write(&role->types, fp); + if (rc) + return rc; + + return 0; +} + +static int type_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct type_datum *typdatum = datum; + struct policy_data *pd = ptr; + struct policydb *p = pd->p; + void *fp = pd->fp; + __le32 buf[4]; + int rc; + size_t items, len; + + len = strlen(key); + items = 0; + buf[items++] = cpu_to_le32(len); + buf[items++] = cpu_to_le32(typdatum->value); + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) { + u32 properties = 0; + + if (typdatum->primary) + properties |= TYPEDATUM_PROPERTY_PRIMARY; + + if (typdatum->attribute) + properties |= TYPEDATUM_PROPERTY_ATTRIBUTE; + + buf[items++] = cpu_to_le32(properties); + buf[items++] = cpu_to_le32(typdatum->bounds); + } else { + buf[items++] = cpu_to_le32(typdatum->primary); + } + BUG_ON(items > (sizeof(buf) / sizeof(buf[0]))); + rc = put_entry(buf, sizeof(u32), items, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + return 0; +} + +static int user_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct user_datum *usrdatum = datum; + struct policy_data *pd = ptr; + struct policydb *p = pd->p; + void *fp = pd->fp; + __le32 buf[3]; + size_t items, len; + int rc; + + len = strlen(key); + items = 0; + buf[items++] = cpu_to_le32(len); + buf[items++] = cpu_to_le32(usrdatum->value); + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + buf[items++] = cpu_to_le32(usrdatum->bounds); + BUG_ON(items > (sizeof(buf) / sizeof(buf[0]))); + rc = put_entry(buf, sizeof(u32), items, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + rc = ebitmap_write(&usrdatum->roles, fp); + if (rc) + return rc; + + rc = mls_write_range_helper(&usrdatum->range, fp); + if (rc) + return rc; + + rc = mls_write_level(&usrdatum->dfltlevel, fp); + if (rc) + return rc; + + return 0; +} + +static int (*write_f[SYM_NUM]) (void *key, void *datum, + void *datap) = +{ + common_write, + class_write, + role_write, + type_write, + user_write, + cond_write_bool, + sens_write, + cat_write, +}; + +static int ocontext_write(struct policydb *p, struct policydb_compat_info *info, + void *fp) +{ + unsigned int i, j, rc; + size_t nel, len; + __le32 buf[3]; + u32 nodebuf[8]; + struct ocontext *c; + for (i = 0; i < info->ocon_num; i++) { + nel = 0; + for (c = p->ocontexts[i]; c; c = c->next) + nel++; + buf[0] = cpu_to_le32(nel); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + for (c = p->ocontexts[i]; c; c = c->next) { + switch (i) { + case OCON_ISID: + buf[0] = cpu_to_le32(c->sid[0]); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + break; + case OCON_FS: + case OCON_NETIF: + len = strlen(c->u.name); + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + rc = put_entry(c->u.name, 1, len, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + rc = context_write(p, &c->context[1], fp); + if (rc) + return rc; + break; + case OCON_PORT: + buf[0] = cpu_to_le32(c->u.port.protocol); + buf[1] = cpu_to_le32(c->u.port.low_port); + buf[2] = cpu_to_le32(c->u.port.high_port); + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + break; + case OCON_NODE: + nodebuf[0] = c->u.node.addr; /* network order */ + nodebuf[1] = c->u.node.mask; /* network order */ + rc = put_entry(nodebuf, sizeof(u32), 2, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + break; + case OCON_FSUSE: + buf[0] = cpu_to_le32(c->v.behavior); + len = strlen(c->u.name); + buf[1] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + rc = put_entry(c->u.name, 1, len, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + break; + case OCON_NODE6: + for (j = 0; j < 4; j++) + nodebuf[j] = c->u.node6.addr[j]; /* network order */ + for (j = 0; j < 4; j++) + nodebuf[j + 4] = c->u.node6.mask[j]; /* network order */ + rc = put_entry(nodebuf, sizeof(u32), 8, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + break; + } + } + } + return 0; +} + +static int genfs_write(struct policydb *p, void *fp) +{ + struct genfs *genfs; + struct ocontext *c; + size_t len; + __le32 buf[1]; + int rc; + + len = 0; + for (genfs = p->genfs; genfs; genfs = genfs->next) + len++; + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + for (genfs = p->genfs; genfs; genfs = genfs->next) { + len = strlen(genfs->fstype); + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + rc = put_entry(genfs->fstype, 1, len, fp); + if (rc) + return rc; + len = 0; + for (c = genfs->head; c; c = c->next) + len++; + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + for (c = genfs->head; c; c = c->next) { + len = strlen(c->u.name); + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + rc = put_entry(c->u.name, 1, len, fp); + if (rc) + return rc; + buf[0] = cpu_to_le32(c->v.sclass); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + } + } + return 0; +} + +static int hashtab_cnt(void *key, void *data, void *ptr) +{ + int *cnt = ptr; + *cnt = *cnt + 1; + + return 0; +} + +static int range_write_helper(void *key, void *data, void *ptr) +{ + __le32 buf[2]; + struct range_trans *rt = key; + struct mls_range *r = data; + struct policy_data *pd = ptr; + void *fp = pd->fp; + struct policydb *p = pd->p; + int rc; + + buf[0] = cpu_to_le32(rt->source_type); + buf[1] = cpu_to_le32(rt->target_type); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + if (p->policyvers >= POLICYDB_VERSION_RANGETRANS) { + buf[0] = cpu_to_le32(rt->target_class); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + } + rc = mls_write_range_helper(r, fp); + if (rc) + return rc; + + return 0; +} + +static int range_write(struct policydb *p, void *fp) +{ + size_t nel; + __le32 buf[1]; + int rc; + struct policy_data pd; + + pd.p = p; + pd.fp = fp; + + /* count the number of entries in the hashtab */ + nel = 0; + rc = hashtab_map(p->range_tr, hashtab_cnt, &nel); + if (rc) + return rc; + + buf[0] = cpu_to_le32(nel); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + /* actually write all of the entries */ + rc = hashtab_map(p->range_tr, range_write_helper, &pd); + if (rc) + return rc; + + return 0; +} + +static int filename_write_helper(void *key, void *data, void *ptr) +{ + __le32 buf[4]; + struct filename_trans *ft = key; + struct filename_trans_datum *otype = data; + void *fp = ptr; + int rc; + u32 len; + + len = strlen(ft->name); + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + rc = put_entry(ft->name, sizeof(char), len, fp); + if (rc) + return rc; + + buf[0] = ft->stype; + buf[1] = ft->ttype; + buf[2] = ft->tclass; + buf[3] = otype->otype; + + rc = put_entry(buf, sizeof(u32), 4, fp); + if (rc) + return rc; + + return 0; +} + +static int filename_trans_write(struct policydb *p, void *fp) +{ + u32 nel; + __le32 buf[1]; + int rc; + + if (p->policyvers < POLICYDB_VERSION_FILENAME_TRANS) + return 0; + + nel = 0; + rc = hashtab_map(p->filename_trans, hashtab_cnt, &nel); + if (rc) + return rc; + + buf[0] = cpu_to_le32(nel); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + rc = hashtab_map(p->filename_trans, filename_write_helper, fp); + if (rc) + return rc; + + return 0; +} + +/* + * Write the configuration data in a policy database + * structure to a policy database binary representation + * file. + */ +int policydb_write(struct policydb *p, void *fp) +{ + unsigned int i, num_syms; + int rc; + __le32 buf[4]; + u32 config; + size_t len; + struct policydb_compat_info *info; + + /* + * refuse to write policy older than compressed avtab + * to simplify the writer. There are other tests dropped + * since we assume this throughout the writer code. Be + * careful if you ever try to remove this restriction + */ + if (p->policyvers < POLICYDB_VERSION_AVTAB) { + printk(KERN_ERR "SELinux: refusing to write policy version %d." + " Because it is less than version %d\n", p->policyvers, + POLICYDB_VERSION_AVTAB); + return -EINVAL; + } + + config = 0; + if (p->mls_enabled) + config |= POLICYDB_CONFIG_MLS; + + if (p->reject_unknown) + config |= REJECT_UNKNOWN; + if (p->allow_unknown) + config |= ALLOW_UNKNOWN; + + /* Write the magic number and string identifiers. */ + buf[0] = cpu_to_le32(POLICYDB_MAGIC); + len = strlen(POLICYDB_STRING); + buf[1] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + rc = put_entry(POLICYDB_STRING, 1, len, fp); + if (rc) + return rc; + + /* Write the version, config, and table sizes. */ + info = policydb_lookup_compat(p->policyvers); + if (!info) { + printk(KERN_ERR "SELinux: compatibility lookup failed for policy " + "version %d", p->policyvers); + return -EINVAL; + } + + buf[0] = cpu_to_le32(p->policyvers); + buf[1] = cpu_to_le32(config); + buf[2] = cpu_to_le32(info->sym_num); + buf[3] = cpu_to_le32(info->ocon_num); + + rc = put_entry(buf, sizeof(u32), 4, fp); + if (rc) + return rc; + + if (p->policyvers >= POLICYDB_VERSION_POLCAP) { + rc = ebitmap_write(&p->policycaps, fp); + if (rc) + return rc; + } + + if (p->policyvers >= POLICYDB_VERSION_PERMISSIVE) { + rc = ebitmap_write(&p->permissive_map, fp); + if (rc) + return rc; + } + + num_syms = info->sym_num; + for (i = 0; i < num_syms; i++) { + struct policy_data pd; + + pd.fp = fp; + pd.p = p; + + buf[0] = cpu_to_le32(p->symtab[i].nprim); + buf[1] = cpu_to_le32(p->symtab[i].table->nel); + + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + rc = hashtab_map(p->symtab[i].table, write_f[i], &pd); + if (rc) + return rc; + } + + rc = avtab_write(p, &p->te_avtab, fp); + if (rc) + return rc; + + rc = cond_write_list(p, p->cond_list, fp); + if (rc) + return rc; + + rc = role_trans_write(p, fp); + if (rc) + return rc; + + rc = role_allow_write(p->role_allow, fp); + if (rc) + return rc; + + rc = filename_trans_write(p, fp); + if (rc) + return rc; + + rc = ocontext_write(p, info, fp); + if (rc) + return rc; + + rc = genfs_write(p, fp); + if (rc) + return rc; + + rc = range_write(p, fp); + if (rc) + return rc; + + for (i = 0; i < p->p_types.nprim; i++) { + struct ebitmap *e = flex_array_get(p->type_attr_map_array, i); + + BUG_ON(!e); + rc = ebitmap_write(e, fp); + if (rc) + return rc; + } + + return 0; +} diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h new file mode 100644 index 00000000..b846c038 --- /dev/null +++ b/security/selinux/ss/policydb.h @@ -0,0 +1,345 @@ +/* + * A policy database (policydb) specifies the + * configuration data for the security policy. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ + +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + * 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. + */ + +#ifndef _SS_POLICYDB_H_ +#define _SS_POLICYDB_H_ + +#include <linux/flex_array.h> + +#include "symtab.h" +#include "avtab.h" +#include "sidtab.h" +#include "ebitmap.h" +#include "mls_types.h" +#include "context.h" +#include "constraint.h" + +/* + * A datum type is defined for each kind of symbol + * in the configuration data: individual permissions, + * common prefixes for access vectors, classes, + * users, roles, types, sensitivities, categories, etc. + */ + +/* Permission attributes */ +struct perm_datum { + u32 value; /* permission bit + 1 */ +}; + +/* Attributes of a common prefix for access vectors */ +struct common_datum { + u32 value; /* internal common value */ + struct symtab permissions; /* common permissions */ +}; + +/* Class attributes */ +struct class_datum { + u32 value; /* class value */ + char *comkey; /* common name */ + struct common_datum *comdatum; /* common datum */ + struct symtab permissions; /* class-specific permission symbol table */ + struct constraint_node *constraints; /* constraints on class permissions */ + struct constraint_node *validatetrans; /* special transition rules */ +}; + +/* Role attributes */ +struct role_datum { + u32 value; /* internal role value */ + u32 bounds; /* boundary of role */ + struct ebitmap dominates; /* set of roles dominated by this role */ + struct ebitmap types; /* set of authorized types for role */ +}; + +struct role_trans { + u32 role; /* current role */ + u32 type; /* program executable type, or new object type */ + u32 tclass; /* process class, or new object class */ + u32 new_role; /* new role */ + struct role_trans *next; +}; + +struct filename_trans { + u32 stype; /* current process */ + u32 ttype; /* parent dir context */ + u16 tclass; /* class of new object */ + const char *name; /* last path component */ +}; + +struct filename_trans_datum { + u32 otype; /* expected of new object */ +}; + +struct role_allow { + u32 role; /* current role */ + u32 new_role; /* new role */ + struct role_allow *next; +}; + +/* Type attributes */ +struct type_datum { + u32 value; /* internal type value */ + u32 bounds; /* boundary of type */ + unsigned char primary; /* primary name? */ + unsigned char attribute;/* attribute ?*/ +}; + +/* User attributes */ +struct user_datum { + u32 value; /* internal user value */ + u32 bounds; /* bounds of user */ + struct ebitmap roles; /* set of authorized roles for user */ + struct mls_range range; /* MLS range (min - max) for user */ + struct mls_level dfltlevel; /* default login MLS level for user */ +}; + + +/* Sensitivity attributes */ +struct level_datum { + struct mls_level *level; /* sensitivity and associated categories */ + unsigned char isalias; /* is this sensitivity an alias for another? */ +}; + +/* Category attributes */ +struct cat_datum { + u32 value; /* internal category bit + 1 */ + unsigned char isalias; /* is this category an alias for another? */ +}; + +struct range_trans { + u32 source_type; + u32 target_type; + u32 target_class; +}; + +/* Boolean data type */ +struct cond_bool_datum { + __u32 value; /* internal type value */ + int state; +}; + +struct cond_node; + +/* + * The configuration data includes security contexts for + * initial SIDs, unlabeled file systems, TCP and UDP port numbers, + * network interfaces, and nodes. This structure stores the + * relevant data for one such entry. Entries of the same kind + * (e.g. all initial SIDs) are linked together into a list. + */ +struct ocontext { + union { + char *name; /* name of initial SID, fs, netif, fstype, path */ + struct { + u8 protocol; + u16 low_port; + u16 high_port; + } port; /* TCP or UDP port information */ + struct { + u32 addr; + u32 mask; + } node; /* node information */ + struct { + u32 addr[4]; + u32 mask[4]; + } node6; /* IPv6 node information */ + } u; + union { + u32 sclass; /* security class for genfs */ + u32 behavior; /* labeling behavior for fs_use */ + } v; + struct context context[2]; /* security context(s) */ + u32 sid[2]; /* SID(s) */ + struct ocontext *next; +}; + +struct genfs { + char *fstype; + struct ocontext *head; + struct genfs *next; +}; + +/* symbol table array indices */ +#define SYM_COMMONS 0 +#define SYM_CLASSES 1 +#define SYM_ROLES 2 +#define SYM_TYPES 3 +#define SYM_USERS 4 +#define SYM_BOOLS 5 +#define SYM_LEVELS 6 +#define SYM_CATS 7 +#define SYM_NUM 8 + +/* object context array indices */ +#define OCON_ISID 0 /* initial SIDs */ +#define OCON_FS 1 /* unlabeled file systems */ +#define OCON_PORT 2 /* TCP and UDP port numbers */ +#define OCON_NETIF 3 /* network interfaces */ +#define OCON_NODE 4 /* nodes */ +#define OCON_FSUSE 5 /* fs_use */ +#define OCON_NODE6 6 /* IPv6 nodes */ +#define OCON_NUM 7 + +/* The policy database */ +struct policydb { + int mls_enabled; + + /* symbol tables */ + struct symtab symtab[SYM_NUM]; +#define p_commons symtab[SYM_COMMONS] +#define p_classes symtab[SYM_CLASSES] +#define p_roles symtab[SYM_ROLES] +#define p_types symtab[SYM_TYPES] +#define p_users symtab[SYM_USERS] +#define p_bools symtab[SYM_BOOLS] +#define p_levels symtab[SYM_LEVELS] +#define p_cats symtab[SYM_CATS] + + /* symbol names indexed by (value - 1) */ + struct flex_array *sym_val_to_name[SYM_NUM]; + + /* class, role, and user attributes indexed by (value - 1) */ + struct class_datum **class_val_to_struct; + struct role_datum **role_val_to_struct; + struct user_datum **user_val_to_struct; + struct flex_array *type_val_to_struct_array; + + /* type enforcement access vectors and transitions */ + struct avtab te_avtab; + + /* role transitions */ + struct role_trans *role_tr; + + /* file transitions with the last path component */ + /* quickly exclude lookups when parent ttype has no rules */ + struct ebitmap filename_trans_ttypes; + /* actual set of filename_trans rules */ + struct hashtab *filename_trans; + + /* bools indexed by (value - 1) */ + struct cond_bool_datum **bool_val_to_struct; + /* type enforcement conditional access vectors and transitions */ + struct avtab te_cond_avtab; + /* linked list indexing te_cond_avtab by conditional */ + struct cond_node *cond_list; + + /* role allows */ + struct role_allow *role_allow; + + /* security contexts of initial SIDs, unlabeled file systems, + TCP or UDP port numbers, network interfaces and nodes */ + struct ocontext *ocontexts[OCON_NUM]; + + /* security contexts for files in filesystems that cannot support + a persistent label mapping or use another + fixed labeling behavior. */ + struct genfs *genfs; + + /* range transitions table (range_trans_key -> mls_range) */ + struct hashtab *range_tr; + + /* type -> attribute reverse mapping */ + struct flex_array *type_attr_map_array; + + struct ebitmap policycaps; + + struct ebitmap permissive_map; + + /* length of this policy when it was loaded */ + size_t len; + + unsigned int policyvers; + + unsigned int reject_unknown : 1; + unsigned int allow_unknown : 1; + + u16 process_class; + u32 process_trans_perms; +}; + +extern void policydb_destroy(struct policydb *p); +extern int policydb_load_isids(struct policydb *p, struct sidtab *s); +extern int policydb_context_isvalid(struct policydb *p, struct context *c); +extern int policydb_class_isvalid(struct policydb *p, unsigned int class); +extern int policydb_type_isvalid(struct policydb *p, unsigned int type); +extern int policydb_role_isvalid(struct policydb *p, unsigned int role); +extern int policydb_read(struct policydb *p, void *fp); +extern int policydb_write(struct policydb *p, void *fp); + +#define PERM_SYMTAB_SIZE 32 + +#define POLICYDB_CONFIG_MLS 1 + +/* the config flags related to unknown classes/perms are bits 2 and 3 */ +#define REJECT_UNKNOWN 0x00000002 +#define ALLOW_UNKNOWN 0x00000004 + +#define OBJECT_R "object_r" +#define OBJECT_R_VAL 1 + +#define POLICYDB_MAGIC SELINUX_MAGIC +#define POLICYDB_STRING "SE Linux" + +struct policy_file { + char *data; + size_t len; +}; + +struct policy_data { + struct policydb *p; + void *fp; +}; + +static inline int next_entry(void *buf, struct policy_file *fp, size_t bytes) +{ + if (bytes > fp->len) + return -EINVAL; + + memcpy(buf, fp->data, bytes); + fp->data += bytes; + fp->len -= bytes; + return 0; +} + +static inline int put_entry(const void *buf, size_t bytes, int num, struct policy_file *fp) +{ + size_t len = bytes * num; + + memcpy(fp->data, buf, len); + fp->data += len; + fp->len -= len; + + return 0; +} + +static inline char *sym_name(struct policydb *p, unsigned int sym_num, unsigned int element_nr) +{ + struct flex_array *fa = p->sym_val_to_name[sym_num]; + + return flex_array_get_ptr(fa, element_nr); +} + +extern u16 string_to_security_class(struct policydb *p, const char *name); +extern u32 string_to_av_perm(struct policydb *p, u16 tclass, const char *name); + +#endif /* _SS_POLICYDB_H_ */ + diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c new file mode 100644 index 00000000..973e00e3 --- /dev/null +++ b/security/selinux/ss/services.c @@ -0,0 +1,3229 @@ +/* + * Implementation of the security services. + * + * Authors : Stephen Smalley, <sds@epoch.ncsc.mil> + * James Morris <jmorris@redhat.com> + * + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * Support for context based audit filters. + * + * Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Updated: Hewlett-Packard <paul.moore@hp.com> + * + * Added support for NetLabel + * Added support for the policy capability bitmap + * + * Updated: Chad Sellers <csellers@tresys.com> + * + * Added validation of kernel classes and permissions + * + * Updated: KaiGai Kohei <kaigai@ak.jp.nec.com> + * + * Added support for bounds domain and audit messaged on masked permissions + * + * Updated: Guido Trentalancia <guido@trentalancia.com> + * + * Added support for runtime switching of the policy type + * + * Copyright (C) 2008, 2009 NEC Corporation + * Copyright (C) 2006, 2007 Hewlett-Packard Development Company, L.P. + * Copyright (C) 2004-2006 Trusted Computer Solutions, Inc. + * Copyright (C) 2003 - 2004, 2006 Tresys Technology, LLC + * Copyright (C) 2003 Red Hat, Inc., James Morris <jmorris@redhat.com> + * 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. + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/spinlock.h> +#include <linux/rcupdate.h> +#include <linux/errno.h> +#include <linux/in.h> +#include <linux/sched.h> +#include <linux/audit.h> +#include <linux/mutex.h> +#include <linux/selinux.h> +#include <linux/flex_array.h> +#include <linux/vmalloc.h> +#include <net/netlabel.h> + +#include "flask.h" +#include "avc.h" +#include "avc_ss.h" +#include "security.h" +#include "context.h" +#include "policydb.h" +#include "sidtab.h" +#include "services.h" +#include "conditional.h" +#include "mls.h" +#include "objsec.h" +#include "netlabel.h" +#include "xfrm.h" +#include "ebitmap.h" +#include "audit.h" + +extern void selnl_notify_policyload(u32 seqno); + +int selinux_policycap_netpeer; +int selinux_policycap_openperm; + +static DEFINE_RWLOCK(policy_rwlock); + +static struct sidtab sidtab; +struct policydb policydb; +int ss_initialized; + +/* + * The largest sequence number that has been used when + * providing an access decision to the access vector cache. + * The sequence number only changes when a policy change + * occurs. + */ +static u32 latest_granting; + +/* Forward declaration. */ +static int context_struct_to_string(struct context *context, char **scontext, + u32 *scontext_len); + +static void context_struct_compute_av(struct context *scontext, + struct context *tcontext, + u16 tclass, + struct av_decision *avd); + +struct selinux_mapping { + u16 value; /* policy value */ + unsigned num_perms; + u32 perms[sizeof(u32) * 8]; +}; + +static struct selinux_mapping *current_mapping; +static u16 current_mapping_size; + +static int selinux_set_mapping(struct policydb *pol, + struct security_class_mapping *map, + struct selinux_mapping **out_map_p, + u16 *out_map_size) +{ + struct selinux_mapping *out_map = NULL; + size_t size = sizeof(struct selinux_mapping); + u16 i, j; + unsigned k; + bool print_unknown_handle = false; + + /* Find number of classes in the input mapping */ + if (!map) + return -EINVAL; + i = 0; + while (map[i].name) + i++; + + /* Allocate space for the class records, plus one for class zero */ + out_map = kcalloc(++i, size, GFP_ATOMIC); + if (!out_map) + return -ENOMEM; + + /* Store the raw class and permission values */ + j = 0; + while (map[j].name) { + struct security_class_mapping *p_in = map + (j++); + struct selinux_mapping *p_out = out_map + j; + + /* An empty class string skips ahead */ + if (!strcmp(p_in->name, "")) { + p_out->num_perms = 0; + continue; + } + + p_out->value = string_to_security_class(pol, p_in->name); + if (!p_out->value) { + printk(KERN_INFO + "SELinux: Class %s not defined in policy.\n", + p_in->name); + if (pol->reject_unknown) + goto err; + p_out->num_perms = 0; + print_unknown_handle = true; + continue; + } + + k = 0; + while (p_in->perms && p_in->perms[k]) { + /* An empty permission string skips ahead */ + if (!*p_in->perms[k]) { + k++; + continue; + } + p_out->perms[k] = string_to_av_perm(pol, p_out->value, + p_in->perms[k]); + if (!p_out->perms[k]) { + printk(KERN_INFO + "SELinux: Permission %s in class %s not defined in policy.\n", + p_in->perms[k], p_in->name); + if (pol->reject_unknown) + goto err; + print_unknown_handle = true; + } + + k++; + } + p_out->num_perms = k; + } + + if (print_unknown_handle) + printk(KERN_INFO "SELinux: the above unknown classes and permissions will be %s\n", + pol->allow_unknown ? "allowed" : "denied"); + + *out_map_p = out_map; + *out_map_size = i; + return 0; +err: + kfree(out_map); + return -EINVAL; +} + +/* + * Get real, policy values from mapped values + */ + +static u16 unmap_class(u16 tclass) +{ + if (tclass < current_mapping_size) + return current_mapping[tclass].value; + + return tclass; +} + +/* + * Get kernel value for class from its policy value + */ +static u16 map_class(u16 pol_value) +{ + u16 i; + + for (i = 1; i < current_mapping_size; i++) { + if (current_mapping[i].value == pol_value) + return i; + } + + return SECCLASS_NULL; +} + +static void map_decision(u16 tclass, struct av_decision *avd, + int allow_unknown) +{ + if (tclass < current_mapping_size) { + unsigned i, n = current_mapping[tclass].num_perms; + u32 result; + + for (i = 0, result = 0; i < n; i++) { + if (avd->allowed & current_mapping[tclass].perms[i]) + result |= 1<<i; + if (allow_unknown && !current_mapping[tclass].perms[i]) + result |= 1<<i; + } + avd->allowed = result; + + for (i = 0, result = 0; i < n; i++) + if (avd->auditallow & current_mapping[tclass].perms[i]) + result |= 1<<i; + avd->auditallow = result; + + for (i = 0, result = 0; i < n; i++) { + if (avd->auditdeny & current_mapping[tclass].perms[i]) + result |= 1<<i; + if (!allow_unknown && !current_mapping[tclass].perms[i]) + result |= 1<<i; + } + /* + * In case the kernel has a bug and requests a permission + * between num_perms and the maximum permission number, we + * should audit that denial + */ + for (; i < (sizeof(u32)*8); i++) + result |= 1<<i; + avd->auditdeny = result; + } +} + +int security_mls_enabled(void) +{ + return policydb.mls_enabled; +} + +/* + * Return the boolean value of a constraint expression + * when it is applied to the specified source and target + * security contexts. + * + * xcontext is a special beast... It is used by the validatetrans rules + * only. For these rules, scontext is the context before the transition, + * tcontext is the context after the transition, and xcontext is the context + * of the process performing the transition. All other callers of + * constraint_expr_eval should pass in NULL for xcontext. + */ +static int constraint_expr_eval(struct context *scontext, + struct context *tcontext, + struct context *xcontext, + struct constraint_expr *cexpr) +{ + u32 val1, val2; + struct context *c; + struct role_datum *r1, *r2; + struct mls_level *l1, *l2; + struct constraint_expr *e; + int s[CEXPR_MAXDEPTH]; + int sp = -1; + + for (e = cexpr; e; e = e->next) { + switch (e->expr_type) { + case CEXPR_NOT: + BUG_ON(sp < 0); + s[sp] = !s[sp]; + break; + case CEXPR_AND: + BUG_ON(sp < 1); + sp--; + s[sp] &= s[sp + 1]; + break; + case CEXPR_OR: + BUG_ON(sp < 1); + sp--; + s[sp] |= s[sp + 1]; + break; + case CEXPR_ATTR: + if (sp == (CEXPR_MAXDEPTH - 1)) + return 0; + switch (e->attr) { + case CEXPR_USER: + val1 = scontext->user; + val2 = tcontext->user; + break; + case CEXPR_TYPE: + val1 = scontext->type; + val2 = tcontext->type; + break; + case CEXPR_ROLE: + val1 = scontext->role; + val2 = tcontext->role; + r1 = policydb.role_val_to_struct[val1 - 1]; + r2 = policydb.role_val_to_struct[val2 - 1]; + switch (e->op) { + case CEXPR_DOM: + s[++sp] = ebitmap_get_bit(&r1->dominates, + val2 - 1); + continue; + case CEXPR_DOMBY: + s[++sp] = ebitmap_get_bit(&r2->dominates, + val1 - 1); + continue; + case CEXPR_INCOMP: + s[++sp] = (!ebitmap_get_bit(&r1->dominates, + val2 - 1) && + !ebitmap_get_bit(&r2->dominates, + val1 - 1)); + continue; + default: + break; + } + break; + case CEXPR_L1L2: + l1 = &(scontext->range.level[0]); + l2 = &(tcontext->range.level[0]); + goto mls_ops; + case CEXPR_L1H2: + l1 = &(scontext->range.level[0]); + l2 = &(tcontext->range.level[1]); + goto mls_ops; + case CEXPR_H1L2: + l1 = &(scontext->range.level[1]); + l2 = &(tcontext->range.level[0]); + goto mls_ops; + case CEXPR_H1H2: + l1 = &(scontext->range.level[1]); + l2 = &(tcontext->range.level[1]); + goto mls_ops; + case CEXPR_L1H1: + l1 = &(scontext->range.level[0]); + l2 = &(scontext->range.level[1]); + goto mls_ops; + case CEXPR_L2H2: + l1 = &(tcontext->range.level[0]); + l2 = &(tcontext->range.level[1]); + goto mls_ops; +mls_ops: + switch (e->op) { + case CEXPR_EQ: + s[++sp] = mls_level_eq(l1, l2); + continue; + case CEXPR_NEQ: + s[++sp] = !mls_level_eq(l1, l2); + continue; + case CEXPR_DOM: + s[++sp] = mls_level_dom(l1, l2); + continue; + case CEXPR_DOMBY: + s[++sp] = mls_level_dom(l2, l1); + continue; + case CEXPR_INCOMP: + s[++sp] = mls_level_incomp(l2, l1); + continue; + default: + BUG(); + return 0; + } + break; + default: + BUG(); + return 0; + } + + switch (e->op) { + case CEXPR_EQ: + s[++sp] = (val1 == val2); + break; + case CEXPR_NEQ: + s[++sp] = (val1 != val2); + break; + default: + BUG(); + return 0; + } + break; + case CEXPR_NAMES: + if (sp == (CEXPR_MAXDEPTH-1)) + return 0; + c = scontext; + if (e->attr & CEXPR_TARGET) + c = tcontext; + else if (e->attr & CEXPR_XTARGET) { + c = xcontext; + if (!c) { + BUG(); + return 0; + } + } + if (e->attr & CEXPR_USER) + val1 = c->user; + else if (e->attr & CEXPR_ROLE) + val1 = c->role; + else if (e->attr & CEXPR_TYPE) + val1 = c->type; + else { + BUG(); + return 0; + } + + switch (e->op) { + case CEXPR_EQ: + s[++sp] = ebitmap_get_bit(&e->names, val1 - 1); + break; + case CEXPR_NEQ: + s[++sp] = !ebitmap_get_bit(&e->names, val1 - 1); + break; + default: + BUG(); + return 0; + } + break; + default: + BUG(); + return 0; + } + } + + BUG_ON(sp != 0); + return s[0]; +} + +/* + * security_dump_masked_av - dumps masked permissions during + * security_compute_av due to RBAC, MLS/Constraint and Type bounds. + */ +static int dump_masked_av_helper(void *k, void *d, void *args) +{ + struct perm_datum *pdatum = d; + char **permission_names = args; + + BUG_ON(pdatum->value < 1 || pdatum->value > 32); + + permission_names[pdatum->value - 1] = (char *)k; + + return 0; +} + +static void security_dump_masked_av(struct context *scontext, + struct context *tcontext, + u16 tclass, + u32 permissions, + const char *reason) +{ + struct common_datum *common_dat; + struct class_datum *tclass_dat; + struct audit_buffer *ab; + char *tclass_name; + char *scontext_name = NULL; + char *tcontext_name = NULL; + char *permission_names[32]; + int index; + u32 length; + bool need_comma = false; + + if (!permissions) + return; + + tclass_name = sym_name(&policydb, SYM_CLASSES, tclass - 1); + tclass_dat = policydb.class_val_to_struct[tclass - 1]; + common_dat = tclass_dat->comdatum; + + /* init permission_names */ + if (common_dat && + hashtab_map(common_dat->permissions.table, + dump_masked_av_helper, permission_names) < 0) + goto out; + + if (hashtab_map(tclass_dat->permissions.table, + dump_masked_av_helper, permission_names) < 0) + goto out; + + /* get scontext/tcontext in text form */ + if (context_struct_to_string(scontext, + &scontext_name, &length) < 0) + goto out; + + if (context_struct_to_string(tcontext, + &tcontext_name, &length) < 0) + goto out; + + /* audit a message */ + ab = audit_log_start(current->audit_context, + GFP_ATOMIC, AUDIT_SELINUX_ERR); + if (!ab) + goto out; + + audit_log_format(ab, "op=security_compute_av reason=%s " + "scontext=%s tcontext=%s tclass=%s perms=", + reason, scontext_name, tcontext_name, tclass_name); + + for (index = 0; index < 32; index++) { + u32 mask = (1 << index); + + if ((mask & permissions) == 0) + continue; + + audit_log_format(ab, "%s%s", + need_comma ? "," : "", + permission_names[index] + ? permission_names[index] : "????"); + need_comma = true; + } + audit_log_end(ab); +out: + /* release scontext/tcontext */ + kfree(tcontext_name); + kfree(scontext_name); + + return; +} + +/* + * security_boundary_permission - drops violated permissions + * on boundary constraint. + */ +static void type_attribute_bounds_av(struct context *scontext, + struct context *tcontext, + u16 tclass, + struct av_decision *avd) +{ + struct context lo_scontext; + struct context lo_tcontext; + struct av_decision lo_avd; + struct type_datum *source; + struct type_datum *target; + u32 masked = 0; + + source = flex_array_get_ptr(policydb.type_val_to_struct_array, + scontext->type - 1); + BUG_ON(!source); + + target = flex_array_get_ptr(policydb.type_val_to_struct_array, + tcontext->type - 1); + BUG_ON(!target); + + if (source->bounds) { + memset(&lo_avd, 0, sizeof(lo_avd)); + + memcpy(&lo_scontext, scontext, sizeof(lo_scontext)); + lo_scontext.type = source->bounds; + + context_struct_compute_av(&lo_scontext, + tcontext, + tclass, + &lo_avd); + if ((lo_avd.allowed & avd->allowed) == avd->allowed) + return; /* no masked permission */ + masked = ~lo_avd.allowed & avd->allowed; + } + + if (target->bounds) { + memset(&lo_avd, 0, sizeof(lo_avd)); + + memcpy(&lo_tcontext, tcontext, sizeof(lo_tcontext)); + lo_tcontext.type = target->bounds; + + context_struct_compute_av(scontext, + &lo_tcontext, + tclass, + &lo_avd); + if ((lo_avd.allowed & avd->allowed) == avd->allowed) + return; /* no masked permission */ + masked = ~lo_avd.allowed & avd->allowed; + } + + if (source->bounds && target->bounds) { + memset(&lo_avd, 0, sizeof(lo_avd)); + /* + * lo_scontext and lo_tcontext are already + * set up. + */ + + context_struct_compute_av(&lo_scontext, + &lo_tcontext, + tclass, + &lo_avd); + if ((lo_avd.allowed & avd->allowed) == avd->allowed) + return; /* no masked permission */ + masked = ~lo_avd.allowed & avd->allowed; + } + + if (masked) { + /* mask violated permissions */ + avd->allowed &= ~masked; + + /* audit masked permissions */ + security_dump_masked_av(scontext, tcontext, + tclass, masked, "bounds"); + } +} + +/* + * Compute access vectors based on a context structure pair for + * the permissions in a particular class. + */ +static void context_struct_compute_av(struct context *scontext, + struct context *tcontext, + u16 tclass, + struct av_decision *avd) +{ + struct constraint_node *constraint; + struct role_allow *ra; + struct avtab_key avkey; + struct avtab_node *node; + struct class_datum *tclass_datum; + struct ebitmap *sattr, *tattr; + struct ebitmap_node *snode, *tnode; + unsigned int i, j; + + avd->allowed = 0; + avd->auditallow = 0; + avd->auditdeny = 0xffffffff; + + if (unlikely(!tclass || tclass > policydb.p_classes.nprim)) { + if (printk_ratelimit()) + printk(KERN_WARNING "SELinux: Invalid class %hu\n", tclass); + return; + } + + tclass_datum = policydb.class_val_to_struct[tclass - 1]; + + /* + * If a specific type enforcement rule was defined for + * this permission check, then use it. + */ + avkey.target_class = tclass; + avkey.specified = AVTAB_AV; + 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; + avkey.target_type = j + 1; + for (node = avtab_search_node(&policydb.te_avtab, &avkey); + node; + node = avtab_search_node_next(node, avkey.specified)) { + if (node->key.specified == AVTAB_ALLOWED) + avd->allowed |= node->datum.data; + else if (node->key.specified == AVTAB_AUDITALLOW) + avd->auditallow |= node->datum.data; + else if (node->key.specified == AVTAB_AUDITDENY) + avd->auditdeny &= node->datum.data; + } + + /* Check conditional av table for additional permissions */ + cond_compute_av(&policydb.te_cond_avtab, &avkey, avd); + + } + } + + /* + * Remove any permissions prohibited by a constraint (this includes + * the MLS policy). + */ + constraint = tclass_datum->constraints; + while (constraint) { + if ((constraint->permissions & (avd->allowed)) && + !constraint_expr_eval(scontext, tcontext, NULL, + constraint->expr)) { + avd->allowed &= ~(constraint->permissions); + } + constraint = constraint->next; + } + + /* + * If checking process transition permission and the + * role is changing, then check the (current_role, new_role) + * pair. + */ + if (tclass == policydb.process_class && + (avd->allowed & policydb.process_trans_perms) && + scontext->role != tcontext->role) { + for (ra = policydb.role_allow; ra; ra = ra->next) { + if (scontext->role == ra->role && + tcontext->role == ra->new_role) + break; + } + if (!ra) + avd->allowed &= ~policydb.process_trans_perms; + } + + /* + * If the given source and target types have boundary + * constraint, lazy checks have to mask any violated + * permission and notice it to userspace via audit. + */ + type_attribute_bounds_av(scontext, tcontext, + tclass, avd); +} + +static int security_validtrans_handle_fail(struct context *ocontext, + struct context *ncontext, + struct context *tcontext, + u16 tclass) +{ + char *o = NULL, *n = NULL, *t = NULL; + u32 olen, nlen, tlen; + + if (context_struct_to_string(ocontext, &o, &olen)) + goto out; + if (context_struct_to_string(ncontext, &n, &nlen)) + goto out; + if (context_struct_to_string(tcontext, &t, &tlen)) + goto out; + audit_log(current->audit_context, GFP_ATOMIC, AUDIT_SELINUX_ERR, + "security_validate_transition: denied for" + " oldcontext=%s newcontext=%s taskcontext=%s tclass=%s", + o, n, t, sym_name(&policydb, SYM_CLASSES, tclass-1)); +out: + kfree(o); + kfree(n); + kfree(t); + + if (!selinux_enforcing) + return 0; + return -EPERM; +} + +int security_validate_transition(u32 oldsid, u32 newsid, u32 tasksid, + u16 orig_tclass) +{ + struct context *ocontext; + struct context *ncontext; + struct context *tcontext; + struct class_datum *tclass_datum; + struct constraint_node *constraint; + u16 tclass; + int rc = 0; + + if (!ss_initialized) + return 0; + + read_lock(&policy_rwlock); + + tclass = unmap_class(orig_tclass); + + if (!tclass || tclass > policydb.p_classes.nprim) { + printk(KERN_ERR "SELinux: %s: unrecognized class %d\n", + __func__, tclass); + rc = -EINVAL; + goto out; + } + tclass_datum = policydb.class_val_to_struct[tclass - 1]; + + ocontext = sidtab_search(&sidtab, oldsid); + if (!ocontext) { + printk(KERN_ERR "SELinux: %s: unrecognized SID %d\n", + __func__, oldsid); + rc = -EINVAL; + goto out; + } + + ncontext = sidtab_search(&sidtab, newsid); + if (!ncontext) { + printk(KERN_ERR "SELinux: %s: unrecognized SID %d\n", + __func__, newsid); + rc = -EINVAL; + goto out; + } + + tcontext = sidtab_search(&sidtab, tasksid); + if (!tcontext) { + printk(KERN_ERR "SELinux: %s: unrecognized SID %d\n", + __func__, tasksid); + rc = -EINVAL; + goto out; + } + + constraint = tclass_datum->validatetrans; + while (constraint) { + if (!constraint_expr_eval(ocontext, ncontext, tcontext, + constraint->expr)) { + rc = security_validtrans_handle_fail(ocontext, ncontext, + tcontext, tclass); + goto out; + } + constraint = constraint->next; + } + +out: + read_unlock(&policy_rwlock); + return rc; +} + +/* + * security_bounded_transition - check whether the given + * transition is directed to bounded, or not. + * It returns 0, if @newsid is bounded by @oldsid. + * Otherwise, it returns error code. + * + * @oldsid : current security identifier + * @newsid : destinated security identifier + */ +int security_bounded_transition(u32 old_sid, u32 new_sid) +{ + struct context *old_context, *new_context; + struct type_datum *type; + int index; + int rc; + + read_lock(&policy_rwlock); + + rc = -EINVAL; + old_context = sidtab_search(&sidtab, old_sid); + if (!old_context) { + printk(KERN_ERR "SELinux: %s: unrecognized SID %u\n", + __func__, old_sid); + goto out; + } + + rc = -EINVAL; + new_context = sidtab_search(&sidtab, new_sid); + if (!new_context) { + printk(KERN_ERR "SELinux: %s: unrecognized SID %u\n", + __func__, new_sid); + goto out; + } + + rc = 0; + /* type/domain unchanged */ + if (old_context->type == new_context->type) + goto out; + + index = new_context->type; + while (true) { + type = flex_array_get_ptr(policydb.type_val_to_struct_array, + index - 1); + BUG_ON(!type); + + /* not bounded anymore */ + rc = -EPERM; + if (!type->bounds) + break; + + /* @newsid is bounded by @oldsid */ + rc = 0; + if (type->bounds == old_context->type) + break; + + index = type->bounds; + } + + if (rc) { + char *old_name = NULL; + char *new_name = NULL; + u32 length; + + if (!context_struct_to_string(old_context, + &old_name, &length) && + !context_struct_to_string(new_context, + &new_name, &length)) { + audit_log(current->audit_context, + GFP_ATOMIC, AUDIT_SELINUX_ERR, + "op=security_bounded_transition " + "result=denied " + "oldcontext=%s newcontext=%s", + old_name, new_name); + } + kfree(new_name); + kfree(old_name); + } +out: + read_unlock(&policy_rwlock); + + return rc; +} + +static void avd_init(struct av_decision *avd) +{ + avd->allowed = 0; + avd->auditallow = 0; + avd->auditdeny = 0xffffffff; + avd->seqno = latest_granting; + avd->flags = 0; +} + + +/** + * security_compute_av - Compute access vector decisions. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @avd: access vector decisions + * + * Compute a set of access vector decisions based on the + * SID pair (@ssid, @tsid) for the permissions in @tclass. + */ +void security_compute_av(u32 ssid, + u32 tsid, + u16 orig_tclass, + struct av_decision *avd) +{ + u16 tclass; + struct context *scontext = NULL, *tcontext = NULL; + + read_lock(&policy_rwlock); + avd_init(avd); + if (!ss_initialized) + goto allow; + + scontext = sidtab_search(&sidtab, ssid); + if (!scontext) { + printk(KERN_ERR "SELinux: %s: unrecognized SID %d\n", + __func__, ssid); + goto out; + } + + /* permissive domain? */ + if (ebitmap_get_bit(&policydb.permissive_map, scontext->type)) + avd->flags |= AVD_FLAGS_PERMISSIVE; + + tcontext = sidtab_search(&sidtab, tsid); + if (!tcontext) { + printk(KERN_ERR "SELinux: %s: unrecognized SID %d\n", + __func__, tsid); + goto out; + } + + tclass = unmap_class(orig_tclass); + if (unlikely(orig_tclass && !tclass)) { + if (policydb.allow_unknown) + goto allow; + goto out; + } + context_struct_compute_av(scontext, tcontext, tclass, avd); + map_decision(orig_tclass, avd, policydb.allow_unknown); +out: + read_unlock(&policy_rwlock); + return; +allow: + avd->allowed = 0xffffffff; + goto out; +} + +void security_compute_av_user(u32 ssid, + u32 tsid, + u16 tclass, + struct av_decision *avd) +{ + struct context *scontext = NULL, *tcontext = NULL; + + read_lock(&policy_rwlock); + avd_init(avd); + if (!ss_initialized) + goto allow; + + scontext = sidtab_search(&sidtab, ssid); + if (!scontext) { + printk(KERN_ERR "SELinux: %s: unrecognized SID %d\n", + __func__, ssid); + goto out; + } + + /* permissive domain? */ + if (ebitmap_get_bit(&policydb.permissive_map, scontext->type)) + avd->flags |= AVD_FLAGS_PERMISSIVE; + + tcontext = sidtab_search(&sidtab, tsid); + if (!tcontext) { + printk(KERN_ERR "SELinux: %s: unrecognized SID %d\n", + __func__, tsid); + goto out; + } + + if (unlikely(!tclass)) { + if (policydb.allow_unknown) + goto allow; + goto out; + } + + context_struct_compute_av(scontext, tcontext, tclass, avd); + out: + read_unlock(&policy_rwlock); + return; +allow: + avd->allowed = 0xffffffff; + goto out; +} + +/* + * Write the security context string representation of + * the context structure `context' into a dynamically + * allocated string of the correct size. Set `*scontext' + * to point to this string and set `*scontext_len' to + * the length of the string. + */ +static int context_struct_to_string(struct context *context, char **scontext, u32 *scontext_len) +{ + char *scontextp; + + if (scontext) + *scontext = NULL; + *scontext_len = 0; + + if (context->len) { + *scontext_len = context->len; + *scontext = kstrdup(context->str, GFP_ATOMIC); + if (!(*scontext)) + return -ENOMEM; + return 0; + } + + /* Compute the size of the context. */ + *scontext_len += strlen(sym_name(&policydb, SYM_USERS, context->user - 1)) + 1; + *scontext_len += strlen(sym_name(&policydb, SYM_ROLES, context->role - 1)) + 1; + *scontext_len += strlen(sym_name(&policydb, SYM_TYPES, context->type - 1)) + 1; + *scontext_len += mls_compute_context_len(context); + + if (!scontext) + return 0; + + /* Allocate space for the context; caller must free this space. */ + scontextp = kmalloc(*scontext_len, GFP_ATOMIC); + if (!scontextp) + return -ENOMEM; + *scontext = scontextp; + + /* + * Copy the user name, role name and type name into the context. + */ + sprintf(scontextp, "%s:%s:%s", + sym_name(&policydb, SYM_USERS, context->user - 1), + sym_name(&policydb, SYM_ROLES, context->role - 1), + sym_name(&policydb, SYM_TYPES, context->type - 1)); + scontextp += strlen(sym_name(&policydb, SYM_USERS, context->user - 1)) + + 1 + strlen(sym_name(&policydb, SYM_ROLES, context->role - 1)) + + 1 + strlen(sym_name(&policydb, SYM_TYPES, context->type - 1)); + + mls_sid_to_context(context, &scontextp); + + *scontextp = 0; + + return 0; +} + +#include "initial_sid_to_string.h" + +const char *security_get_initial_sid_context(u32 sid) +{ + if (unlikely(sid > SECINITSID_NUM)) + return NULL; + return initial_sid_to_string[sid]; +} + +static int security_sid_to_context_core(u32 sid, char **scontext, + u32 *scontext_len, int force) +{ + struct context *context; + int rc = 0; + + if (scontext) + *scontext = NULL; + *scontext_len = 0; + + if (!ss_initialized) { + if (sid <= SECINITSID_NUM) { + char *scontextp; + + *scontext_len = strlen(initial_sid_to_string[sid]) + 1; + if (!scontext) + goto out; + scontextp = kmalloc(*scontext_len, GFP_ATOMIC); + if (!scontextp) { + rc = -ENOMEM; + goto out; + } + strcpy(scontextp, initial_sid_to_string[sid]); + *scontext = scontextp; + goto out; + } + printk(KERN_ERR "SELinux: %s: called before initial " + "load_policy on unknown SID %d\n", __func__, sid); + rc = -EINVAL; + goto out; + } + read_lock(&policy_rwlock); + if (force) + context = sidtab_search_force(&sidtab, sid); + else + context = sidtab_search(&sidtab, sid); + if (!context) { + printk(KERN_ERR "SELinux: %s: unrecognized SID %d\n", + __func__, sid); + rc = -EINVAL; + goto out_unlock; + } + rc = context_struct_to_string(context, scontext, scontext_len); +out_unlock: + read_unlock(&policy_rwlock); +out: + return rc; + +} + +/** + * security_sid_to_context - Obtain a context for a given SID. + * @sid: security identifier, SID + * @scontext: security context + * @scontext_len: length in bytes + * + * Write the string representation of the context associated with @sid + * into a dynamically allocated string of the correct size. Set @scontext + * to point to this string and set @scontext_len to the length of the string. + */ +int security_sid_to_context(u32 sid, char **scontext, u32 *scontext_len) +{ + return security_sid_to_context_core(sid, scontext, scontext_len, 0); +} + +int security_sid_to_context_force(u32 sid, char **scontext, u32 *scontext_len) +{ + return security_sid_to_context_core(sid, scontext, scontext_len, 1); +} + +/* + * Caveat: Mutates scontext. + */ +static int string_to_context_struct(struct policydb *pol, + struct sidtab *sidtabp, + char *scontext, + u32 scontext_len, + struct context *ctx, + u32 def_sid) +{ + struct role_datum *role; + struct type_datum *typdatum; + struct user_datum *usrdatum; + char *scontextp, *p, oldc; + int rc = 0; + + context_init(ctx); + + /* Parse the security context. */ + + rc = -EINVAL; + scontextp = (char *) scontext; + + /* Extract the user. */ + p = scontextp; + while (*p && *p != ':') + p++; + + if (*p == 0) + goto out; + + *p++ = 0; + + usrdatum = hashtab_search(pol->p_users.table, scontextp); + if (!usrdatum) + goto out; + + ctx->user = usrdatum->value; + + /* Extract role. */ + scontextp = p; + while (*p && *p != ':') + p++; + + if (*p == 0) + goto out; + + *p++ = 0; + + role = hashtab_search(pol->p_roles.table, scontextp); + if (!role) + goto out; + ctx->role = role->value; + + /* Extract type. */ + scontextp = p; + while (*p && *p != ':') + p++; + oldc = *p; + *p++ = 0; + + typdatum = hashtab_search(pol->p_types.table, scontextp); + if (!typdatum || typdatum->attribute) + goto out; + + ctx->type = typdatum->value; + + rc = mls_context_to_sid(pol, oldc, &p, ctx, sidtabp, def_sid); + if (rc) + goto out; + + rc = -EINVAL; + if ((p - scontext) < scontext_len) + goto out; + + /* Check the validity of the new context. */ + if (!policydb_context_isvalid(pol, ctx)) + goto out; + rc = 0; +out: + if (rc) + context_destroy(ctx); + return rc; +} + +static int security_context_to_sid_core(const char *scontext, u32 scontext_len, + u32 *sid, u32 def_sid, gfp_t gfp_flags, + int force) +{ + char *scontext2, *str = NULL; + struct context context; + int rc = 0; + + if (!ss_initialized) { + int i; + + for (i = 1; i < SECINITSID_NUM; i++) { + if (!strcmp(initial_sid_to_string[i], scontext)) { + *sid = i; + return 0; + } + } + *sid = SECINITSID_KERNEL; + return 0; + } + *sid = SECSID_NULL; + + /* Copy the string so that we can modify the copy as we parse it. */ + scontext2 = kmalloc(scontext_len + 1, gfp_flags); + if (!scontext2) + return -ENOMEM; + memcpy(scontext2, scontext, scontext_len); + scontext2[scontext_len] = 0; + + if (force) { + /* Save another copy for storing in uninterpreted form */ + rc = -ENOMEM; + str = kstrdup(scontext2, gfp_flags); + if (!str) + goto out; + } + + read_lock(&policy_rwlock); + rc = string_to_context_struct(&policydb, &sidtab, scontext2, + scontext_len, &context, def_sid); + if (rc == -EINVAL && force) { + context.str = str; + context.len = scontext_len; + str = NULL; + } else if (rc) + goto out_unlock; + rc = sidtab_context_to_sid(&sidtab, &context, sid); + context_destroy(&context); +out_unlock: + read_unlock(&policy_rwlock); +out: + kfree(scontext2); + kfree(str); + return rc; +} + +/** + * security_context_to_sid - Obtain a SID for a given security context. + * @scontext: security context + * @scontext_len: length in bytes + * @sid: security identifier, SID + * + * Obtains a SID associated with the security context that + * has the string representation specified by @scontext. + * Returns -%EINVAL if the context is invalid, -%ENOMEM if insufficient + * memory is available, or 0 on success. + */ +int security_context_to_sid(const char *scontext, u32 scontext_len, u32 *sid) +{ + return security_context_to_sid_core(scontext, scontext_len, + sid, SECSID_NULL, GFP_KERNEL, 0); +} + +/** + * security_context_to_sid_default - Obtain a SID for a given security context, + * falling back to specified default if needed. + * + * @scontext: security context + * @scontext_len: length in bytes + * @sid: security identifier, SID + * @def_sid: default SID to assign on error + * + * Obtains a SID associated with the security context that + * has the string representation specified by @scontext. + * The default SID is passed to the MLS layer to be used to allow + * kernel labeling of the MLS field if the MLS field is not present + * (for upgrading to MLS without full relabel). + * Implicitly forces adding of the context even if it cannot be mapped yet. + * Returns -%EINVAL if the context is invalid, -%ENOMEM if insufficient + * memory is available, or 0 on success. + */ +int security_context_to_sid_default(const char *scontext, u32 scontext_len, + u32 *sid, u32 def_sid, gfp_t gfp_flags) +{ + return security_context_to_sid_core(scontext, scontext_len, + sid, def_sid, gfp_flags, 1); +} + +int security_context_to_sid_force(const char *scontext, u32 scontext_len, + u32 *sid) +{ + return security_context_to_sid_core(scontext, scontext_len, + sid, SECSID_NULL, GFP_KERNEL, 1); +} + +static int compute_sid_handle_invalid_context( + struct context *scontext, + struct context *tcontext, + u16 tclass, + struct context *newcontext) +{ + char *s = NULL, *t = NULL, *n = NULL; + u32 slen, tlen, nlen; + + if (context_struct_to_string(scontext, &s, &slen)) + goto out; + if (context_struct_to_string(tcontext, &t, &tlen)) + goto out; + if (context_struct_to_string(newcontext, &n, &nlen)) + goto out; + audit_log(current->audit_context, GFP_ATOMIC, AUDIT_SELINUX_ERR, + "security_compute_sid: invalid context %s" + " for scontext=%s" + " tcontext=%s" + " tclass=%s", + n, s, t, sym_name(&policydb, SYM_CLASSES, tclass-1)); +out: + kfree(s); + kfree(t); + kfree(n); + if (!selinux_enforcing) + return 0; + return -EACCES; +} + +static void filename_compute_type(struct policydb *p, struct context *newcontext, + u32 stype, u32 ttype, u16 tclass, + const char *objname) +{ + struct filename_trans ft; + struct filename_trans_datum *otype; + + /* + * Most filename trans rules are going to live in specific directories + * like /dev or /var/run. This bitmap will quickly skip rule searches + * if the ttype does not contain any rules. + */ + if (!ebitmap_get_bit(&p->filename_trans_ttypes, ttype)) + return; + + ft.stype = stype; + ft.ttype = ttype; + ft.tclass = tclass; + ft.name = objname; + + otype = hashtab_search(p->filename_trans, &ft); + if (otype) + newcontext->type = otype->otype; +} + +static int security_compute_sid(u32 ssid, + u32 tsid, + u16 orig_tclass, + u32 specified, + const char *objname, + u32 *out_sid, + bool kern) +{ + struct context *scontext = NULL, *tcontext = NULL, newcontext; + struct role_trans *roletr = NULL; + struct avtab_key avkey; + struct avtab_datum *avdatum; + struct avtab_node *node; + u16 tclass; + int rc = 0; + bool sock; + + if (!ss_initialized) { + switch (orig_tclass) { + case SECCLASS_PROCESS: /* kernel value */ + *out_sid = ssid; + break; + default: + *out_sid = tsid; + break; + } + goto out; + } + + context_init(&newcontext); + + read_lock(&policy_rwlock); + + if (kern) { + tclass = unmap_class(orig_tclass); + sock = security_is_socket_class(orig_tclass); + } else { + tclass = orig_tclass; + sock = security_is_socket_class(map_class(tclass)); + } + + scontext = sidtab_search(&sidtab, ssid); + if (!scontext) { + printk(KERN_ERR "SELinux: %s: unrecognized SID %d\n", + __func__, ssid); + rc = -EINVAL; + goto out_unlock; + } + tcontext = sidtab_search(&sidtab, tsid); + if (!tcontext) { + printk(KERN_ERR "SELinux: %s: unrecognized SID %d\n", + __func__, tsid); + rc = -EINVAL; + goto out_unlock; + } + + /* Set the user identity. */ + switch (specified) { + case AVTAB_TRANSITION: + case AVTAB_CHANGE: + /* Use the process user identity. */ + newcontext.user = scontext->user; + break; + case AVTAB_MEMBER: + /* Use the related object owner. */ + newcontext.user = tcontext->user; + break; + } + + /* Set the role and type to default values. */ + if ((tclass == policydb.process_class) || (sock == true)) { + /* Use the current role and type of process. */ + newcontext.role = scontext->role; + newcontext.type = scontext->type; + } else { + /* Use the well-defined object role. */ + newcontext.role = OBJECT_R_VAL; + /* Use the type of the related object. */ + newcontext.type = tcontext->type; + } + + /* Look for a type transition/member/change rule. */ + avkey.source_type = scontext->type; + avkey.target_type = tcontext->type; + avkey.target_class = tclass; + avkey.specified = specified; + avdatum = avtab_search(&policydb.te_avtab, &avkey); + + /* If no permanent rule, also check for enabled conditional rules */ + if (!avdatum) { + node = avtab_search_node(&policydb.te_cond_avtab, &avkey); + for (; node; node = avtab_search_node_next(node, specified)) { + if (node->key.specified & AVTAB_ENABLED) { + avdatum = &node->datum; + break; + } + } + } + + if (avdatum) { + /* Use the type from the type transition/member/change rule. */ + newcontext.type = avdatum->data; + } + + /* if we have a objname this is a file trans check so check those rules */ + if (objname) + filename_compute_type(&policydb, &newcontext, scontext->type, + tcontext->type, tclass, objname); + + /* Check for class-specific changes. */ + if (specified & AVTAB_TRANSITION) { + /* Look for a role transition rule. */ + for (roletr = policydb.role_tr; roletr; roletr = roletr->next) { + if ((roletr->role == scontext->role) && + (roletr->type == tcontext->type) && + (roletr->tclass == tclass)) { + /* Use the role transition rule. */ + newcontext.role = roletr->new_role; + break; + } + } + } + + /* Set the MLS attributes. + This is done last because it may allocate memory. */ + rc = mls_compute_sid(scontext, tcontext, tclass, specified, + &newcontext, sock); + if (rc) + goto out_unlock; + + /* Check the validity of the context. */ + if (!policydb_context_isvalid(&policydb, &newcontext)) { + rc = compute_sid_handle_invalid_context(scontext, + tcontext, + tclass, + &newcontext); + if (rc) + goto out_unlock; + } + /* Obtain the sid for the context. */ + rc = sidtab_context_to_sid(&sidtab, &newcontext, out_sid); +out_unlock: + read_unlock(&policy_rwlock); + context_destroy(&newcontext); +out: + return rc; +} + +/** + * security_transition_sid - Compute the SID for a new subject/object. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @out_sid: security identifier for new subject/object + * + * Compute a SID to use for labeling a new subject or object in the + * class @tclass based on a SID pair (@ssid, @tsid). + * Return -%EINVAL if any of the parameters are invalid, -%ENOMEM + * if insufficient memory is available, or %0 if the new SID was + * computed successfully. + */ +int security_transition_sid(u32 ssid, u32 tsid, u16 tclass, + const struct qstr *qstr, u32 *out_sid) +{ + return security_compute_sid(ssid, tsid, tclass, AVTAB_TRANSITION, + qstr ? qstr->name : NULL, out_sid, true); +} + +int security_transition_sid_user(u32 ssid, u32 tsid, u16 tclass, + const char *objname, u32 *out_sid) +{ + return security_compute_sid(ssid, tsid, tclass, AVTAB_TRANSITION, + objname, out_sid, false); +} + +/** + * security_member_sid - Compute the SID for member selection. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @out_sid: security identifier for selected member + * + * Compute a SID to use when selecting a member of a polyinstantiated + * object of class @tclass based on a SID pair (@ssid, @tsid). + * Return -%EINVAL if any of the parameters are invalid, -%ENOMEM + * if insufficient memory is available, or %0 if the SID was + * computed successfully. + */ +int security_member_sid(u32 ssid, + u32 tsid, + u16 tclass, + u32 *out_sid) +{ + return security_compute_sid(ssid, tsid, tclass, AVTAB_MEMBER, NULL, + out_sid, false); +} + +/** + * security_change_sid - Compute the SID for object relabeling. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @out_sid: security identifier for selected member + * + * Compute a SID to use for relabeling an object of class @tclass + * based on a SID pair (@ssid, @tsid). + * Return -%EINVAL if any of the parameters are invalid, -%ENOMEM + * if insufficient memory is available, or %0 if the SID was + * computed successfully. + */ +int security_change_sid(u32 ssid, + u32 tsid, + u16 tclass, + u32 *out_sid) +{ + return security_compute_sid(ssid, tsid, tclass, AVTAB_CHANGE, NULL, + out_sid, false); +} + +/* Clone the SID into the new SID table. */ +static int clone_sid(u32 sid, + struct context *context, + void *arg) +{ + struct sidtab *s = arg; + + if (sid > SECINITSID_NUM) + return sidtab_insert(s, sid, context); + else + return 0; +} + +static inline int convert_context_handle_invalid_context(struct context *context) +{ + char *s; + u32 len; + + if (selinux_enforcing) + return -EINVAL; + + if (!context_struct_to_string(context, &s, &len)) { + printk(KERN_WARNING "SELinux: Context %s would be invalid if enforcing\n", s); + kfree(s); + } + return 0; +} + +struct convert_context_args { + struct policydb *oldp; + struct policydb *newp; +}; + +/* + * Convert the values in the security context + * structure `c' from the values specified + * in the policy `p->oldp' to the values specified + * in the policy `p->newp'. Verify that the + * context is valid under the new policy. + */ +static int convert_context(u32 key, + struct context *c, + void *p) +{ + struct convert_context_args *args; + struct context oldc; + struct ocontext *oc; + struct mls_range *range; + struct role_datum *role; + struct type_datum *typdatum; + struct user_datum *usrdatum; + char *s; + u32 len; + int rc = 0; + + if (key <= SECINITSID_NUM) + goto out; + + args = p; + + if (c->str) { + struct context ctx; + + rc = -ENOMEM; + s = kstrdup(c->str, GFP_KERNEL); + if (!s) + goto out; + + rc = string_to_context_struct(args->newp, NULL, s, + c->len, &ctx, SECSID_NULL); + kfree(s); + if (!rc) { + printk(KERN_INFO "SELinux: Context %s became valid (mapped).\n", + c->str); + /* Replace string with mapped representation. */ + kfree(c->str); + memcpy(c, &ctx, sizeof(*c)); + goto out; + } else if (rc == -EINVAL) { + /* Retain string representation for later mapping. */ + rc = 0; + goto out; + } else { + /* Other error condition, e.g. ENOMEM. */ + printk(KERN_ERR "SELinux: Unable to map context %s, rc = %d.\n", + c->str, -rc); + goto out; + } + } + + rc = context_cpy(&oldc, c); + if (rc) + goto out; + + /* Convert the user. */ + rc = -EINVAL; + usrdatum = hashtab_search(args->newp->p_users.table, + sym_name(args->oldp, SYM_USERS, c->user - 1)); + if (!usrdatum) + goto bad; + c->user = usrdatum->value; + + /* Convert the role. */ + rc = -EINVAL; + role = hashtab_search(args->newp->p_roles.table, + sym_name(args->oldp, SYM_ROLES, c->role - 1)); + if (!role) + goto bad; + c->role = role->value; + + /* Convert the type. */ + rc = -EINVAL; + typdatum = hashtab_search(args->newp->p_types.table, + sym_name(args->oldp, SYM_TYPES, c->type - 1)); + if (!typdatum) + goto bad; + c->type = typdatum->value; + + /* Convert the MLS fields if dealing with MLS policies */ + if (args->oldp->mls_enabled && args->newp->mls_enabled) { + rc = mls_convert_context(args->oldp, args->newp, c); + if (rc) + goto bad; + } else if (args->oldp->mls_enabled && !args->newp->mls_enabled) { + /* + * Switching between MLS and non-MLS policy: + * free any storage used by the MLS fields in the + * context for all existing entries in the sidtab. + */ + mls_context_destroy(c); + } else if (!args->oldp->mls_enabled && args->newp->mls_enabled) { + /* + * Switching between non-MLS and MLS policy: + * ensure that the MLS fields of the context for all + * existing entries in the sidtab are filled in with a + * suitable default value, likely taken from one of the + * initial SIDs. + */ + oc = args->newp->ocontexts[OCON_ISID]; + while (oc && oc->sid[0] != SECINITSID_UNLABELED) + oc = oc->next; + rc = -EINVAL; + if (!oc) { + printk(KERN_ERR "SELinux: unable to look up" + " the initial SIDs list\n"); + goto bad; + } + range = &oc->context[0].range; + rc = mls_range_set(c, range); + if (rc) + goto bad; + } + + /* Check the validity of the new context. */ + if (!policydb_context_isvalid(args->newp, c)) { + rc = convert_context_handle_invalid_context(&oldc); + if (rc) + goto bad; + } + + context_destroy(&oldc); + + rc = 0; +out: + return rc; +bad: + /* Map old representation to string and save it. */ + rc = context_struct_to_string(&oldc, &s, &len); + if (rc) + return rc; + context_destroy(&oldc); + context_destroy(c); + c->str = s; + c->len = len; + printk(KERN_INFO "SELinux: Context %s became invalid (unmapped).\n", + c->str); + rc = 0; + goto out; +} + +static void security_load_policycaps(void) +{ + selinux_policycap_netpeer = ebitmap_get_bit(&policydb.policycaps, + POLICYDB_CAPABILITY_NETPEER); + selinux_policycap_openperm = ebitmap_get_bit(&policydb.policycaps, + POLICYDB_CAPABILITY_OPENPERM); +} + +extern void selinux_complete_init(void); +static int security_preserve_bools(struct policydb *p); + +/** + * security_load_policy - Load a security policy configuration. + * @data: binary policy data + * @len: length of data in bytes + * + * Load a new set of security policy configuration data, + * validate it and convert the SID table as necessary. + * This function will flush the access vector cache after + * loading the new policy. + */ +int security_load_policy(void *data, size_t len) +{ + struct policydb oldpolicydb, newpolicydb; + struct sidtab oldsidtab, newsidtab; + struct selinux_mapping *oldmap, *map = NULL; + struct convert_context_args args; + u32 seqno; + u16 map_size; + int rc = 0; + struct policy_file file = { data, len }, *fp = &file; + + if (!ss_initialized) { + avtab_cache_init(); + rc = policydb_read(&policydb, fp); + if (rc) { + avtab_cache_destroy(); + return rc; + } + + policydb.len = len; + rc = selinux_set_mapping(&policydb, secclass_map, + ¤t_mapping, + ¤t_mapping_size); + if (rc) { + policydb_destroy(&policydb); + avtab_cache_destroy(); + return rc; + } + + rc = policydb_load_isids(&policydb, &sidtab); + if (rc) { + policydb_destroy(&policydb); + avtab_cache_destroy(); + return rc; + } + + security_load_policycaps(); + ss_initialized = 1; + seqno = ++latest_granting; + selinux_complete_init(); + avc_ss_reset(seqno); + selnl_notify_policyload(seqno); + selinux_status_update_policyload(seqno); + selinux_netlbl_cache_invalidate(); + selinux_xfrm_notify_policyload(); + return 0; + } + +#if 0 + sidtab_hash_eval(&sidtab, "sids"); +#endif + + rc = policydb_read(&newpolicydb, fp); + if (rc) + return rc; + + newpolicydb.len = len; + /* If switching between different policy types, log MLS status */ + if (policydb.mls_enabled && !newpolicydb.mls_enabled) + printk(KERN_INFO "SELinux: Disabling MLS support...\n"); + else if (!policydb.mls_enabled && newpolicydb.mls_enabled) + printk(KERN_INFO "SELinux: Enabling MLS support...\n"); + + rc = policydb_load_isids(&newpolicydb, &newsidtab); + if (rc) { + printk(KERN_ERR "SELinux: unable to load the initial SIDs\n"); + policydb_destroy(&newpolicydb); + return rc; + } + + rc = selinux_set_mapping(&newpolicydb, secclass_map, &map, &map_size); + if (rc) + goto err; + + rc = security_preserve_bools(&newpolicydb); + if (rc) { + printk(KERN_ERR "SELinux: unable to preserve booleans\n"); + goto err; + } + + /* Clone the SID table. */ + sidtab_shutdown(&sidtab); + + rc = sidtab_map(&sidtab, clone_sid, &newsidtab); + if (rc) + goto err; + + /* + * Convert the internal representations of contexts + * in the new SID table. + */ + args.oldp = &policydb; + args.newp = &newpolicydb; + rc = sidtab_map(&newsidtab, convert_context, &args); + if (rc) { + printk(KERN_ERR "SELinux: unable to convert the internal" + " representation of contexts in the new SID" + " table\n"); + goto err; + } + + /* Save the old policydb and SID table to free later. */ + memcpy(&oldpolicydb, &policydb, sizeof policydb); + sidtab_set(&oldsidtab, &sidtab); + + /* Install the new policydb and SID table. */ + write_lock_irq(&policy_rwlock); + memcpy(&policydb, &newpolicydb, sizeof policydb); + sidtab_set(&sidtab, &newsidtab); + security_load_policycaps(); + oldmap = current_mapping; + current_mapping = map; + current_mapping_size = map_size; + seqno = ++latest_granting; + write_unlock_irq(&policy_rwlock); + + /* Free the old policydb and SID table. */ + policydb_destroy(&oldpolicydb); + sidtab_destroy(&oldsidtab); + kfree(oldmap); + + avc_ss_reset(seqno); + selnl_notify_policyload(seqno); + selinux_status_update_policyload(seqno); + selinux_netlbl_cache_invalidate(); + selinux_xfrm_notify_policyload(); + + return 0; + +err: + kfree(map); + sidtab_destroy(&newsidtab); + policydb_destroy(&newpolicydb); + return rc; + +} + +size_t security_policydb_len(void) +{ + size_t len; + + read_lock(&policy_rwlock); + len = policydb.len; + read_unlock(&policy_rwlock); + + return len; +} + +/** + * security_port_sid - Obtain the SID for a port. + * @protocol: protocol number + * @port: port number + * @out_sid: security identifier + */ +int security_port_sid(u8 protocol, u16 port, u32 *out_sid) +{ + struct ocontext *c; + int rc = 0; + + read_lock(&policy_rwlock); + + c = policydb.ocontexts[OCON_PORT]; + while (c) { + if (c->u.port.protocol == protocol && + c->u.port.low_port <= port && + c->u.port.high_port >= port) + break; + c = c->next; + } + + if (c) { + if (!c->sid[0]) { + rc = sidtab_context_to_sid(&sidtab, + &c->context[0], + &c->sid[0]); + if (rc) + goto out; + } + *out_sid = c->sid[0]; + } else { + *out_sid = SECINITSID_PORT; + } + +out: + read_unlock(&policy_rwlock); + return rc; +} + +/** + * security_netif_sid - Obtain the SID for a network interface. + * @name: interface name + * @if_sid: interface SID + */ +int security_netif_sid(char *name, u32 *if_sid) +{ + int rc = 0; + struct ocontext *c; + + read_lock(&policy_rwlock); + + c = policydb.ocontexts[OCON_NETIF]; + while (c) { + if (strcmp(name, c->u.name) == 0) + break; + c = c->next; + } + + if (c) { + if (!c->sid[0] || !c->sid[1]) { + rc = sidtab_context_to_sid(&sidtab, + &c->context[0], + &c->sid[0]); + if (rc) + goto out; + rc = sidtab_context_to_sid(&sidtab, + &c->context[1], + &c->sid[1]); + if (rc) + goto out; + } + *if_sid = c->sid[0]; + } else + *if_sid = SECINITSID_NETIF; + +out: + read_unlock(&policy_rwlock); + return rc; +} + +static int match_ipv6_addrmask(u32 *input, u32 *addr, u32 *mask) +{ + int i, fail = 0; + + for (i = 0; i < 4; i++) + if (addr[i] != (input[i] & mask[i])) { + fail = 1; + break; + } + + return !fail; +} + +/** + * security_node_sid - Obtain the SID for a node (host). + * @domain: communication domain aka address family + * @addrp: address + * @addrlen: address length in bytes + * @out_sid: security identifier + */ +int security_node_sid(u16 domain, + void *addrp, + u32 addrlen, + u32 *out_sid) +{ + int rc; + struct ocontext *c; + + read_lock(&policy_rwlock); + + switch (domain) { + case AF_INET: { + u32 addr; + + rc = -EINVAL; + if (addrlen != sizeof(u32)) + goto out; + + addr = *((u32 *)addrp); + + c = policydb.ocontexts[OCON_NODE]; + while (c) { + if (c->u.node.addr == (addr & c->u.node.mask)) + break; + c = c->next; + } + break; + } + + case AF_INET6: + rc = -EINVAL; + if (addrlen != sizeof(u64) * 2) + goto out; + c = policydb.ocontexts[OCON_NODE6]; + while (c) { + if (match_ipv6_addrmask(addrp, c->u.node6.addr, + c->u.node6.mask)) + break; + c = c->next; + } + break; + + default: + rc = 0; + *out_sid = SECINITSID_NODE; + goto out; + } + + if (c) { + if (!c->sid[0]) { + rc = sidtab_context_to_sid(&sidtab, + &c->context[0], + &c->sid[0]); + if (rc) + goto out; + } + *out_sid = c->sid[0]; + } else { + *out_sid = SECINITSID_NODE; + } + + rc = 0; +out: + read_unlock(&policy_rwlock); + return rc; +} + +#define SIDS_NEL 25 + +/** + * security_get_user_sids - Obtain reachable SIDs for a user. + * @fromsid: starting SID + * @username: username + * @sids: array of reachable SIDs for user + * @nel: number of elements in @sids + * + * Generate the set of SIDs for legal security contexts + * for a given user that can be reached by @fromsid. + * Set *@sids to point to a dynamically allocated + * array containing the set of SIDs. Set *@nel to the + * number of elements in the array. + */ + +int security_get_user_sids(u32 fromsid, + char *username, + u32 **sids, + u32 *nel) +{ + struct context *fromcon, usercon; + u32 *mysids = NULL, *mysids2, sid; + u32 mynel = 0, maxnel = SIDS_NEL; + struct user_datum *user; + struct role_datum *role; + struct ebitmap_node *rnode, *tnode; + int rc = 0, i, j; + + *sids = NULL; + *nel = 0; + + if (!ss_initialized) + goto out; + + read_lock(&policy_rwlock); + + context_init(&usercon); + + rc = -EINVAL; + fromcon = sidtab_search(&sidtab, fromsid); + if (!fromcon) + goto out_unlock; + + rc = -EINVAL; + user = hashtab_search(policydb.p_users.table, username); + if (!user) + goto out_unlock; + + usercon.user = user->value; + + rc = -ENOMEM; + mysids = kcalloc(maxnel, sizeof(*mysids), GFP_ATOMIC); + if (!mysids) + goto out_unlock; + + ebitmap_for_each_positive_bit(&user->roles, rnode, i) { + role = policydb.role_val_to_struct[i]; + usercon.role = i + 1; + ebitmap_for_each_positive_bit(&role->types, tnode, j) { + usercon.type = j + 1; + + if (mls_setup_user_range(fromcon, user, &usercon)) + continue; + + rc = sidtab_context_to_sid(&sidtab, &usercon, &sid); + if (rc) + goto out_unlock; + if (mynel < maxnel) { + mysids[mynel++] = sid; + } else { + rc = -ENOMEM; + maxnel += SIDS_NEL; + mysids2 = kcalloc(maxnel, sizeof(*mysids2), GFP_ATOMIC); + if (!mysids2) + goto out_unlock; + memcpy(mysids2, mysids, mynel * sizeof(*mysids2)); + kfree(mysids); + mysids = mysids2; + mysids[mynel++] = sid; + } + } + } + rc = 0; +out_unlock: + read_unlock(&policy_rwlock); + if (rc || !mynel) { + kfree(mysids); + goto out; + } + + rc = -ENOMEM; + mysids2 = kcalloc(mynel, sizeof(*mysids2), GFP_KERNEL); + if (!mysids2) { + kfree(mysids); + goto out; + } + for (i = 0, j = 0; i < mynel; i++) { + struct av_decision dummy_avd; + rc = avc_has_perm_noaudit(fromsid, mysids[i], + SECCLASS_PROCESS, /* kernel value */ + PROCESS__TRANSITION, AVC_STRICT, + &dummy_avd); + if (!rc) + mysids2[j++] = mysids[i]; + cond_resched(); + } + rc = 0; + kfree(mysids); + *sids = mysids2; + *nel = j; +out: + return rc; +} + +/** + * security_genfs_sid - Obtain a SID for a file in a filesystem + * @fstype: filesystem type + * @path: path from root of mount + * @sclass: file security class + * @sid: SID for path + * + * Obtain a SID to use for a file in a filesystem that + * cannot support xattr or use a fixed labeling behavior like + * transition SIDs or task SIDs. + */ +int security_genfs_sid(const char *fstype, + char *path, + u16 orig_sclass, + u32 *sid) +{ + int len; + u16 sclass; + struct genfs *genfs; + struct ocontext *c; + int rc, cmp = 0; + + while (path[0] == '/' && path[1] == '/') + path++; + + read_lock(&policy_rwlock); + + sclass = unmap_class(orig_sclass); + *sid = SECINITSID_UNLABELED; + + for (genfs = policydb.genfs; genfs; genfs = genfs->next) { + cmp = strcmp(fstype, genfs->fstype); + if (cmp <= 0) + break; + } + + rc = -ENOENT; + if (!genfs || cmp) + goto out; + + for (c = genfs->head; c; c = c->next) { + len = strlen(c->u.name); + if ((!c->v.sclass || sclass == c->v.sclass) && + (strncmp(c->u.name, path, len) == 0)) + break; + } + + rc = -ENOENT; + if (!c) + goto out; + + if (!c->sid[0]) { + rc = sidtab_context_to_sid(&sidtab, &c->context[0], &c->sid[0]); + if (rc) + goto out; + } + + *sid = c->sid[0]; + rc = 0; +out: + read_unlock(&policy_rwlock); + return rc; +} + +/** + * security_fs_use - Determine how to handle labeling for a filesystem. + * @fstype: filesystem type + * @behavior: labeling behavior + * @sid: SID for filesystem (superblock) + */ +int security_fs_use( + const char *fstype, + unsigned int *behavior, + u32 *sid) +{ + int rc = 0; + struct ocontext *c; + + read_lock(&policy_rwlock); + + c = policydb.ocontexts[OCON_FSUSE]; + while (c) { + if (strcmp(fstype, c->u.name) == 0) + break; + c = c->next; + } + + if (c) { + *behavior = c->v.behavior; + if (!c->sid[0]) { + rc = sidtab_context_to_sid(&sidtab, &c->context[0], + &c->sid[0]); + if (rc) + goto out; + } + *sid = c->sid[0]; + } else { + rc = security_genfs_sid(fstype, "/", SECCLASS_DIR, sid); + if (rc) { + *behavior = SECURITY_FS_USE_NONE; + rc = 0; + } else { + *behavior = SECURITY_FS_USE_GENFS; + } + } + +out: + read_unlock(&policy_rwlock); + return rc; +} + +int security_get_bools(int *len, char ***names, int **values) +{ + int i, rc; + + read_lock(&policy_rwlock); + *names = NULL; + *values = NULL; + + rc = 0; + *len = policydb.p_bools.nprim; + if (!*len) + goto out; + + rc = -ENOMEM; + *names = kcalloc(*len, sizeof(char *), GFP_ATOMIC); + if (!*names) + goto err; + + rc = -ENOMEM; + *values = kcalloc(*len, sizeof(int), GFP_ATOMIC); + if (!*values) + goto err; + + for (i = 0; i < *len; i++) { + size_t name_len; + + (*values)[i] = policydb.bool_val_to_struct[i]->state; + name_len = strlen(sym_name(&policydb, SYM_BOOLS, i)) + 1; + + rc = -ENOMEM; + (*names)[i] = kmalloc(sizeof(char) * name_len, GFP_ATOMIC); + if (!(*names)[i]) + goto err; + + strncpy((*names)[i], sym_name(&policydb, SYM_BOOLS, i), name_len); + (*names)[i][name_len - 1] = 0; + } + rc = 0; +out: + read_unlock(&policy_rwlock); + return rc; +err: + if (*names) { + for (i = 0; i < *len; i++) + kfree((*names)[i]); + } + kfree(*values); + goto out; +} + + +int security_set_bools(int len, int *values) +{ + int i, rc; + int lenp, seqno = 0; + struct cond_node *cur; + + write_lock_irq(&policy_rwlock); + + rc = -EFAULT; + lenp = policydb.p_bools.nprim; + if (len != lenp) + goto out; + + for (i = 0; i < len; i++) { + if (!!values[i] != policydb.bool_val_to_struct[i]->state) { + audit_log(current->audit_context, GFP_ATOMIC, + AUDIT_MAC_CONFIG_CHANGE, + "bool=%s val=%d old_val=%d auid=%u ses=%u", + sym_name(&policydb, SYM_BOOLS, i), + !!values[i], + policydb.bool_val_to_struct[i]->state, + audit_get_loginuid(current), + audit_get_sessionid(current)); + } + if (values[i]) + policydb.bool_val_to_struct[i]->state = 1; + else + policydb.bool_val_to_struct[i]->state = 0; + } + + for (cur = policydb.cond_list; cur; cur = cur->next) { + rc = evaluate_cond_node(&policydb, cur); + if (rc) + goto out; + } + + seqno = ++latest_granting; + rc = 0; +out: + write_unlock_irq(&policy_rwlock); + if (!rc) { + avc_ss_reset(seqno); + selnl_notify_policyload(seqno); + selinux_status_update_policyload(seqno); + selinux_xfrm_notify_policyload(); + } + return rc; +} + +int security_get_bool_value(int bool) +{ + int rc; + int len; + + read_lock(&policy_rwlock); + + rc = -EFAULT; + len = policydb.p_bools.nprim; + if (bool >= len) + goto out; + + rc = policydb.bool_val_to_struct[bool]->state; +out: + read_unlock(&policy_rwlock); + return rc; +} + +static int security_preserve_bools(struct policydb *p) +{ + int rc, nbools = 0, *bvalues = NULL, i; + char **bnames = NULL; + struct cond_bool_datum *booldatum; + struct cond_node *cur; + + rc = security_get_bools(&nbools, &bnames, &bvalues); + if (rc) + goto out; + for (i = 0; i < nbools; i++) { + booldatum = hashtab_search(p->p_bools.table, bnames[i]); + if (booldatum) + booldatum->state = bvalues[i]; + } + for (cur = p->cond_list; cur; cur = cur->next) { + rc = evaluate_cond_node(p, cur); + if (rc) + goto out; + } + +out: + if (bnames) { + for (i = 0; i < nbools; i++) + kfree(bnames[i]); + } + kfree(bnames); + kfree(bvalues); + return rc; +} + +/* + * security_sid_mls_copy() - computes a new sid based on the given + * sid and the mls portion of mls_sid. + */ +int security_sid_mls_copy(u32 sid, u32 mls_sid, u32 *new_sid) +{ + struct context *context1; + struct context *context2; + struct context newcon; + char *s; + u32 len; + int rc; + + rc = 0; + if (!ss_initialized || !policydb.mls_enabled) { + *new_sid = sid; + goto out; + } + + context_init(&newcon); + + read_lock(&policy_rwlock); + + rc = -EINVAL; + context1 = sidtab_search(&sidtab, sid); + if (!context1) { + printk(KERN_ERR "SELinux: %s: unrecognized SID %d\n", + __func__, sid); + goto out_unlock; + } + + rc = -EINVAL; + context2 = sidtab_search(&sidtab, mls_sid); + if (!context2) { + printk(KERN_ERR "SELinux: %s: unrecognized SID %d\n", + __func__, mls_sid); + goto out_unlock; + } + + newcon.user = context1->user; + newcon.role = context1->role; + newcon.type = context1->type; + rc = mls_context_cpy(&newcon, context2); + if (rc) + goto out_unlock; + + /* Check the validity of the new context. */ + if (!policydb_context_isvalid(&policydb, &newcon)) { + rc = convert_context_handle_invalid_context(&newcon); + if (rc) { + if (!context_struct_to_string(&newcon, &s, &len)) { + audit_log(current->audit_context, GFP_ATOMIC, AUDIT_SELINUX_ERR, + "security_sid_mls_copy: invalid context %s", s); + kfree(s); + } + goto out_unlock; + } + } + + rc = sidtab_context_to_sid(&sidtab, &newcon, new_sid); +out_unlock: + read_unlock(&policy_rwlock); + context_destroy(&newcon); +out: + return rc; +} + +/** + * security_net_peersid_resolve - Compare and resolve two network peer SIDs + * @nlbl_sid: NetLabel SID + * @nlbl_type: NetLabel labeling protocol type + * @xfrm_sid: XFRM SID + * + * Description: + * Compare the @nlbl_sid and @xfrm_sid values and if the two SIDs can be + * resolved into a single SID it is returned via @peer_sid and the function + * returns zero. Otherwise @peer_sid is set to SECSID_NULL and the function + * returns a negative value. A table summarizing the behavior is below: + * + * | function return | @sid + * ------------------------------+-----------------+----------------- + * no peer labels | 0 | SECSID_NULL + * single peer label | 0 | <peer_label> + * multiple, consistent labels | 0 | <peer_label> + * multiple, inconsistent labels | -<errno> | SECSID_NULL + * + */ +int security_net_peersid_resolve(u32 nlbl_sid, u32 nlbl_type, + u32 xfrm_sid, + u32 *peer_sid) +{ + int rc; + struct context *nlbl_ctx; + struct context *xfrm_ctx; + + *peer_sid = SECSID_NULL; + + /* handle the common (which also happens to be the set of easy) cases + * right away, these two if statements catch everything involving a + * single or absent peer SID/label */ + if (xfrm_sid == SECSID_NULL) { + *peer_sid = nlbl_sid; + return 0; + } + /* NOTE: an nlbl_type == NETLBL_NLTYPE_UNLABELED is a "fallback" label + * and is treated as if nlbl_sid == SECSID_NULL when a XFRM SID/label + * is present */ + if (nlbl_sid == SECSID_NULL || nlbl_type == NETLBL_NLTYPE_UNLABELED) { + *peer_sid = xfrm_sid; + return 0; + } + + /* we don't need to check ss_initialized here since the only way both + * nlbl_sid and xfrm_sid are not equal to SECSID_NULL would be if the + * security server was initialized and ss_initialized was true */ + if (!policydb.mls_enabled) + return 0; + + read_lock(&policy_rwlock); + + rc = -EINVAL; + nlbl_ctx = sidtab_search(&sidtab, nlbl_sid); + if (!nlbl_ctx) { + printk(KERN_ERR "SELinux: %s: unrecognized SID %d\n", + __func__, nlbl_sid); + goto out; + } + rc = -EINVAL; + xfrm_ctx = sidtab_search(&sidtab, xfrm_sid); + if (!xfrm_ctx) { + printk(KERN_ERR "SELinux: %s: unrecognized SID %d\n", + __func__, xfrm_sid); + goto out; + } + rc = (mls_context_cmp(nlbl_ctx, xfrm_ctx) ? 0 : -EACCES); + if (rc) + goto out; + + /* at present NetLabel SIDs/labels really only carry MLS + * information so if the MLS portion of the NetLabel SID + * matches the MLS portion of the labeled XFRM SID/label + * then pass along the XFRM SID as it is the most + * expressive */ + *peer_sid = xfrm_sid; +out: + read_unlock(&policy_rwlock); + return rc; +} + +static int get_classes_callback(void *k, void *d, void *args) +{ + struct class_datum *datum = d; + char *name = k, **classes = args; + int value = datum->value - 1; + + classes[value] = kstrdup(name, GFP_ATOMIC); + if (!classes[value]) + return -ENOMEM; + + return 0; +} + +int security_get_classes(char ***classes, int *nclasses) +{ + int rc; + + read_lock(&policy_rwlock); + + rc = -ENOMEM; + *nclasses = policydb.p_classes.nprim; + *classes = kcalloc(*nclasses, sizeof(**classes), GFP_ATOMIC); + if (!*classes) + goto out; + + rc = hashtab_map(policydb.p_classes.table, get_classes_callback, + *classes); + if (rc) { + int i; + for (i = 0; i < *nclasses; i++) + kfree((*classes)[i]); + kfree(*classes); + } + +out: + read_unlock(&policy_rwlock); + return rc; +} + +static int get_permissions_callback(void *k, void *d, void *args) +{ + struct perm_datum *datum = d; + char *name = k, **perms = args; + int value = datum->value - 1; + + perms[value] = kstrdup(name, GFP_ATOMIC); + if (!perms[value]) + return -ENOMEM; + + return 0; +} + +int security_get_permissions(char *class, char ***perms, int *nperms) +{ + int rc, i; + struct class_datum *match; + + read_lock(&policy_rwlock); + + rc = -EINVAL; + match = hashtab_search(policydb.p_classes.table, class); + if (!match) { + printk(KERN_ERR "SELinux: %s: unrecognized class %s\n", + __func__, class); + goto out; + } + + rc = -ENOMEM; + *nperms = match->permissions.nprim; + *perms = kcalloc(*nperms, sizeof(**perms), GFP_ATOMIC); + if (!*perms) + goto out; + + if (match->comdatum) { + rc = hashtab_map(match->comdatum->permissions.table, + get_permissions_callback, *perms); + if (rc) + goto err; + } + + rc = hashtab_map(match->permissions.table, get_permissions_callback, + *perms); + if (rc) + goto err; + +out: + read_unlock(&policy_rwlock); + return rc; + +err: + read_unlock(&policy_rwlock); + for (i = 0; i < *nperms; i++) + kfree((*perms)[i]); + kfree(*perms); + return rc; +} + +int security_get_reject_unknown(void) +{ + return policydb.reject_unknown; +} + +int security_get_allow_unknown(void) +{ + return policydb.allow_unknown; +} + +/** + * security_policycap_supported - Check for a specific policy capability + * @req_cap: capability + * + * Description: + * This function queries the currently loaded policy to see if it supports the + * capability specified by @req_cap. Returns true (1) if the capability is + * supported, false (0) if it isn't supported. + * + */ +int security_policycap_supported(unsigned int req_cap) +{ + int rc; + + read_lock(&policy_rwlock); + rc = ebitmap_get_bit(&policydb.policycaps, req_cap); + read_unlock(&policy_rwlock); + + return rc; +} + +struct selinux_audit_rule { + u32 au_seqno; + struct context au_ctxt; +}; + +void selinux_audit_rule_free(void *vrule) +{ + struct selinux_audit_rule *rule = vrule; + + if (rule) { + context_destroy(&rule->au_ctxt); + kfree(rule); + } +} + +int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule) +{ + struct selinux_audit_rule *tmprule; + struct role_datum *roledatum; + struct type_datum *typedatum; + struct user_datum *userdatum; + struct selinux_audit_rule **rule = (struct selinux_audit_rule **)vrule; + int rc = 0; + + *rule = NULL; + + if (!ss_initialized) + return -EOPNOTSUPP; + + switch (field) { + case AUDIT_SUBJ_USER: + case AUDIT_SUBJ_ROLE: + case AUDIT_SUBJ_TYPE: + case AUDIT_OBJ_USER: + case AUDIT_OBJ_ROLE: + case AUDIT_OBJ_TYPE: + /* only 'equals' and 'not equals' fit user, role, and type */ + if (op != Audit_equal && op != Audit_not_equal) + return -EINVAL; + break; + case AUDIT_SUBJ_SEN: + case AUDIT_SUBJ_CLR: + case AUDIT_OBJ_LEV_LOW: + case AUDIT_OBJ_LEV_HIGH: + /* we do not allow a range, indicated by the presence of '-' */ + if (strchr(rulestr, '-')) + return -EINVAL; + break; + default: + /* only the above fields are valid */ + return -EINVAL; + } + + tmprule = kzalloc(sizeof(struct selinux_audit_rule), GFP_KERNEL); + if (!tmprule) + return -ENOMEM; + + context_init(&tmprule->au_ctxt); + + read_lock(&policy_rwlock); + + tmprule->au_seqno = latest_granting; + + switch (field) { + case AUDIT_SUBJ_USER: + case AUDIT_OBJ_USER: + rc = -EINVAL; + userdatum = hashtab_search(policydb.p_users.table, rulestr); + if (!userdatum) + goto out; + tmprule->au_ctxt.user = userdatum->value; + break; + case AUDIT_SUBJ_ROLE: + case AUDIT_OBJ_ROLE: + rc = -EINVAL; + roledatum = hashtab_search(policydb.p_roles.table, rulestr); + if (!roledatum) + goto out; + tmprule->au_ctxt.role = roledatum->value; + break; + case AUDIT_SUBJ_TYPE: + case AUDIT_OBJ_TYPE: + rc = -EINVAL; + typedatum = hashtab_search(policydb.p_types.table, rulestr); + if (!typedatum) + goto out; + tmprule->au_ctxt.type = typedatum->value; + break; + case AUDIT_SUBJ_SEN: + case AUDIT_SUBJ_CLR: + case AUDIT_OBJ_LEV_LOW: + case AUDIT_OBJ_LEV_HIGH: + rc = mls_from_string(rulestr, &tmprule->au_ctxt, GFP_ATOMIC); + if (rc) + goto out; + break; + } + rc = 0; +out: + read_unlock(&policy_rwlock); + + if (rc) { + selinux_audit_rule_free(tmprule); + tmprule = NULL; + } + + *rule = tmprule; + + return rc; +} + +/* Check to see if the rule contains any selinux fields */ +int selinux_audit_rule_known(struct audit_krule *rule) +{ + int i; + + for (i = 0; i < rule->field_count; i++) { + struct audit_field *f = &rule->fields[i]; + switch (f->type) { + case AUDIT_SUBJ_USER: + case AUDIT_SUBJ_ROLE: + case AUDIT_SUBJ_TYPE: + case AUDIT_SUBJ_SEN: + case AUDIT_SUBJ_CLR: + case AUDIT_OBJ_USER: + case AUDIT_OBJ_ROLE: + case AUDIT_OBJ_TYPE: + case AUDIT_OBJ_LEV_LOW: + case AUDIT_OBJ_LEV_HIGH: + return 1; + } + } + + return 0; +} + +int selinux_audit_rule_match(u32 sid, u32 field, u32 op, void *vrule, + struct audit_context *actx) +{ + struct context *ctxt; + struct mls_level *level; + struct selinux_audit_rule *rule = vrule; + int match = 0; + + if (!rule) { + audit_log(actx, GFP_ATOMIC, AUDIT_SELINUX_ERR, + "selinux_audit_rule_match: missing rule\n"); + return -ENOENT; + } + + read_lock(&policy_rwlock); + + if (rule->au_seqno < latest_granting) { + audit_log(actx, GFP_ATOMIC, AUDIT_SELINUX_ERR, + "selinux_audit_rule_match: stale rule\n"); + match = -ESTALE; + goto out; + } + + ctxt = sidtab_search(&sidtab, sid); + if (!ctxt) { + audit_log(actx, GFP_ATOMIC, AUDIT_SELINUX_ERR, + "selinux_audit_rule_match: unrecognized SID %d\n", + sid); + match = -ENOENT; + goto out; + } + + /* a field/op pair that is not caught here will simply fall through + without a match */ + switch (field) { + case AUDIT_SUBJ_USER: + case AUDIT_OBJ_USER: + switch (op) { + case Audit_equal: + match = (ctxt->user == rule->au_ctxt.user); + break; + case Audit_not_equal: + match = (ctxt->user != rule->au_ctxt.user); + break; + } + break; + case AUDIT_SUBJ_ROLE: + case AUDIT_OBJ_ROLE: + switch (op) { + case Audit_equal: + match = (ctxt->role == rule->au_ctxt.role); + break; + case Audit_not_equal: + match = (ctxt->role != rule->au_ctxt.role); + break; + } + break; + case AUDIT_SUBJ_TYPE: + case AUDIT_OBJ_TYPE: + switch (op) { + case Audit_equal: + match = (ctxt->type == rule->au_ctxt.type); + break; + case Audit_not_equal: + match = (ctxt->type != rule->au_ctxt.type); + break; + } + break; + case AUDIT_SUBJ_SEN: + case AUDIT_SUBJ_CLR: + case AUDIT_OBJ_LEV_LOW: + case AUDIT_OBJ_LEV_HIGH: + level = ((field == AUDIT_SUBJ_SEN || + field == AUDIT_OBJ_LEV_LOW) ? + &ctxt->range.level[0] : &ctxt->range.level[1]); + switch (op) { + case Audit_equal: + match = mls_level_eq(&rule->au_ctxt.range.level[0], + level); + break; + case Audit_not_equal: + match = !mls_level_eq(&rule->au_ctxt.range.level[0], + level); + break; + case Audit_lt: + match = (mls_level_dom(&rule->au_ctxt.range.level[0], + level) && + !mls_level_eq(&rule->au_ctxt.range.level[0], + level)); + break; + case Audit_le: + match = mls_level_dom(&rule->au_ctxt.range.level[0], + level); + break; + case Audit_gt: + match = (mls_level_dom(level, + &rule->au_ctxt.range.level[0]) && + !mls_level_eq(level, + &rule->au_ctxt.range.level[0])); + break; + case Audit_ge: + match = mls_level_dom(level, + &rule->au_ctxt.range.level[0]); + break; + } + } + +out: + read_unlock(&policy_rwlock); + return match; +} + +static int (*aurule_callback)(void) = audit_update_lsm_rules; + +static int aurule_avc_callback(u32 event, u32 ssid, u32 tsid, + u16 class, u32 perms, u32 *retained) +{ + int err = 0; + + if (event == AVC_CALLBACK_RESET && aurule_callback) + err = aurule_callback(); + return err; +} + +static int __init aurule_init(void) +{ + int err; + + err = avc_add_callback(aurule_avc_callback, AVC_CALLBACK_RESET, + SECSID_NULL, SECSID_NULL, SECCLASS_NULL, 0); + if (err) + panic("avc_add_callback() failed, error %d\n", err); + + return err; +} +__initcall(aurule_init); + +#ifdef CONFIG_NETLABEL +/** + * security_netlbl_cache_add - Add an entry to the NetLabel cache + * @secattr: the NetLabel packet security attributes + * @sid: the SELinux SID + * + * Description: + * Attempt to cache the context in @ctx, which was derived from the packet in + * @skb, in the NetLabel subsystem cache. This function assumes @secattr has + * already been initialized. + * + */ +static void security_netlbl_cache_add(struct netlbl_lsm_secattr *secattr, + u32 sid) +{ + u32 *sid_cache; + + sid_cache = kmalloc(sizeof(*sid_cache), GFP_ATOMIC); + if (sid_cache == NULL) + return; + secattr->cache = netlbl_secattr_cache_alloc(GFP_ATOMIC); + if (secattr->cache == NULL) { + kfree(sid_cache); + return; + } + + *sid_cache = sid; + secattr->cache->free = kfree; + secattr->cache->data = sid_cache; + secattr->flags |= NETLBL_SECATTR_CACHE; +} + +/** + * security_netlbl_secattr_to_sid - Convert a NetLabel secattr to a SELinux SID + * @secattr: the NetLabel packet security attributes + * @sid: the SELinux SID + * + * Description: + * Convert the given NetLabel security attributes in @secattr into a + * SELinux SID. If the @secattr field does not contain a full SELinux + * SID/context then use SECINITSID_NETMSG as the foundation. If possible the + * 'cache' field of @secattr is set and the CACHE flag is set; this is to + * allow the @secattr to be used by NetLabel to cache the secattr to SID + * conversion for future lookups. Returns zero on success, negative values on + * failure. + * + */ +int security_netlbl_secattr_to_sid(struct netlbl_lsm_secattr *secattr, + u32 *sid) +{ + int rc; + struct context *ctx; + struct context ctx_new; + + if (!ss_initialized) { + *sid = SECSID_NULL; + return 0; + } + + read_lock(&policy_rwlock); + + if (secattr->flags & NETLBL_SECATTR_CACHE) + *sid = *(u32 *)secattr->cache->data; + else if (secattr->flags & NETLBL_SECATTR_SECID) + *sid = secattr->attr.secid; + else if (secattr->flags & NETLBL_SECATTR_MLS_LVL) { + rc = -EIDRM; + ctx = sidtab_search(&sidtab, SECINITSID_NETMSG); + if (ctx == NULL) + goto out; + + context_init(&ctx_new); + ctx_new.user = ctx->user; + ctx_new.role = ctx->role; + ctx_new.type = ctx->type; + mls_import_netlbl_lvl(&ctx_new, secattr); + if (secattr->flags & NETLBL_SECATTR_MLS_CAT) { + rc = ebitmap_netlbl_import(&ctx_new.range.level[0].cat, + secattr->attr.mls.cat); + if (rc) + goto out; + memcpy(&ctx_new.range.level[1].cat, + &ctx_new.range.level[0].cat, + sizeof(ctx_new.range.level[0].cat)); + } + rc = -EIDRM; + if (!mls_context_isvalid(&policydb, &ctx_new)) + goto out_free; + + rc = sidtab_context_to_sid(&sidtab, &ctx_new, sid); + if (rc) + goto out_free; + + security_netlbl_cache_add(secattr, *sid); + + ebitmap_destroy(&ctx_new.range.level[0].cat); + } else + *sid = SECSID_NULL; + + read_unlock(&policy_rwlock); + return 0; +out_free: + ebitmap_destroy(&ctx_new.range.level[0].cat); +out: + read_unlock(&policy_rwlock); + return rc; +} + +/** + * security_netlbl_sid_to_secattr - Convert a SELinux SID to a NetLabel secattr + * @sid: the SELinux SID + * @secattr: the NetLabel packet security attributes + * + * Description: + * Convert the given SELinux SID in @sid into a NetLabel security attribute. + * Returns zero on success, negative values on failure. + * + */ +int security_netlbl_sid_to_secattr(u32 sid, struct netlbl_lsm_secattr *secattr) +{ + int rc; + struct context *ctx; + + if (!ss_initialized) + return 0; + + read_lock(&policy_rwlock); + + rc = -ENOENT; + ctx = sidtab_search(&sidtab, sid); + if (ctx == NULL) + goto out; + + rc = -ENOMEM; + secattr->domain = kstrdup(sym_name(&policydb, SYM_TYPES, ctx->type - 1), + GFP_ATOMIC); + if (secattr->domain == NULL) + goto out; + + secattr->attr.secid = sid; + secattr->flags |= NETLBL_SECATTR_DOMAIN_CPY | NETLBL_SECATTR_SECID; + mls_export_netlbl_lvl(ctx, secattr); + rc = mls_export_netlbl_cat(ctx, secattr); +out: + read_unlock(&policy_rwlock); + return rc; +} +#endif /* CONFIG_NETLABEL */ + +/** + * security_read_policy - read the policy. + * @data: binary policy data + * @len: length of data in bytes + * + */ +int security_read_policy(void **data, size_t *len) +{ + int rc; + struct policy_file fp; + + if (!ss_initialized) + return -EINVAL; + + *len = security_policydb_len(); + + *data = vmalloc_user(*len); + if (!*data) + return -ENOMEM; + + fp.data = *data; + fp.len = *len; + + read_lock(&policy_rwlock); + rc = policydb_write(&policydb, &fp); + read_unlock(&policy_rwlock); + + if (rc) + return rc; + + *len = (unsigned long)fp.data - (unsigned long)*data; + return 0; + +} diff --git a/security/selinux/ss/services.h b/security/selinux/ss/services.h new file mode 100644 index 00000000..e8d907e9 --- /dev/null +++ b/security/selinux/ss/services.h @@ -0,0 +1,15 @@ +/* + * Implementation of the security services. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SS_SERVICES_H_ +#define _SS_SERVICES_H_ + +#include "policydb.h" +#include "sidtab.h" + +extern struct policydb policydb; + +#endif /* _SS_SERVICES_H_ */ + diff --git a/security/selinux/ss/sidtab.c b/security/selinux/ss/sidtab.c new file mode 100644 index 00000000..5840a351 --- /dev/null +++ b/security/selinux/ss/sidtab.c @@ -0,0 +1,313 @@ +/* + * Implementation of the SID table type. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/errno.h> +#include "flask.h" +#include "security.h" +#include "sidtab.h" + +#define SIDTAB_HASH(sid) \ +(sid & SIDTAB_HASH_MASK) + +int sidtab_init(struct sidtab *s) +{ + int i; + + s->htable = kmalloc(sizeof(*(s->htable)) * SIDTAB_SIZE, GFP_ATOMIC); + if (!s->htable) + return -ENOMEM; + for (i = 0; i < SIDTAB_SIZE; i++) + s->htable[i] = NULL; + s->nel = 0; + s->next_sid = 1; + s->shutdown = 0; + spin_lock_init(&s->lock); + return 0; +} + +int sidtab_insert(struct sidtab *s, u32 sid, struct context *context) +{ + int hvalue, rc = 0; + struct sidtab_node *prev, *cur, *newnode; + + if (!s) { + rc = -ENOMEM; + goto out; + } + + hvalue = SIDTAB_HASH(sid); + prev = NULL; + cur = s->htable[hvalue]; + while (cur && sid > cur->sid) { + prev = cur; + cur = cur->next; + } + + if (cur && sid == cur->sid) { + rc = -EEXIST; + goto out; + } + + newnode = kmalloc(sizeof(*newnode), GFP_ATOMIC); + if (newnode == NULL) { + rc = -ENOMEM; + goto out; + } + newnode->sid = sid; + if (context_cpy(&newnode->context, context)) { + kfree(newnode); + rc = -ENOMEM; + goto out; + } + + if (prev) { + newnode->next = prev->next; + wmb(); + prev->next = newnode; + } else { + newnode->next = s->htable[hvalue]; + wmb(); + s->htable[hvalue] = newnode; + } + + s->nel++; + if (sid >= s->next_sid) + s->next_sid = sid + 1; +out: + return rc; +} + +static struct context *sidtab_search_core(struct sidtab *s, u32 sid, int force) +{ + int hvalue; + struct sidtab_node *cur; + + if (!s) + return NULL; + + hvalue = SIDTAB_HASH(sid); + cur = s->htable[hvalue]; + while (cur && sid > cur->sid) + cur = cur->next; + + if (force && cur && sid == cur->sid && cur->context.len) + return &cur->context; + + if (cur == NULL || sid != cur->sid || cur->context.len) { + /* Remap invalid SIDs to the unlabeled SID. */ + sid = SECINITSID_UNLABELED; + hvalue = SIDTAB_HASH(sid); + cur = s->htable[hvalue]; + while (cur && sid > cur->sid) + cur = cur->next; + if (!cur || sid != cur->sid) + return NULL; + } + + return &cur->context; +} + +struct context *sidtab_search(struct sidtab *s, u32 sid) +{ + return sidtab_search_core(s, sid, 0); +} + +struct context *sidtab_search_force(struct sidtab *s, u32 sid) +{ + return sidtab_search_core(s, sid, 1); +} + +int sidtab_map(struct sidtab *s, + int (*apply) (u32 sid, + struct context *context, + void *args), + void *args) +{ + int i, rc = 0; + struct sidtab_node *cur; + + if (!s) + goto out; + + for (i = 0; i < SIDTAB_SIZE; i++) { + cur = s->htable[i]; + while (cur) { + rc = apply(cur->sid, &cur->context, args); + if (rc) + goto out; + cur = cur->next; + } + } +out: + return rc; +} + +static void sidtab_update_cache(struct sidtab *s, struct sidtab_node *n, int loc) +{ + BUG_ON(loc >= SIDTAB_CACHE_LEN); + + while (loc > 0) { + s->cache[loc] = s->cache[loc - 1]; + loc--; + } + s->cache[0] = n; +} + +static inline u32 sidtab_search_context(struct sidtab *s, + struct context *context) +{ + int i; + struct sidtab_node *cur; + + for (i = 0; i < SIDTAB_SIZE; i++) { + cur = s->htable[i]; + while (cur) { + if (context_cmp(&cur->context, context)) { + sidtab_update_cache(s, cur, SIDTAB_CACHE_LEN - 1); + return cur->sid; + } + cur = cur->next; + } + } + return 0; +} + +static inline u32 sidtab_search_cache(struct sidtab *s, struct context *context) +{ + int i; + struct sidtab_node *node; + + for (i = 0; i < SIDTAB_CACHE_LEN; i++) { + node = s->cache[i]; + if (unlikely(!node)) + return 0; + if (context_cmp(&node->context, context)) { + sidtab_update_cache(s, node, i); + return node->sid; + } + } + return 0; +} + +int sidtab_context_to_sid(struct sidtab *s, + struct context *context, + u32 *out_sid) +{ + u32 sid; + int ret = 0; + unsigned long flags; + + *out_sid = SECSID_NULL; + + sid = sidtab_search_cache(s, context); + if (!sid) + sid = sidtab_search_context(s, context); + if (!sid) { + spin_lock_irqsave(&s->lock, flags); + /* Rescan now that we hold the lock. */ + sid = sidtab_search_context(s, context); + if (sid) + goto unlock_out; + /* No SID exists for the context. Allocate a new one. */ + if (s->next_sid == UINT_MAX || s->shutdown) { + ret = -ENOMEM; + goto unlock_out; + } + sid = s->next_sid++; + if (context->len) + printk(KERN_INFO + "SELinux: Context %s is not valid (left unmapped).\n", + context->str); + ret = sidtab_insert(s, sid, context); + if (ret) + s->next_sid--; +unlock_out: + spin_unlock_irqrestore(&s->lock, flags); + } + + if (ret) + return ret; + + *out_sid = sid; + return 0; +} + +void sidtab_hash_eval(struct sidtab *h, char *tag) +{ + int i, chain_len, slots_used, max_chain_len; + struct sidtab_node *cur; + + slots_used = 0; + max_chain_len = 0; + for (i = 0; i < SIDTAB_SIZE; i++) { + cur = h->htable[i]; + if (cur) { + slots_used++; + chain_len = 0; + while (cur) { + chain_len++; + cur = cur->next; + } + + if (chain_len > max_chain_len) + max_chain_len = chain_len; + } + } + + printk(KERN_DEBUG "%s: %d entries and %d/%d buckets used, longest " + "chain length %d\n", tag, h->nel, slots_used, SIDTAB_SIZE, + max_chain_len); +} + +void sidtab_destroy(struct sidtab *s) +{ + int i; + struct sidtab_node *cur, *temp; + + if (!s) + return; + + for (i = 0; i < SIDTAB_SIZE; i++) { + cur = s->htable[i]; + while (cur) { + temp = cur; + cur = cur->next; + context_destroy(&temp->context); + kfree(temp); + } + s->htable[i] = NULL; + } + kfree(s->htable); + s->htable = NULL; + s->nel = 0; + s->next_sid = 1; +} + +void sidtab_set(struct sidtab *dst, struct sidtab *src) +{ + unsigned long flags; + int i; + + spin_lock_irqsave(&src->lock, flags); + dst->htable = src->htable; + dst->nel = src->nel; + dst->next_sid = src->next_sid; + dst->shutdown = 0; + for (i = 0; i < SIDTAB_CACHE_LEN; i++) + dst->cache[i] = NULL; + spin_unlock_irqrestore(&src->lock, flags); +} + +void sidtab_shutdown(struct sidtab *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + s->shutdown = 1; + spin_unlock_irqrestore(&s->lock, flags); +} diff --git a/security/selinux/ss/sidtab.h b/security/selinux/ss/sidtab.h new file mode 100644 index 00000000..84dc154d --- /dev/null +++ b/security/selinux/ss/sidtab.h @@ -0,0 +1,56 @@ +/* + * A security identifier table (sidtab) is a hash table + * of security context structures indexed by SID value. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SS_SIDTAB_H_ +#define _SS_SIDTAB_H_ + +#include "context.h" + +struct sidtab_node { + u32 sid; /* security identifier */ + struct context context; /* security context structure */ + struct sidtab_node *next; +}; + +#define SIDTAB_HASH_BITS 7 +#define SIDTAB_HASH_BUCKETS (1 << SIDTAB_HASH_BITS) +#define SIDTAB_HASH_MASK (SIDTAB_HASH_BUCKETS-1) + +#define SIDTAB_SIZE SIDTAB_HASH_BUCKETS + +struct sidtab { + struct sidtab_node **htable; + unsigned int nel; /* number of elements */ + unsigned int next_sid; /* next SID to allocate */ + unsigned char shutdown; +#define SIDTAB_CACHE_LEN 3 + struct sidtab_node *cache[SIDTAB_CACHE_LEN]; + spinlock_t lock; +}; + +int sidtab_init(struct sidtab *s); +int sidtab_insert(struct sidtab *s, u32 sid, struct context *context); +struct context *sidtab_search(struct sidtab *s, u32 sid); +struct context *sidtab_search_force(struct sidtab *s, u32 sid); + +int sidtab_map(struct sidtab *s, + int (*apply) (u32 sid, + struct context *context, + void *args), + void *args); + +int sidtab_context_to_sid(struct sidtab *s, + struct context *context, + u32 *sid); + +void sidtab_hash_eval(struct sidtab *h, char *tag); +void sidtab_destroy(struct sidtab *s); +void sidtab_set(struct sidtab *dst, struct sidtab *src); +void sidtab_shutdown(struct sidtab *s); + +#endif /* _SS_SIDTAB_H_ */ + + diff --git a/security/selinux/ss/status.c b/security/selinux/ss/status.c new file mode 100644 index 00000000..d982365f --- /dev/null +++ b/security/selinux/ss/status.c @@ -0,0 +1,126 @@ +/* + * mmap based event notifications for SELinux + * + * Author: KaiGai Kohei <kaigai@ak.jp.nec.com> + * + * Copyright (C) 2010 NEC corporation + * + * 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/kernel.h> +#include <linux/gfp.h> +#include <linux/mm.h> +#include <linux/mutex.h> +#include "avc.h" +#include "services.h" + +/* + * The selinux_status_page shall be exposed to userspace applications + * using mmap interface on /selinux/status. + * It enables to notify applications a few events that will cause reset + * of userspace access vector without context switching. + * + * The selinux_kernel_status structure on the head of status page is + * protected from concurrent accesses using seqlock logic, so userspace + * application should reference the status page according to the seqlock + * logic. + * + * Typically, application checks status->sequence at the head of access + * control routine. If it is odd-number, kernel is updating the status, + * so please wait for a moment. If it is changed from the last sequence + * number, it means something happen, so application will reset userspace + * avc, if needed. + * In most cases, application shall confirm the kernel status is not + * changed without any system call invocations. + */ +static struct page *selinux_status_page; +static DEFINE_MUTEX(selinux_status_lock); + +/* + * selinux_kernel_status_page + * + * It returns a reference to selinux_status_page. If the status page is + * not allocated yet, it also tries to allocate it at the first time. + */ +struct page *selinux_kernel_status_page(void) +{ + struct selinux_kernel_status *status; + struct page *result = NULL; + + mutex_lock(&selinux_status_lock); + if (!selinux_status_page) { + selinux_status_page = alloc_page(GFP_KERNEL|__GFP_ZERO); + + if (selinux_status_page) { + status = page_address(selinux_status_page); + + status->version = SELINUX_KERNEL_STATUS_VERSION; + status->sequence = 0; + status->enforcing = selinux_enforcing; + /* + * NOTE: the next policyload event shall set + * a positive value on the status->policyload, + * although it may not be 1, but never zero. + * So, application can know it was updated. + */ + status->policyload = 0; + status->deny_unknown = !security_get_allow_unknown(); + } + } + result = selinux_status_page; + mutex_unlock(&selinux_status_lock); + + return result; +} + +/* + * selinux_status_update_setenforce + * + * It updates status of the current enforcing/permissive mode. + */ +void selinux_status_update_setenforce(int enforcing) +{ + struct selinux_kernel_status *status; + + mutex_lock(&selinux_status_lock); + if (selinux_status_page) { + status = page_address(selinux_status_page); + + status->sequence++; + smp_wmb(); + + status->enforcing = enforcing; + + smp_wmb(); + status->sequence++; + } + mutex_unlock(&selinux_status_lock); +} + +/* + * selinux_status_update_policyload + * + * It updates status of the times of policy reloaded, and current + * setting of deny_unknown. + */ +void selinux_status_update_policyload(int seqno) +{ + struct selinux_kernel_status *status; + + mutex_lock(&selinux_status_lock); + if (selinux_status_page) { + status = page_address(selinux_status_page); + + status->sequence++; + smp_wmb(); + + status->policyload = seqno; + status->deny_unknown = !security_get_allow_unknown(); + + smp_wmb(); + status->sequence++; + } + mutex_unlock(&selinux_status_lock); +} diff --git a/security/selinux/ss/symtab.c b/security/selinux/ss/symtab.c new file mode 100644 index 00000000..160326ee --- /dev/null +++ b/security/selinux/ss/symtab.c @@ -0,0 +1,43 @@ +/* + * Implementation of the symbol table type. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include "symtab.h" + +static unsigned int symhash(struct hashtab *h, const void *key) +{ + const char *p, *keyp; + unsigned int size; + unsigned int val; + + val = 0; + keyp = key; + size = strlen(keyp); + for (p = keyp; (p - keyp) < size; p++) + val = (val << 4 | (val >> (8*sizeof(unsigned int)-4))) ^ (*p); + return val & (h->size - 1); +} + +static int symcmp(struct hashtab *h, const void *key1, const void *key2) +{ + const char *keyp1, *keyp2; + + keyp1 = key1; + keyp2 = key2; + return strcmp(keyp1, keyp2); +} + + +int symtab_init(struct symtab *s, unsigned int size) +{ + s->table = hashtab_create(symhash, symcmp, size); + if (!s->table) + return -ENOMEM; + s->nprim = 0; + return 0; +} + diff --git a/security/selinux/ss/symtab.h b/security/selinux/ss/symtab.h new file mode 100644 index 00000000..ca422b42 --- /dev/null +++ b/security/selinux/ss/symtab.h @@ -0,0 +1,23 @@ +/* + * A symbol table (symtab) maintains associations between symbol + * strings and datum values. The type of the datum values + * is arbitrary. The symbol table type is implemented + * using the hash table type (hashtab). + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SS_SYMTAB_H_ +#define _SS_SYMTAB_H_ + +#include "hashtab.h" + +struct symtab { + struct hashtab *table; /* hash table (keyed on a string) */ + u32 nprim; /* number of primary names in table */ +}; + +int symtab_init(struct symtab *s, unsigned int size); + +#endif /* _SS_SYMTAB_H_ */ + + diff --git a/security/selinux/xfrm.c b/security/selinux/xfrm.c new file mode 100644 index 00000000..68178b76 --- /dev/null +++ b/security/selinux/xfrm.c @@ -0,0 +1,490 @@ +/* + * NSA Security-Enhanced Linux (SELinux) security module + * + * This file contains the SELinux XFRM hook function implementations. + * + * Authors: Serge Hallyn <sergeh@us.ibm.com> + * Trent Jaeger <jaegert@us.ibm.com> + * + * Updated: Venkat Yekkirala <vyekkirala@TrustedCS.com> + * + * Granular IPSec Associations for use in MLS environments. + * + * Copyright (C) 2005 International Business Machines Corporation + * Copyright (C) 2006 Trusted Computer Solutions, Inc. + * + * 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. + */ + +/* + * USAGE: + * NOTES: + * 1. Make sure to enable the following options in your kernel config: + * CONFIG_SECURITY=y + * CONFIG_SECURITY_NETWORK=y + * CONFIG_SECURITY_NETWORK_XFRM=y + * CONFIG_SECURITY_SELINUX=m/y + * ISSUES: + * 1. Caching packets, so they are not dropped during negotiation + * 2. Emulating a reasonable SO_PEERSEC across machines + * 3. Testing addition of sk_policy's with security context via setsockopt + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/security.h> +#include <linux/types.h> +#include <linux/netfilter.h> +#include <linux/netfilter_ipv4.h> +#include <linux/netfilter_ipv6.h> +#include <linux/slab.h> +#include <linux/ip.h> +#include <linux/tcp.h> +#include <linux/skbuff.h> +#include <linux/xfrm.h> +#include <net/xfrm.h> +#include <net/checksum.h> +#include <net/udp.h> +#include <asm/atomic.h> + +#include "avc.h" +#include "objsec.h" +#include "xfrm.h" + +/* Labeled XFRM instance counter */ +atomic_t selinux_xfrm_refcount = ATOMIC_INIT(0); + +/* + * Returns true if an LSM/SELinux context + */ +static inline int selinux_authorizable_ctx(struct xfrm_sec_ctx *ctx) +{ + return (ctx && + (ctx->ctx_doi == XFRM_SC_DOI_LSM) && + (ctx->ctx_alg == XFRM_SC_ALG_SELINUX)); +} + +/* + * Returns true if the xfrm contains a security blob for SELinux + */ +static inline int selinux_authorizable_xfrm(struct xfrm_state *x) +{ + return selinux_authorizable_ctx(x->security); +} + +/* + * LSM hook implementation that authorizes that a flow can use + * a xfrm policy rule. + */ +int selinux_xfrm_policy_lookup(struct xfrm_sec_ctx *ctx, u32 fl_secid, u8 dir) +{ + int rc; + u32 sel_sid; + + /* Context sid is either set to label or ANY_ASSOC */ + if (ctx) { + if (!selinux_authorizable_ctx(ctx)) + return -EINVAL; + + sel_sid = ctx->ctx_sid; + } else + /* + * All flows should be treated as polmatch'ing an + * otherwise applicable "non-labeled" policy. This + * would prevent inadvertent "leaks". + */ + return 0; + + rc = avc_has_perm(fl_secid, sel_sid, SECCLASS_ASSOCIATION, + ASSOCIATION__POLMATCH, + NULL); + + if (rc == -EACCES) + return -ESRCH; + + return rc; +} + +/* + * LSM hook implementation that authorizes that a state matches + * the given policy, flow combo. + */ + +int selinux_xfrm_state_pol_flow_match(struct xfrm_state *x, struct xfrm_policy *xp, + const struct flowi *fl) +{ + u32 state_sid; + int rc; + + if (!xp->security) + if (x->security) + /* unlabeled policy and labeled SA can't match */ + return 0; + else + /* unlabeled policy and unlabeled SA match all flows */ + return 1; + else + if (!x->security) + /* unlabeled SA and labeled policy can't match */ + return 0; + else + if (!selinux_authorizable_xfrm(x)) + /* Not a SELinux-labeled SA */ + return 0; + + state_sid = x->security->ctx_sid; + + if (fl->flowi_secid != state_sid) + return 0; + + rc = avc_has_perm(fl->flowi_secid, state_sid, SECCLASS_ASSOCIATION, + ASSOCIATION__SENDTO, + NULL)? 0:1; + + /* + * We don't need a separate SA Vs. policy polmatch check + * since the SA is now of the same label as the flow and + * a flow Vs. policy polmatch check had already happened + * in selinux_xfrm_policy_lookup() above. + */ + + return rc; +} + +/* + * LSM hook implementation that checks and/or returns the xfrm sid for the + * incoming packet. + */ + +int selinux_xfrm_decode_session(struct sk_buff *skb, u32 *sid, int ckall) +{ + struct sec_path *sp; + + *sid = SECSID_NULL; + + if (skb == NULL) + return 0; + + sp = skb->sp; + if (sp) { + int i, sid_set = 0; + + for (i = sp->len-1; i >= 0; i--) { + struct xfrm_state *x = sp->xvec[i]; + if (selinux_authorizable_xfrm(x)) { + struct xfrm_sec_ctx *ctx = x->security; + + if (!sid_set) { + *sid = ctx->ctx_sid; + sid_set = 1; + + if (!ckall) + break; + } else if (*sid != ctx->ctx_sid) + return -EINVAL; + } + } + } + + return 0; +} + +/* + * Security blob allocation for xfrm_policy and xfrm_state + * CTX does not have a meaningful value on input + */ +static int selinux_xfrm_sec_ctx_alloc(struct xfrm_sec_ctx **ctxp, + struct xfrm_user_sec_ctx *uctx, u32 sid) +{ + int rc = 0; + const struct task_security_struct *tsec = current_security(); + struct xfrm_sec_ctx *ctx = NULL; + char *ctx_str = NULL; + u32 str_len; + + BUG_ON(uctx && sid); + + if (!uctx) + goto not_from_user; + + if (uctx->ctx_alg != XFRM_SC_ALG_SELINUX) + return -EINVAL; + + str_len = uctx->ctx_len; + if (str_len >= PAGE_SIZE) + return -ENOMEM; + + *ctxp = ctx = kmalloc(sizeof(*ctx) + + str_len + 1, + GFP_KERNEL); + + if (!ctx) + return -ENOMEM; + + ctx->ctx_doi = uctx->ctx_doi; + ctx->ctx_len = str_len; + ctx->ctx_alg = uctx->ctx_alg; + + memcpy(ctx->ctx_str, + uctx+1, + str_len); + ctx->ctx_str[str_len] = 0; + rc = security_context_to_sid(ctx->ctx_str, + str_len, + &ctx->ctx_sid); + + if (rc) + goto out; + + /* + * Does the subject have permission to set security context? + */ + rc = avc_has_perm(tsec->sid, ctx->ctx_sid, + SECCLASS_ASSOCIATION, + ASSOCIATION__SETCONTEXT, NULL); + if (rc) + goto out; + + return rc; + +not_from_user: + rc = security_sid_to_context(sid, &ctx_str, &str_len); + if (rc) + goto out; + + *ctxp = ctx = kmalloc(sizeof(*ctx) + + str_len, + GFP_ATOMIC); + + if (!ctx) { + rc = -ENOMEM; + goto out; + } + + ctx->ctx_doi = XFRM_SC_DOI_LSM; + ctx->ctx_alg = XFRM_SC_ALG_SELINUX; + ctx->ctx_sid = sid; + ctx->ctx_len = str_len; + memcpy(ctx->ctx_str, + ctx_str, + str_len); + + goto out2; + +out: + *ctxp = NULL; + kfree(ctx); +out2: + kfree(ctx_str); + return rc; +} + +/* + * LSM hook implementation that allocs and transfers uctx spec to + * xfrm_policy. + */ +int selinux_xfrm_policy_alloc(struct xfrm_sec_ctx **ctxp, + struct xfrm_user_sec_ctx *uctx) +{ + int err; + + BUG_ON(!uctx); + + err = selinux_xfrm_sec_ctx_alloc(ctxp, uctx, 0); + if (err == 0) + atomic_inc(&selinux_xfrm_refcount); + + return err; +} + + +/* + * LSM hook implementation that copies security data structure from old to + * new for policy cloning. + */ +int selinux_xfrm_policy_clone(struct xfrm_sec_ctx *old_ctx, + struct xfrm_sec_ctx **new_ctxp) +{ + struct xfrm_sec_ctx *new_ctx; + + if (old_ctx) { + new_ctx = kmalloc(sizeof(*old_ctx) + old_ctx->ctx_len, + GFP_KERNEL); + if (!new_ctx) + return -ENOMEM; + + memcpy(new_ctx, old_ctx, sizeof(*new_ctx)); + memcpy(new_ctx->ctx_str, old_ctx->ctx_str, new_ctx->ctx_len); + *new_ctxp = new_ctx; + } + return 0; +} + +/* + * LSM hook implementation that frees xfrm_sec_ctx security information. + */ +void selinux_xfrm_policy_free(struct xfrm_sec_ctx *ctx) +{ + kfree(ctx); +} + +/* + * LSM hook implementation that authorizes deletion of labeled policies. + */ +int selinux_xfrm_policy_delete(struct xfrm_sec_ctx *ctx) +{ + const struct task_security_struct *tsec = current_security(); + int rc = 0; + + if (ctx) { + rc = avc_has_perm(tsec->sid, ctx->ctx_sid, + SECCLASS_ASSOCIATION, + ASSOCIATION__SETCONTEXT, NULL); + if (rc == 0) + atomic_dec(&selinux_xfrm_refcount); + } + + return rc; +} + +/* + * LSM hook implementation that allocs and transfers sec_ctx spec to + * xfrm_state. + */ +int selinux_xfrm_state_alloc(struct xfrm_state *x, struct xfrm_user_sec_ctx *uctx, + u32 secid) +{ + int err; + + BUG_ON(!x); + + err = selinux_xfrm_sec_ctx_alloc(&x->security, uctx, secid); + if (err == 0) + atomic_inc(&selinux_xfrm_refcount); + return err; +} + +/* + * LSM hook implementation that frees xfrm_state security information. + */ +void selinux_xfrm_state_free(struct xfrm_state *x) +{ + struct xfrm_sec_ctx *ctx = x->security; + kfree(ctx); +} + + /* + * LSM hook implementation that authorizes deletion of labeled SAs. + */ +int selinux_xfrm_state_delete(struct xfrm_state *x) +{ + const struct task_security_struct *tsec = current_security(); + struct xfrm_sec_ctx *ctx = x->security; + int rc = 0; + + if (ctx) { + rc = avc_has_perm(tsec->sid, ctx->ctx_sid, + SECCLASS_ASSOCIATION, + ASSOCIATION__SETCONTEXT, NULL); + if (rc == 0) + atomic_dec(&selinux_xfrm_refcount); + } + + return rc; +} + +/* + * LSM hook that controls access to unlabelled packets. If + * a xfrm_state is authorizable (defined by macro) then it was + * already authorized by the IPSec process. If not, then + * we need to check for unlabelled access since this may not have + * gone thru the IPSec process. + */ +int selinux_xfrm_sock_rcv_skb(u32 isec_sid, struct sk_buff *skb, + struct common_audit_data *ad) +{ + int i, rc = 0; + struct sec_path *sp; + u32 sel_sid = SECINITSID_UNLABELED; + + sp = skb->sp; + + if (sp) { + for (i = 0; i < sp->len; i++) { + struct xfrm_state *x = sp->xvec[i]; + + if (x && selinux_authorizable_xfrm(x)) { + struct xfrm_sec_ctx *ctx = x->security; + sel_sid = ctx->ctx_sid; + break; + } + } + } + + /* + * This check even when there's no association involved is + * intended, according to Trent Jaeger, to make sure a + * process can't engage in non-ipsec communication unless + * explicitly allowed by policy. + */ + + rc = avc_has_perm(isec_sid, sel_sid, SECCLASS_ASSOCIATION, + ASSOCIATION__RECVFROM, ad); + + return rc; +} + +/* + * POSTROUTE_LAST hook's XFRM processing: + * If we have no security association, then we need to determine + * whether the socket is allowed to send to an unlabelled destination. + * If we do have a authorizable security association, then it has already been + * checked in the selinux_xfrm_state_pol_flow_match hook above. + */ +int selinux_xfrm_postroute_last(u32 isec_sid, struct sk_buff *skb, + struct common_audit_data *ad, u8 proto) +{ + struct dst_entry *dst; + int rc = 0; + + dst = skb_dst(skb); + + if (dst) { + struct dst_entry *dst_test; + + for (dst_test = dst; dst_test != NULL; + dst_test = dst_test->child) { + struct xfrm_state *x = dst_test->xfrm; + + if (x && selinux_authorizable_xfrm(x)) + goto out; + } + } + + switch (proto) { + case IPPROTO_AH: + case IPPROTO_ESP: + case IPPROTO_COMP: + /* + * We should have already seen this packet once before + * it underwent xfrm(s). No need to subject it to the + * unlabeled check. + */ + goto out; + default: + break; + } + + /* + * This check even when there's no association involved is + * intended, according to Trent Jaeger, to make sure a + * process can't engage in non-ipsec communication unless + * explicitly allowed by policy. + */ + + rc = avc_has_perm(isec_sid, SECINITSID_UNLABELED, SECCLASS_ASSOCIATION, + ASSOCIATION__SENDTO, ad); +out: + return rc; +} diff --git a/security/smack/Kconfig b/security/smack/Kconfig new file mode 100644 index 00000000..603b0878 --- /dev/null +++ b/security/smack/Kconfig @@ -0,0 +1,10 @@ +config SECURITY_SMACK + bool "Simplified Mandatory Access Control Kernel Support" + depends on NETLABEL && SECURITY_NETWORK + default n + help + This selects the Simplified Mandatory Access Control Kernel. + Smack is useful for sensitivity, integrity, and a variety + of other mandatory security schemes. + If you are unsure how to answer this question, answer N. + diff --git a/security/smack/Makefile b/security/smack/Makefile new file mode 100644 index 00000000..67a63aae --- /dev/null +++ b/security/smack/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the SMACK LSM +# + +obj-$(CONFIG_SECURITY_SMACK) := smack.o + +smack-y := smack_lsm.o smack_access.o smackfs.o diff --git a/security/smack/smack.h b/security/smack/smack.h new file mode 100644 index 00000000..2b6c6a51 --- /dev/null +++ b/security/smack/smack.h @@ -0,0 +1,369 @@ +/* + * Copyright (C) 2007 Casey Schaufler <casey@schaufler-ca.com> + * + * 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. + * + * Author: + * Casey Schaufler <casey@schaufler-ca.com> + * + */ + +#ifndef _SECURITY_SMACK_H +#define _SECURITY_SMACK_H + +#include <linux/capability.h> +#include <linux/spinlock.h> +#include <linux/security.h> +#include <linux/in.h> +#include <net/netlabel.h> +#include <linux/list.h> +#include <linux/rculist.h> +#include <linux/lsm_audit.h> + +/* + * Why 23? CIPSO is constrained to 30, so a 32 byte buffer is + * bigger than can be used, and 24 is the next lower multiple + * of 8, and there are too many issues if there isn't space set + * aside for the terminating null byte. + */ +#define SMK_MAXLEN 23 +#define SMK_LABELLEN (SMK_MAXLEN+1) + +struct superblock_smack { + char *smk_root; + char *smk_floor; + char *smk_hat; + char *smk_default; + int smk_initialized; + spinlock_t smk_sblock; /* for initialization */ +}; + +struct socket_smack { + char *smk_out; /* outbound label */ + char *smk_in; /* inbound label */ + char smk_packet[SMK_LABELLEN]; /* TCP peer label */ +}; + +/* + * Inode smack data + */ +struct inode_smack { + char *smk_inode; /* label of the fso */ + char *smk_task; /* label of the task */ + char *smk_mmap; /* label of the mmap domain */ + struct mutex smk_lock; /* initialization lock */ + int smk_flags; /* smack inode flags */ +}; + +struct task_smack { + char *smk_task; /* label for access control */ + char *smk_forked; /* label when forked */ + struct list_head smk_rules; /* per task access rules */ + struct mutex smk_rules_lock; /* lock for the rules */ +}; + +#define SMK_INODE_INSTANT 0x01 /* inode is instantiated */ +#define SMK_INODE_TRANSMUTE 0x02 /* directory is transmuting */ + +/* + * A label access rule. + */ +struct smack_rule { + struct list_head list; + char *smk_subject; + char *smk_object; + int smk_access; +}; + +/* + * An entry in the table mapping smack values to + * CIPSO level/category-set values. + */ +struct smack_cipso { + int smk_level; + char smk_catset[SMK_LABELLEN]; +}; + +/* + * An entry in the table identifying hosts. + */ +struct smk_netlbladdr { + struct list_head list; + struct sockaddr_in smk_host; /* network address */ + struct in_addr smk_mask; /* network mask */ + char *smk_label; /* label */ +}; + +/* + * This is the repository for labels seen so that it is + * not necessary to keep allocating tiny chuncks of memory + * and so that they can be shared. + * + * Labels are never modified in place. Anytime a label + * is imported (e.g. xattrset on a file) the list is checked + * for it and it is added if it doesn't exist. The address + * is passed out in either case. Entries are added, but + * never deleted. + * + * Since labels are hanging around anyway it doesn't + * hurt to maintain a secid for those awkward situations + * where kernel components that ought to use LSM independent + * interfaces don't. The secid should go away when all of + * these components have been repaired. + * + * If there is a cipso value associated with the label it + * gets stored here, too. This will most likely be rare as + * the cipso direct mapping in used internally. + */ +struct smack_known { + struct list_head list; + char smk_known[SMK_LABELLEN]; + u32 smk_secid; + struct smack_cipso *smk_cipso; + spinlock_t smk_cipsolock; /* for changing cipso map */ +}; + +/* + * Mount options + */ +#define SMK_FSDEFAULT "smackfsdef=" +#define SMK_FSFLOOR "smackfsfloor=" +#define SMK_FSHAT "smackfshat=" +#define SMK_FSROOT "smackfsroot=" + +#define SMACK_CIPSO_OPTION "-CIPSO" + +/* + * How communications on this socket are treated. + * Usually it's determined by the underlying netlabel code + * but there are certain cases, including single label hosts + * and potentially single label interfaces for which the + * treatment can not be known in advance. + * + * The possibility of additional labeling schemes being + * introduced in the future exists as well. + */ +#define SMACK_UNLABELED_SOCKET 0 +#define SMACK_CIPSO_SOCKET 1 + +/* + * smackfs magic number + * smackfs macic number + */ +#define SMACK_MAGIC 0x43415d53 /* "SMAC" */ + +/* + * CIPSO defaults. + */ +#define SMACK_CIPSO_DOI_DEFAULT 3 /* Historical */ +#define SMACK_CIPSO_DOI_INVALID -1 /* Not a DOI */ +#define SMACK_CIPSO_DIRECT_DEFAULT 250 /* Arbitrary */ +#define SMACK_CIPSO_MAXCATVAL 63 /* Bigger gets harder */ +#define SMACK_CIPSO_MAXLEVEL 255 /* CIPSO 2.2 standard */ +#define SMACK_CIPSO_MAXCATNUM 239 /* CIPSO 2.2 standard */ + +/* + * Flag for transmute access + */ +#define MAY_TRANSMUTE 64 +/* + * Just to make the common cases easier to deal with + */ +#define MAY_ANYREAD (MAY_READ | MAY_EXEC) +#define MAY_READWRITE (MAY_READ | MAY_WRITE) +#define MAY_NOT 0 + +/* + * Number of access types used by Smack (rwxa) + */ +#define SMK_NUM_ACCESS_TYPE 4 + +/* + * Smack audit data; is empty if CONFIG_AUDIT not set + * to save some stack + */ +struct smk_audit_info { +#ifdef CONFIG_AUDIT + struct common_audit_data a; +#endif +}; +/* + * These functions are in smack_lsm.c + */ +struct inode_smack *new_inode_smack(char *); + +/* + * These functions are in smack_access.c + */ +int smk_access_entry(char *, char *, struct list_head *); +int smk_access(char *, char *, int, struct smk_audit_info *); +int smk_curacc(char *, u32, struct smk_audit_info *); +int smack_to_cipso(const char *, struct smack_cipso *); +void smack_from_cipso(u32, char *, char *); +char *smack_from_secid(const u32); +char *smk_import(const char *, int); +struct smack_known *smk_import_entry(const char *, int); +u32 smack_to_secid(const char *); + +/* + * Shared data. + */ +extern int smack_cipso_direct; +extern char *smack_net_ambient; +extern char *smack_onlycap; +extern const char *smack_cipso_option; + +extern struct smack_known smack_known_floor; +extern struct smack_known smack_known_hat; +extern struct smack_known smack_known_huh; +extern struct smack_known smack_known_invalid; +extern struct smack_known smack_known_star; +extern struct smack_known smack_known_web; + +extern struct list_head smack_known_list; +extern struct list_head smack_rule_list; +extern struct list_head smk_netlbladdr_list; + +extern struct security_operations smack_ops; + +/* + * Stricly for CIPSO level manipulation. + * Set the category bit number in a smack label sized buffer. + */ +static inline void smack_catset_bit(int cat, char *catsetp) +{ + if (cat > SMK_LABELLEN * 8) + return; + + catsetp[(cat - 1) / 8] |= 0x80 >> ((cat - 1) % 8); +} + +/* + * Is the directory transmuting? + */ +static inline int smk_inode_transmutable(const struct inode *isp) +{ + struct inode_smack *sip = isp->i_security; + return (sip->smk_flags & SMK_INODE_TRANSMUTE) != 0; +} + +/* + * Present a pointer to the smack label in an inode blob. + */ +static inline char *smk_of_inode(const struct inode *isp) +{ + struct inode_smack *sip = isp->i_security; + return sip->smk_inode; +} + +/* + * Present a pointer to the smack label in an task blob. + */ +static inline char *smk_of_task(const struct task_smack *tsp) +{ + return tsp->smk_task; +} + +/* + * Present a pointer to the forked smack label in an task blob. + */ +static inline char *smk_of_forked(const struct task_smack *tsp) +{ + return tsp->smk_forked; +} + +/* + * Present a pointer to the smack label in the current task blob. + */ +static inline char *smk_of_current(void) +{ + return smk_of_task(current_security()); +} + +/* + * logging functions + */ +#define SMACK_AUDIT_DENIED 0x1 +#define SMACK_AUDIT_ACCEPT 0x2 +extern int log_policy; + +void smack_log(char *subject_label, char *object_label, + int request, + int result, struct smk_audit_info *auditdata); + +#ifdef CONFIG_AUDIT + +/* + * some inline functions to set up audit data + * they do nothing if CONFIG_AUDIT is not set + * + */ +static inline void smk_ad_init(struct smk_audit_info *a, const char *func, + char type) +{ + memset(a, 0, sizeof(*a)); + a->a.type = type; + a->a.smack_audit_data.function = func; +} + +static inline void smk_ad_setfield_u_tsk(struct smk_audit_info *a, + struct task_struct *t) +{ + a->a.u.tsk = t; +} +static inline void smk_ad_setfield_u_fs_path_dentry(struct smk_audit_info *a, + struct dentry *d) +{ + a->a.u.dentry = d; +} +static inline void smk_ad_setfield_u_fs_inode(struct smk_audit_info *a, + struct inode *i) +{ + a->a.u.inode = i; +} +static inline void smk_ad_setfield_u_fs_path(struct smk_audit_info *a, + struct path p) +{ + a->a.u.path = p; +} +static inline void smk_ad_setfield_u_net_sk(struct smk_audit_info *a, + struct sock *sk) +{ + a->a.u.net.sk = sk; +} + +#else /* no AUDIT */ + +static inline void smk_ad_init(struct smk_audit_info *a, const char *func, + char type) +{ +} +static inline void smk_ad_setfield_u_tsk(struct smk_audit_info *a, + struct task_struct *t) +{ +} +static inline void smk_ad_setfield_u_fs_path_dentry(struct smk_audit_info *a, + struct dentry *d) +{ +} +static inline void smk_ad_setfield_u_fs_path_mnt(struct smk_audit_info *a, + struct vfsmount *m) +{ +} +static inline void smk_ad_setfield_u_fs_inode(struct smk_audit_info *a, + struct inode *i) +{ +} +static inline void smk_ad_setfield_u_fs_path(struct smk_audit_info *a, + struct path p) +{ +} +static inline void smk_ad_setfield_u_net_sk(struct smk_audit_info *a, + struct sock *sk) +{ +} +#endif + +#endif /* _SECURITY_SMACK_H */ diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c new file mode 100644 index 00000000..9637e107 --- /dev/null +++ b/security/smack/smack_access.c @@ -0,0 +1,547 @@ +/* + * Copyright (C) 2007 Casey Schaufler <casey@schaufler-ca.com> + * + * 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. + * + * Author: + * Casey Schaufler <casey@schaufler-ca.com> + * + */ + +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/sched.h> +#include "smack.h" + +struct smack_known smack_known_huh = { + .smk_known = "?", + .smk_secid = 2, + .smk_cipso = NULL, +}; + +struct smack_known smack_known_hat = { + .smk_known = "^", + .smk_secid = 3, + .smk_cipso = NULL, +}; + +struct smack_known smack_known_star = { + .smk_known = "*", + .smk_secid = 4, + .smk_cipso = NULL, +}; + +struct smack_known smack_known_floor = { + .smk_known = "_", + .smk_secid = 5, + .smk_cipso = NULL, +}; + +struct smack_known smack_known_invalid = { + .smk_known = "", + .smk_secid = 6, + .smk_cipso = NULL, +}; + +struct smack_known smack_known_web = { + .smk_known = "@", + .smk_secid = 7, + .smk_cipso = NULL, +}; + +LIST_HEAD(smack_known_list); + +/* + * The initial value needs to be bigger than any of the + * known values above. + */ +static u32 smack_next_secid = 10; + +/* + * what events do we log + * can be overwritten at run-time by /smack/logging + */ +int log_policy = SMACK_AUDIT_DENIED; + +/** + * smk_access_entry - look up matching access rule + * @subject_label: a pointer to the subject's Smack label + * @object_label: a pointer to the object's Smack label + * @rule_list: the list of rules to search + * + * This function looks up the subject/object pair in the + * access rule list and returns the access mode. If no + * entry is found returns -ENOENT. + * + * NOTE: + * Even though Smack labels are usually shared on smack_list + * labels that come in off the network can't be imported + * and added to the list for locking reasons. + * + * Therefore, it is necessary to check the contents of the labels, + * not just the pointer values. Of course, in most cases the labels + * will be on the list, so checking the pointers may be a worthwhile + * optimization. + */ +int smk_access_entry(char *subject_label, char *object_label, + struct list_head *rule_list) +{ + int may = -ENOENT; + struct smack_rule *srp; + + list_for_each_entry_rcu(srp, rule_list, list) { + if (srp->smk_subject == subject_label || + strcmp(srp->smk_subject, subject_label) == 0) { + if (srp->smk_object == object_label || + strcmp(srp->smk_object, object_label) == 0) { + may = srp->smk_access; + break; + } + } + } + + return may; +} + +/** + * smk_access - determine if a subject has a specific access to an object + * @subject_label: a pointer to the subject's Smack label + * @object_label: a pointer to the object's Smack label + * @request: the access requested, in "MAY" format + * @a : a pointer to the audit data + * + * This function looks up the subject/object pair in the + * access rule list and returns 0 if the access is permitted, + * non zero otherwise. + * + * Even though Smack labels are usually shared on smack_list + * labels that come in off the network can't be imported + * and added to the list for locking reasons. + * + * Therefore, it is necessary to check the contents of the labels, + * not just the pointer values. Of course, in most cases the labels + * will be on the list, so checking the pointers may be a worthwhile + * optimization. + */ +int smk_access(char *subject_label, char *object_label, int request, + struct smk_audit_info *a) +{ + int may = MAY_NOT; + int rc = 0; + + /* + * Hardcoded comparisons. + * + * A star subject can't access any object. + */ + if (subject_label == smack_known_star.smk_known || + strcmp(subject_label, smack_known_star.smk_known) == 0) { + rc = -EACCES; + goto out_audit; + } + /* + * An internet object can be accessed by any subject. + * Tasks cannot be assigned the internet label. + * An internet subject can access any object. + */ + if (object_label == smack_known_web.smk_known || + subject_label == smack_known_web.smk_known || + strcmp(object_label, smack_known_web.smk_known) == 0 || + strcmp(subject_label, smack_known_web.smk_known) == 0) + goto out_audit; + /* + * A star object can be accessed by any subject. + */ + if (object_label == smack_known_star.smk_known || + strcmp(object_label, smack_known_star.smk_known) == 0) + goto out_audit; + /* + * An object can be accessed in any way by a subject + * with the same label. + */ + if (subject_label == object_label || + strcmp(subject_label, object_label) == 0) + goto out_audit; + /* + * A hat subject can read any object. + * A floor object can be read by any subject. + */ + if ((request & MAY_ANYREAD) == request) { + if (object_label == smack_known_floor.smk_known || + strcmp(object_label, smack_known_floor.smk_known) == 0) + goto out_audit; + if (subject_label == smack_known_hat.smk_known || + strcmp(subject_label, smack_known_hat.smk_known) == 0) + goto out_audit; + } + /* + * Beyond here an explicit relationship is required. + * If the requested access is contained in the available + * access (e.g. read is included in readwrite) it's + * good. A negative response from smk_access_entry() + * indicates there is no entry for this pair. + */ + rcu_read_lock(); + may = smk_access_entry(subject_label, object_label, &smack_rule_list); + rcu_read_unlock(); + + if (may > 0 && (request & may) == request) + goto out_audit; + + rc = -EACCES; +out_audit: +#ifdef CONFIG_AUDIT + if (a) + smack_log(subject_label, object_label, request, rc, a); +#endif + return rc; +} + +/** + * smk_curacc - determine if current has a specific access to an object + * @obj_label: a pointer to the object's Smack label + * @mode: the access requested, in "MAY" format + * @a : common audit data + * + * This function checks the current subject label/object label pair + * in the access rule list and returns 0 if the access is permitted, + * non zero otherwise. It allows that current may have the capability + * to override the rules. + */ +int smk_curacc(char *obj_label, u32 mode, struct smk_audit_info *a) +{ + struct task_smack *tsp = current_security(); + char *sp = smk_of_task(tsp); + int may; + int rc; + + /* + * Check the global rule list + */ + rc = smk_access(sp, obj_label, mode, NULL); + if (rc == 0) { + /* + * If there is an entry in the task's rule list + * it can further restrict access. + */ + may = smk_access_entry(sp, obj_label, &tsp->smk_rules); + if (may < 0) + goto out_audit; + if ((mode & may) == mode) + goto out_audit; + rc = -EACCES; + } + + /* + * Return if a specific label has been designated as the + * only one that gets privilege and current does not + * have that label. + */ + if (smack_onlycap != NULL && smack_onlycap != sp) + goto out_audit; + + if (capable(CAP_MAC_OVERRIDE)) + rc = 0; + +out_audit: +#ifdef CONFIG_AUDIT + if (a) + smack_log(sp, obj_label, mode, rc, a); +#endif + return rc; +} + +#ifdef CONFIG_AUDIT +/** + * smack_str_from_perm : helper to transalate an int to a + * readable string + * @string : the string to fill + * @access : the int + * + */ +static inline void smack_str_from_perm(char *string, int access) +{ + int i = 0; + if (access & MAY_READ) + string[i++] = 'r'; + if (access & MAY_WRITE) + string[i++] = 'w'; + if (access & MAY_EXEC) + string[i++] = 'x'; + if (access & MAY_APPEND) + string[i++] = 'a'; + string[i] = '\0'; +} +/** + * smack_log_callback - SMACK specific information + * will be called by generic audit code + * @ab : the audit_buffer + * @a : audit_data + * + */ +static void smack_log_callback(struct audit_buffer *ab, void *a) +{ + struct common_audit_data *ad = a; + struct smack_audit_data *sad = &ad->smack_audit_data; + audit_log_format(ab, "lsm=SMACK fn=%s action=%s", + ad->smack_audit_data.function, + sad->result ? "denied" : "granted"); + audit_log_format(ab, " subject="); + audit_log_untrustedstring(ab, sad->subject); + audit_log_format(ab, " object="); + audit_log_untrustedstring(ab, sad->object); + audit_log_format(ab, " requested=%s", sad->request); +} + +/** + * smack_log - Audit the granting or denial of permissions. + * @subject_label : smack label of the requester + * @object_label : smack label of the object being accessed + * @request: requested permissions + * @result: result from smk_access + * @a: auxiliary audit data + * + * Audit the granting or denial of permissions in accordance + * with the policy. + */ +void smack_log(char *subject_label, char *object_label, int request, + int result, struct smk_audit_info *ad) +{ + char request_buffer[SMK_NUM_ACCESS_TYPE + 1]; + struct smack_audit_data *sad; + struct common_audit_data *a = &ad->a; + + /* check if we have to log the current event */ + if (result != 0 && (log_policy & SMACK_AUDIT_DENIED) == 0) + return; + if (result == 0 && (log_policy & SMACK_AUDIT_ACCEPT) == 0) + return; + + if (a->smack_audit_data.function == NULL) + a->smack_audit_data.function = "unknown"; + + /* end preparing the audit data */ + sad = &a->smack_audit_data; + smack_str_from_perm(request_buffer, request); + sad->subject = subject_label; + sad->object = object_label; + sad->request = request_buffer; + sad->result = result; + a->lsm_pre_audit = smack_log_callback; + + common_lsm_audit(a); +} +#else /* #ifdef CONFIG_AUDIT */ +void smack_log(char *subject_label, char *object_label, int request, + int result, struct smk_audit_info *ad) +{ +} +#endif + +static DEFINE_MUTEX(smack_known_lock); + +/** + * smk_import_entry - import a label, return the list entry + * @string: a text string that might be a Smack label + * @len: the maximum size, or zero if it is NULL terminated. + * + * Returns a pointer to the entry in the label list that + * matches the passed string, adding it if necessary. + */ +struct smack_known *smk_import_entry(const char *string, int len) +{ + struct smack_known *skp; + char smack[SMK_LABELLEN]; + int found; + int i; + + if (len <= 0 || len > SMK_MAXLEN) + len = SMK_MAXLEN; + + for (i = 0, found = 0; i < SMK_LABELLEN; i++) { + if (found) + smack[i] = '\0'; + else if (i >= len || string[i] > '~' || string[i] <= ' ' || + string[i] == '/' || string[i] == '"' || + string[i] == '\\' || string[i] == '\'') { + smack[i] = '\0'; + found = 1; + } else + smack[i] = string[i]; + } + + if (smack[0] == '\0') + return NULL; + + mutex_lock(&smack_known_lock); + + found = 0; + list_for_each_entry_rcu(skp, &smack_known_list, list) { + if (strncmp(skp->smk_known, smack, SMK_MAXLEN) == 0) { + found = 1; + break; + } + } + + if (found == 0) { + skp = kzalloc(sizeof(struct smack_known), GFP_KERNEL); + if (skp != NULL) { + strncpy(skp->smk_known, smack, SMK_MAXLEN); + skp->smk_secid = smack_next_secid++; + skp->smk_cipso = NULL; + spin_lock_init(&skp->smk_cipsolock); + /* + * Make sure that the entry is actually + * filled before putting it on the list. + */ + list_add_rcu(&skp->list, &smack_known_list); + } + } + + mutex_unlock(&smack_known_lock); + + return skp; +} + +/** + * smk_import - import a smack label + * @string: a text string that might be a Smack label + * @len: the maximum size, or zero if it is NULL terminated. + * + * Returns a pointer to the label in the label list that + * matches the passed string, adding it if necessary. + */ +char *smk_import(const char *string, int len) +{ + struct smack_known *skp; + + /* labels cannot begin with a '-' */ + if (string[0] == '-') + return NULL; + skp = smk_import_entry(string, len); + if (skp == NULL) + return NULL; + return skp->smk_known; +} + +/** + * smack_from_secid - find the Smack label associated with a secid + * @secid: an integer that might be associated with a Smack label + * + * Returns a pointer to the appropriate Smack label if there is one, + * otherwise a pointer to the invalid Smack label. + */ +char *smack_from_secid(const u32 secid) +{ + struct smack_known *skp; + + rcu_read_lock(); + list_for_each_entry_rcu(skp, &smack_known_list, list) { + if (skp->smk_secid == secid) { + rcu_read_unlock(); + return skp->smk_known; + } + } + + /* + * If we got this far someone asked for the translation + * of a secid that is not on the list. + */ + rcu_read_unlock(); + return smack_known_invalid.smk_known; +} + +/** + * smack_to_secid - find the secid associated with a Smack label + * @smack: the Smack label + * + * Returns the appropriate secid if there is one, + * otherwise 0 + */ +u32 smack_to_secid(const char *smack) +{ + struct smack_known *skp; + + rcu_read_lock(); + list_for_each_entry_rcu(skp, &smack_known_list, list) { + if (strncmp(skp->smk_known, smack, SMK_MAXLEN) == 0) { + rcu_read_unlock(); + return skp->smk_secid; + } + } + rcu_read_unlock(); + return 0; +} + +/** + * smack_from_cipso - find the Smack label associated with a CIPSO option + * @level: Bell & LaPadula level from the network + * @cp: Bell & LaPadula categories from the network + * @result: where to put the Smack value + * + * This is a simple lookup in the label table. + * + * This is an odd duck as far as smack handling goes in that + * it sends back a copy of the smack label rather than a pointer + * to the master list. This is done because it is possible for + * a foreign host to send a smack label that is new to this + * machine and hence not on the list. That would not be an + * issue except that adding an entry to the master list can't + * be done at that point. + */ +void smack_from_cipso(u32 level, char *cp, char *result) +{ + struct smack_known *kp; + char *final = NULL; + + rcu_read_lock(); + list_for_each_entry(kp, &smack_known_list, list) { + if (kp->smk_cipso == NULL) + continue; + + spin_lock_bh(&kp->smk_cipsolock); + + if (kp->smk_cipso->smk_level == level && + memcmp(kp->smk_cipso->smk_catset, cp, SMK_LABELLEN) == 0) + final = kp->smk_known; + + spin_unlock_bh(&kp->smk_cipsolock); + } + rcu_read_unlock(); + if (final == NULL) + final = smack_known_huh.smk_known; + strncpy(result, final, SMK_MAXLEN); + return; +} + +/** + * smack_to_cipso - find the CIPSO option to go with a Smack label + * @smack: a pointer to the smack label in question + * @cp: where to put the result + * + * Returns zero if a value is available, non-zero otherwise. + */ +int smack_to_cipso(const char *smack, struct smack_cipso *cp) +{ + struct smack_known *kp; + int found = 0; + + rcu_read_lock(); + list_for_each_entry_rcu(kp, &smack_known_list, list) { + if (kp->smk_known == smack || + strcmp(kp->smk_known, smack) == 0) { + found = 1; + break; + } + } + rcu_read_unlock(); + + if (found == 0 || kp->smk_cipso == NULL) + return -ENOENT; + + memcpy(cp, kp->smk_cipso, sizeof(struct smack_cipso)); + return 0; +} diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c new file mode 100644 index 00000000..9831a39c --- /dev/null +++ b/security/smack/smack_lsm.c @@ -0,0 +1,3591 @@ +/* + * Simplified MAC Kernel (smack) security module + * + * This file contains the smack hook function implementations. + * + * Authors: + * Casey Schaufler <casey@schaufler-ca.com> + * Jarkko Sakkinen <ext-jarkko.2.sakkinen@nokia.com> + * + * Copyright (C) 2007 Casey Schaufler <casey@schaufler-ca.com> + * Copyright (C) 2009 Hewlett-Packard Development Company, L.P. + * Paul Moore <paul.moore@hp.com> + * Copyright (C) 2010 Nokia Corporation + * + * 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/xattr.h> +#include <linux/pagemap.h> +#include <linux/mount.h> +#include <linux/stat.h> +#include <linux/kd.h> +#include <asm/ioctls.h> +#include <linux/ip.h> +#include <linux/tcp.h> +#include <linux/udp.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/pipe_fs_i.h> +#include <net/netlabel.h> +#include <net/cipso_ipv4.h> +#include <linux/audit.h> +#include <linux/magic.h> +#include <linux/dcache.h> +#include "smack.h" + +#define task_security(task) (task_cred_xxx((task), security)) + +#define TRANS_TRUE "TRUE" +#define TRANS_TRUE_SIZE 4 + +/** + * smk_fetch - Fetch the smack label from a file. + * @ip: a pointer to the inode + * @dp: a pointer to the dentry + * + * Returns a pointer to the master list entry for the Smack label + * or NULL if there was no label to fetch. + */ +static char *smk_fetch(const char *name, struct inode *ip, struct dentry *dp) +{ + int rc; + char in[SMK_LABELLEN]; + + if (ip->i_op->getxattr == NULL) + return NULL; + + rc = ip->i_op->getxattr(dp, name, in, SMK_LABELLEN); + if (rc < 0) + return NULL; + + return smk_import(in, rc); +} + +/** + * new_inode_smack - allocate an inode security blob + * @smack: a pointer to the Smack label to use in the blob + * + * Returns the new blob or NULL if there's no memory available + */ +struct inode_smack *new_inode_smack(char *smack) +{ + struct inode_smack *isp; + + isp = kzalloc(sizeof(struct inode_smack), GFP_KERNEL); + if (isp == NULL) + return NULL; + + isp->smk_inode = smack; + isp->smk_flags = 0; + mutex_init(&isp->smk_lock); + + return isp; +} + +/** + * new_task_smack - allocate a task security blob + * @smack: a pointer to the Smack label to use in the blob + * + * Returns the new blob or NULL if there's no memory available + */ +static struct task_smack *new_task_smack(char *task, char *forked, gfp_t gfp) +{ + struct task_smack *tsp; + + tsp = kzalloc(sizeof(struct task_smack), gfp); + if (tsp == NULL) + return NULL; + + tsp->smk_task = task; + tsp->smk_forked = forked; + INIT_LIST_HEAD(&tsp->smk_rules); + mutex_init(&tsp->smk_rules_lock); + + return tsp; +} + +/** + * smk_copy_rules - copy a rule set + * @nhead - new rules header pointer + * @ohead - old rules header pointer + * + * Returns 0 on success, -ENOMEM on error + */ +static int smk_copy_rules(struct list_head *nhead, struct list_head *ohead, + gfp_t gfp) +{ + struct smack_rule *nrp; + struct smack_rule *orp; + int rc = 0; + + INIT_LIST_HEAD(nhead); + + list_for_each_entry_rcu(orp, ohead, list) { + nrp = kzalloc(sizeof(struct smack_rule), gfp); + if (nrp == NULL) { + rc = -ENOMEM; + break; + } + *nrp = *orp; + list_add_rcu(&nrp->list, nhead); + } + return rc; +} + +/* + * LSM hooks. + * We he, that is fun! + */ + +/** + * smack_ptrace_access_check - Smack approval on PTRACE_ATTACH + * @ctp: child task pointer + * @mode: ptrace attachment mode + * + * Returns 0 if access is OK, an error code otherwise + * + * Do the capability checks, and require read and write. + */ +static int smack_ptrace_access_check(struct task_struct *ctp, unsigned int mode) +{ + int rc; + struct smk_audit_info ad; + char *tsp; + + rc = cap_ptrace_access_check(ctp, mode); + if (rc != 0) + return rc; + + tsp = smk_of_task(task_security(ctp)); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_TASK); + smk_ad_setfield_u_tsk(&ad, ctp); + + rc = smk_curacc(tsp, MAY_READWRITE, &ad); + return rc; +} + +/** + * smack_ptrace_traceme - Smack approval on PTRACE_TRACEME + * @ptp: parent task pointer + * + * Returns 0 if access is OK, an error code otherwise + * + * Do the capability checks, and require read and write. + */ +static int smack_ptrace_traceme(struct task_struct *ptp) +{ + int rc; + struct smk_audit_info ad; + char *tsp; + + rc = cap_ptrace_traceme(ptp); + if (rc != 0) + return rc; + + tsp = smk_of_task(task_security(ptp)); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_TASK); + smk_ad_setfield_u_tsk(&ad, ptp); + + rc = smk_curacc(tsp, MAY_READWRITE, &ad); + return rc; +} + +/** + * smack_syslog - Smack approval on syslog + * @type: message type + * + * Require that the task has the floor label + * + * Returns 0 on success, error code otherwise. + */ +static int smack_syslog(int typefrom_file) +{ + int rc = 0; + char *sp = smk_of_current(); + + if (capable(CAP_MAC_OVERRIDE)) + return 0; + + if (sp != smack_known_floor.smk_known) + rc = -EACCES; + + return rc; +} + + +/* + * Superblock Hooks. + */ + +/** + * smack_sb_alloc_security - allocate a superblock blob + * @sb: the superblock getting the blob + * + * Returns 0 on success or -ENOMEM on error. + */ +static int smack_sb_alloc_security(struct super_block *sb) +{ + struct superblock_smack *sbsp; + + sbsp = kzalloc(sizeof(struct superblock_smack), GFP_KERNEL); + + if (sbsp == NULL) + return -ENOMEM; + + sbsp->smk_root = smack_known_floor.smk_known; + sbsp->smk_default = smack_known_floor.smk_known; + sbsp->smk_floor = smack_known_floor.smk_known; + sbsp->smk_hat = smack_known_hat.smk_known; + sbsp->smk_initialized = 0; + spin_lock_init(&sbsp->smk_sblock); + + sb->s_security = sbsp; + + return 0; +} + +/** + * smack_sb_free_security - free a superblock blob + * @sb: the superblock getting the blob + * + */ +static void smack_sb_free_security(struct super_block *sb) +{ + kfree(sb->s_security); + sb->s_security = NULL; +} + +/** + * smack_sb_copy_data - copy mount options data for processing + * @orig: where to start + * @smackopts: mount options string + * + * Returns 0 on success or -ENOMEM on error. + * + * Copy the Smack specific mount options out of the mount + * options list. + */ +static int smack_sb_copy_data(char *orig, char *smackopts) +{ + char *cp, *commap, *otheropts, *dp; + + otheropts = (char *)get_zeroed_page(GFP_KERNEL); + if (otheropts == NULL) + return -ENOMEM; + + for (cp = orig, commap = orig; commap != NULL; cp = commap + 1) { + if (strstr(cp, SMK_FSDEFAULT) == cp) + dp = smackopts; + else if (strstr(cp, SMK_FSFLOOR) == cp) + dp = smackopts; + else if (strstr(cp, SMK_FSHAT) == cp) + dp = smackopts; + else if (strstr(cp, SMK_FSROOT) == cp) + dp = smackopts; + else + dp = otheropts; + + commap = strchr(cp, ','); + if (commap != NULL) + *commap = '\0'; + + if (*dp != '\0') + strcat(dp, ","); + strcat(dp, cp); + } + + strcpy(orig, otheropts); + free_page((unsigned long)otheropts); + + return 0; +} + +/** + * smack_sb_kern_mount - Smack specific mount processing + * @sb: the file system superblock + * @flags: the mount flags + * @data: the smack mount options + * + * Returns 0 on success, an error code on failure + */ +static int smack_sb_kern_mount(struct super_block *sb, int flags, void *data) +{ + struct dentry *root = sb->s_root; + struct inode *inode = root->d_inode; + struct superblock_smack *sp = sb->s_security; + struct inode_smack *isp; + char *op; + char *commap; + char *nsp; + + spin_lock(&sp->smk_sblock); + if (sp->smk_initialized != 0) { + spin_unlock(&sp->smk_sblock); + return 0; + } + sp->smk_initialized = 1; + spin_unlock(&sp->smk_sblock); + + for (op = data; op != NULL; op = commap) { + commap = strchr(op, ','); + if (commap != NULL) + *commap++ = '\0'; + + if (strncmp(op, SMK_FSHAT, strlen(SMK_FSHAT)) == 0) { + op += strlen(SMK_FSHAT); + nsp = smk_import(op, 0); + if (nsp != NULL) + sp->smk_hat = nsp; + } else if (strncmp(op, SMK_FSFLOOR, strlen(SMK_FSFLOOR)) == 0) { + op += strlen(SMK_FSFLOOR); + nsp = smk_import(op, 0); + if (nsp != NULL) + sp->smk_floor = nsp; + } else if (strncmp(op, SMK_FSDEFAULT, + strlen(SMK_FSDEFAULT)) == 0) { + op += strlen(SMK_FSDEFAULT); + nsp = smk_import(op, 0); + if (nsp != NULL) + sp->smk_default = nsp; + } else if (strncmp(op, SMK_FSROOT, strlen(SMK_FSROOT)) == 0) { + op += strlen(SMK_FSROOT); + nsp = smk_import(op, 0); + if (nsp != NULL) + sp->smk_root = nsp; + } + } + + /* + * Initialize the root inode. + */ + isp = inode->i_security; + if (isp == NULL) + inode->i_security = new_inode_smack(sp->smk_root); + else + isp->smk_inode = sp->smk_root; + + return 0; +} + +/** + * smack_sb_statfs - Smack check on statfs + * @dentry: identifies the file system in question + * + * Returns 0 if current can read the floor of the filesystem, + * and error code otherwise + */ +static int smack_sb_statfs(struct dentry *dentry) +{ + struct superblock_smack *sbp = dentry->d_sb->s_security; + int rc; + struct smk_audit_info ad; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); + smk_ad_setfield_u_fs_path_dentry(&ad, dentry); + + rc = smk_curacc(sbp->smk_floor, MAY_READ, &ad); + return rc; +} + +/** + * smack_sb_mount - Smack check for mounting + * @dev_name: unused + * @path: mount point + * @type: unused + * @flags: unused + * @data: unused + * + * Returns 0 if current can write the floor of the filesystem + * being mounted on, an error code otherwise. + */ +static int smack_sb_mount(char *dev_name, struct path *path, + char *type, unsigned long flags, void *data) +{ + struct superblock_smack *sbp = path->mnt->mnt_sb->s_security; + struct smk_audit_info ad; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); + smk_ad_setfield_u_fs_path(&ad, *path); + + return smk_curacc(sbp->smk_floor, MAY_WRITE, &ad); +} + +/** + * smack_sb_umount - Smack check for unmounting + * @mnt: file system to unmount + * @flags: unused + * + * Returns 0 if current can write the floor of the filesystem + * being unmounted, an error code otherwise. + */ +static int smack_sb_umount(struct vfsmount *mnt, int flags) +{ + struct superblock_smack *sbp; + struct smk_audit_info ad; + struct path path; + + path.dentry = mnt->mnt_root; + path.mnt = mnt; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); + smk_ad_setfield_u_fs_path(&ad, path); + + sbp = mnt->mnt_sb->s_security; + return smk_curacc(sbp->smk_floor, MAY_WRITE, &ad); +} + +/* + * BPRM hooks + */ + +static int smack_bprm_set_creds(struct linux_binprm *bprm) +{ + struct task_smack *tsp = bprm->cred->security; + struct inode_smack *isp; + struct dentry *dp; + int rc; + + rc = cap_bprm_set_creds(bprm); + if (rc != 0) + return rc; + + if (bprm->cred_prepared) + return 0; + + if (bprm->file == NULL || bprm->file->f_dentry == NULL) + return 0; + + dp = bprm->file->f_dentry; + + if (dp->d_inode == NULL) + return 0; + + isp = dp->d_inode->i_security; + + if (isp->smk_task != NULL) + tsp->smk_task = isp->smk_task; + + return 0; +} + +/* + * Inode hooks + */ + +/** + * smack_inode_alloc_security - allocate an inode blob + * @inode: the inode in need of a blob + * + * Returns 0 if it gets a blob, -ENOMEM otherwise + */ +static int smack_inode_alloc_security(struct inode *inode) +{ + inode->i_security = new_inode_smack(smk_of_current()); + if (inode->i_security == NULL) + return -ENOMEM; + return 0; +} + +/** + * smack_inode_free_security - free an inode blob + * @inode: the inode with a blob + * + * Clears the blob pointer in inode + */ +static void smack_inode_free_security(struct inode *inode) +{ + kfree(inode->i_security); + inode->i_security = NULL; +} + +/** + * smack_inode_init_security - copy out the smack from an inode + * @inode: the inode + * @dir: unused + * @qstr: unused + * @name: where to put the attribute name + * @value: where to put the attribute value + * @len: where to put the length of the attribute + * + * Returns 0 if it all works out, -ENOMEM if there's no memory + */ +static int smack_inode_init_security(struct inode *inode, struct inode *dir, + const struct qstr *qstr, char **name, + void **value, size_t *len) +{ + char *isp = smk_of_inode(inode); + char *dsp = smk_of_inode(dir); + int may; + + if (name) { + *name = kstrdup(XATTR_SMACK_SUFFIX, GFP_KERNEL); + if (*name == NULL) + return -ENOMEM; + } + + if (value) { + rcu_read_lock(); + may = smk_access_entry(smk_of_current(), dsp, &smack_rule_list); + rcu_read_unlock(); + + /* + * If the access rule allows transmutation and + * the directory requests transmutation then + * by all means transmute. + */ + if (may > 0 && ((may & MAY_TRANSMUTE) != 0) && + smk_inode_transmutable(dir)) + isp = dsp; + + *value = kstrdup(isp, GFP_KERNEL); + if (*value == NULL) + return -ENOMEM; + } + + if (len) + *len = strlen(isp) + 1; + + return 0; +} + +/** + * smack_inode_link - Smack check on link + * @old_dentry: the existing object + * @dir: unused + * @new_dentry: the new object + * + * Returns 0 if access is permitted, an error code otherwise + */ +static int smack_inode_link(struct dentry *old_dentry, struct inode *dir, + struct dentry *new_dentry) +{ + char *isp; + struct smk_audit_info ad; + int rc; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); + smk_ad_setfield_u_fs_path_dentry(&ad, old_dentry); + + isp = smk_of_inode(old_dentry->d_inode); + rc = smk_curacc(isp, MAY_WRITE, &ad); + + if (rc == 0 && new_dentry->d_inode != NULL) { + isp = smk_of_inode(new_dentry->d_inode); + smk_ad_setfield_u_fs_path_dentry(&ad, new_dentry); + rc = smk_curacc(isp, MAY_WRITE, &ad); + } + + return rc; +} + +/** + * smack_inode_unlink - Smack check on inode deletion + * @dir: containing directory object + * @dentry: file to unlink + * + * Returns 0 if current can write the containing directory + * and the object, error code otherwise + */ +static int smack_inode_unlink(struct inode *dir, struct dentry *dentry) +{ + struct inode *ip = dentry->d_inode; + struct smk_audit_info ad; + int rc; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); + smk_ad_setfield_u_fs_path_dentry(&ad, dentry); + + /* + * You need write access to the thing you're unlinking + */ + rc = smk_curacc(smk_of_inode(ip), MAY_WRITE, &ad); + if (rc == 0) { + /* + * You also need write access to the containing directory + */ + smk_ad_setfield_u_fs_path_dentry(&ad, NULL); + smk_ad_setfield_u_fs_inode(&ad, dir); + rc = smk_curacc(smk_of_inode(dir), MAY_WRITE, &ad); + } + return rc; +} + +/** + * smack_inode_rmdir - Smack check on directory deletion + * @dir: containing directory object + * @dentry: directory to unlink + * + * Returns 0 if current can write the containing directory + * and the directory, error code otherwise + */ +static int smack_inode_rmdir(struct inode *dir, struct dentry *dentry) +{ + struct smk_audit_info ad; + int rc; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); + smk_ad_setfield_u_fs_path_dentry(&ad, dentry); + + /* + * You need write access to the thing you're removing + */ + rc = smk_curacc(smk_of_inode(dentry->d_inode), MAY_WRITE, &ad); + if (rc == 0) { + /* + * You also need write access to the containing directory + */ + smk_ad_setfield_u_fs_path_dentry(&ad, NULL); + smk_ad_setfield_u_fs_inode(&ad, dir); + rc = smk_curacc(smk_of_inode(dir), MAY_WRITE, &ad); + } + + return rc; +} + +/** + * smack_inode_rename - Smack check on rename + * @old_inode: the old directory + * @old_dentry: unused + * @new_inode: the new directory + * @new_dentry: unused + * + * Read and write access is required on both the old and + * new directories. + * + * Returns 0 if access is permitted, an error code otherwise + */ +static int smack_inode_rename(struct inode *old_inode, + struct dentry *old_dentry, + struct inode *new_inode, + struct dentry *new_dentry) +{ + int rc; + char *isp; + struct smk_audit_info ad; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); + smk_ad_setfield_u_fs_path_dentry(&ad, old_dentry); + + isp = smk_of_inode(old_dentry->d_inode); + rc = smk_curacc(isp, MAY_READWRITE, &ad); + + if (rc == 0 && new_dentry->d_inode != NULL) { + isp = smk_of_inode(new_dentry->d_inode); + smk_ad_setfield_u_fs_path_dentry(&ad, new_dentry); + rc = smk_curacc(isp, MAY_READWRITE, &ad); + } + return rc; +} + +/** + * smack_inode_permission - Smack version of permission() + * @inode: the inode in question + * @mask: the access requested + * + * This is the important Smack hook. + * + * Returns 0 if access is permitted, -EACCES otherwise + */ +static int smack_inode_permission(struct inode *inode, int mask, unsigned flags) +{ + struct smk_audit_info ad; + + mask &= (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND); + /* + * No permission to check. Existence test. Yup, it's there. + */ + if (mask == 0) + return 0; + + /* May be droppable after audit */ + if (flags & IPERM_FLAG_RCU) + return -ECHILD; + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_INODE); + smk_ad_setfield_u_fs_inode(&ad, inode); + return smk_curacc(smk_of_inode(inode), mask, &ad); +} + +/** + * smack_inode_setattr - Smack check for setting attributes + * @dentry: the object + * @iattr: for the force flag + * + * Returns 0 if access is permitted, an error code otherwise + */ +static int smack_inode_setattr(struct dentry *dentry, struct iattr *iattr) +{ + struct smk_audit_info ad; + /* + * Need to allow for clearing the setuid bit. + */ + if (iattr->ia_valid & ATTR_FORCE) + return 0; + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); + smk_ad_setfield_u_fs_path_dentry(&ad, dentry); + + return smk_curacc(smk_of_inode(dentry->d_inode), MAY_WRITE, &ad); +} + +/** + * smack_inode_getattr - Smack check for getting attributes + * @mnt: unused + * @dentry: the object + * + * Returns 0 if access is permitted, an error code otherwise + */ +static int smack_inode_getattr(struct vfsmount *mnt, struct dentry *dentry) +{ + struct smk_audit_info ad; + struct path path; + + path.dentry = dentry; + path.mnt = mnt; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); + smk_ad_setfield_u_fs_path(&ad, path); + return smk_curacc(smk_of_inode(dentry->d_inode), MAY_READ, &ad); +} + +/** + * smack_inode_setxattr - Smack check for setting xattrs + * @dentry: the object + * @name: name of the attribute + * @value: unused + * @size: unused + * @flags: unused + * + * This protects the Smack attribute explicitly. + * + * Returns 0 if access is permitted, an error code otherwise + */ +static int smack_inode_setxattr(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags) +{ + struct smk_audit_info ad; + int rc = 0; + + if (strcmp(name, XATTR_NAME_SMACK) == 0 || + strcmp(name, XATTR_NAME_SMACKIPIN) == 0 || + strcmp(name, XATTR_NAME_SMACKIPOUT) == 0 || + strcmp(name, XATTR_NAME_SMACKEXEC) == 0 || + strcmp(name, XATTR_NAME_SMACKMMAP) == 0) { + if (!capable(CAP_MAC_ADMIN)) + rc = -EPERM; + /* + * check label validity here so import wont fail on + * post_setxattr + */ + if (size == 0 || size >= SMK_LABELLEN || + smk_import(value, size) == NULL) + rc = -EINVAL; + } else if (strcmp(name, XATTR_NAME_SMACKTRANSMUTE) == 0) { + if (!capable(CAP_MAC_ADMIN)) + rc = -EPERM; + if (size != TRANS_TRUE_SIZE || + strncmp(value, TRANS_TRUE, TRANS_TRUE_SIZE) != 0) + rc = -EINVAL; + } else + rc = cap_inode_setxattr(dentry, name, value, size, flags); + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); + smk_ad_setfield_u_fs_path_dentry(&ad, dentry); + + if (rc == 0) + rc = smk_curacc(smk_of_inode(dentry->d_inode), MAY_WRITE, &ad); + + return rc; +} + +/** + * smack_inode_post_setxattr - Apply the Smack update approved above + * @dentry: object + * @name: attribute name + * @value: attribute value + * @size: attribute size + * @flags: unused + * + * Set the pointer in the inode blob to the entry found + * in the master label list. + */ +static void smack_inode_post_setxattr(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags) +{ + char *nsp; + struct inode_smack *isp = dentry->d_inode->i_security; + + if (strcmp(name, XATTR_NAME_SMACK) == 0) { + nsp = smk_import(value, size); + if (nsp != NULL) + isp->smk_inode = nsp; + else + isp->smk_inode = smack_known_invalid.smk_known; + } else if (strcmp(name, XATTR_NAME_SMACKEXEC) == 0) { + nsp = smk_import(value, size); + if (nsp != NULL) + isp->smk_task = nsp; + else + isp->smk_task = smack_known_invalid.smk_known; + } else if (strcmp(name, XATTR_NAME_SMACKMMAP) == 0) { + nsp = smk_import(value, size); + if (nsp != NULL) + isp->smk_mmap = nsp; + else + isp->smk_mmap = smack_known_invalid.smk_known; + } else if (strcmp(name, XATTR_NAME_SMACKTRANSMUTE) == 0) + isp->smk_flags |= SMK_INODE_TRANSMUTE; + + return; +} + +/* + * smack_inode_getxattr - Smack check on getxattr + * @dentry: the object + * @name: unused + * + * Returns 0 if access is permitted, an error code otherwise + */ +static int smack_inode_getxattr(struct dentry *dentry, const char *name) +{ + struct smk_audit_info ad; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); + smk_ad_setfield_u_fs_path_dentry(&ad, dentry); + + return smk_curacc(smk_of_inode(dentry->d_inode), MAY_READ, &ad); +} + +/* + * smack_inode_removexattr - Smack check on removexattr + * @dentry: the object + * @name: name of the attribute + * + * Removing the Smack attribute requires CAP_MAC_ADMIN + * + * Returns 0 if access is permitted, an error code otherwise + */ +static int smack_inode_removexattr(struct dentry *dentry, const char *name) +{ + struct inode_smack *isp; + struct smk_audit_info ad; + int rc = 0; + + if (strcmp(name, XATTR_NAME_SMACK) == 0 || + strcmp(name, XATTR_NAME_SMACKIPIN) == 0 || + strcmp(name, XATTR_NAME_SMACKIPOUT) == 0 || + strcmp(name, XATTR_NAME_SMACKEXEC) == 0 || + strcmp(name, XATTR_NAME_SMACKTRANSMUTE) == 0 || + strcmp(name, XATTR_NAME_SMACKMMAP)) { + if (!capable(CAP_MAC_ADMIN)) + rc = -EPERM; + } else + rc = cap_inode_removexattr(dentry, name); + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); + smk_ad_setfield_u_fs_path_dentry(&ad, dentry); + if (rc == 0) + rc = smk_curacc(smk_of_inode(dentry->d_inode), MAY_WRITE, &ad); + + if (rc == 0) { + isp = dentry->d_inode->i_security; + isp->smk_task = NULL; + isp->smk_mmap = NULL; + } + + return rc; +} + +/** + * smack_inode_getsecurity - get smack xattrs + * @inode: the object + * @name: attribute name + * @buffer: where to put the result + * @alloc: unused + * + * Returns the size of the attribute or an error code + */ +static int smack_inode_getsecurity(const struct inode *inode, + const char *name, void **buffer, + bool alloc) +{ + struct socket_smack *ssp; + struct socket *sock; + struct super_block *sbp; + struct inode *ip = (struct inode *)inode; + char *isp; + int ilen; + int rc = 0; + + if (strcmp(name, XATTR_SMACK_SUFFIX) == 0) { + isp = smk_of_inode(inode); + ilen = strlen(isp) + 1; + *buffer = isp; + return ilen; + } + + /* + * The rest of the Smack xattrs are only on sockets. + */ + sbp = ip->i_sb; + if (sbp->s_magic != SOCKFS_MAGIC) + return -EOPNOTSUPP; + + sock = SOCKET_I(ip); + if (sock == NULL || sock->sk == NULL) + return -EOPNOTSUPP; + + ssp = sock->sk->sk_security; + + if (strcmp(name, XATTR_SMACK_IPIN) == 0) + isp = ssp->smk_in; + else if (strcmp(name, XATTR_SMACK_IPOUT) == 0) + isp = ssp->smk_out; + else + return -EOPNOTSUPP; + + ilen = strlen(isp) + 1; + if (rc == 0) { + *buffer = isp; + rc = ilen; + } + + return rc; +} + + +/** + * smack_inode_listsecurity - list the Smack attributes + * @inode: the object + * @buffer: where they go + * @buffer_size: size of buffer + * + * Returns 0 on success, -EINVAL otherwise + */ +static int smack_inode_listsecurity(struct inode *inode, char *buffer, + size_t buffer_size) +{ + int len = strlen(XATTR_NAME_SMACK); + + if (buffer != NULL && len <= buffer_size) { + memcpy(buffer, XATTR_NAME_SMACK, len); + return len; + } + return -EINVAL; +} + +/** + * smack_inode_getsecid - Extract inode's security id + * @inode: inode to extract the info from + * @secid: where result will be saved + */ +static void smack_inode_getsecid(const struct inode *inode, u32 *secid) +{ + struct inode_smack *isp = inode->i_security; + + *secid = smack_to_secid(isp->smk_inode); +} + +/* + * File Hooks + */ + +/** + * smack_file_permission - Smack check on file operations + * @file: unused + * @mask: unused + * + * Returns 0 + * + * Should access checks be done on each read or write? + * UNICOS and SELinux say yes. + * Trusted Solaris, Trusted Irix, and just about everyone else says no. + * + * I'll say no for now. Smack does not do the frequent + * label changing that SELinux does. + */ +static int smack_file_permission(struct file *file, int mask) +{ + return 0; +} + +/** + * smack_file_alloc_security - assign a file security blob + * @file: the object + * + * The security blob for a file is a pointer to the master + * label list, so no allocation is done. + * + * Returns 0 + */ +static int smack_file_alloc_security(struct file *file) +{ + file->f_security = smk_of_current(); + return 0; +} + +/** + * smack_file_free_security - clear a file security blob + * @file: the object + * + * The security blob for a file is a pointer to the master + * label list, so no memory is freed. + */ +static void smack_file_free_security(struct file *file) +{ + file->f_security = NULL; +} + +/** + * smack_file_ioctl - Smack check on ioctls + * @file: the object + * @cmd: what to do + * @arg: unused + * + * Relies heavily on the correct use of the ioctl command conventions. + * + * Returns 0 if allowed, error code otherwise + */ +static int smack_file_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int rc = 0; + struct smk_audit_info ad; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); + smk_ad_setfield_u_fs_path(&ad, file->f_path); + + if (_IOC_DIR(cmd) & _IOC_WRITE) + rc = smk_curacc(file->f_security, MAY_WRITE, &ad); + + if (rc == 0 && (_IOC_DIR(cmd) & _IOC_READ)) + rc = smk_curacc(file->f_security, MAY_READ, &ad); + + return rc; +} + +/** + * smack_file_lock - Smack check on file locking + * @file: the object + * @cmd: unused + * + * Returns 0 if current has write access, error code otherwise + */ +static int smack_file_lock(struct file *file, unsigned int cmd) +{ + struct smk_audit_info ad; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); + smk_ad_setfield_u_fs_path(&ad, file->f_path); + return smk_curacc(file->f_security, MAY_WRITE, &ad); +} + +/** + * smack_file_fcntl - Smack check on fcntl + * @file: the object + * @cmd: what action to check + * @arg: unused + * + * Returns 0 if current has access, error code otherwise + */ +static int smack_file_fcntl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct smk_audit_info ad; + int rc; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); + smk_ad_setfield_u_fs_path(&ad, file->f_path); + + switch (cmd) { + case F_DUPFD: + case F_GETFD: + case F_GETFL: + case F_GETLK: + case F_GETOWN: + case F_GETSIG: + rc = smk_curacc(file->f_security, MAY_READ, &ad); + break; + case F_SETFD: + case F_SETFL: + case F_SETLK: + case F_SETLKW: + case F_SETOWN: + case F_SETSIG: + rc = smk_curacc(file->f_security, MAY_WRITE, &ad); + break; + default: + rc = smk_curacc(file->f_security, MAY_READWRITE, &ad); + } + + return rc; +} + +/** + * smack_file_mmap : + * Check permissions for a mmap operation. The @file may be NULL, e.g. + * if mapping anonymous memory. + * @file contains the file structure for file to map (may be NULL). + * @reqprot contains the protection requested by the application. + * @prot contains the protection that will be applied by the kernel. + * @flags contains the operational flags. + * Return 0 if permission is granted. + */ +static int smack_file_mmap(struct file *file, + unsigned long reqprot, unsigned long prot, + unsigned long flags, unsigned long addr, + unsigned long addr_only) +{ + struct smack_rule *srp; + struct task_smack *tsp; + char *sp; + char *msmack; + char *osmack; + struct inode_smack *isp; + struct dentry *dp; + int may; + int mmay; + int tmay; + int rc; + + /* do DAC check on address space usage */ + rc = cap_file_mmap(file, reqprot, prot, flags, addr, addr_only); + if (rc || addr_only) + return rc; + + if (file == NULL || file->f_dentry == NULL) + return 0; + + dp = file->f_dentry; + + if (dp->d_inode == NULL) + return 0; + + isp = dp->d_inode->i_security; + if (isp->smk_mmap == NULL) + return 0; + msmack = isp->smk_mmap; + + tsp = current_security(); + sp = smk_of_current(); + rc = 0; + + rcu_read_lock(); + /* + * For each Smack rule associated with the subject + * label verify that the SMACK64MMAP also has access + * to that rule's object label. + * + * Because neither of the labels comes + * from the networking code it is sufficient + * to compare pointers. + */ + list_for_each_entry_rcu(srp, &smack_rule_list, list) { + if (srp->smk_subject != sp) + continue; + + osmack = srp->smk_object; + /* + * Matching labels always allows access. + */ + if (msmack == osmack) + continue; + /* + * If there is a matching local rule take + * that into account as well. + */ + may = smk_access_entry(srp->smk_subject, osmack, + &tsp->smk_rules); + if (may == -ENOENT) + may = srp->smk_access; + else + may &= srp->smk_access; + /* + * If may is zero the SMACK64MMAP subject can't + * possibly have less access. + */ + if (may == 0) + continue; + + /* + * Fetch the global list entry. + * If there isn't one a SMACK64MMAP subject + * can't have as much access as current. + */ + mmay = smk_access_entry(msmack, osmack, &smack_rule_list); + if (mmay == -ENOENT) { + rc = -EACCES; + break; + } + /* + * If there is a local entry it modifies the + * potential access, too. + */ + tmay = smk_access_entry(msmack, osmack, &tsp->smk_rules); + if (tmay != -ENOENT) + mmay &= tmay; + + /* + * If there is any access available to current that is + * not available to a SMACK64MMAP subject + * deny access. + */ + if ((may | mmay) != mmay) { + rc = -EACCES; + break; + } + } + + rcu_read_unlock(); + + return rc; +} + +/** + * smack_file_set_fowner - set the file security blob value + * @file: object in question + * + * Returns 0 + * Further research may be required on this one. + */ +static int smack_file_set_fowner(struct file *file) +{ + file->f_security = smk_of_current(); + return 0; +} + +/** + * smack_file_send_sigiotask - Smack on sigio + * @tsk: The target task + * @fown: the object the signal come from + * @signum: unused + * + * Allow a privileged task to get signals even if it shouldn't + * + * Returns 0 if a subject with the object's smack could + * write to the task, an error code otherwise. + */ +static int smack_file_send_sigiotask(struct task_struct *tsk, + struct fown_struct *fown, int signum) +{ + struct file *file; + int rc; + char *tsp = smk_of_task(tsk->cred->security); + struct smk_audit_info ad; + + /* + * struct fown_struct is never outside the context of a struct file + */ + file = container_of(fown, struct file, f_owner); + + /* we don't log here as rc can be overriden */ + rc = smk_access(file->f_security, tsp, MAY_WRITE, NULL); + if (rc != 0 && has_capability(tsk, CAP_MAC_OVERRIDE)) + rc = 0; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_TASK); + smk_ad_setfield_u_tsk(&ad, tsk); + smack_log(file->f_security, tsp, MAY_WRITE, rc, &ad); + return rc; +} + +/** + * smack_file_receive - Smack file receive check + * @file: the object + * + * Returns 0 if current has access, error code otherwise + */ +static int smack_file_receive(struct file *file) +{ + int may = 0; + struct smk_audit_info ad; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_TASK); + smk_ad_setfield_u_fs_path(&ad, file->f_path); + /* + * This code relies on bitmasks. + */ + if (file->f_mode & FMODE_READ) + may = MAY_READ; + if (file->f_mode & FMODE_WRITE) + may |= MAY_WRITE; + + return smk_curacc(file->f_security, may, &ad); +} + +/* + * Task hooks + */ + +/** + * smack_cred_alloc_blank - "allocate" blank task-level security credentials + * @new: the new credentials + * @gfp: the atomicity of any memory allocations + * + * Prepare a blank set of credentials for modification. This must allocate all + * the memory the LSM module might require such that cred_transfer() can + * complete without error. + */ +static int smack_cred_alloc_blank(struct cred *cred, gfp_t gfp) +{ + struct task_smack *tsp; + + tsp = new_task_smack(NULL, NULL, gfp); + if (tsp == NULL) + return -ENOMEM; + + cred->security = tsp; + + return 0; +} + + +/** + * smack_cred_free - "free" task-level security credentials + * @cred: the credentials in question + * + */ +static void smack_cred_free(struct cred *cred) +{ + struct task_smack *tsp = cred->security; + struct smack_rule *rp; + struct list_head *l; + struct list_head *n; + + if (tsp == NULL) + return; + cred->security = NULL; + + list_for_each_safe(l, n, &tsp->smk_rules) { + rp = list_entry(l, struct smack_rule, list); + list_del(&rp->list); + kfree(rp); + } + kfree(tsp); +} + +/** + * smack_cred_prepare - prepare new set of credentials for modification + * @new: the new credentials + * @old: the original credentials + * @gfp: the atomicity of any memory allocations + * + * Prepare a new set of credentials for modification. + */ +static int smack_cred_prepare(struct cred *new, const struct cred *old, + gfp_t gfp) +{ + struct task_smack *old_tsp = old->security; + struct task_smack *new_tsp; + int rc; + + new_tsp = new_task_smack(old_tsp->smk_task, old_tsp->smk_task, gfp); + if (new_tsp == NULL) + return -ENOMEM; + + rc = smk_copy_rules(&new_tsp->smk_rules, &old_tsp->smk_rules, gfp); + if (rc != 0) + return rc; + + new->security = new_tsp; + return 0; +} + +/** + * smack_cred_transfer - Transfer the old credentials to the new credentials + * @new: the new credentials + * @old: the original credentials + * + * Fill in a set of blank credentials from another set of credentials. + */ +static void smack_cred_transfer(struct cred *new, const struct cred *old) +{ + struct task_smack *old_tsp = old->security; + struct task_smack *new_tsp = new->security; + + new_tsp->smk_task = old_tsp->smk_task; + new_tsp->smk_forked = old_tsp->smk_task; + mutex_init(&new_tsp->smk_rules_lock); + INIT_LIST_HEAD(&new_tsp->smk_rules); + + + /* cbs copy rule list */ +} + +/** + * smack_kernel_act_as - Set the subjective context in a set of credentials + * @new: points to the set of credentials to be modified. + * @secid: specifies the security ID to be set + * + * Set the security data for a kernel service. + */ +static int smack_kernel_act_as(struct cred *new, u32 secid) +{ + struct task_smack *new_tsp = new->security; + char *smack = smack_from_secid(secid); + + if (smack == NULL) + return -EINVAL; + + new_tsp->smk_task = smack; + return 0; +} + +/** + * smack_kernel_create_files_as - Set the file creation label in a set of creds + * @new: points to the set of credentials to be modified + * @inode: points to the inode to use as a reference + * + * Set the file creation context in a set of credentials to the same + * as the objective context of the specified inode + */ +static int smack_kernel_create_files_as(struct cred *new, + struct inode *inode) +{ + struct inode_smack *isp = inode->i_security; + struct task_smack *tsp = new->security; + + tsp->smk_forked = isp->smk_inode; + tsp->smk_task = isp->smk_inode; + return 0; +} + +/** + * smk_curacc_on_task - helper to log task related access + * @p: the task object + * @access : the access requested + * + * Return 0 if access is permitted + */ +static int smk_curacc_on_task(struct task_struct *p, int access) +{ + struct smk_audit_info ad; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_TASK); + smk_ad_setfield_u_tsk(&ad, p); + return smk_curacc(smk_of_task(task_security(p)), access, &ad); +} + +/** + * smack_task_setpgid - Smack check on setting pgid + * @p: the task object + * @pgid: unused + * + * Return 0 if write access is permitted + */ +static int smack_task_setpgid(struct task_struct *p, pid_t pgid) +{ + return smk_curacc_on_task(p, MAY_WRITE); +} + +/** + * smack_task_getpgid - Smack access check for getpgid + * @p: the object task + * + * Returns 0 if current can read the object task, error code otherwise + */ +static int smack_task_getpgid(struct task_struct *p) +{ + return smk_curacc_on_task(p, MAY_READ); +} + +/** + * smack_task_getsid - Smack access check for getsid + * @p: the object task + * + * Returns 0 if current can read the object task, error code otherwise + */ +static int smack_task_getsid(struct task_struct *p) +{ + return smk_curacc_on_task(p, MAY_READ); +} + +/** + * smack_task_getsecid - get the secid of the task + * @p: the object task + * @secid: where to put the result + * + * Sets the secid to contain a u32 version of the smack label. + */ +static void smack_task_getsecid(struct task_struct *p, u32 *secid) +{ + *secid = smack_to_secid(smk_of_task(task_security(p))); +} + +/** + * smack_task_setnice - Smack check on setting nice + * @p: the task object + * @nice: unused + * + * Return 0 if write access is permitted + */ +static int smack_task_setnice(struct task_struct *p, int nice) +{ + int rc; + + rc = cap_task_setnice(p, nice); + if (rc == 0) + rc = smk_curacc_on_task(p, MAY_WRITE); + return rc; +} + +/** + * smack_task_setioprio - Smack check on setting ioprio + * @p: the task object + * @ioprio: unused + * + * Return 0 if write access is permitted + */ +static int smack_task_setioprio(struct task_struct *p, int ioprio) +{ + int rc; + + rc = cap_task_setioprio(p, ioprio); + if (rc == 0) + rc = smk_curacc_on_task(p, MAY_WRITE); + return rc; +} + +/** + * smack_task_getioprio - Smack check on reading ioprio + * @p: the task object + * + * Return 0 if read access is permitted + */ +static int smack_task_getioprio(struct task_struct *p) +{ + return smk_curacc_on_task(p, MAY_READ); +} + +/** + * smack_task_setscheduler - Smack check on setting scheduler + * @p: the task object + * @policy: unused + * @lp: unused + * + * Return 0 if read access is permitted + */ +static int smack_task_setscheduler(struct task_struct *p) +{ + int rc; + + rc = cap_task_setscheduler(p); + if (rc == 0) + rc = smk_curacc_on_task(p, MAY_WRITE); + return rc; +} + +/** + * smack_task_getscheduler - Smack check on reading scheduler + * @p: the task object + * + * Return 0 if read access is permitted + */ +static int smack_task_getscheduler(struct task_struct *p) +{ + return smk_curacc_on_task(p, MAY_READ); +} + +/** + * smack_task_movememory - Smack check on moving memory + * @p: the task object + * + * Return 0 if write access is permitted + */ +static int smack_task_movememory(struct task_struct *p) +{ + return smk_curacc_on_task(p, MAY_WRITE); +} + +/** + * smack_task_kill - Smack check on signal delivery + * @p: the task object + * @info: unused + * @sig: unused + * @secid: identifies the smack to use in lieu of current's + * + * Return 0 if write access is permitted + * + * The secid behavior is an artifact of an SELinux hack + * in the USB code. Someday it may go away. + */ +static int smack_task_kill(struct task_struct *p, struct siginfo *info, + int sig, u32 secid) +{ + struct smk_audit_info ad; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_TASK); + smk_ad_setfield_u_tsk(&ad, p); + /* + * Sending a signal requires that the sender + * can write the receiver. + */ + if (secid == 0) + return smk_curacc(smk_of_task(task_security(p)), MAY_WRITE, + &ad); + /* + * If the secid isn't 0 we're dealing with some USB IO + * specific behavior. This is not clean. For one thing + * we can't take privilege into account. + */ + return smk_access(smack_from_secid(secid), + smk_of_task(task_security(p)), MAY_WRITE, &ad); +} + +/** + * smack_task_wait - Smack access check for waiting + * @p: task to wait for + * + * Returns 0 if current can wait for p, error code otherwise + */ +static int smack_task_wait(struct task_struct *p) +{ + struct smk_audit_info ad; + char *sp = smk_of_current(); + char *tsp = smk_of_forked(task_security(p)); + int rc; + + /* we don't log here, we can be overriden */ + rc = smk_access(tsp, sp, MAY_WRITE, NULL); + if (rc == 0) + goto out_log; + + /* + * Allow the operation to succeed if either task + * has privilege to perform operations that might + * account for the smack labels having gotten to + * be different in the first place. + * + * This breaks the strict subject/object access + * control ideal, taking the object's privilege + * state into account in the decision as well as + * the smack value. + */ + if (capable(CAP_MAC_OVERRIDE) || has_capability(p, CAP_MAC_OVERRIDE)) + rc = 0; + /* we log only if we didn't get overriden */ + out_log: + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_TASK); + smk_ad_setfield_u_tsk(&ad, p); + smack_log(tsp, sp, MAY_WRITE, rc, &ad); + return rc; +} + +/** + * smack_task_to_inode - copy task smack into the inode blob + * @p: task to copy from + * @inode: inode to copy to + * + * Sets the smack pointer in the inode security blob + */ +static void smack_task_to_inode(struct task_struct *p, struct inode *inode) +{ + struct inode_smack *isp = inode->i_security; + isp->smk_inode = smk_of_task(task_security(p)); +} + +/* + * Socket hooks. + */ + +/** + * smack_sk_alloc_security - Allocate a socket blob + * @sk: the socket + * @family: unused + * @gfp_flags: memory allocation flags + * + * Assign Smack pointers to current + * + * Returns 0 on success, -ENOMEM is there's no memory + */ +static int smack_sk_alloc_security(struct sock *sk, int family, gfp_t gfp_flags) +{ + char *csp = smk_of_current(); + struct socket_smack *ssp; + + ssp = kzalloc(sizeof(struct socket_smack), gfp_flags); + if (ssp == NULL) + return -ENOMEM; + + ssp->smk_in = csp; + ssp->smk_out = csp; + ssp->smk_packet[0] = '\0'; + + sk->sk_security = ssp; + + return 0; +} + +/** + * smack_sk_free_security - Free a socket blob + * @sk: the socket + * + * Clears the blob pointer + */ +static void smack_sk_free_security(struct sock *sk) +{ + kfree(sk->sk_security); +} + +/** +* smack_host_label - check host based restrictions +* @sip: the object end +* +* looks for host based access restrictions +* +* This version will only be appropriate for really small sets of single label +* hosts. The caller is responsible for ensuring that the RCU read lock is +* taken before calling this function. +* +* Returns the label of the far end or NULL if it's not special. +*/ +static char *smack_host_label(struct sockaddr_in *sip) +{ + struct smk_netlbladdr *snp; + struct in_addr *siap = &sip->sin_addr; + + if (siap->s_addr == 0) + return NULL; + + list_for_each_entry_rcu(snp, &smk_netlbladdr_list, list) + /* + * we break after finding the first match because + * the list is sorted from longest to shortest mask + * so we have found the most specific match + */ + if ((&snp->smk_host.sin_addr)->s_addr == + (siap->s_addr & (&snp->smk_mask)->s_addr)) { + /* we have found the special CIPSO option */ + if (snp->smk_label == smack_cipso_option) + return NULL; + return snp->smk_label; + } + + return NULL; +} + +/** + * smack_set_catset - convert a capset to netlabel mls categories + * @catset: the Smack categories + * @sap: where to put the netlabel categories + * + * Allocates and fills attr.mls.cat + */ +static void smack_set_catset(char *catset, struct netlbl_lsm_secattr *sap) +{ + unsigned char *cp; + unsigned char m; + int cat; + int rc; + int byte; + + if (!catset) + return; + + sap->flags |= NETLBL_SECATTR_MLS_CAT; + sap->attr.mls.cat = netlbl_secattr_catmap_alloc(GFP_ATOMIC); + sap->attr.mls.cat->startbit = 0; + + for (cat = 1, cp = catset, byte = 0; byte < SMK_LABELLEN; cp++, byte++) + for (m = 0x80; m != 0; m >>= 1, cat++) { + if ((m & *cp) == 0) + continue; + rc = netlbl_secattr_catmap_setbit(sap->attr.mls.cat, + cat, GFP_ATOMIC); + } +} + +/** + * smack_to_secattr - fill a secattr from a smack value + * @smack: the smack value + * @nlsp: where the result goes + * + * Casey says that CIPSO is good enough for now. + * It can be used to effect. + * It can also be abused to effect when necessary. + * Apologies to the TSIG group in general and GW in particular. + */ +static void smack_to_secattr(char *smack, struct netlbl_lsm_secattr *nlsp) +{ + struct smack_cipso cipso; + int rc; + + nlsp->domain = smack; + nlsp->flags = NETLBL_SECATTR_DOMAIN | NETLBL_SECATTR_MLS_LVL; + + rc = smack_to_cipso(smack, &cipso); + if (rc == 0) { + nlsp->attr.mls.lvl = cipso.smk_level; + smack_set_catset(cipso.smk_catset, nlsp); + } else { + nlsp->attr.mls.lvl = smack_cipso_direct; + smack_set_catset(smack, nlsp); + } +} + +/** + * smack_netlabel - Set the secattr on a socket + * @sk: the socket + * @labeled: socket label scheme + * + * Convert the outbound smack value (smk_out) to a + * secattr and attach it to the socket. + * + * Returns 0 on success or an error code + */ +static int smack_netlabel(struct sock *sk, int labeled) +{ + struct socket_smack *ssp = sk->sk_security; + struct netlbl_lsm_secattr secattr; + int rc = 0; + + /* + * Usually the netlabel code will handle changing the + * packet labeling based on the label. + * The case of a single label host is different, because + * a single label host should never get a labeled packet + * even though the label is usually associated with a packet + * label. + */ + local_bh_disable(); + bh_lock_sock_nested(sk); + + if (ssp->smk_out == smack_net_ambient || + labeled == SMACK_UNLABELED_SOCKET) + netlbl_sock_delattr(sk); + else { + netlbl_secattr_init(&secattr); + smack_to_secattr(ssp->smk_out, &secattr); + rc = netlbl_sock_setattr(sk, sk->sk_family, &secattr); + netlbl_secattr_destroy(&secattr); + } + + bh_unlock_sock(sk); + local_bh_enable(); + + return rc; +} + +/** + * smack_netlbel_send - Set the secattr on a socket and perform access checks + * @sk: the socket + * @sap: the destination address + * + * Set the correct secattr for the given socket based on the destination + * address and perform any outbound access checks needed. + * + * Returns 0 on success or an error code. + * + */ +static int smack_netlabel_send(struct sock *sk, struct sockaddr_in *sap) +{ + int rc; + int sk_lbl; + char *hostsp; + struct socket_smack *ssp = sk->sk_security; + struct smk_audit_info ad; + + rcu_read_lock(); + hostsp = smack_host_label(sap); + if (hostsp != NULL) { + sk_lbl = SMACK_UNLABELED_SOCKET; +#ifdef CONFIG_AUDIT + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_NET); + ad.a.u.net.family = sap->sin_family; + ad.a.u.net.dport = sap->sin_port; + ad.a.u.net.v4info.daddr = sap->sin_addr.s_addr; +#endif + rc = smk_access(ssp->smk_out, hostsp, MAY_WRITE, &ad); + } else { + sk_lbl = SMACK_CIPSO_SOCKET; + rc = 0; + } + rcu_read_unlock(); + if (rc != 0) + return rc; + + return smack_netlabel(sk, sk_lbl); +} + +/** + * smack_inode_setsecurity - set smack xattrs + * @inode: the object + * @name: attribute name + * @value: attribute value + * @size: size of the attribute + * @flags: unused + * + * Sets the named attribute in the appropriate blob + * + * Returns 0 on success, or an error code + */ +static int smack_inode_setsecurity(struct inode *inode, const char *name, + const void *value, size_t size, int flags) +{ + char *sp; + struct inode_smack *nsp = inode->i_security; + struct socket_smack *ssp; + struct socket *sock; + int rc = 0; + + if (value == NULL || size > SMK_LABELLEN || size == 0) + return -EACCES; + + sp = smk_import(value, size); + if (sp == NULL) + return -EINVAL; + + if (strcmp(name, XATTR_SMACK_SUFFIX) == 0) { + nsp->smk_inode = sp; + nsp->smk_flags |= SMK_INODE_INSTANT; + return 0; + } + /* + * The rest of the Smack xattrs are only on sockets. + */ + if (inode->i_sb->s_magic != SOCKFS_MAGIC) + return -EOPNOTSUPP; + + sock = SOCKET_I(inode); + if (sock == NULL || sock->sk == NULL) + return -EOPNOTSUPP; + + ssp = sock->sk->sk_security; + + if (strcmp(name, XATTR_SMACK_IPIN) == 0) + ssp->smk_in = sp; + else if (strcmp(name, XATTR_SMACK_IPOUT) == 0) { + ssp->smk_out = sp; + if (sock->sk->sk_family != PF_UNIX) { + rc = smack_netlabel(sock->sk, SMACK_CIPSO_SOCKET); + if (rc != 0) + printk(KERN_WARNING + "Smack: \"%s\" netlbl error %d.\n", + __func__, -rc); + } + } else + return -EOPNOTSUPP; + + return 0; +} + +/** + * smack_socket_post_create - finish socket setup + * @sock: the socket + * @family: protocol family + * @type: unused + * @protocol: unused + * @kern: unused + * + * Sets the netlabel information on the socket + * + * Returns 0 on success, and error code otherwise + */ +static int smack_socket_post_create(struct socket *sock, int family, + int type, int protocol, int kern) +{ + if (family != PF_INET || sock->sk == NULL) + return 0; + /* + * Set the outbound netlbl. + */ + return smack_netlabel(sock->sk, SMACK_CIPSO_SOCKET); +} + +/** + * smack_socket_connect - connect access check + * @sock: the socket + * @sap: the other end + * @addrlen: size of sap + * + * Verifies that a connection may be possible + * + * Returns 0 on success, and error code otherwise + */ +static int smack_socket_connect(struct socket *sock, struct sockaddr *sap, + int addrlen) +{ + if (sock->sk == NULL || sock->sk->sk_family != PF_INET) + return 0; + if (addrlen < sizeof(struct sockaddr_in)) + return -EINVAL; + + return smack_netlabel_send(sock->sk, (struct sockaddr_in *)sap); +} + +/** + * smack_flags_to_may - convert S_ to MAY_ values + * @flags: the S_ value + * + * Returns the equivalent MAY_ value + */ +static int smack_flags_to_may(int flags) +{ + int may = 0; + + if (flags & S_IRUGO) + may |= MAY_READ; + if (flags & S_IWUGO) + may |= MAY_WRITE; + if (flags & S_IXUGO) + may |= MAY_EXEC; + + return may; +} + +/** + * smack_msg_msg_alloc_security - Set the security blob for msg_msg + * @msg: the object + * + * Returns 0 + */ +static int smack_msg_msg_alloc_security(struct msg_msg *msg) +{ + msg->security = smk_of_current(); + return 0; +} + +/** + * smack_msg_msg_free_security - Clear the security blob for msg_msg + * @msg: the object + * + * Clears the blob pointer + */ +static void smack_msg_msg_free_security(struct msg_msg *msg) +{ + msg->security = NULL; +} + +/** + * smack_of_shm - the smack pointer for the shm + * @shp: the object + * + * Returns a pointer to the smack value + */ +static char *smack_of_shm(struct shmid_kernel *shp) +{ + return (char *)shp->shm_perm.security; +} + +/** + * smack_shm_alloc_security - Set the security blob for shm + * @shp: the object + * + * Returns 0 + */ +static int smack_shm_alloc_security(struct shmid_kernel *shp) +{ + struct kern_ipc_perm *isp = &shp->shm_perm; + + isp->security = smk_of_current(); + return 0; +} + +/** + * smack_shm_free_security - Clear the security blob for shm + * @shp: the object + * + * Clears the blob pointer + */ +static void smack_shm_free_security(struct shmid_kernel *shp) +{ + struct kern_ipc_perm *isp = &shp->shm_perm; + + isp->security = NULL; +} + +/** + * smk_curacc_shm : check if current has access on shm + * @shp : the object + * @access : access requested + * + * Returns 0 if current has the requested access, error code otherwise + */ +static int smk_curacc_shm(struct shmid_kernel *shp, int access) +{ + char *ssp = smack_of_shm(shp); + struct smk_audit_info ad; + +#ifdef CONFIG_AUDIT + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_IPC); + ad.a.u.ipc_id = shp->shm_perm.id; +#endif + return smk_curacc(ssp, access, &ad); +} + +/** + * smack_shm_associate - Smack access check for shm + * @shp: the object + * @shmflg: access requested + * + * Returns 0 if current has the requested access, error code otherwise + */ +static int smack_shm_associate(struct shmid_kernel *shp, int shmflg) +{ + int may; + + may = smack_flags_to_may(shmflg); + return smk_curacc_shm(shp, may); +} + +/** + * smack_shm_shmctl - Smack access check for shm + * @shp: the object + * @cmd: what it wants to do + * + * Returns 0 if current has the requested access, error code otherwise + */ +static int smack_shm_shmctl(struct shmid_kernel *shp, int cmd) +{ + int may; + + switch (cmd) { + case IPC_STAT: + case SHM_STAT: + may = MAY_READ; + break; + case IPC_SET: + case SHM_LOCK: + case SHM_UNLOCK: + case IPC_RMID: + may = MAY_READWRITE; + break; + case IPC_INFO: + case SHM_INFO: + /* + * System level information. + */ + return 0; + default: + return -EINVAL; + } + return smk_curacc_shm(shp, may); +} + +/** + * smack_shm_shmat - Smack access for shmat + * @shp: the object + * @shmaddr: unused + * @shmflg: access requested + * + * Returns 0 if current has the requested access, error code otherwise + */ +static int smack_shm_shmat(struct shmid_kernel *shp, char __user *shmaddr, + int shmflg) +{ + int may; + + may = smack_flags_to_may(shmflg); + return smk_curacc_shm(shp, may); +} + +/** + * smack_of_sem - the smack pointer for the sem + * @sma: the object + * + * Returns a pointer to the smack value + */ +static char *smack_of_sem(struct sem_array *sma) +{ + return (char *)sma->sem_perm.security; +} + +/** + * smack_sem_alloc_security - Set the security blob for sem + * @sma: the object + * + * Returns 0 + */ +static int smack_sem_alloc_security(struct sem_array *sma) +{ + struct kern_ipc_perm *isp = &sma->sem_perm; + + isp->security = smk_of_current(); + return 0; +} + +/** + * smack_sem_free_security - Clear the security blob for sem + * @sma: the object + * + * Clears the blob pointer + */ +static void smack_sem_free_security(struct sem_array *sma) +{ + struct kern_ipc_perm *isp = &sma->sem_perm; + + isp->security = NULL; +} + +/** + * smk_curacc_sem : check if current has access on sem + * @sma : the object + * @access : access requested + * + * Returns 0 if current has the requested access, error code otherwise + */ +static int smk_curacc_sem(struct sem_array *sma, int access) +{ + char *ssp = smack_of_sem(sma); + struct smk_audit_info ad; + +#ifdef CONFIG_AUDIT + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_IPC); + ad.a.u.ipc_id = sma->sem_perm.id; +#endif + return smk_curacc(ssp, access, &ad); +} + +/** + * smack_sem_associate - Smack access check for sem + * @sma: the object + * @semflg: access requested + * + * Returns 0 if current has the requested access, error code otherwise + */ +static int smack_sem_associate(struct sem_array *sma, int semflg) +{ + int may; + + may = smack_flags_to_may(semflg); + return smk_curacc_sem(sma, may); +} + +/** + * smack_sem_shmctl - Smack access check for sem + * @sma: the object + * @cmd: what it wants to do + * + * Returns 0 if current has the requested access, error code otherwise + */ +static int smack_sem_semctl(struct sem_array *sma, int cmd) +{ + int may; + + switch (cmd) { + case GETPID: + case GETNCNT: + case GETZCNT: + case GETVAL: + case GETALL: + case IPC_STAT: + case SEM_STAT: + may = MAY_READ; + break; + case SETVAL: + case SETALL: + case IPC_RMID: + case IPC_SET: + may = MAY_READWRITE; + break; + case IPC_INFO: + case SEM_INFO: + /* + * System level information + */ + return 0; + default: + return -EINVAL; + } + + return smk_curacc_sem(sma, may); +} + +/** + * smack_sem_semop - Smack checks of semaphore operations + * @sma: the object + * @sops: unused + * @nsops: unused + * @alter: unused + * + * Treated as read and write in all cases. + * + * Returns 0 if access is allowed, error code otherwise + */ +static int smack_sem_semop(struct sem_array *sma, struct sembuf *sops, + unsigned nsops, int alter) +{ + return smk_curacc_sem(sma, MAY_READWRITE); +} + +/** + * smack_msg_alloc_security - Set the security blob for msg + * @msq: the object + * + * Returns 0 + */ +static int smack_msg_queue_alloc_security(struct msg_queue *msq) +{ + struct kern_ipc_perm *kisp = &msq->q_perm; + + kisp->security = smk_of_current(); + return 0; +} + +/** + * smack_msg_free_security - Clear the security blob for msg + * @msq: the object + * + * Clears the blob pointer + */ +static void smack_msg_queue_free_security(struct msg_queue *msq) +{ + struct kern_ipc_perm *kisp = &msq->q_perm; + + kisp->security = NULL; +} + +/** + * smack_of_msq - the smack pointer for the msq + * @msq: the object + * + * Returns a pointer to the smack value + */ +static char *smack_of_msq(struct msg_queue *msq) +{ + return (char *)msq->q_perm.security; +} + +/** + * smk_curacc_msq : helper to check if current has access on msq + * @msq : the msq + * @access : access requested + * + * return 0 if current has access, error otherwise + */ +static int smk_curacc_msq(struct msg_queue *msq, int access) +{ + char *msp = smack_of_msq(msq); + struct smk_audit_info ad; + +#ifdef CONFIG_AUDIT + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_IPC); + ad.a.u.ipc_id = msq->q_perm.id; +#endif + return smk_curacc(msp, access, &ad); +} + +/** + * smack_msg_queue_associate - Smack access check for msg_queue + * @msq: the object + * @msqflg: access requested + * + * Returns 0 if current has the requested access, error code otherwise + */ +static int smack_msg_queue_associate(struct msg_queue *msq, int msqflg) +{ + int may; + + may = smack_flags_to_may(msqflg); + return smk_curacc_msq(msq, may); +} + +/** + * smack_msg_queue_msgctl - Smack access check for msg_queue + * @msq: the object + * @cmd: what it wants to do + * + * Returns 0 if current has the requested access, error code otherwise + */ +static int smack_msg_queue_msgctl(struct msg_queue *msq, int cmd) +{ + int may; + + switch (cmd) { + case IPC_STAT: + case MSG_STAT: + may = MAY_READ; + break; + case IPC_SET: + case IPC_RMID: + may = MAY_READWRITE; + break; + case IPC_INFO: + case MSG_INFO: + /* + * System level information + */ + return 0; + default: + return -EINVAL; + } + + return smk_curacc_msq(msq, may); +} + +/** + * smack_msg_queue_msgsnd - Smack access check for msg_queue + * @msq: the object + * @msg: unused + * @msqflg: access requested + * + * Returns 0 if current has the requested access, error code otherwise + */ +static int smack_msg_queue_msgsnd(struct msg_queue *msq, struct msg_msg *msg, + int msqflg) +{ + int may; + + may = smack_flags_to_may(msqflg); + return smk_curacc_msq(msq, may); +} + +/** + * smack_msg_queue_msgsnd - Smack access check for msg_queue + * @msq: the object + * @msg: unused + * @target: unused + * @type: unused + * @mode: unused + * + * Returns 0 if current has read and write access, error code otherwise + */ +static int smack_msg_queue_msgrcv(struct msg_queue *msq, struct msg_msg *msg, + struct task_struct *target, long type, int mode) +{ + return smk_curacc_msq(msq, MAY_READWRITE); +} + +/** + * smack_ipc_permission - Smack access for ipc_permission() + * @ipp: the object permissions + * @flag: access requested + * + * Returns 0 if current has read and write access, error code otherwise + */ +static int smack_ipc_permission(struct kern_ipc_perm *ipp, short flag) +{ + char *isp = ipp->security; + int may = smack_flags_to_may(flag); + struct smk_audit_info ad; + +#ifdef CONFIG_AUDIT + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_IPC); + ad.a.u.ipc_id = ipp->id; +#endif + return smk_curacc(isp, may, &ad); +} + +/** + * smack_ipc_getsecid - Extract smack security id + * @ipp: the object permissions + * @secid: where result will be saved + */ +static void smack_ipc_getsecid(struct kern_ipc_perm *ipp, u32 *secid) +{ + char *smack = ipp->security; + + *secid = smack_to_secid(smack); +} + +/** + * smack_d_instantiate - Make sure the blob is correct on an inode + * @opt_dentry: dentry where inode will be attached + * @inode: the object + * + * Set the inode's security blob if it hasn't been done already. + */ +static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) +{ + struct super_block *sbp; + struct superblock_smack *sbsp; + struct inode_smack *isp; + char *csp = smk_of_current(); + char *fetched; + char *final; + char trattr[TRANS_TRUE_SIZE]; + int transflag = 0; + struct dentry *dp; + + if (inode == NULL) + return; + + isp = inode->i_security; + + mutex_lock(&isp->smk_lock); + /* + * If the inode is already instantiated + * take the quick way out + */ + if (isp->smk_flags & SMK_INODE_INSTANT) + goto unlockandout; + + sbp = inode->i_sb; + sbsp = sbp->s_security; + /* + * We're going to use the superblock default label + * if there's no label on the file. + */ + final = sbsp->smk_default; + + /* + * If this is the root inode the superblock + * may be in the process of initialization. + * If that is the case use the root value out + * of the superblock. + */ + if (opt_dentry->d_parent == opt_dentry) { + isp->smk_inode = sbsp->smk_root; + isp->smk_flags |= SMK_INODE_INSTANT; + goto unlockandout; + } + + /* + * This is pretty hackish. + * Casey says that we shouldn't have to do + * file system specific code, but it does help + * with keeping it simple. + */ + switch (sbp->s_magic) { + case SMACK_MAGIC: + /* + * Casey says that it's a little embarrassing + * that the smack file system doesn't do + * extended attributes. + */ + final = smack_known_star.smk_known; + break; + case PIPEFS_MAGIC: + /* + * Casey says pipes are easy (?) + */ + final = smack_known_star.smk_known; + break; + case DEVPTS_SUPER_MAGIC: + /* + * devpts seems content with the label of the task. + * Programs that change smack have to treat the + * pty with respect. + */ + final = csp; + break; + case SOCKFS_MAGIC: + /* + * Socket access is controlled by the socket + * structures associated with the task involved. + */ + final = smack_known_star.smk_known; + break; + case PROC_SUPER_MAGIC: + /* + * Casey says procfs appears not to care. + * The superblock default suffices. + */ + break; + case TMPFS_MAGIC: + /* + * Device labels should come from the filesystem, + * but watch out, because they're volitile, + * getting recreated on every reboot. + */ + final = smack_known_star.smk_known; + /* + * No break. + * + * If a smack value has been set we want to use it, + * but since tmpfs isn't giving us the opportunity + * to set mount options simulate setting the + * superblock default. + */ + default: + /* + * This isn't an understood special case. + * Get the value from the xattr. + */ + + /* + * UNIX domain sockets use lower level socket data. + */ + if (S_ISSOCK(inode->i_mode)) { + final = smack_known_star.smk_known; + break; + } + /* + * No xattr support means, alas, no SMACK label. + * Use the aforeapplied default. + * It would be curious if the label of the task + * does not match that assigned. + */ + if (inode->i_op->getxattr == NULL) + break; + /* + * Get the dentry for xattr. + */ + dp = dget(opt_dentry); + fetched = smk_fetch(XATTR_NAME_SMACK, inode, dp); + if (fetched != NULL) { + final = fetched; + if (S_ISDIR(inode->i_mode)) { + trattr[0] = '\0'; + inode->i_op->getxattr(dp, + XATTR_NAME_SMACKTRANSMUTE, + trattr, TRANS_TRUE_SIZE); + if (strncmp(trattr, TRANS_TRUE, + TRANS_TRUE_SIZE) == 0) + transflag = SMK_INODE_TRANSMUTE; + } + } + isp->smk_task = smk_fetch(XATTR_NAME_SMACKEXEC, inode, dp); + isp->smk_mmap = smk_fetch(XATTR_NAME_SMACKMMAP, inode, dp); + + dput(dp); + break; + } + + if (final == NULL) + isp->smk_inode = csp; + else + isp->smk_inode = final; + + isp->smk_flags |= (SMK_INODE_INSTANT | transflag); + +unlockandout: + mutex_unlock(&isp->smk_lock); + return; +} + +/** + * smack_getprocattr - Smack process attribute access + * @p: the object task + * @name: the name of the attribute in /proc/.../attr + * @value: where to put the result + * + * Places a copy of the task Smack into value + * + * Returns the length of the smack label or an error code + */ +static int smack_getprocattr(struct task_struct *p, char *name, char **value) +{ + char *cp; + int slen; + + if (strcmp(name, "current") != 0) + return -EINVAL; + + cp = kstrdup(smk_of_task(task_security(p)), GFP_KERNEL); + if (cp == NULL) + return -ENOMEM; + + slen = strlen(cp); + *value = cp; + return slen; +} + +/** + * smack_setprocattr - Smack process attribute setting + * @p: the object task + * @name: the name of the attribute in /proc/.../attr + * @value: the value to set + * @size: the size of the value + * + * Sets the Smack value of the task. Only setting self + * is permitted and only with privilege + * + * Returns the length of the smack label or an error code + */ +static int smack_setprocattr(struct task_struct *p, char *name, + void *value, size_t size) +{ + int rc; + struct task_smack *tsp; + struct task_smack *oldtsp; + struct cred *new; + char *newsmack; + + /* + * Changing another process' Smack value is too dangerous + * and supports no sane use case. + */ + if (p != current) + return -EPERM; + + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + + if (value == NULL || size == 0 || size >= SMK_LABELLEN) + return -EINVAL; + + if (strcmp(name, "current") != 0) + return -EINVAL; + + newsmack = smk_import(value, size); + if (newsmack == NULL) + return -EINVAL; + + /* + * No process is ever allowed the web ("@") label. + */ + if (newsmack == smack_known_web.smk_known) + return -EPERM; + + oldtsp = p->cred->security; + new = prepare_creds(); + if (new == NULL) + return -ENOMEM; + + tsp = new_task_smack(newsmack, oldtsp->smk_forked, GFP_KERNEL); + if (tsp == NULL) { + kfree(new); + return -ENOMEM; + } + rc = smk_copy_rules(&tsp->smk_rules, &oldtsp->smk_rules, GFP_KERNEL); + if (rc != 0) + return rc; + + new->security = tsp; + commit_creds(new); + return size; +} + +/** + * smack_unix_stream_connect - Smack access on UDS + * @sock: one sock + * @other: the other sock + * @newsk: unused + * + * Return 0 if a subject with the smack of sock could access + * an object with the smack of other, otherwise an error code + */ +static int smack_unix_stream_connect(struct sock *sock, + struct sock *other, struct sock *newsk) +{ + struct socket_smack *ssp = sock->sk_security; + struct socket_smack *osp = other->sk_security; + struct smk_audit_info ad; + int rc = 0; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_NET); + smk_ad_setfield_u_net_sk(&ad, other); + + if (!capable(CAP_MAC_OVERRIDE)) + rc = smk_access(ssp->smk_out, osp->smk_in, MAY_WRITE, &ad); + + return rc; +} + +/** + * smack_unix_may_send - Smack access on UDS + * @sock: one socket + * @other: the other socket + * + * Return 0 if a subject with the smack of sock could access + * an object with the smack of other, otherwise an error code + */ +static int smack_unix_may_send(struct socket *sock, struct socket *other) +{ + struct socket_smack *ssp = sock->sk->sk_security; + struct socket_smack *osp = other->sk->sk_security; + struct smk_audit_info ad; + int rc = 0; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_NET); + smk_ad_setfield_u_net_sk(&ad, other->sk); + + if (!capable(CAP_MAC_OVERRIDE)) + rc = smk_access(ssp->smk_out, osp->smk_in, MAY_WRITE, &ad); + + return rc; +} + +/** + * smack_socket_sendmsg - Smack check based on destination host + * @sock: the socket + * @msg: the message + * @size: the size of the message + * + * Return 0 if the current subject can write to the destination + * host. This is only a question if the destination is a single + * label host. + */ +static int smack_socket_sendmsg(struct socket *sock, struct msghdr *msg, + int size) +{ + struct sockaddr_in *sip = (struct sockaddr_in *) msg->msg_name; + + /* + * Perfectly reasonable for this to be NULL + */ + if (sip == NULL || sip->sin_family != AF_INET) + return 0; + + return smack_netlabel_send(sock->sk, sip); +} + + +/** + * smack_from_secattr - Convert a netlabel attr.mls.lvl/attr.mls.cat pair to smack + * @sap: netlabel secattr + * @sip: where to put the result + * + * Copies a smack label into sip + */ +static void smack_from_secattr(struct netlbl_lsm_secattr *sap, char *sip) +{ + char smack[SMK_LABELLEN]; + char *sp; + int pcat; + + if ((sap->flags & NETLBL_SECATTR_MLS_LVL) != 0) { + /* + * Looks like a CIPSO packet. + * If there are flags but no level netlabel isn't + * behaving the way we expect it to. + * + * Get the categories, if any + * Without guidance regarding the smack value + * for the packet fall back on the network + * ambient value. + */ + memset(smack, '\0', SMK_LABELLEN); + if ((sap->flags & NETLBL_SECATTR_MLS_CAT) != 0) + for (pcat = -1;;) { + pcat = netlbl_secattr_catmap_walk( + sap->attr.mls.cat, pcat + 1); + if (pcat < 0) + break; + smack_catset_bit(pcat, smack); + } + /* + * If it is CIPSO using smack direct mapping + * we are already done. WeeHee. + */ + if (sap->attr.mls.lvl == smack_cipso_direct) { + memcpy(sip, smack, SMK_MAXLEN); + return; + } + /* + * Look it up in the supplied table if it is not + * a direct mapping. + */ + smack_from_cipso(sap->attr.mls.lvl, smack, sip); + return; + } + if ((sap->flags & NETLBL_SECATTR_SECID) != 0) { + /* + * Looks like a fallback, which gives us a secid. + */ + sp = smack_from_secid(sap->attr.secid); + /* + * This has got to be a bug because it is + * impossible to specify a fallback without + * specifying the label, which will ensure + * it has a secid, and the only way to get a + * secid is from a fallback. + */ + BUG_ON(sp == NULL); + strncpy(sip, sp, SMK_MAXLEN); + return; + } + /* + * Without guidance regarding the smack value + * for the packet fall back on the network + * ambient value. + */ + strncpy(sip, smack_net_ambient, SMK_MAXLEN); + return; +} + +/** + * smack_socket_sock_rcv_skb - Smack packet delivery access check + * @sk: socket + * @skb: packet + * + * Returns 0 if the packet should be delivered, an error code otherwise + */ +static int smack_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) +{ + struct netlbl_lsm_secattr secattr; + struct socket_smack *ssp = sk->sk_security; + char smack[SMK_LABELLEN]; + char *csp; + int rc; + struct smk_audit_info ad; + if (sk->sk_family != PF_INET && sk->sk_family != PF_INET6) + return 0; + + /* + * Translate what netlabel gave us. + */ + netlbl_secattr_init(&secattr); + + rc = netlbl_skbuff_getattr(skb, sk->sk_family, &secattr); + if (rc == 0) { + smack_from_secattr(&secattr, smack); + csp = smack; + } else + csp = smack_net_ambient; + + netlbl_secattr_destroy(&secattr); + +#ifdef CONFIG_AUDIT + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_NET); + ad.a.u.net.family = sk->sk_family; + ad.a.u.net.netif = skb->skb_iif; + ipv4_skb_to_auditdata(skb, &ad.a, NULL); +#endif + /* + * Receiving a packet requires that the other end + * be able to write here. Read access is not required. + * This is the simplist possible security model + * for networking. + */ + rc = smk_access(csp, ssp->smk_in, MAY_WRITE, &ad); + if (rc != 0) + netlbl_skbuff_err(skb, rc, 0); + return rc; +} + +/** + * smack_socket_getpeersec_stream - pull in packet label + * @sock: the socket + * @optval: user's destination + * @optlen: size thereof + * @len: max thereof + * + * returns zero on success, an error code otherwise + */ +static int smack_socket_getpeersec_stream(struct socket *sock, + char __user *optval, + int __user *optlen, unsigned len) +{ + struct socket_smack *ssp; + int slen; + int rc = 0; + + ssp = sock->sk->sk_security; + slen = strlen(ssp->smk_packet) + 1; + + if (slen > len) + rc = -ERANGE; + else if (copy_to_user(optval, ssp->smk_packet, slen) != 0) + rc = -EFAULT; + + if (put_user(slen, optlen) != 0) + rc = -EFAULT; + + return rc; +} + + +/** + * smack_socket_getpeersec_dgram - pull in packet label + * @sock: the peer socket + * @skb: packet data + * @secid: pointer to where to put the secid of the packet + * + * Sets the netlabel socket state on sk from parent + */ +static int smack_socket_getpeersec_dgram(struct socket *sock, + struct sk_buff *skb, u32 *secid) + +{ + struct netlbl_lsm_secattr secattr; + struct socket_smack *sp; + char smack[SMK_LABELLEN]; + int family = PF_UNSPEC; + u32 s = 0; /* 0 is the invalid secid */ + int rc; + + if (skb != NULL) { + if (skb->protocol == htons(ETH_P_IP)) + family = PF_INET; + else if (skb->protocol == htons(ETH_P_IPV6)) + family = PF_INET6; + } + if (family == PF_UNSPEC && sock != NULL) + family = sock->sk->sk_family; + + if (family == PF_UNIX) { + sp = sock->sk->sk_security; + s = smack_to_secid(sp->smk_out); + } else if (family == PF_INET || family == PF_INET6) { + /* + * Translate what netlabel gave us. + */ + netlbl_secattr_init(&secattr); + rc = netlbl_skbuff_getattr(skb, family, &secattr); + if (rc == 0) { + smack_from_secattr(&secattr, smack); + s = smack_to_secid(smack); + } + netlbl_secattr_destroy(&secattr); + } + *secid = s; + if (s == 0) + return -EINVAL; + return 0; +} + +/** + * smack_sock_graft - Initialize a newly created socket with an existing sock + * @sk: child sock + * @parent: parent socket + * + * Set the smk_{in,out} state of an existing sock based on the process that + * is creating the new socket. + */ +static void smack_sock_graft(struct sock *sk, struct socket *parent) +{ + struct socket_smack *ssp; + + if (sk == NULL || + (sk->sk_family != PF_INET && sk->sk_family != PF_INET6)) + return; + + ssp = sk->sk_security; + ssp->smk_in = ssp->smk_out = smk_of_current(); + /* cssp->smk_packet is already set in smack_inet_csk_clone() */ +} + +/** + * smack_inet_conn_request - Smack access check on connect + * @sk: socket involved + * @skb: packet + * @req: unused + * + * Returns 0 if a task with the packet label could write to + * the socket, otherwise an error code + */ +static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb, + struct request_sock *req) +{ + u16 family = sk->sk_family; + struct socket_smack *ssp = sk->sk_security; + struct netlbl_lsm_secattr secattr; + struct sockaddr_in addr; + struct iphdr *hdr; + char smack[SMK_LABELLEN]; + int rc; + struct smk_audit_info ad; + + /* handle mapped IPv4 packets arriving via IPv6 sockets */ + if (family == PF_INET6 && skb->protocol == htons(ETH_P_IP)) + family = PF_INET; + + netlbl_secattr_init(&secattr); + rc = netlbl_skbuff_getattr(skb, family, &secattr); + if (rc == 0) + smack_from_secattr(&secattr, smack); + else + strncpy(smack, smack_known_huh.smk_known, SMK_MAXLEN); + netlbl_secattr_destroy(&secattr); + +#ifdef CONFIG_AUDIT + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_NET); + ad.a.u.net.family = family; + ad.a.u.net.netif = skb->skb_iif; + ipv4_skb_to_auditdata(skb, &ad.a, NULL); +#endif + /* + * Receiving a packet requires that the other end be able to write + * here. Read access is not required. + */ + rc = smk_access(smack, ssp->smk_in, MAY_WRITE, &ad); + if (rc != 0) + return rc; + + /* + * Save the peer's label in the request_sock so we can later setup + * smk_packet in the child socket so that SO_PEERCRED can report it. + */ + req->peer_secid = smack_to_secid(smack); + + /* + * We need to decide if we want to label the incoming connection here + * if we do we only need to label the request_sock and the stack will + * propagate the wire-label to the sock when it is created. + */ + hdr = ip_hdr(skb); + addr.sin_addr.s_addr = hdr->saddr; + rcu_read_lock(); + if (smack_host_label(&addr) == NULL) { + rcu_read_unlock(); + netlbl_secattr_init(&secattr); + smack_to_secattr(smack, &secattr); + rc = netlbl_req_setattr(req, &secattr); + netlbl_secattr_destroy(&secattr); + } else { + rcu_read_unlock(); + netlbl_req_delattr(req); + } + + return rc; +} + +/** + * smack_inet_csk_clone - Copy the connection information to the new socket + * @sk: the new socket + * @req: the connection's request_sock + * + * Transfer the connection's peer label to the newly created socket. + */ +static void smack_inet_csk_clone(struct sock *sk, + const struct request_sock *req) +{ + struct socket_smack *ssp = sk->sk_security; + char *smack; + + if (req->peer_secid != 0) { + smack = smack_from_secid(req->peer_secid); + strncpy(ssp->smk_packet, smack, SMK_MAXLEN); + } else + ssp->smk_packet[0] = '\0'; +} + +/* + * Key management security hooks + * + * Casey has not tested key support very heavily. + * The permission check is most likely too restrictive. + * If you care about keys please have a look. + */ +#ifdef CONFIG_KEYS + +/** + * smack_key_alloc - Set the key security blob + * @key: object + * @cred: the credentials to use + * @flags: unused + * + * No allocation required + * + * Returns 0 + */ +static int smack_key_alloc(struct key *key, const struct cred *cred, + unsigned long flags) +{ + key->security = smk_of_task(cred->security); + return 0; +} + +/** + * smack_key_free - Clear the key security blob + * @key: the object + * + * Clear the blob pointer + */ +static void smack_key_free(struct key *key) +{ + key->security = NULL; +} + +/* + * smack_key_permission - Smack access on a key + * @key_ref: gets to the object + * @cred: the credentials to use + * @perm: unused + * + * Return 0 if the task has read and write to the object, + * an error code otherwise + */ +static int smack_key_permission(key_ref_t key_ref, + const struct cred *cred, key_perm_t perm) +{ + struct key *keyp; + struct smk_audit_info ad; + char *tsp = smk_of_task(cred->security); + + keyp = key_ref_to_ptr(key_ref); + if (keyp == NULL) + return -EINVAL; + /* + * If the key hasn't been initialized give it access so that + * it may do so. + */ + if (keyp->security == NULL) + return 0; + /* + * This should not occur + */ + if (tsp == NULL) + return -EACCES; +#ifdef CONFIG_AUDIT + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_KEY); + ad.a.u.key_struct.key = keyp->serial; + ad.a.u.key_struct.key_desc = keyp->description; +#endif + return smk_access(tsp, keyp->security, + MAY_READWRITE, &ad); +} +#endif /* CONFIG_KEYS */ + +/* + * Smack Audit hooks + * + * Audit requires a unique representation of each Smack specific + * rule. This unique representation is used to distinguish the + * object to be audited from remaining kernel objects and also + * works as a glue between the audit hooks. + * + * Since repository entries are added but never deleted, we'll use + * the smack_known label address related to the given audit rule as + * the needed unique representation. This also better fits the smack + * model where nearly everything is a label. + */ +#ifdef CONFIG_AUDIT + +/** + * smack_audit_rule_init - Initialize a smack audit rule + * @field: audit rule fields given from user-space (audit.h) + * @op: required testing operator (=, !=, >, <, ...) + * @rulestr: smack label to be audited + * @vrule: pointer to save our own audit rule representation + * + * Prepare to audit cases where (@field @op @rulestr) is true. + * The label to be audited is created if necessay. + */ +static int smack_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule) +{ + char **rule = (char **)vrule; + *rule = NULL; + + if (field != AUDIT_SUBJ_USER && field != AUDIT_OBJ_USER) + return -EINVAL; + + if (op != Audit_equal && op != Audit_not_equal) + return -EINVAL; + + *rule = smk_import(rulestr, 0); + + return 0; +} + +/** + * smack_audit_rule_known - Distinguish Smack audit rules + * @krule: rule of interest, in Audit kernel representation format + * + * This is used to filter Smack rules from remaining Audit ones. + * If it's proved that this rule belongs to us, the + * audit_rule_match hook will be called to do the final judgement. + */ +static int smack_audit_rule_known(struct audit_krule *krule) +{ + struct audit_field *f; + int i; + + for (i = 0; i < krule->field_count; i++) { + f = &krule->fields[i]; + + if (f->type == AUDIT_SUBJ_USER || f->type == AUDIT_OBJ_USER) + return 1; + } + + return 0; +} + +/** + * smack_audit_rule_match - Audit given object ? + * @secid: security id for identifying the object to test + * @field: audit rule flags given from user-space + * @op: required testing operator + * @vrule: smack internal rule presentation + * @actx: audit context associated with the check + * + * The core Audit hook. It's used to take the decision of + * whether to audit or not to audit a given object. + */ +static int smack_audit_rule_match(u32 secid, u32 field, u32 op, void *vrule, + struct audit_context *actx) +{ + char *smack; + char *rule = vrule; + + if (!rule) { + audit_log(actx, GFP_KERNEL, AUDIT_SELINUX_ERR, + "Smack: missing rule\n"); + return -ENOENT; + } + + if (field != AUDIT_SUBJ_USER && field != AUDIT_OBJ_USER) + return 0; + + smack = smack_from_secid(secid); + + /* + * No need to do string comparisons. If a match occurs, + * both pointers will point to the same smack_known + * label. + */ + if (op == Audit_equal) + return (rule == smack); + if (op == Audit_not_equal) + return (rule != smack); + + return 0; +} + +/** + * smack_audit_rule_free - free smack rule representation + * @vrule: rule to be freed. + * + * No memory was allocated. + */ +static void smack_audit_rule_free(void *vrule) +{ + /* No-op */ +} + +#endif /* CONFIG_AUDIT */ + +/** + * smack_secid_to_secctx - return the smack label for a secid + * @secid: incoming integer + * @secdata: destination + * @seclen: how long it is + * + * Exists for networking code. + */ +static int smack_secid_to_secctx(u32 secid, char **secdata, u32 *seclen) +{ + char *sp = smack_from_secid(secid); + + if (secdata) + *secdata = sp; + *seclen = strlen(sp); + return 0; +} + +/** + * smack_secctx_to_secid - return the secid for a smack label + * @secdata: smack label + * @seclen: how long result is + * @secid: outgoing integer + * + * Exists for audit and networking code. + */ +static int smack_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid) +{ + *secid = smack_to_secid(secdata); + return 0; +} + +/** + * smack_release_secctx - don't do anything. + * @secdata: unused + * @seclen: unused + * + * Exists to make sure nothing gets done, and properly + */ +static void smack_release_secctx(char *secdata, u32 seclen) +{ +} + +static int smack_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen) +{ + return smack_inode_setsecurity(inode, XATTR_SMACK_SUFFIX, ctx, ctxlen, 0); +} + +static int smack_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen) +{ + return __vfs_setxattr_noperm(dentry, XATTR_NAME_SMACK, ctx, ctxlen, 0); +} + +static int smack_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen) +{ + int len = 0; + len = smack_inode_getsecurity(inode, XATTR_SMACK_SUFFIX, ctx, true); + + if (len < 0) + return len; + *ctxlen = len; + return 0; +} + +struct security_operations smack_ops = { + .name = "smack", + + .ptrace_access_check = smack_ptrace_access_check, + .ptrace_traceme = smack_ptrace_traceme, + .syslog = smack_syslog, + + .sb_alloc_security = smack_sb_alloc_security, + .sb_free_security = smack_sb_free_security, + .sb_copy_data = smack_sb_copy_data, + .sb_kern_mount = smack_sb_kern_mount, + .sb_statfs = smack_sb_statfs, + .sb_mount = smack_sb_mount, + .sb_umount = smack_sb_umount, + + .bprm_set_creds = smack_bprm_set_creds, + + .inode_alloc_security = smack_inode_alloc_security, + .inode_free_security = smack_inode_free_security, + .inode_init_security = smack_inode_init_security, + .inode_link = smack_inode_link, + .inode_unlink = smack_inode_unlink, + .inode_rmdir = smack_inode_rmdir, + .inode_rename = smack_inode_rename, + .inode_permission = smack_inode_permission, + .inode_setattr = smack_inode_setattr, + .inode_getattr = smack_inode_getattr, + .inode_setxattr = smack_inode_setxattr, + .inode_post_setxattr = smack_inode_post_setxattr, + .inode_getxattr = smack_inode_getxattr, + .inode_removexattr = smack_inode_removexattr, + .inode_getsecurity = smack_inode_getsecurity, + .inode_setsecurity = smack_inode_setsecurity, + .inode_listsecurity = smack_inode_listsecurity, + .inode_getsecid = smack_inode_getsecid, + + .file_permission = smack_file_permission, + .file_alloc_security = smack_file_alloc_security, + .file_free_security = smack_file_free_security, + .file_ioctl = smack_file_ioctl, + .file_lock = smack_file_lock, + .file_fcntl = smack_file_fcntl, + .file_mmap = smack_file_mmap, + .file_set_fowner = smack_file_set_fowner, + .file_send_sigiotask = smack_file_send_sigiotask, + .file_receive = smack_file_receive, + + .cred_alloc_blank = smack_cred_alloc_blank, + .cred_free = smack_cred_free, + .cred_prepare = smack_cred_prepare, + .cred_transfer = smack_cred_transfer, + .kernel_act_as = smack_kernel_act_as, + .kernel_create_files_as = smack_kernel_create_files_as, + .task_setpgid = smack_task_setpgid, + .task_getpgid = smack_task_getpgid, + .task_getsid = smack_task_getsid, + .task_getsecid = smack_task_getsecid, + .task_setnice = smack_task_setnice, + .task_setioprio = smack_task_setioprio, + .task_getioprio = smack_task_getioprio, + .task_setscheduler = smack_task_setscheduler, + .task_getscheduler = smack_task_getscheduler, + .task_movememory = smack_task_movememory, + .task_kill = smack_task_kill, + .task_wait = smack_task_wait, + .task_to_inode = smack_task_to_inode, + + .ipc_permission = smack_ipc_permission, + .ipc_getsecid = smack_ipc_getsecid, + + .msg_msg_alloc_security = smack_msg_msg_alloc_security, + .msg_msg_free_security = smack_msg_msg_free_security, + + .msg_queue_alloc_security = smack_msg_queue_alloc_security, + .msg_queue_free_security = smack_msg_queue_free_security, + .msg_queue_associate = smack_msg_queue_associate, + .msg_queue_msgctl = smack_msg_queue_msgctl, + .msg_queue_msgsnd = smack_msg_queue_msgsnd, + .msg_queue_msgrcv = smack_msg_queue_msgrcv, + + .shm_alloc_security = smack_shm_alloc_security, + .shm_free_security = smack_shm_free_security, + .shm_associate = smack_shm_associate, + .shm_shmctl = smack_shm_shmctl, + .shm_shmat = smack_shm_shmat, + + .sem_alloc_security = smack_sem_alloc_security, + .sem_free_security = smack_sem_free_security, + .sem_associate = smack_sem_associate, + .sem_semctl = smack_sem_semctl, + .sem_semop = smack_sem_semop, + + .d_instantiate = smack_d_instantiate, + + .getprocattr = smack_getprocattr, + .setprocattr = smack_setprocattr, + + .unix_stream_connect = smack_unix_stream_connect, + .unix_may_send = smack_unix_may_send, + + .socket_post_create = smack_socket_post_create, + .socket_connect = smack_socket_connect, + .socket_sendmsg = smack_socket_sendmsg, + .socket_sock_rcv_skb = smack_socket_sock_rcv_skb, + .socket_getpeersec_stream = smack_socket_getpeersec_stream, + .socket_getpeersec_dgram = smack_socket_getpeersec_dgram, + .sk_alloc_security = smack_sk_alloc_security, + .sk_free_security = smack_sk_free_security, + .sock_graft = smack_sock_graft, + .inet_conn_request = smack_inet_conn_request, + .inet_csk_clone = smack_inet_csk_clone, + + /* key management security hooks */ +#ifdef CONFIG_KEYS + .key_alloc = smack_key_alloc, + .key_free = smack_key_free, + .key_permission = smack_key_permission, +#endif /* CONFIG_KEYS */ + + /* Audit hooks */ +#ifdef CONFIG_AUDIT + .audit_rule_init = smack_audit_rule_init, + .audit_rule_known = smack_audit_rule_known, + .audit_rule_match = smack_audit_rule_match, + .audit_rule_free = smack_audit_rule_free, +#endif /* CONFIG_AUDIT */ + + .secid_to_secctx = smack_secid_to_secctx, + .secctx_to_secid = smack_secctx_to_secid, + .release_secctx = smack_release_secctx, + .inode_notifysecctx = smack_inode_notifysecctx, + .inode_setsecctx = smack_inode_setsecctx, + .inode_getsecctx = smack_inode_getsecctx, +}; + + +static __init void init_smack_know_list(void) +{ + list_add(&smack_known_huh.list, &smack_known_list); + list_add(&smack_known_hat.list, &smack_known_list); + list_add(&smack_known_star.list, &smack_known_list); + list_add(&smack_known_floor.list, &smack_known_list); + list_add(&smack_known_invalid.list, &smack_known_list); + list_add(&smack_known_web.list, &smack_known_list); +} + +/** + * smack_init - initialize the smack system + * + * Returns 0 + */ +static __init int smack_init(void) +{ + struct cred *cred; + struct task_smack *tsp; + + if (!security_module_enable(&smack_ops)) + return 0; + + tsp = new_task_smack(smack_known_floor.smk_known, + smack_known_floor.smk_known, GFP_KERNEL); + if (tsp == NULL) + return -ENOMEM; + + printk(KERN_INFO "Smack: Initializing.\n"); + + /* + * Set the security state for the initial task. + */ + cred = (struct cred *) current->cred; + cred->security = tsp; + + /* initialize the smack_know_list */ + init_smack_know_list(); + /* + * Initialize locks + */ + spin_lock_init(&smack_known_huh.smk_cipsolock); + spin_lock_init(&smack_known_hat.smk_cipsolock); + spin_lock_init(&smack_known_star.smk_cipsolock); + spin_lock_init(&smack_known_floor.smk_cipsolock); + spin_lock_init(&smack_known_invalid.smk_cipsolock); + + /* + * Register with LSM + */ + if (register_security(&smack_ops)) + panic("smack: Unable to register with kernel.\n"); + + return 0; +} + +/* + * Smack requires early initialization in order to label + * all processes and objects when they are created. + */ +security_initcall(smack_init); diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c new file mode 100644 index 00000000..f9346015 --- /dev/null +++ b/security/smack/smackfs.c @@ -0,0 +1,1540 @@ +/* + * Copyright (C) 2007 Casey Schaufler <casey@schaufler-ca.com> + * + * 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. + * + * Authors: + * Casey Schaufler <casey@schaufler-ca.com> + * Ahmed S. Darwish <darwish.07@gmail.com> + * + * Special thanks to the authors of selinuxfs. + * + * Karl MacMillan <kmacmillan@tresys.com> + * James Morris <jmorris@redhat.com> + * + */ + +#include <linux/kernel.h> +#include <linux/vmalloc.h> +#include <linux/security.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <net/net_namespace.h> +#include <net/netlabel.h> +#include <net/cipso_ipv4.h> +#include <linux/seq_file.h> +#include <linux/ctype.h> +#include <linux/audit.h> +#include "smack.h" + +/* + * smackfs pseudo filesystem. + */ + +enum smk_inos { + SMK_ROOT_INO = 2, + SMK_LOAD = 3, /* load policy */ + SMK_CIPSO = 4, /* load label -> CIPSO mapping */ + SMK_DOI = 5, /* CIPSO DOI */ + SMK_DIRECT = 6, /* CIPSO level indicating direct label */ + SMK_AMBIENT = 7, /* internet ambient label */ + SMK_NETLBLADDR = 8, /* single label hosts */ + SMK_ONLYCAP = 9, /* the only "capable" label */ + SMK_LOGGING = 10, /* logging */ + SMK_LOAD_SELF = 11, /* task specific rules */ +}; + +/* + * List locks + */ +static DEFINE_MUTEX(smack_list_lock); +static DEFINE_MUTEX(smack_cipso_lock); +static DEFINE_MUTEX(smack_ambient_lock); +static DEFINE_MUTEX(smk_netlbladdr_lock); + +/* + * This is the "ambient" label for network traffic. + * If it isn't somehow marked, use this. + * It can be reset via smackfs/ambient + */ +char *smack_net_ambient = smack_known_floor.smk_known; + +/* + * This is the level in a CIPSO header that indicates a + * smack label is contained directly in the category set. + * It can be reset via smackfs/direct + */ +int smack_cipso_direct = SMACK_CIPSO_DIRECT_DEFAULT; + +/* + * Unless a process is running with this label even + * having CAP_MAC_OVERRIDE isn't enough to grant + * privilege to violate MAC policy. If no label is + * designated (the NULL case) capabilities apply to + * everyone. It is expected that the hat (^) label + * will be used if any label is used. + */ +char *smack_onlycap; + +/* + * Certain IP addresses may be designated as single label hosts. + * Packets are sent there unlabeled, but only from tasks that + * can write to the specified label. + */ + +LIST_HEAD(smk_netlbladdr_list); +LIST_HEAD(smack_rule_list); + +static int smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT; + +const char *smack_cipso_option = SMACK_CIPSO_OPTION; + + +#define SEQ_READ_FINISHED 1 + +/* + * Values for parsing cipso rules + * SMK_DIGITLEN: Length of a digit field in a rule. + * SMK_CIPSOMIN: Minimum possible cipso rule length. + * SMK_CIPSOMAX: Maximum possible cipso rule length. + */ +#define SMK_DIGITLEN 4 +#define SMK_CIPSOMIN (SMK_LABELLEN + 2 * SMK_DIGITLEN) +#define SMK_CIPSOMAX (SMK_CIPSOMIN + SMACK_CIPSO_MAXCATNUM * SMK_DIGITLEN) + +/* + * Values for parsing MAC rules + * SMK_ACCESS: Maximum possible combination of access permissions + * SMK_ACCESSLEN: Maximum length for a rule access field + * SMK_LOADLEN: Smack rule length + */ +#define SMK_OACCESS "rwxa" +#define SMK_ACCESS "rwxat" +#define SMK_OACCESSLEN (sizeof(SMK_OACCESS) - 1) +#define SMK_ACCESSLEN (sizeof(SMK_ACCESS) - 1) +#define SMK_OLOADLEN (SMK_LABELLEN + SMK_LABELLEN + SMK_OACCESSLEN) +#define SMK_LOADLEN (SMK_LABELLEN + SMK_LABELLEN + SMK_ACCESSLEN) + +/** + * smk_netlabel_audit_set - fill a netlbl_audit struct + * @nap: structure to fill + */ +static void smk_netlabel_audit_set(struct netlbl_audit *nap) +{ + nap->loginuid = audit_get_loginuid(current); + nap->sessionid = audit_get_sessionid(current); + nap->secid = smack_to_secid(smk_of_current()); +} + +/* + * Values for parsing single label host rules + * "1.2.3.4 X" + * "192.168.138.129/32 abcdefghijklmnopqrstuvw" + */ +#define SMK_NETLBLADDRMIN 9 +#define SMK_NETLBLADDRMAX 42 + +/** + * smk_set_access - add a rule to the rule list + * @srp: the new rule to add + * @rule_list: the list of rules + * @rule_lock: the rule list lock + * + * Looks through the current subject/object/access list for + * the subject/object pair and replaces the access that was + * there. If the pair isn't found add it with the specified + * access. + * + * Returns 1 if a rule was found to exist already, 0 if it is new + * Returns 0 if nothing goes wrong or -ENOMEM if it fails + * during the allocation of the new pair to add. + */ +static int smk_set_access(struct smack_rule *srp, struct list_head *rule_list, + struct mutex *rule_lock) +{ + struct smack_rule *sp; + int found = 0; + + mutex_lock(rule_lock); + + list_for_each_entry_rcu(sp, rule_list, list) { + if (sp->smk_subject == srp->smk_subject && + sp->smk_object == srp->smk_object) { + found = 1; + sp->smk_access = srp->smk_access; + break; + } + } + if (found == 0) + list_add_rcu(&srp->list, rule_list); + + mutex_unlock(rule_lock); + + return found; +} + +/** + * smk_write_load_list - write() for any /smack/load + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start - must be 0 + * @rule_list: the list of rules to write to + * @rule_lock: lock for the rule list + * + * Get one smack access rule from above. + * The format is exactly: + * char subject[SMK_LABELLEN] + * char object[SMK_LABELLEN] + * char access[SMK_ACCESSLEN] + * + * writes must be SMK_LABELLEN+SMK_LABELLEN+SMK_ACCESSLEN bytes. + */ +static ssize_t smk_write_load_list(struct file *file, const char __user *buf, + size_t count, loff_t *ppos, + struct list_head *rule_list, + struct mutex *rule_lock) +{ + struct smack_rule *rule; + char *data; + int rc = -EINVAL; + + /* + * No partial writes. + * Enough data must be present. + */ + if (*ppos != 0) + return -EINVAL; + /* + * Minor hack for backward compatibility + */ + if (count < (SMK_OLOADLEN) || count > SMK_LOADLEN) + return -EINVAL; + + data = kzalloc(SMK_LOADLEN, GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + if (copy_from_user(data, buf, count) != 0) { + rc = -EFAULT; + goto out; + } + + /* + * More on the minor hack for backward compatibility + */ + if (count == (SMK_OLOADLEN)) + data[SMK_OLOADLEN] = '-'; + + rule = kzalloc(sizeof(*rule), GFP_KERNEL); + if (rule == NULL) { + rc = -ENOMEM; + goto out; + } + + rule->smk_subject = smk_import(data, 0); + if (rule->smk_subject == NULL) + goto out_free_rule; + + rule->smk_object = smk_import(data + SMK_LABELLEN, 0); + if (rule->smk_object == NULL) + goto out_free_rule; + + rule->smk_access = 0; + + switch (data[SMK_LABELLEN + SMK_LABELLEN]) { + case '-': + break; + case 'r': + case 'R': + rule->smk_access |= MAY_READ; + break; + default: + goto out_free_rule; + } + + switch (data[SMK_LABELLEN + SMK_LABELLEN + 1]) { + case '-': + break; + case 'w': + case 'W': + rule->smk_access |= MAY_WRITE; + break; + default: + goto out_free_rule; + } + + switch (data[SMK_LABELLEN + SMK_LABELLEN + 2]) { + case '-': + break; + case 'x': + case 'X': + rule->smk_access |= MAY_EXEC; + break; + default: + goto out_free_rule; + } + + switch (data[SMK_LABELLEN + SMK_LABELLEN + 3]) { + case '-': + break; + case 'a': + case 'A': + rule->smk_access |= MAY_APPEND; + break; + default: + goto out_free_rule; + } + + switch (data[SMK_LABELLEN + SMK_LABELLEN + 4]) { + case '-': + break; + case 't': + case 'T': + rule->smk_access |= MAY_TRANSMUTE; + break; + default: + goto out_free_rule; + } + + rc = count; + /* + * smk_set_access returns true if there was already a rule + * for the subject/object pair, and false if it was new. + */ + if (!smk_set_access(rule, rule_list, rule_lock)) + goto out; + +out_free_rule: + kfree(rule); +out: + kfree(data); + return rc; +} + + +/* + * Seq_file read operations for /smack/load + */ + +static void *load_seq_start(struct seq_file *s, loff_t *pos) +{ + if (*pos == SEQ_READ_FINISHED) + return NULL; + if (list_empty(&smack_rule_list)) + return NULL; + return smack_rule_list.next; +} + +static void *load_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + struct list_head *list = v; + + if (list_is_last(list, &smack_rule_list)) { + *pos = SEQ_READ_FINISHED; + return NULL; + } + return list->next; +} + +static int load_seq_show(struct seq_file *s, void *v) +{ + struct list_head *list = v; + struct smack_rule *srp = + list_entry(list, struct smack_rule, list); + + seq_printf(s, "%s %s", (char *)srp->smk_subject, + (char *)srp->smk_object); + + seq_putc(s, ' '); + + if (srp->smk_access & MAY_READ) + seq_putc(s, 'r'); + if (srp->smk_access & MAY_WRITE) + seq_putc(s, 'w'); + if (srp->smk_access & MAY_EXEC) + seq_putc(s, 'x'); + if (srp->smk_access & MAY_APPEND) + seq_putc(s, 'a'); + if (srp->smk_access & MAY_TRANSMUTE) + seq_putc(s, 't'); + if (srp->smk_access == 0) + seq_putc(s, '-'); + + seq_putc(s, '\n'); + + return 0; +} + +static void load_seq_stop(struct seq_file *s, void *v) +{ + /* No-op */ +} + +static const struct seq_operations load_seq_ops = { + .start = load_seq_start, + .next = load_seq_next, + .show = load_seq_show, + .stop = load_seq_stop, +}; + +/** + * smk_open_load - open() for /smack/load + * @inode: inode structure representing file + * @file: "load" file pointer + * + * For reading, use load_seq_* seq_file reading operations. + */ +static int smk_open_load(struct inode *inode, struct file *file) +{ + return seq_open(file, &load_seq_ops); +} + +/** + * smk_write_load - write() for /smack/load + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start - must be 0 + * + */ +static ssize_t smk_write_load(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + + /* + * Must have privilege. + * No partial writes. + * Enough data must be present. + */ + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + + return smk_write_load_list(file, buf, count, ppos, &smack_rule_list, + &smack_list_lock); +} + +static const struct file_operations smk_load_ops = { + .open = smk_open_load, + .read = seq_read, + .llseek = seq_lseek, + .write = smk_write_load, + .release = seq_release, +}; + +/** + * smk_cipso_doi - initialize the CIPSO domain + */ +static void smk_cipso_doi(void) +{ + int rc; + struct cipso_v4_doi *doip; + struct netlbl_audit nai; + + smk_netlabel_audit_set(&nai); + + rc = netlbl_cfg_map_del(NULL, PF_INET, NULL, NULL, &nai); + if (rc != 0) + printk(KERN_WARNING "%s:%d remove rc = %d\n", + __func__, __LINE__, rc); + + doip = kmalloc(sizeof(struct cipso_v4_doi), GFP_KERNEL); + if (doip == NULL) + panic("smack: Failed to initialize cipso DOI.\n"); + doip->map.std = NULL; + doip->doi = smk_cipso_doi_value; + doip->type = CIPSO_V4_MAP_PASS; + doip->tags[0] = CIPSO_V4_TAG_RBITMAP; + for (rc = 1; rc < CIPSO_V4_TAG_MAXCNT; rc++) + doip->tags[rc] = CIPSO_V4_TAG_INVALID; + + rc = netlbl_cfg_cipsov4_add(doip, &nai); + if (rc != 0) { + printk(KERN_WARNING "%s:%d cipso add rc = %d\n", + __func__, __LINE__, rc); + kfree(doip); + return; + } + rc = netlbl_cfg_cipsov4_map_add(doip->doi, NULL, NULL, NULL, &nai); + if (rc != 0) { + printk(KERN_WARNING "%s:%d map add rc = %d\n", + __func__, __LINE__, rc); + kfree(doip); + return; + } +} + +/** + * smk_unlbl_ambient - initialize the unlabeled domain + * @oldambient: previous domain string + */ +static void smk_unlbl_ambient(char *oldambient) +{ + int rc; + struct netlbl_audit nai; + + smk_netlabel_audit_set(&nai); + + if (oldambient != NULL) { + rc = netlbl_cfg_map_del(oldambient, PF_INET, NULL, NULL, &nai); + if (rc != 0) + printk(KERN_WARNING "%s:%d remove rc = %d\n", + __func__, __LINE__, rc); + } + + rc = netlbl_cfg_unlbl_map_add(smack_net_ambient, PF_INET, + NULL, NULL, &nai); + if (rc != 0) + printk(KERN_WARNING "%s:%d add rc = %d\n", + __func__, __LINE__, rc); +} + +/* + * Seq_file read operations for /smack/cipso + */ + +static void *cipso_seq_start(struct seq_file *s, loff_t *pos) +{ + if (*pos == SEQ_READ_FINISHED) + return NULL; + if (list_empty(&smack_known_list)) + return NULL; + + return smack_known_list.next; +} + +static void *cipso_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + struct list_head *list = v; + + /* + * labels with no associated cipso value wont be printed + * in cipso_seq_show + */ + if (list_is_last(list, &smack_known_list)) { + *pos = SEQ_READ_FINISHED; + return NULL; + } + + return list->next; +} + +/* + * Print cipso labels in format: + * label level[/cat[,cat]] + */ +static int cipso_seq_show(struct seq_file *s, void *v) +{ + struct list_head *list = v; + struct smack_known *skp = + list_entry(list, struct smack_known, list); + struct smack_cipso *scp = skp->smk_cipso; + char *cbp; + char sep = '/'; + int cat = 1; + int i; + unsigned char m; + + if (scp == NULL) + return 0; + + seq_printf(s, "%s %3d", (char *)&skp->smk_known, scp->smk_level); + + cbp = scp->smk_catset; + for (i = 0; i < SMK_LABELLEN; i++) + for (m = 0x80; m != 0; m >>= 1) { + if (m & cbp[i]) { + seq_printf(s, "%c%d", sep, cat); + sep = ','; + } + cat++; + } + + seq_putc(s, '\n'); + + return 0; +} + +static void cipso_seq_stop(struct seq_file *s, void *v) +{ + /* No-op */ +} + +static const struct seq_operations cipso_seq_ops = { + .start = cipso_seq_start, + .stop = cipso_seq_stop, + .next = cipso_seq_next, + .show = cipso_seq_show, +}; + +/** + * smk_open_cipso - open() for /smack/cipso + * @inode: inode structure representing file + * @file: "cipso" file pointer + * + * Connect our cipso_seq_* operations with /smack/cipso + * file_operations + */ +static int smk_open_cipso(struct inode *inode, struct file *file) +{ + return seq_open(file, &cipso_seq_ops); +} + +/** + * smk_write_cipso - write() for /smack/cipso + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start + * + * Accepts only one cipso rule per write call. + * Returns number of bytes written or error code, as appropriate + */ +static ssize_t smk_write_cipso(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct smack_known *skp; + struct smack_cipso *scp = NULL; + char mapcatset[SMK_LABELLEN]; + int maplevel; + int cat; + int catlen; + ssize_t rc = -EINVAL; + char *data = NULL; + char *rule; + int ret; + int i; + + /* + * Must have privilege. + * No partial writes. + * Enough data must be present. + */ + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + if (*ppos != 0) + return -EINVAL; + if (count < SMK_CIPSOMIN || count > SMK_CIPSOMAX) + return -EINVAL; + + data = kzalloc(count + 1, GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + if (copy_from_user(data, buf, count) != 0) { + rc = -EFAULT; + goto unlockedout; + } + + /* labels cannot begin with a '-' */ + if (data[0] == '-') { + rc = -EINVAL; + goto unlockedout; + } + data[count] = '\0'; + rule = data; + /* + * Only allow one writer at a time. Writes should be + * quite rare and small in any case. + */ + mutex_lock(&smack_cipso_lock); + + skp = smk_import_entry(rule, 0); + if (skp == NULL) + goto out; + + rule += SMK_LABELLEN; + ret = sscanf(rule, "%d", &maplevel); + if (ret != 1 || maplevel > SMACK_CIPSO_MAXLEVEL) + goto out; + + rule += SMK_DIGITLEN; + ret = sscanf(rule, "%d", &catlen); + if (ret != 1 || catlen > SMACK_CIPSO_MAXCATNUM) + goto out; + + if (count != (SMK_CIPSOMIN + catlen * SMK_DIGITLEN)) + goto out; + + memset(mapcatset, 0, sizeof(mapcatset)); + + for (i = 0; i < catlen; i++) { + rule += SMK_DIGITLEN; + ret = sscanf(rule, "%d", &cat); + if (ret != 1 || cat > SMACK_CIPSO_MAXCATVAL) + goto out; + + smack_catset_bit(cat, mapcatset); + } + + if (skp->smk_cipso == NULL) { + scp = kzalloc(sizeof(struct smack_cipso), GFP_KERNEL); + if (scp == NULL) { + rc = -ENOMEM; + goto out; + } + } + + spin_lock_bh(&skp->smk_cipsolock); + + if (scp == NULL) + scp = skp->smk_cipso; + else + skp->smk_cipso = scp; + + scp->smk_level = maplevel; + memcpy(scp->smk_catset, mapcatset, sizeof(mapcatset)); + + spin_unlock_bh(&skp->smk_cipsolock); + + rc = count; +out: + mutex_unlock(&smack_cipso_lock); +unlockedout: + kfree(data); + return rc; +} + +static const struct file_operations smk_cipso_ops = { + .open = smk_open_cipso, + .read = seq_read, + .llseek = seq_lseek, + .write = smk_write_cipso, + .release = seq_release, +}; + +/* + * Seq_file read operations for /smack/netlabel + */ + +static void *netlbladdr_seq_start(struct seq_file *s, loff_t *pos) +{ + if (*pos == SEQ_READ_FINISHED) + return NULL; + if (list_empty(&smk_netlbladdr_list)) + return NULL; + return smk_netlbladdr_list.next; +} + +static void *netlbladdr_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + struct list_head *list = v; + + if (list_is_last(list, &smk_netlbladdr_list)) { + *pos = SEQ_READ_FINISHED; + return NULL; + } + + return list->next; +} +#define BEBITS (sizeof(__be32) * 8) + +/* + * Print host/label pairs + */ +static int netlbladdr_seq_show(struct seq_file *s, void *v) +{ + struct list_head *list = v; + struct smk_netlbladdr *skp = + list_entry(list, struct smk_netlbladdr, list); + unsigned char *hp = (char *) &skp->smk_host.sin_addr.s_addr; + int maskn; + u32 temp_mask = be32_to_cpu(skp->smk_mask.s_addr); + + for (maskn = 0; temp_mask; temp_mask <<= 1, maskn++); + + seq_printf(s, "%u.%u.%u.%u/%d %s\n", + hp[0], hp[1], hp[2], hp[3], maskn, skp->smk_label); + + return 0; +} + +static void netlbladdr_seq_stop(struct seq_file *s, void *v) +{ + /* No-op */ +} + +static const struct seq_operations netlbladdr_seq_ops = { + .start = netlbladdr_seq_start, + .stop = netlbladdr_seq_stop, + .next = netlbladdr_seq_next, + .show = netlbladdr_seq_show, +}; + +/** + * smk_open_netlbladdr - open() for /smack/netlabel + * @inode: inode structure representing file + * @file: "netlabel" file pointer + * + * Connect our netlbladdr_seq_* operations with /smack/netlabel + * file_operations + */ +static int smk_open_netlbladdr(struct inode *inode, struct file *file) +{ + return seq_open(file, &netlbladdr_seq_ops); +} + +/** + * smk_netlbladdr_insert + * @new : netlabel to insert + * + * This helper insert netlabel in the smack_netlbladdrs list + * sorted by netmask length (longest to smallest) + * locked by &smk_netlbladdr_lock in smk_write_netlbladdr + * + */ +static void smk_netlbladdr_insert(struct smk_netlbladdr *new) +{ + struct smk_netlbladdr *m, *m_next; + + if (list_empty(&smk_netlbladdr_list)) { + list_add_rcu(&new->list, &smk_netlbladdr_list); + return; + } + + m = list_entry_rcu(smk_netlbladdr_list.next, + struct smk_netlbladdr, list); + + /* the comparison '>' is a bit hacky, but works */ + if (new->smk_mask.s_addr > m->smk_mask.s_addr) { + list_add_rcu(&new->list, &smk_netlbladdr_list); + return; + } + + list_for_each_entry_rcu(m, &smk_netlbladdr_list, list) { + if (list_is_last(&m->list, &smk_netlbladdr_list)) { + list_add_rcu(&new->list, &m->list); + return; + } + m_next = list_entry_rcu(m->list.next, + struct smk_netlbladdr, list); + if (new->smk_mask.s_addr > m_next->smk_mask.s_addr) { + list_add_rcu(&new->list, &m->list); + return; + } + } +} + + +/** + * smk_write_netlbladdr - write() for /smack/netlabel + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start + * + * Accepts only one netlbladdr per write call. + * Returns number of bytes written or error code, as appropriate + */ +static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct smk_netlbladdr *skp; + struct sockaddr_in newname; + char smack[SMK_LABELLEN]; + char *sp; + char data[SMK_NETLBLADDRMAX + 1]; + char *host = (char *)&newname.sin_addr.s_addr; + int rc; + struct netlbl_audit audit_info; + struct in_addr mask; + unsigned int m; + int found; + u32 mask_bits = (1<<31); + __be32 nsa; + u32 temp_mask; + + /* + * Must have privilege. + * No partial writes. + * Enough data must be present. + * "<addr/mask, as a.b.c.d/e><space><label>" + * "<addr, as a.b.c.d><space><label>" + */ + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + if (*ppos != 0) + return -EINVAL; + if (count < SMK_NETLBLADDRMIN || count > SMK_NETLBLADDRMAX) + return -EINVAL; + if (copy_from_user(data, buf, count) != 0) + return -EFAULT; + + data[count] = '\0'; + + rc = sscanf(data, "%hhd.%hhd.%hhd.%hhd/%d %s", + &host[0], &host[1], &host[2], &host[3], &m, smack); + if (rc != 6) { + rc = sscanf(data, "%hhd.%hhd.%hhd.%hhd %s", + &host[0], &host[1], &host[2], &host[3], smack); + if (rc != 5) + return -EINVAL; + m = BEBITS; + } + if (m > BEBITS) + return -EINVAL; + + /* if smack begins with '-', its an option, don't import it */ + if (smack[0] != '-') { + sp = smk_import(smack, 0); + if (sp == NULL) + return -EINVAL; + } else { + /* check known options */ + if (strcmp(smack, smack_cipso_option) == 0) + sp = (char *)smack_cipso_option; + else + return -EINVAL; + } + + for (temp_mask = 0; m > 0; m--) { + temp_mask |= mask_bits; + mask_bits >>= 1; + } + mask.s_addr = cpu_to_be32(temp_mask); + + newname.sin_addr.s_addr &= mask.s_addr; + /* + * Only allow one writer at a time. Writes should be + * quite rare and small in any case. + */ + mutex_lock(&smk_netlbladdr_lock); + + nsa = newname.sin_addr.s_addr; + /* try to find if the prefix is already in the list */ + found = 0; + list_for_each_entry_rcu(skp, &smk_netlbladdr_list, list) { + if (skp->smk_host.sin_addr.s_addr == nsa && + skp->smk_mask.s_addr == mask.s_addr) { + found = 1; + break; + } + } + smk_netlabel_audit_set(&audit_info); + + if (found == 0) { + skp = kzalloc(sizeof(*skp), GFP_KERNEL); + if (skp == NULL) + rc = -ENOMEM; + else { + rc = 0; + skp->smk_host.sin_addr.s_addr = newname.sin_addr.s_addr; + skp->smk_mask.s_addr = mask.s_addr; + skp->smk_label = sp; + smk_netlbladdr_insert(skp); + } + } else { + /* we delete the unlabeled entry, only if the previous label + * wasn't the special CIPSO option */ + if (skp->smk_label != smack_cipso_option) + rc = netlbl_cfg_unlbl_static_del(&init_net, NULL, + &skp->smk_host.sin_addr, &skp->smk_mask, + PF_INET, &audit_info); + else + rc = 0; + skp->smk_label = sp; + } + + /* + * Now tell netlabel about the single label nature of + * this host so that incoming packets get labeled. + * but only if we didn't get the special CIPSO option + */ + if (rc == 0 && sp != smack_cipso_option) + rc = netlbl_cfg_unlbl_static_add(&init_net, NULL, + &skp->smk_host.sin_addr, &skp->smk_mask, PF_INET, + smack_to_secid(skp->smk_label), &audit_info); + + if (rc == 0) + rc = count; + + mutex_unlock(&smk_netlbladdr_lock); + + return rc; +} + +static const struct file_operations smk_netlbladdr_ops = { + .open = smk_open_netlbladdr, + .read = seq_read, + .llseek = seq_lseek, + .write = smk_write_netlbladdr, + .release = seq_release, +}; + +/** + * smk_read_doi - read() for /smack/doi + * @filp: file pointer, not actually used + * @buf: where to put the result + * @count: maximum to send along + * @ppos: where to start + * + * Returns number of bytes read or error code, as appropriate + */ +static ssize_t smk_read_doi(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char temp[80]; + ssize_t rc; + + if (*ppos != 0) + return 0; + + sprintf(temp, "%d", smk_cipso_doi_value); + rc = simple_read_from_buffer(buf, count, ppos, temp, strlen(temp)); + + return rc; +} + +/** + * smk_write_doi - write() for /smack/doi + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start + * + * Returns number of bytes written or error code, as appropriate + */ +static ssize_t smk_write_doi(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + char temp[80]; + int i; + + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + + if (count >= sizeof(temp) || count == 0) + return -EINVAL; + + if (copy_from_user(temp, buf, count) != 0) + return -EFAULT; + + temp[count] = '\0'; + + if (sscanf(temp, "%d", &i) != 1) + return -EINVAL; + + smk_cipso_doi_value = i; + + smk_cipso_doi(); + + return count; +} + +static const struct file_operations smk_doi_ops = { + .read = smk_read_doi, + .write = smk_write_doi, + .llseek = default_llseek, +}; + +/** + * smk_read_direct - read() for /smack/direct + * @filp: file pointer, not actually used + * @buf: where to put the result + * @count: maximum to send along + * @ppos: where to start + * + * Returns number of bytes read or error code, as appropriate + */ +static ssize_t smk_read_direct(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char temp[80]; + ssize_t rc; + + if (*ppos != 0) + return 0; + + sprintf(temp, "%d", smack_cipso_direct); + rc = simple_read_from_buffer(buf, count, ppos, temp, strlen(temp)); + + return rc; +} + +/** + * smk_write_direct - write() for /smack/direct + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start + * + * Returns number of bytes written or error code, as appropriate + */ +static ssize_t smk_write_direct(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + char temp[80]; + int i; + + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + + if (count >= sizeof(temp) || count == 0) + return -EINVAL; + + if (copy_from_user(temp, buf, count) != 0) + return -EFAULT; + + temp[count] = '\0'; + + if (sscanf(temp, "%d", &i) != 1) + return -EINVAL; + + smack_cipso_direct = i; + + return count; +} + +static const struct file_operations smk_direct_ops = { + .read = smk_read_direct, + .write = smk_write_direct, + .llseek = default_llseek, +}; + +/** + * smk_read_ambient - read() for /smack/ambient + * @filp: file pointer, not actually used + * @buf: where to put the result + * @cn: maximum to send along + * @ppos: where to start + * + * Returns number of bytes read or error code, as appropriate + */ +static ssize_t smk_read_ambient(struct file *filp, char __user *buf, + size_t cn, loff_t *ppos) +{ + ssize_t rc; + int asize; + + if (*ppos != 0) + return 0; + /* + * Being careful to avoid a problem in the case where + * smack_net_ambient gets changed in midstream. + */ + mutex_lock(&smack_ambient_lock); + + asize = strlen(smack_net_ambient) + 1; + + if (cn >= asize) + rc = simple_read_from_buffer(buf, cn, ppos, + smack_net_ambient, asize); + else + rc = -EINVAL; + + mutex_unlock(&smack_ambient_lock); + + return rc; +} + +/** + * smk_write_ambient - write() for /smack/ambient + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start + * + * Returns number of bytes written or error code, as appropriate + */ +static ssize_t smk_write_ambient(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + char in[SMK_LABELLEN]; + char *oldambient; + char *smack; + + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + + if (count >= SMK_LABELLEN) + return -EINVAL; + + if (copy_from_user(in, buf, count) != 0) + return -EFAULT; + + smack = smk_import(in, count); + if (smack == NULL) + return -EINVAL; + + mutex_lock(&smack_ambient_lock); + + oldambient = smack_net_ambient; + smack_net_ambient = smack; + smk_unlbl_ambient(oldambient); + + mutex_unlock(&smack_ambient_lock); + + return count; +} + +static const struct file_operations smk_ambient_ops = { + .read = smk_read_ambient, + .write = smk_write_ambient, + .llseek = default_llseek, +}; + +/** + * smk_read_onlycap - read() for /smack/onlycap + * @filp: file pointer, not actually used + * @buf: where to put the result + * @cn: maximum to send along + * @ppos: where to start + * + * Returns number of bytes read or error code, as appropriate + */ +static ssize_t smk_read_onlycap(struct file *filp, char __user *buf, + size_t cn, loff_t *ppos) +{ + char *smack = ""; + ssize_t rc = -EINVAL; + int asize; + + if (*ppos != 0) + return 0; + + if (smack_onlycap != NULL) + smack = smack_onlycap; + + asize = strlen(smack) + 1; + + if (cn >= asize) + rc = simple_read_from_buffer(buf, cn, ppos, smack, asize); + + return rc; +} + +/** + * smk_write_onlycap - write() for /smack/onlycap + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start + * + * Returns number of bytes written or error code, as appropriate + */ +static ssize_t smk_write_onlycap(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + char in[SMK_LABELLEN]; + char *sp = smk_of_task(current->cred->security); + + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + + /* + * This can be done using smk_access() but is done + * explicitly for clarity. The smk_access() implementation + * would use smk_access(smack_onlycap, MAY_WRITE) + */ + if (smack_onlycap != NULL && smack_onlycap != sp) + return -EPERM; + + if (count >= SMK_LABELLEN) + return -EINVAL; + + if (copy_from_user(in, buf, count) != 0) + return -EFAULT; + + /* + * Should the null string be passed in unset the onlycap value. + * This seems like something to be careful with as usually + * smk_import only expects to return NULL for errors. It + * is usually the case that a nullstring or "\n" would be + * bad to pass to smk_import but in fact this is useful here. + */ + smack_onlycap = smk_import(in, count); + + return count; +} + +static const struct file_operations smk_onlycap_ops = { + .read = smk_read_onlycap, + .write = smk_write_onlycap, + .llseek = default_llseek, +}; + +/** + * smk_read_logging - read() for /smack/logging + * @filp: file pointer, not actually used + * @buf: where to put the result + * @cn: maximum to send along + * @ppos: where to start + * + * Returns number of bytes read or error code, as appropriate + */ +static ssize_t smk_read_logging(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char temp[32]; + ssize_t rc; + + if (*ppos != 0) + return 0; + + sprintf(temp, "%d\n", log_policy); + rc = simple_read_from_buffer(buf, count, ppos, temp, strlen(temp)); + return rc; +} + +/** + * smk_write_logging - write() for /smack/logging + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start + * + * Returns number of bytes written or error code, as appropriate + */ +static ssize_t smk_write_logging(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + char temp[32]; + int i; + + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + + if (count >= sizeof(temp) || count == 0) + return -EINVAL; + + if (copy_from_user(temp, buf, count) != 0) + return -EFAULT; + + temp[count] = '\0'; + + if (sscanf(temp, "%d", &i) != 1) + return -EINVAL; + if (i < 0 || i > 3) + return -EINVAL; + log_policy = i; + return count; +} + + + +static const struct file_operations smk_logging_ops = { + .read = smk_read_logging, + .write = smk_write_logging, + .llseek = default_llseek, +}; + +/* + * Seq_file read operations for /smack/load-self + */ + +static void *load_self_seq_start(struct seq_file *s, loff_t *pos) +{ + struct task_smack *tsp = current_security(); + + if (*pos == SEQ_READ_FINISHED) + return NULL; + if (list_empty(&tsp->smk_rules)) + return NULL; + return tsp->smk_rules.next; +} + +static void *load_self_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + struct task_smack *tsp = current_security(); + struct list_head *list = v; + + if (list_is_last(list, &tsp->smk_rules)) { + *pos = SEQ_READ_FINISHED; + return NULL; + } + return list->next; +} + +static int load_self_seq_show(struct seq_file *s, void *v) +{ + struct list_head *list = v; + struct smack_rule *srp = + list_entry(list, struct smack_rule, list); + + seq_printf(s, "%s %s", (char *)srp->smk_subject, + (char *)srp->smk_object); + + seq_putc(s, ' '); + + if (srp->smk_access & MAY_READ) + seq_putc(s, 'r'); + if (srp->smk_access & MAY_WRITE) + seq_putc(s, 'w'); + if (srp->smk_access & MAY_EXEC) + seq_putc(s, 'x'); + if (srp->smk_access & MAY_APPEND) + seq_putc(s, 'a'); + if (srp->smk_access & MAY_TRANSMUTE) + seq_putc(s, 't'); + if (srp->smk_access == 0) + seq_putc(s, '-'); + + seq_putc(s, '\n'); + + return 0; +} + +static void load_self_seq_stop(struct seq_file *s, void *v) +{ + /* No-op */ +} + +static const struct seq_operations load_self_seq_ops = { + .start = load_self_seq_start, + .next = load_self_seq_next, + .show = load_self_seq_show, + .stop = load_self_seq_stop, +}; + + +/** + * smk_open_load_self - open() for /smack/load-self + * @inode: inode structure representing file + * @file: "load" file pointer + * + * For reading, use load_seq_* seq_file reading operations. + */ +static int smk_open_load_self(struct inode *inode, struct file *file) +{ + return seq_open(file, &load_self_seq_ops); +} + +/** + * smk_write_load_self - write() for /smack/load-self + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start - must be 0 + * + */ +static ssize_t smk_write_load_self(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct task_smack *tsp = current_security(); + + return smk_write_load_list(file, buf, count, ppos, &tsp->smk_rules, + &tsp->smk_rules_lock); +} + +static const struct file_operations smk_load_self_ops = { + .open = smk_open_load_self, + .read = seq_read, + .llseek = seq_lseek, + .write = smk_write_load_self, + .release = seq_release, +}; +/** + * smk_fill_super - fill the /smackfs superblock + * @sb: the empty superblock + * @data: unused + * @silent: unused + * + * Fill in the well known entries for /smack + * + * Returns 0 on success, an error code on failure + */ +static int smk_fill_super(struct super_block *sb, void *data, int silent) +{ + int rc; + struct inode *root_inode; + + static struct tree_descr smack_files[] = { + [SMK_LOAD] = { + "load", &smk_load_ops, S_IRUGO|S_IWUSR}, + [SMK_CIPSO] = { + "cipso", &smk_cipso_ops, S_IRUGO|S_IWUSR}, + [SMK_DOI] = { + "doi", &smk_doi_ops, S_IRUGO|S_IWUSR}, + [SMK_DIRECT] = { + "direct", &smk_direct_ops, S_IRUGO|S_IWUSR}, + [SMK_AMBIENT] = { + "ambient", &smk_ambient_ops, S_IRUGO|S_IWUSR}, + [SMK_NETLBLADDR] = { + "netlabel", &smk_netlbladdr_ops, S_IRUGO|S_IWUSR}, + [SMK_ONLYCAP] = { + "onlycap", &smk_onlycap_ops, S_IRUGO|S_IWUSR}, + [SMK_LOGGING] = { + "logging", &smk_logging_ops, S_IRUGO|S_IWUSR}, + [SMK_LOAD_SELF] = { + "load-self", &smk_load_self_ops, S_IRUGO|S_IWUGO}, + /* last one */ + {""} + }; + + rc = simple_fill_super(sb, SMACK_MAGIC, smack_files); + if (rc != 0) { + printk(KERN_ERR "%s failed %d while creating inodes\n", + __func__, rc); + return rc; + } + + root_inode = sb->s_root->d_inode; + root_inode->i_security = new_inode_smack(smack_known_floor.smk_known); + + return 0; +} + +/** + * smk_mount - get the smackfs superblock + * @fs_type: passed along without comment + * @flags: passed along without comment + * @dev_name: passed along without comment + * @data: passed along without comment + * + * Just passes everything along. + * + * Returns what the lower level code does. + */ +static struct dentry *smk_mount(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) +{ + return mount_single(fs_type, flags, data, smk_fill_super); +} + +static struct file_system_type smk_fs_type = { + .name = "smackfs", + .mount = smk_mount, + .kill_sb = kill_litter_super, +}; + +static struct vfsmount *smackfs_mount; + +/** + * init_smk_fs - get the smackfs superblock + * + * register the smackfs + * + * Do not register smackfs if Smack wasn't enabled + * on boot. We can not put this method normally under the + * smack_init() code path since the security subsystem get + * initialized before the vfs caches. + * + * Returns true if we were not chosen on boot or if + * we were chosen and filesystem registration succeeded. + */ +static int __init init_smk_fs(void) +{ + int err; + + if (!security_module_enable(&smack_ops)) + return 0; + + err = register_filesystem(&smk_fs_type); + if (!err) { + smackfs_mount = kern_mount(&smk_fs_type); + if (IS_ERR(smackfs_mount)) { + printk(KERN_ERR "smackfs: could not mount!\n"); + err = PTR_ERR(smackfs_mount); + smackfs_mount = NULL; + } + } + + smk_cipso_doi(); + smk_unlbl_ambient(NULL); + + return err; +} + +__initcall(init_smk_fs); diff --git a/security/tomoyo/Kconfig b/security/tomoyo/Kconfig new file mode 100644 index 00000000..c8f38579 --- /dev/null +++ b/security/tomoyo/Kconfig @@ -0,0 +1,11 @@ +config SECURITY_TOMOYO + bool "TOMOYO Linux Support" + depends on SECURITY + select SECURITYFS + select SECURITY_PATH + default n + help + This selects TOMOYO Linux, pathname-based access control. + Required userspace tools and further information may be + found at <http://tomoyo.sourceforge.jp/>. + If you are unsure how to answer this question, answer N. diff --git a/security/tomoyo/Makefile b/security/tomoyo/Makefile new file mode 100644 index 00000000..91640e96 --- /dev/null +++ b/security/tomoyo/Makefile @@ -0,0 +1 @@ +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 diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c new file mode 100644 index 00000000..a0d09e56 --- /dev/null +++ b/security/tomoyo/common.c @@ -0,0 +1,2081 @@ +/* + * security/tomoyo/common.c + * + * Common functions for TOMOYO. + * + * Copyright (C) 2005-2010 NTT DATA CORPORATION + */ + +#include <linux/uaccess.h> +#include <linux/slab.h> +#include <linux/security.h> +#include "common.h" + +static struct tomoyo_profile tomoyo_default_profile = { + .learning = &tomoyo_default_profile.preference, + .permissive = &tomoyo_default_profile.preference, + .enforcing = &tomoyo_default_profile.preference, + .preference.enforcing_verbose = true, + .preference.learning_max_entry = 2048, + .preference.learning_verbose = false, + .preference.permissive_verbose = true +}; + +/* Profile version. Currently only 20090903 is defined. */ +static unsigned int tomoyo_profile_version; + +/* Profile table. Memory is allocated as needed. */ +static struct tomoyo_profile *tomoyo_profile_ptr[TOMOYO_MAX_PROFILES]; + +/* String table for functionality that takes 4 modes. */ +static const char *tomoyo_mode[4] = { + "disabled", "learning", "permissive", "enforcing" +}; + +/* String table for /sys/kernel/security/tomoyo/profile */ +static const char *tomoyo_mac_keywords[TOMOYO_MAX_MAC_INDEX + + TOMOYO_MAX_MAC_CATEGORY_INDEX] = { + [TOMOYO_MAC_FILE_EXECUTE] = "file::execute", + [TOMOYO_MAC_FILE_OPEN] = "file::open", + [TOMOYO_MAC_FILE_CREATE] = "file::create", + [TOMOYO_MAC_FILE_UNLINK] = "file::unlink", + [TOMOYO_MAC_FILE_MKDIR] = "file::mkdir", + [TOMOYO_MAC_FILE_RMDIR] = "file::rmdir", + [TOMOYO_MAC_FILE_MKFIFO] = "file::mkfifo", + [TOMOYO_MAC_FILE_MKSOCK] = "file::mksock", + [TOMOYO_MAC_FILE_TRUNCATE] = "file::truncate", + [TOMOYO_MAC_FILE_SYMLINK] = "file::symlink", + [TOMOYO_MAC_FILE_REWRITE] = "file::rewrite", + [TOMOYO_MAC_FILE_MKBLOCK] = "file::mkblock", + [TOMOYO_MAC_FILE_MKCHAR] = "file::mkchar", + [TOMOYO_MAC_FILE_LINK] = "file::link", + [TOMOYO_MAC_FILE_RENAME] = "file::rename", + [TOMOYO_MAC_FILE_CHMOD] = "file::chmod", + [TOMOYO_MAC_FILE_CHOWN] = "file::chown", + [TOMOYO_MAC_FILE_CHGRP] = "file::chgrp", + [TOMOYO_MAC_FILE_IOCTL] = "file::ioctl", + [TOMOYO_MAC_FILE_CHROOT] = "file::chroot", + [TOMOYO_MAC_FILE_MOUNT] = "file::mount", + [TOMOYO_MAC_FILE_UMOUNT] = "file::umount", + [TOMOYO_MAC_FILE_PIVOT_ROOT] = "file::pivot_root", + [TOMOYO_MAX_MAC_INDEX + TOMOYO_MAC_CATEGORY_FILE] = "file", +}; + +/* Permit policy management by non-root user? */ +static bool tomoyo_manage_by_non_root; + +/* Utility functions. */ + +/** + * tomoyo_yesno - Return "yes" or "no". + * + * @value: Bool value. + */ +static const char *tomoyo_yesno(const unsigned int value) +{ + return value ? "yes" : "no"; +} + +static void tomoyo_addprintf(char *buffer, int len, const char *fmt, ...) +{ + va_list args; + const int pos = strlen(buffer); + va_start(args, fmt); + vsnprintf(buffer + pos, len - pos - 1, fmt, args); + va_end(args); +} + +/** + * tomoyo_flush - Flush queued string to userspace's buffer. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns true if all data was flushed, false otherwise. + */ +static bool tomoyo_flush(struct tomoyo_io_buffer *head) +{ + while (head->r.w_pos) { + const char *w = head->r.w[0]; + int len = strlen(w); + if (len) { + if (len > head->read_user_buf_avail) + len = head->read_user_buf_avail; + if (!len) + return false; + if (copy_to_user(head->read_user_buf, w, len)) + return false; + head->read_user_buf_avail -= len; + head->read_user_buf += len; + w += len; + } + head->r.w[0] = w; + if (*w) + return false; + /* Add '\0' for query. */ + if (head->poll) { + if (!head->read_user_buf_avail || + copy_to_user(head->read_user_buf, "", 1)) + return false; + head->read_user_buf_avail--; + head->read_user_buf++; + } + head->r.w_pos--; + for (len = 0; len < head->r.w_pos; len++) + head->r.w[len] = head->r.w[len + 1]; + } + head->r.avail = 0; + return true; +} + +/** + * tomoyo_set_string - Queue string to "struct tomoyo_io_buffer" structure. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @string: String to print. + * + * Note that @string has to be kept valid until @head is kfree()d. + * This means that char[] allocated on stack memory cannot be passed to + * this function. Use tomoyo_io_printf() for char[] allocated on stack memory. + */ +static void tomoyo_set_string(struct tomoyo_io_buffer *head, const char *string) +{ + if (head->r.w_pos < TOMOYO_MAX_IO_READ_QUEUE) { + head->r.w[head->r.w_pos++] = string; + tomoyo_flush(head); + } else + WARN_ON(1); +} + +/** + * tomoyo_io_printf - printf() to "struct tomoyo_io_buffer" structure. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @fmt: The printf()'s format string, followed by parameters. + */ +void tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt, ...) +{ + va_list args; + int len; + int pos = head->r.avail; + int size = head->readbuf_size - pos; + if (size <= 0) + return; + va_start(args, fmt); + len = vsnprintf(head->read_buf + pos, size, fmt, args) + 1; + va_end(args); + if (pos + len >= head->readbuf_size) { + WARN_ON(1); + return; + } + head->r.avail += len; + tomoyo_set_string(head, head->read_buf + pos); +} + +static void tomoyo_set_space(struct tomoyo_io_buffer *head) +{ + tomoyo_set_string(head, " "); +} + +static bool tomoyo_set_lf(struct tomoyo_io_buffer *head) +{ + tomoyo_set_string(head, "\n"); + return !head->r.w_pos; +} + +/** + * tomoyo_print_name_union - Print a tomoyo_name_union. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @ptr: Pointer to "struct tomoyo_name_union". + */ +static void tomoyo_print_name_union(struct tomoyo_io_buffer *head, + const struct tomoyo_name_union *ptr) +{ + tomoyo_set_space(head); + if (ptr->is_group) { + tomoyo_set_string(head, "@"); + tomoyo_set_string(head, ptr->group->group_name->name); + } else { + tomoyo_set_string(head, ptr->filename->name); + } +} + +/** + * tomoyo_print_number_union - Print a tomoyo_number_union. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @ptr: Pointer to "struct tomoyo_number_union". + */ +static void tomoyo_print_number_union(struct tomoyo_io_buffer *head, + const struct tomoyo_number_union *ptr) +{ + tomoyo_set_space(head); + if (ptr->is_group) { + tomoyo_set_string(head, "@"); + tomoyo_set_string(head, ptr->group->group_name->name); + } else { + int i; + unsigned long min = ptr->values[0]; + const unsigned long max = ptr->values[1]; + u8 min_type = ptr->min_type; + const u8 max_type = ptr->max_type; + char buffer[128]; + buffer[0] = '\0'; + for (i = 0; i < 2; i++) { + switch (min_type) { + case TOMOYO_VALUE_TYPE_HEXADECIMAL: + tomoyo_addprintf(buffer, sizeof(buffer), + "0x%lX", min); + break; + case TOMOYO_VALUE_TYPE_OCTAL: + tomoyo_addprintf(buffer, sizeof(buffer), + "0%lo", min); + break; + default: + tomoyo_addprintf(buffer, sizeof(buffer), + "%lu", min); + break; + } + if (min == max && min_type == max_type) + break; + tomoyo_addprintf(buffer, sizeof(buffer), "-"); + min_type = max_type; + min = max; + } + tomoyo_io_printf(head, "%s", buffer); + } +} + +/** + * tomoyo_assign_profile - Create a new profile. + * + * @profile: Profile number to create. + * + * Returns pointer to "struct tomoyo_profile" on success, NULL otherwise. + */ +static struct tomoyo_profile *tomoyo_assign_profile(const unsigned int profile) +{ + struct tomoyo_profile *ptr; + struct tomoyo_profile *entry; + if (profile >= TOMOYO_MAX_PROFILES) + return NULL; + ptr = tomoyo_profile_ptr[profile]; + if (ptr) + return ptr; + entry = kzalloc(sizeof(*entry), GFP_NOFS); + if (mutex_lock_interruptible(&tomoyo_policy_lock)) + goto out; + ptr = tomoyo_profile_ptr[profile]; + if (!ptr && tomoyo_memory_ok(entry)) { + ptr = entry; + ptr->learning = &tomoyo_default_profile.preference; + ptr->permissive = &tomoyo_default_profile.preference; + ptr->enforcing = &tomoyo_default_profile.preference; + ptr->default_config = TOMOYO_CONFIG_DISABLED; + memset(ptr->config, TOMOYO_CONFIG_USE_DEFAULT, + sizeof(ptr->config)); + mb(); /* Avoid out-of-order execution. */ + tomoyo_profile_ptr[profile] = ptr; + entry = NULL; + } + mutex_unlock(&tomoyo_policy_lock); + out: + kfree(entry); + return ptr; +} + +/** + * tomoyo_profile - Find a profile. + * + * @profile: Profile number to find. + * + * Returns pointer to "struct tomoyo_profile". + */ +struct tomoyo_profile *tomoyo_profile(const u8 profile) +{ + struct tomoyo_profile *ptr = tomoyo_profile_ptr[profile]; + if (!tomoyo_policy_loaded) + return &tomoyo_default_profile; + BUG_ON(!ptr); + return ptr; +} + +static s8 tomoyo_find_yesno(const char *string, const char *find) +{ + const char *cp = strstr(string, find); + if (cp) { + cp += strlen(find); + if (!strncmp(cp, "=yes", 4)) + return 1; + else if (!strncmp(cp, "=no", 3)) + return 0; + } + return -1; +} + +static void tomoyo_set_bool(bool *b, const char *string, const char *find) +{ + switch (tomoyo_find_yesno(string, find)) { + case 1: + *b = true; + break; + case 0: + *b = false; + break; + } +} + +static void tomoyo_set_uint(unsigned int *i, const char *string, + const char *find) +{ + const char *cp = strstr(string, find); + if (cp) + sscanf(cp + strlen(find), "=%u", i); +} + +static void tomoyo_set_pref(const char *name, const char *value, + const bool use_default, + struct tomoyo_profile *profile) +{ + struct tomoyo_preference **pref; + bool *verbose; + if (!strcmp(name, "enforcing")) { + if (use_default) { + pref = &profile->enforcing; + goto set_default; + } + profile->enforcing = &profile->preference; + verbose = &profile->preference.enforcing_verbose; + goto set_verbose; + } + if (!strcmp(name, "permissive")) { + if (use_default) { + pref = &profile->permissive; + goto set_default; + } + profile->permissive = &profile->preference; + verbose = &profile->preference.permissive_verbose; + goto set_verbose; + } + if (!strcmp(name, "learning")) { + if (use_default) { + pref = &profile->learning; + goto set_default; + } + profile->learning = &profile->preference; + tomoyo_set_uint(&profile->preference.learning_max_entry, value, + "max_entry"); + verbose = &profile->preference.learning_verbose; + goto set_verbose; + } + return; + set_default: + *pref = &tomoyo_default_profile.preference; + return; + set_verbose: + tomoyo_set_bool(verbose, value, "verbose"); +} + +static int tomoyo_set_mode(char *name, const char *value, + const bool use_default, + struct tomoyo_profile *profile) +{ + u8 i; + u8 config; + if (!strcmp(name, "CONFIG")) { + i = TOMOYO_MAX_MAC_INDEX + TOMOYO_MAX_MAC_CATEGORY_INDEX; + config = profile->default_config; + } else if (tomoyo_str_starts(&name, "CONFIG::")) { + config = 0; + for (i = 0; i < TOMOYO_MAX_MAC_INDEX + + TOMOYO_MAX_MAC_CATEGORY_INDEX; i++) { + if (strcmp(name, tomoyo_mac_keywords[i])) + continue; + config = profile->config[i]; + break; + } + if (i == TOMOYO_MAX_MAC_INDEX + TOMOYO_MAX_MAC_CATEGORY_INDEX) + return -EINVAL; + } else { + return -EINVAL; + } + if (use_default) { + config = TOMOYO_CONFIG_USE_DEFAULT; + } else { + u8 mode; + for (mode = 0; mode < 4; mode++) + if (strstr(value, tomoyo_mode[mode])) + /* + * Update lower 3 bits in order to distinguish + * 'config' from 'TOMOYO_CONFIG_USE_DEAFULT'. + */ + config = (config & ~7) | mode; + } + if (i < TOMOYO_MAX_MAC_INDEX + TOMOYO_MAX_MAC_CATEGORY_INDEX) + profile->config[i] = config; + else if (config != TOMOYO_CONFIG_USE_DEFAULT) + profile->default_config = config; + return 0; +} + +/** + * tomoyo_write_profile - Write profile table. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_write_profile(struct tomoyo_io_buffer *head) +{ + char *data = head->write_buf; + unsigned int i; + bool use_default = false; + char *cp; + struct tomoyo_profile *profile; + if (sscanf(data, "PROFILE_VERSION=%u", &tomoyo_profile_version) == 1) + return 0; + i = simple_strtoul(data, &cp, 10); + if (data == cp) { + profile = &tomoyo_default_profile; + } else { + if (*cp != '-') + return -EINVAL; + data = cp + 1; + profile = tomoyo_assign_profile(i); + if (!profile) + return -EINVAL; + } + cp = strchr(data, '='); + if (!cp) + return -EINVAL; + *cp++ = '\0'; + if (profile != &tomoyo_default_profile) + use_default = strstr(cp, "use_default") != NULL; + if (tomoyo_str_starts(&data, "PREFERENCE::")) { + tomoyo_set_pref(data, cp, use_default, profile); + return 0; + } + if (profile == &tomoyo_default_profile) + return -EINVAL; + if (!strcmp(data, "COMMENT")) { + static DEFINE_SPINLOCK(lock); + const struct tomoyo_path_info *new_comment + = tomoyo_get_name(cp); + const struct tomoyo_path_info *old_comment; + if (!new_comment) + return -ENOMEM; + spin_lock(&lock); + old_comment = profile->comment; + profile->comment = new_comment; + spin_unlock(&lock); + tomoyo_put_name(old_comment); + return 0; + } + return tomoyo_set_mode(data, cp, use_default, profile); +} + +static void tomoyo_print_preference(struct tomoyo_io_buffer *head, + const int idx) +{ + struct tomoyo_preference *pref = &tomoyo_default_profile.preference; + const struct tomoyo_profile *profile = idx >= 0 ? + tomoyo_profile_ptr[idx] : NULL; + char buffer[16] = ""; + if (profile) { + buffer[sizeof(buffer) - 1] = '\0'; + snprintf(buffer, sizeof(buffer) - 1, "%u-", idx); + } + if (profile) { + pref = profile->learning; + if (pref == &tomoyo_default_profile.preference) + goto skip1; + } + tomoyo_io_printf(head, "%sPREFERENCE::%s={ " + "verbose=%s max_entry=%u }\n", + buffer, "learning", + tomoyo_yesno(pref->learning_verbose), + pref->learning_max_entry); + skip1: + if (profile) { + pref = profile->permissive; + if (pref == &tomoyo_default_profile.preference) + goto skip2; + } + tomoyo_io_printf(head, "%sPREFERENCE::%s={ verbose=%s }\n", + buffer, "permissive", + tomoyo_yesno(pref->permissive_verbose)); + skip2: + if (profile) { + pref = profile->enforcing; + if (pref == &tomoyo_default_profile.preference) + return; + } + tomoyo_io_printf(head, "%sPREFERENCE::%s={ verbose=%s }\n", + buffer, "enforcing", + tomoyo_yesno(pref->enforcing_verbose)); +} + +static void tomoyo_print_config(struct tomoyo_io_buffer *head, const u8 config) +{ + tomoyo_io_printf(head, "={ mode=%s }\n", tomoyo_mode[config & 3]); +} + +/** + * tomoyo_read_profile - Read profile table. + * + * @head: Pointer to "struct tomoyo_io_buffer". + */ +static void tomoyo_read_profile(struct tomoyo_io_buffer *head) +{ + u8 index; + const struct tomoyo_profile *profile; + next: + index = head->r.index; + profile = tomoyo_profile_ptr[index]; + switch (head->r.step) { + case 0: + tomoyo_io_printf(head, "PROFILE_VERSION=%s\n", "20090903"); + tomoyo_print_preference(head, -1); + head->r.step++; + break; + case 1: + for ( ; head->r.index < TOMOYO_MAX_PROFILES; + head->r.index++) + if (tomoyo_profile_ptr[head->r.index]) + break; + if (head->r.index == TOMOYO_MAX_PROFILES) + return; + head->r.step++; + break; + case 2: + { + const struct tomoyo_path_info *comment = + profile->comment; + tomoyo_io_printf(head, "%u-COMMENT=", index); + tomoyo_set_string(head, comment ? comment->name : ""); + tomoyo_set_lf(head); + head->r.step++; + } + break; + case 3: + { + tomoyo_io_printf(head, "%u-%s", index, "CONFIG"); + tomoyo_print_config(head, profile->default_config); + head->r.bit = 0; + head->r.step++; + } + break; + case 4: + for ( ; head->r.bit < TOMOYO_MAX_MAC_INDEX + + TOMOYO_MAX_MAC_CATEGORY_INDEX; head->r.bit++) { + const u8 i = head->r.bit; + const u8 config = profile->config[i]; + if (config == TOMOYO_CONFIG_USE_DEFAULT) + continue; + tomoyo_io_printf(head, "%u-%s%s", index, "CONFIG::", + tomoyo_mac_keywords[i]); + tomoyo_print_config(head, config); + head->r.bit++; + break; + } + if (head->r.bit == TOMOYO_MAX_MAC_INDEX + + TOMOYO_MAX_MAC_CATEGORY_INDEX) { + tomoyo_print_preference(head, index); + head->r.index++; + head->r.step = 1; + } + break; + } + if (tomoyo_flush(head)) + goto next; +} + +static bool tomoyo_same_manager(const struct tomoyo_acl_head *a, + const struct tomoyo_acl_head *b) +{ + return container_of(a, struct tomoyo_manager, head)->manager == + container_of(b, struct tomoyo_manager, head)->manager; +} + +/** + * tomoyo_update_manager_entry - Add a manager entry. + * + * @manager: The path to manager or the domainnamme. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_update_manager_entry(const char *manager, + const bool is_delete) +{ + struct tomoyo_manager e = { }; + int error; + + if (tomoyo_domain_def(manager)) { + if (!tomoyo_correct_domain(manager)) + return -EINVAL; + e.is_domain = true; + } else { + if (!tomoyo_correct_path(manager)) + return -EINVAL; + } + e.manager = tomoyo_get_name(manager); + if (!e.manager) + return -ENOMEM; + error = tomoyo_update_policy(&e.head, sizeof(e), is_delete, + &tomoyo_policy_list[TOMOYO_ID_MANAGER], + tomoyo_same_manager); + tomoyo_put_name(e.manager); + return error; +} + +/** + * tomoyo_write_manager - Write manager policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_write_manager(struct tomoyo_io_buffer *head) +{ + char *data = head->write_buf; + bool is_delete = tomoyo_str_starts(&data, TOMOYO_KEYWORD_DELETE); + + if (!strcmp(data, "manage_by_non_root")) { + tomoyo_manage_by_non_root = !is_delete; + return 0; + } + return tomoyo_update_manager_entry(data, is_delete); +} + +/** + * tomoyo_read_manager - Read manager policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Caller holds tomoyo_read_lock(). + */ +static void tomoyo_read_manager(struct tomoyo_io_buffer *head) +{ + if (head->r.eof) + return; + list_for_each_cookie(head->r.acl, + &tomoyo_policy_list[TOMOYO_ID_MANAGER]) { + struct tomoyo_manager *ptr = + list_entry(head->r.acl, typeof(*ptr), head.list); + if (ptr->head.is_deleted) + continue; + if (!tomoyo_flush(head)) + return; + tomoyo_set_string(head, ptr->manager->name); + tomoyo_set_lf(head); + } + head->r.eof = true; +} + +/** + * tomoyo_manager - Check whether the current process is a policy manager. + * + * Returns true if the current process is permitted to modify policy + * via /sys/kernel/security/tomoyo/ interface. + * + * Caller holds tomoyo_read_lock(). + */ +static bool tomoyo_manager(void) +{ + struct tomoyo_manager *ptr; + const char *exe; + const struct task_struct *task = current; + const struct tomoyo_path_info *domainname = tomoyo_domain()->domainname; + bool found = false; + + if (!tomoyo_policy_loaded) + return true; + if (!tomoyo_manage_by_non_root && (task->cred->uid || task->cred->euid)) + return false; + list_for_each_entry_rcu(ptr, &tomoyo_policy_list[TOMOYO_ID_MANAGER], + head.list) { + if (!ptr->head.is_deleted && ptr->is_domain + && !tomoyo_pathcmp(domainname, ptr->manager)) { + found = true; + break; + } + } + if (found) + return true; + exe = tomoyo_get_exe(); + if (!exe) + return false; + list_for_each_entry_rcu(ptr, &tomoyo_policy_list[TOMOYO_ID_MANAGER], + head.list) { + if (!ptr->head.is_deleted && !ptr->is_domain + && !strcmp(exe, ptr->manager->name)) { + found = true; + break; + } + } + if (!found) { /* Reduce error messages. */ + static pid_t last_pid; + const pid_t pid = current->pid; + if (last_pid != pid) { + printk(KERN_WARNING "%s ( %s ) is not permitted to " + "update policies.\n", domainname->name, exe); + last_pid = pid; + } + } + kfree(exe); + return found; +} + +/** + * tomoyo_select_one - Parse select command. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @data: String to parse. + * + * Returns true on success, false otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static bool tomoyo_select_one(struct tomoyo_io_buffer *head, const char *data) +{ + unsigned int pid; + struct tomoyo_domain_info *domain = NULL; + bool global_pid = false; + + if (!strcmp(data, "allow_execute")) { + head->r.print_execute_only = true; + return true; + } + if (sscanf(data, "pid=%u", &pid) == 1 || + (global_pid = true, sscanf(data, "global-pid=%u", &pid) == 1)) { + struct task_struct *p; + rcu_read_lock(); + read_lock(&tasklist_lock); + if (global_pid) + p = find_task_by_pid_ns(pid, &init_pid_ns); + else + p = find_task_by_vpid(pid); + if (p) + domain = tomoyo_real_domain(p); + read_unlock(&tasklist_lock); + rcu_read_unlock(); + } else if (!strncmp(data, "domain=", 7)) { + if (tomoyo_domain_def(data + 7)) + domain = tomoyo_find_domain(data + 7); + } else + return false; + head->write_var1 = domain; + /* Accessing read_buf is safe because head->io_sem is held. */ + if (!head->read_buf) + return true; /* Do nothing if open(O_WRONLY). */ + memset(&head->r, 0, sizeof(head->r)); + head->r.print_this_domain_only = true; + if (domain) + head->r.domain = &domain->list; + else + head->r.eof = 1; + tomoyo_io_printf(head, "# select %s\n", data); + if (domain && domain->is_deleted) + tomoyo_io_printf(head, "# This is a deleted domain.\n"); + return true; +} + +/** + * tomoyo_delete_domain - Delete a domain. + * + * @domainname: The name of domain. + * + * Returns 0. + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_delete_domain(char *domainname) +{ + struct tomoyo_domain_info *domain; + struct tomoyo_path_info name; + + name.name = domainname; + tomoyo_fill_path_info(&name); + if (mutex_lock_interruptible(&tomoyo_policy_lock)) + return 0; + /* Is there an active domain? */ + list_for_each_entry_rcu(domain, &tomoyo_domain_list, list) { + /* Never delete tomoyo_kernel_domain */ + if (domain == &tomoyo_kernel_domain) + continue; + if (domain->is_deleted || + tomoyo_pathcmp(domain->domainname, &name)) + continue; + domain->is_deleted = true; + break; + } + mutex_unlock(&tomoyo_policy_lock); + return 0; +} + +/** + * tomoyo_write_domain2 - Write domain policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_write_domain2(char *data, struct tomoyo_domain_info *domain, + const bool is_delete) +{ + if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_ALLOW_MOUNT)) + return tomoyo_write_mount(data, domain, is_delete); + return tomoyo_write_file(data, domain, is_delete); +} + +/** + * tomoyo_write_domain - Write domain policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_write_domain(struct tomoyo_io_buffer *head) +{ + char *data = head->write_buf; + struct tomoyo_domain_info *domain = head->write_var1; + bool is_delete = false; + bool is_select = false; + unsigned int profile; + + if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_DELETE)) + is_delete = true; + else if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_SELECT)) + is_select = true; + if (is_select && tomoyo_select_one(head, data)) + return 0; + /* Don't allow updating policies by non manager programs. */ + if (!tomoyo_manager()) + return -EPERM; + if (tomoyo_domain_def(data)) { + domain = NULL; + if (is_delete) + tomoyo_delete_domain(data); + else if (is_select) + domain = tomoyo_find_domain(data); + else + domain = tomoyo_assign_domain(data, 0); + head->write_var1 = domain; + return 0; + } + if (!domain) + return -EINVAL; + + if (sscanf(data, TOMOYO_KEYWORD_USE_PROFILE "%u", &profile) == 1 + && profile < TOMOYO_MAX_PROFILES) { + if (tomoyo_profile_ptr[profile] || !tomoyo_policy_loaded) + domain->profile = (u8) profile; + return 0; + } + if (!strcmp(data, TOMOYO_KEYWORD_IGNORE_GLOBAL_ALLOW_READ)) { + domain->ignore_global_allow_read = !is_delete; + return 0; + } + if (!strcmp(data, TOMOYO_KEYWORD_QUOTA_EXCEEDED)) { + domain->quota_warned = !is_delete; + return 0; + } + if (!strcmp(data, TOMOYO_KEYWORD_TRANSITION_FAILED)) { + domain->transition_failed = !is_delete; + return 0; + } + return tomoyo_write_domain2(data, domain, is_delete); +} + +/** + * tomoyo_fns - Find next set bit. + * + * @perm: 8 bits value. + * @bit: First bit to find. + * + * Returns next on-bit on success, 8 otherwise. + */ +static u8 tomoyo_fns(const u8 perm, u8 bit) +{ + for ( ; bit < 8; bit++) + if (perm & (1 << bit)) + break; + return bit; +} + +/** + * tomoyo_print_entry - Print an ACL entry. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @acl: Pointer to an ACL entry. + * + * Returns true on success, false otherwise. + */ +static bool tomoyo_print_entry(struct tomoyo_io_buffer *head, + struct tomoyo_acl_info *acl) +{ + const u8 acl_type = acl->type; + u8 bit; + + if (acl->is_deleted) + return true; + next: + bit = head->r.bit; + if (!tomoyo_flush(head)) + return false; + else if (acl_type == TOMOYO_TYPE_PATH_ACL) { + struct tomoyo_path_acl *ptr = + container_of(acl, typeof(*ptr), head); + const u16 perm = ptr->perm; + for ( ; bit < TOMOYO_MAX_PATH_OPERATION; bit++) { + if (!(perm & (1 << bit))) + continue; + if (head->r.print_execute_only && + bit != TOMOYO_TYPE_EXECUTE) + continue; + /* Print "read/write" instead of "read" and "write". */ + if ((bit == TOMOYO_TYPE_READ || + bit == TOMOYO_TYPE_WRITE) + && (perm & (1 << TOMOYO_TYPE_READ_WRITE))) + continue; + break; + } + if (bit >= TOMOYO_MAX_PATH_OPERATION) + goto done; + tomoyo_io_printf(head, "allow_%s", tomoyo_path_keyword[bit]); + tomoyo_print_name_union(head, &ptr->name); + } else if (head->r.print_execute_only) { + return true; + } else if (acl_type == TOMOYO_TYPE_PATH2_ACL) { + struct tomoyo_path2_acl *ptr = + container_of(acl, typeof(*ptr), head); + bit = tomoyo_fns(ptr->perm, bit); + if (bit >= TOMOYO_MAX_PATH2_OPERATION) + goto done; + tomoyo_io_printf(head, "allow_%s", tomoyo_path2_keyword[bit]); + tomoyo_print_name_union(head, &ptr->name1); + tomoyo_print_name_union(head, &ptr->name2); + } else if (acl_type == TOMOYO_TYPE_PATH_NUMBER_ACL) { + struct tomoyo_path_number_acl *ptr = + container_of(acl, typeof(*ptr), head); + bit = tomoyo_fns(ptr->perm, bit); + if (bit >= TOMOYO_MAX_PATH_NUMBER_OPERATION) + goto done; + tomoyo_io_printf(head, "allow_%s", + tomoyo_path_number_keyword[bit]); + tomoyo_print_name_union(head, &ptr->name); + tomoyo_print_number_union(head, &ptr->number); + } else if (acl_type == TOMOYO_TYPE_MKDEV_ACL) { + struct tomoyo_mkdev_acl *ptr = + container_of(acl, typeof(*ptr), head); + bit = tomoyo_fns(ptr->perm, bit); + if (bit >= TOMOYO_MAX_MKDEV_OPERATION) + goto done; + tomoyo_io_printf(head, "allow_%s", tomoyo_mkdev_keyword[bit]); + tomoyo_print_name_union(head, &ptr->name); + tomoyo_print_number_union(head, &ptr->mode); + tomoyo_print_number_union(head, &ptr->major); + tomoyo_print_number_union(head, &ptr->minor); + } else if (acl_type == TOMOYO_TYPE_MOUNT_ACL) { + struct tomoyo_mount_acl *ptr = + container_of(acl, typeof(*ptr), head); + tomoyo_io_printf(head, "allow_mount"); + tomoyo_print_name_union(head, &ptr->dev_name); + tomoyo_print_name_union(head, &ptr->dir_name); + tomoyo_print_name_union(head, &ptr->fs_type); + tomoyo_print_number_union(head, &ptr->flags); + } + head->r.bit = bit + 1; + tomoyo_io_printf(head, "\n"); + if (acl_type != TOMOYO_TYPE_MOUNT_ACL) + goto next; + done: + head->r.bit = 0; + return true; +} + +/** + * tomoyo_read_domain2 - Read domain policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @domain: Pointer to "struct tomoyo_domain_info". + * + * Caller holds tomoyo_read_lock(). + * + * Returns true on success, false otherwise. + */ +static bool tomoyo_read_domain2(struct tomoyo_io_buffer *head, + struct tomoyo_domain_info *domain) +{ + list_for_each_cookie(head->r.acl, &domain->acl_info_list) { + struct tomoyo_acl_info *ptr = + list_entry(head->r.acl, typeof(*ptr), list); + if (!tomoyo_print_entry(head, ptr)) + return false; + } + head->r.acl = NULL; + return true; +} + +/** + * tomoyo_read_domain - Read domain policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Caller holds tomoyo_read_lock(). + */ +static void tomoyo_read_domain(struct tomoyo_io_buffer *head) +{ + if (head->r.eof) + return; + list_for_each_cookie(head->r.domain, &tomoyo_domain_list) { + struct tomoyo_domain_info *domain = + list_entry(head->r.domain, typeof(*domain), list); + switch (head->r.step) { + case 0: + if (domain->is_deleted && + !head->r.print_this_domain_only) + continue; + /* Print domainname and flags. */ + tomoyo_set_string(head, domain->domainname->name); + tomoyo_set_lf(head); + tomoyo_io_printf(head, + TOMOYO_KEYWORD_USE_PROFILE "%u\n", + domain->profile); + if (domain->quota_warned) + tomoyo_set_string(head, "quota_exceeded\n"); + if (domain->transition_failed) + tomoyo_set_string(head, "transition_failed\n"); + if (domain->ignore_global_allow_read) + tomoyo_set_string(head, + TOMOYO_KEYWORD_IGNORE_GLOBAL_ALLOW_READ + "\n"); + head->r.step++; + tomoyo_set_lf(head); + /* fall through */ + case 1: + if (!tomoyo_read_domain2(head, domain)) + return; + head->r.step++; + if (!tomoyo_set_lf(head)) + return; + /* fall through */ + case 2: + head->r.step = 0; + if (head->r.print_this_domain_only) + goto done; + } + } + done: + head->r.eof = true; +} + +/** + * tomoyo_write_domain_profile - Assign profile for specified domain. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0 on success, -EINVAL otherwise. + * + * This is equivalent to doing + * + * ( echo "select " $domainname; echo "use_profile " $profile ) | + * /usr/sbin/tomoyo-loadpolicy -d + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_write_domain_profile(struct tomoyo_io_buffer *head) +{ + char *data = head->write_buf; + char *cp = strchr(data, ' '); + struct tomoyo_domain_info *domain; + unsigned long profile; + + if (!cp) + return -EINVAL; + *cp = '\0'; + domain = tomoyo_find_domain(cp + 1); + if (strict_strtoul(data, 10, &profile)) + return -EINVAL; + if (domain && profile < TOMOYO_MAX_PROFILES + && (tomoyo_profile_ptr[profile] || !tomoyo_policy_loaded)) + domain->profile = (u8) profile; + return 0; +} + +/** + * tomoyo_read_domain_profile - Read only domainname and profile. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns list of profile number and domainname pairs. + * + * This is equivalent to doing + * + * grep -A 1 '^<kernel>' /sys/kernel/security/tomoyo/domain_policy | + * awk ' { if ( domainname == "" ) { if ( $1 == "<kernel>" ) + * domainname = $0; } else if ( $1 == "use_profile" ) { + * print $2 " " domainname; domainname = ""; } } ; ' + * + * Caller holds tomoyo_read_lock(). + */ +static void tomoyo_read_domain_profile(struct tomoyo_io_buffer *head) +{ + if (head->r.eof) + return; + list_for_each_cookie(head->r.domain, &tomoyo_domain_list) { + struct tomoyo_domain_info *domain = + list_entry(head->r.domain, typeof(*domain), list); + if (domain->is_deleted) + continue; + if (!tomoyo_flush(head)) + return; + tomoyo_io_printf(head, "%u ", domain->profile); + tomoyo_set_string(head, domain->domainname->name); + tomoyo_set_lf(head); + } + head->r.eof = true; +} + +/** + * tomoyo_write_pid: Specify PID to obtain domainname. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0. + */ +static int tomoyo_write_pid(struct tomoyo_io_buffer *head) +{ + head->r.eof = false; + return 0; +} + +/** + * tomoyo_read_pid - Get domainname of the specified PID. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns the domainname which the specified PID is in on success, + * empty string otherwise. + * The PID is specified by tomoyo_write_pid() so that the user can obtain + * using read()/write() interface rather than sysctl() interface. + */ +static void tomoyo_read_pid(struct tomoyo_io_buffer *head) +{ + char *buf = head->write_buf; + bool global_pid = false; + unsigned int pid; + struct task_struct *p; + struct tomoyo_domain_info *domain = NULL; + + /* Accessing write_buf is safe because head->io_sem is held. */ + if (!buf) { + head->r.eof = true; + return; /* Do nothing if open(O_RDONLY). */ + } + if (head->r.w_pos || head->r.eof) + return; + head->r.eof = true; + if (tomoyo_str_starts(&buf, "global-pid ")) + global_pid = true; + pid = (unsigned int) simple_strtoul(buf, NULL, 10); + rcu_read_lock(); + read_lock(&tasklist_lock); + if (global_pid) + p = find_task_by_pid_ns(pid, &init_pid_ns); + else + p = find_task_by_vpid(pid); + if (p) + domain = tomoyo_real_domain(p); + read_unlock(&tasklist_lock); + rcu_read_unlock(); + if (!domain) + return; + tomoyo_io_printf(head, "%u %u ", pid, domain->profile); + tomoyo_set_string(head, domain->domainname->name); +} + +static const char *tomoyo_transition_type[TOMOYO_MAX_TRANSITION_TYPE] = { + [TOMOYO_TRANSITION_CONTROL_NO_INITIALIZE] + = TOMOYO_KEYWORD_NO_INITIALIZE_DOMAIN, + [TOMOYO_TRANSITION_CONTROL_INITIALIZE] + = TOMOYO_KEYWORD_INITIALIZE_DOMAIN, + [TOMOYO_TRANSITION_CONTROL_NO_KEEP] = TOMOYO_KEYWORD_NO_KEEP_DOMAIN, + [TOMOYO_TRANSITION_CONTROL_KEEP] = TOMOYO_KEYWORD_KEEP_DOMAIN +}; + +static const char *tomoyo_group_name[TOMOYO_MAX_GROUP] = { + [TOMOYO_PATH_GROUP] = TOMOYO_KEYWORD_PATH_GROUP, + [TOMOYO_NUMBER_GROUP] = TOMOYO_KEYWORD_NUMBER_GROUP +}; + +/** + * tomoyo_write_exception - Write exception policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_write_exception(struct tomoyo_io_buffer *head) +{ + char *data = head->write_buf; + bool is_delete = tomoyo_str_starts(&data, TOMOYO_KEYWORD_DELETE); + u8 i; + static const struct { + const char *keyword; + int (*write) (char *, const bool); + } tomoyo_callback[4] = { + { TOMOYO_KEYWORD_AGGREGATOR, tomoyo_write_aggregator }, + { TOMOYO_KEYWORD_FILE_PATTERN, tomoyo_write_pattern }, + { TOMOYO_KEYWORD_DENY_REWRITE, tomoyo_write_no_rewrite }, + { TOMOYO_KEYWORD_ALLOW_READ, tomoyo_write_globally_readable }, + }; + + for (i = 0; i < TOMOYO_MAX_TRANSITION_TYPE; i++) + if (tomoyo_str_starts(&data, tomoyo_transition_type[i])) + return tomoyo_write_transition_control(data, is_delete, + i); + for (i = 0; i < 4; i++) + if (tomoyo_str_starts(&data, tomoyo_callback[i].keyword)) + return tomoyo_callback[i].write(data, is_delete); + for (i = 0; i < TOMOYO_MAX_GROUP; i++) + if (tomoyo_str_starts(&data, tomoyo_group_name[i])) + return tomoyo_write_group(data, is_delete, i); + return -EINVAL; +} + +/** + * tomoyo_read_group - Read "struct tomoyo_path_group"/"struct tomoyo_number_group" list. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @idx: Index number. + * + * Returns true on success, false otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static bool tomoyo_read_group(struct tomoyo_io_buffer *head, const int idx) +{ + list_for_each_cookie(head->r.group, &tomoyo_group_list[idx]) { + struct tomoyo_group *group = + list_entry(head->r.group, typeof(*group), list); + list_for_each_cookie(head->r.acl, &group->member_list) { + struct tomoyo_acl_head *ptr = + list_entry(head->r.acl, typeof(*ptr), list); + if (ptr->is_deleted) + continue; + if (!tomoyo_flush(head)) + return false; + tomoyo_set_string(head, tomoyo_group_name[idx]); + tomoyo_set_string(head, group->group_name->name); + if (idx == TOMOYO_PATH_GROUP) { + tomoyo_set_space(head); + tomoyo_set_string(head, container_of + (ptr, struct tomoyo_path_group, + head)->member_name->name); + } else if (idx == TOMOYO_NUMBER_GROUP) { + tomoyo_print_number_union(head, &container_of + (ptr, + struct tomoyo_number_group, + head)->number); + } + tomoyo_set_lf(head); + } + head->r.acl = NULL; + } + head->r.group = NULL; + return true; +} + +/** + * tomoyo_read_policy - Read "struct tomoyo_..._entry" list. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @idx: Index number. + * + * Returns true on success, false otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static bool tomoyo_read_policy(struct tomoyo_io_buffer *head, const int idx) +{ + list_for_each_cookie(head->r.acl, &tomoyo_policy_list[idx]) { + struct tomoyo_acl_head *acl = + container_of(head->r.acl, typeof(*acl), list); + if (acl->is_deleted) + continue; + if (!tomoyo_flush(head)) + return false; + switch (idx) { + case TOMOYO_ID_TRANSITION_CONTROL: + { + struct tomoyo_transition_control *ptr = + container_of(acl, typeof(*ptr), head); + tomoyo_set_string(head, + tomoyo_transition_type + [ptr->type]); + if (ptr->program) + tomoyo_set_string(head, + ptr->program->name); + if (ptr->program && ptr->domainname) + tomoyo_set_string(head, " from "); + if (ptr->domainname) + tomoyo_set_string(head, + ptr->domainname-> + name); + } + break; + case TOMOYO_ID_GLOBALLY_READABLE: + { + struct tomoyo_readable_file *ptr = + container_of(acl, typeof(*ptr), head); + tomoyo_set_string(head, + TOMOYO_KEYWORD_ALLOW_READ); + tomoyo_set_string(head, ptr->filename->name); + } + break; + case TOMOYO_ID_AGGREGATOR: + { + struct tomoyo_aggregator *ptr = + container_of(acl, typeof(*ptr), head); + tomoyo_set_string(head, + TOMOYO_KEYWORD_AGGREGATOR); + tomoyo_set_string(head, + ptr->original_name->name); + tomoyo_set_space(head); + tomoyo_set_string(head, + ptr->aggregated_name->name); + } + break; + case TOMOYO_ID_PATTERN: + { + struct tomoyo_no_pattern *ptr = + container_of(acl, typeof(*ptr), head); + tomoyo_set_string(head, + TOMOYO_KEYWORD_FILE_PATTERN); + tomoyo_set_string(head, ptr->pattern->name); + } + break; + case TOMOYO_ID_NO_REWRITE: + { + struct tomoyo_no_rewrite *ptr = + container_of(acl, typeof(*ptr), head); + tomoyo_set_string(head, + TOMOYO_KEYWORD_DENY_REWRITE); + tomoyo_set_string(head, ptr->pattern->name); + } + break; + default: + continue; + } + tomoyo_set_lf(head); + } + head->r.acl = NULL; + return true; +} + +/** + * tomoyo_read_exception - Read exception policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Caller holds tomoyo_read_lock(). + */ +static void tomoyo_read_exception(struct tomoyo_io_buffer *head) +{ + if (head->r.eof) + return; + while (head->r.step < TOMOYO_MAX_POLICY && + tomoyo_read_policy(head, head->r.step)) + head->r.step++; + if (head->r.step < TOMOYO_MAX_POLICY) + return; + while (head->r.step < TOMOYO_MAX_POLICY + TOMOYO_MAX_GROUP && + tomoyo_read_group(head, head->r.step - TOMOYO_MAX_POLICY)) + head->r.step++; + if (head->r.step < TOMOYO_MAX_POLICY + TOMOYO_MAX_GROUP) + return; + head->r.eof = true; +} + +/** + * tomoyo_print_header - Get header line of audit log. + * + * @r: Pointer to "struct tomoyo_request_info". + * + * Returns string representation. + * + * This function uses kmalloc(), so caller must kfree() if this function + * didn't return NULL. + */ +static char *tomoyo_print_header(struct tomoyo_request_info *r) +{ + struct timeval tv; + const pid_t gpid = task_pid_nr(current); + static const int tomoyo_buffer_len = 4096; + char *buffer = kmalloc(tomoyo_buffer_len, GFP_NOFS); + pid_t ppid; + if (!buffer) + return NULL; + do_gettimeofday(&tv); + rcu_read_lock(); + ppid = task_tgid_vnr(current->real_parent); + rcu_read_unlock(); + snprintf(buffer, tomoyo_buffer_len - 1, + "#timestamp=%lu profile=%u mode=%s (global-pid=%u)" + " task={ pid=%u ppid=%u uid=%u gid=%u euid=%u" + " egid=%u suid=%u sgid=%u fsuid=%u fsgid=%u }", + tv.tv_sec, r->profile, tomoyo_mode[r->mode], gpid, + task_tgid_vnr(current), ppid, + current_uid(), current_gid(), current_euid(), + current_egid(), current_suid(), current_sgid(), + current_fsuid(), current_fsgid()); + return buffer; +} + +/** + * tomoyo_init_audit_log - Allocate buffer for audit logs. + * + * @len: Required size. + * @r: Pointer to "struct tomoyo_request_info". + * + * Returns pointer to allocated memory. + * + * The @len is updated to add the header lines' size on success. + * + * This function uses kzalloc(), so caller must kfree() if this function + * didn't return NULL. + */ +static char *tomoyo_init_audit_log(int *len, struct tomoyo_request_info *r) +{ + char *buf = NULL; + const char *header; + const char *domainname; + if (!r->domain) + r->domain = tomoyo_domain(); + domainname = r->domain->domainname->name; + header = tomoyo_print_header(r); + if (!header) + return NULL; + *len += strlen(domainname) + strlen(header) + 10; + buf = kzalloc(*len, GFP_NOFS); + if (buf) + snprintf(buf, (*len) - 1, "%s\n%s\n", header, domainname); + kfree(header); + return buf; +} + +/* Wait queue for tomoyo_query_list. */ +static DECLARE_WAIT_QUEUE_HEAD(tomoyo_query_wait); + +/* Lock for manipulating tomoyo_query_list. */ +static DEFINE_SPINLOCK(tomoyo_query_list_lock); + +/* Structure for query. */ +struct tomoyo_query { + struct list_head list; + char *query; + int query_len; + unsigned int serial; + int timer; + int answer; +}; + +/* The list for "struct tomoyo_query". */ +static LIST_HEAD(tomoyo_query_list); + +/* + * Number of "struct file" referring /sys/kernel/security/tomoyo/query + * interface. + */ +static atomic_t tomoyo_query_observers = ATOMIC_INIT(0); + +/** + * tomoyo_supervisor - Ask for the supervisor's decision. + * + * @r: Pointer to "struct tomoyo_request_info". + * @fmt: The printf()'s format string, followed by parameters. + * + * Returns 0 if the supervisor decided to permit the access request which + * violated the policy in enforcing mode, TOMOYO_RETRY_REQUEST if the + * supervisor decided to retry the access request which violated the policy in + * enforcing mode, 0 if it is not in enforcing mode, -EPERM otherwise. + */ +int tomoyo_supervisor(struct tomoyo_request_info *r, const char *fmt, ...) +{ + va_list args; + int error = -EPERM; + int pos; + int len; + static unsigned int tomoyo_serial; + struct tomoyo_query *entry = NULL; + bool quota_exceeded = false; + char *header; + switch (r->mode) { + char *buffer; + case TOMOYO_CONFIG_LEARNING: + if (!tomoyo_domain_quota_is_ok(r)) + return 0; + va_start(args, fmt); + len = vsnprintf((char *) &pos, sizeof(pos) - 1, fmt, args) + 4; + va_end(args); + buffer = kmalloc(len, GFP_NOFS); + if (!buffer) + return 0; + va_start(args, fmt); + vsnprintf(buffer, len - 1, fmt, args); + va_end(args); + tomoyo_normalize_line(buffer); + tomoyo_write_domain2(buffer, r->domain, false); + kfree(buffer); + /* fall through */ + case TOMOYO_CONFIG_PERMISSIVE: + return 0; + } + if (!r->domain) + r->domain = tomoyo_domain(); + if (!atomic_read(&tomoyo_query_observers)) + return -EPERM; + va_start(args, fmt); + len = vsnprintf((char *) &pos, sizeof(pos) - 1, fmt, args) + 32; + va_end(args); + header = tomoyo_init_audit_log(&len, r); + if (!header) + goto out; + entry = kzalloc(sizeof(*entry), GFP_NOFS); + if (!entry) + goto out; + entry->query = kzalloc(len, GFP_NOFS); + if (!entry->query) + goto out; + len = ksize(entry->query); + spin_lock(&tomoyo_query_list_lock); + if (tomoyo_quota_for_query && tomoyo_query_memory_size + len + + sizeof(*entry) >= tomoyo_quota_for_query) { + quota_exceeded = true; + } else { + tomoyo_query_memory_size += len + sizeof(*entry); + entry->serial = tomoyo_serial++; + } + spin_unlock(&tomoyo_query_list_lock); + if (quota_exceeded) + goto out; + pos = snprintf(entry->query, len - 1, "Q%u-%hu\n%s", + entry->serial, r->retry, header); + kfree(header); + header = NULL; + va_start(args, fmt); + vsnprintf(entry->query + pos, len - 1 - pos, fmt, args); + entry->query_len = strlen(entry->query) + 1; + va_end(args); + spin_lock(&tomoyo_query_list_lock); + list_add_tail(&entry->list, &tomoyo_query_list); + spin_unlock(&tomoyo_query_list_lock); + /* Give 10 seconds for supervisor's opinion. */ + for (entry->timer = 0; + atomic_read(&tomoyo_query_observers) && entry->timer < 100; + entry->timer++) { + wake_up(&tomoyo_query_wait); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ / 10); + if (entry->answer) + break; + } + spin_lock(&tomoyo_query_list_lock); + list_del(&entry->list); + tomoyo_query_memory_size -= len + sizeof(*entry); + spin_unlock(&tomoyo_query_list_lock); + switch (entry->answer) { + case 3: /* Asked to retry by administrator. */ + error = TOMOYO_RETRY_REQUEST; + r->retry++; + break; + case 1: + /* Granted by administrator. */ + error = 0; + break; + case 0: + /* Timed out. */ + break; + default: + /* Rejected by administrator. */ + break; + } + out: + if (entry) + kfree(entry->query); + kfree(entry); + kfree(header); + return error; +} + +/** + * tomoyo_poll_query - poll() for /sys/kernel/security/tomoyo/query. + * + * @file: Pointer to "struct file". + * @wait: Pointer to "poll_table". + * + * Returns POLLIN | POLLRDNORM when ready to read, 0 otherwise. + * + * Waits for access requests which violated policy in enforcing mode. + */ +static 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); + } + return 0; +} + +/** + * tomoyo_read_query - Read access requests which violated policy in enforcing mode. + * + * @head: Pointer to "struct tomoyo_io_buffer". + */ +static void tomoyo_read_query(struct tomoyo_io_buffer *head) +{ + struct list_head *tmp; + int pos = 0; + int len = 0; + char *buf; + if (head->r.w_pos) + return; + if (head->read_buf) { + kfree(head->read_buf); + head->read_buf = NULL; + } + 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; + break; + } + spin_unlock(&tomoyo_query_list_lock); + if (!len) { + head->r.query_index = 0; + return; + } + buf = kzalloc(len, GFP_NOFS); + if (!buf) + return; + pos = 0; + 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; + /* + * Some query can be skipped because tomoyo_query_list + * can change, but I don't care. + */ + if (len == ptr->query_len) + memmove(buf, ptr->query, len); + break; + } + spin_unlock(&tomoyo_query_list_lock); + if (buf[0]) { + head->read_buf = buf; + head->r.w[head->r.w_pos++] = buf; + head->r.query_index++; + } else { + kfree(buf); + } +} + +/** + * tomoyo_write_answer - Write the supervisor's decision. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0 on success, -EINVAL otherwise. + */ +static int tomoyo_write_answer(struct tomoyo_io_buffer *head) +{ + char *data = head->write_buf; + struct list_head *tmp; + unsigned int serial; + unsigned int answer; + spin_lock(&tomoyo_query_list_lock); + list_for_each(tmp, &tomoyo_query_list) { + struct tomoyo_query *ptr = list_entry(tmp, typeof(*ptr), list); + ptr->timer = 0; + } + spin_unlock(&tomoyo_query_list_lock); + if (sscanf(data, "A%u=%u", &serial, &answer) != 2) + return -EINVAL; + 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->serial != serial) + continue; + if (!ptr->answer) + ptr->answer = answer; + break; + } + spin_unlock(&tomoyo_query_list_lock); + return 0; +} + +/** + * tomoyo_read_version: Get version. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns version information. + */ +static void tomoyo_read_version(struct tomoyo_io_buffer *head) +{ + if (!head->r.eof) { + tomoyo_io_printf(head, "2.3.0"); + head->r.eof = true; + } +} + +/** + * tomoyo_read_self_domain - Get the current process's domainname. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns the current process's domainname. + */ +static void tomoyo_read_self_domain(struct tomoyo_io_buffer *head) +{ + if (!head->r.eof) { + /* + * tomoyo_domain()->domainname != NULL + * because every process belongs to a domain and + * the domain's name cannot be NULL. + */ + tomoyo_io_printf(head, "%s", tomoyo_domain()->domainname->name); + head->r.eof = true; + } +} + +/** + * tomoyo_open_control - open() for /sys/kernel/security/tomoyo/ interface. + * + * @type: Type of interface. + * @file: Pointer to "struct file". + * + * Associates policy handler and returns 0 on success, -ENOMEM otherwise. + * + * Caller acquires tomoyo_read_lock(). + */ +int tomoyo_open_control(const u8 type, struct file *file) +{ + struct tomoyo_io_buffer *head = kzalloc(sizeof(*head), GFP_NOFS); + + if (!head) + return -ENOMEM; + mutex_init(&head->io_sem); + head->type = type; + switch (type) { + case TOMOYO_DOMAINPOLICY: + /* /sys/kernel/security/tomoyo/domain_policy */ + head->write = tomoyo_write_domain; + head->read = tomoyo_read_domain; + break; + case TOMOYO_EXCEPTIONPOLICY: + /* /sys/kernel/security/tomoyo/exception_policy */ + head->write = tomoyo_write_exception; + head->read = tomoyo_read_exception; + break; + case TOMOYO_SELFDOMAIN: + /* /sys/kernel/security/tomoyo/self_domain */ + head->read = tomoyo_read_self_domain; + break; + case TOMOYO_DOMAIN_STATUS: + /* /sys/kernel/security/tomoyo/.domain_status */ + head->write = tomoyo_write_domain_profile; + head->read = tomoyo_read_domain_profile; + break; + case TOMOYO_PROCESS_STATUS: + /* /sys/kernel/security/tomoyo/.process_status */ + head->write = tomoyo_write_pid; + head->read = tomoyo_read_pid; + break; + case TOMOYO_VERSION: + /* /sys/kernel/security/tomoyo/version */ + head->read = tomoyo_read_version; + head->readbuf_size = 128; + break; + case TOMOYO_MEMINFO: + /* /sys/kernel/security/tomoyo/meminfo */ + head->write = tomoyo_write_memory_quota; + head->read = tomoyo_read_memory_counter; + head->readbuf_size = 512; + break; + case TOMOYO_PROFILE: + /* /sys/kernel/security/tomoyo/profile */ + head->write = tomoyo_write_profile; + head->read = tomoyo_read_profile; + break; + case TOMOYO_QUERY: /* /sys/kernel/security/tomoyo/query */ + head->poll = tomoyo_poll_query; + head->write = tomoyo_write_answer; + head->read = tomoyo_read_query; + break; + case TOMOYO_MANAGER: + /* /sys/kernel/security/tomoyo/manager */ + head->write = tomoyo_write_manager; + head->read = tomoyo_read_manager; + break; + } + if (!(file->f_mode & FMODE_READ)) { + /* + * No need to allocate read_buf since it is not opened + * for reading. + */ + head->read = NULL; + head->poll = NULL; + } else if (!head->poll) { + /* Don't allocate read_buf for poll() access. */ + if (!head->readbuf_size) + head->readbuf_size = 4096 * 2; + head->read_buf = kzalloc(head->readbuf_size, GFP_NOFS); + if (!head->read_buf) { + kfree(head); + return -ENOMEM; + } + } + if (!(file->f_mode & FMODE_WRITE)) { + /* + * No need to allocate write_buf since it is not opened + * for writing. + */ + head->write = NULL; + } else if (head->write) { + head->writebuf_size = 4096 * 2; + head->write_buf = kzalloc(head->writebuf_size, GFP_NOFS); + if (!head->write_buf) { + kfree(head->read_buf); + kfree(head); + return -ENOMEM; + } + } + if (type != TOMOYO_QUERY) + head->reader_idx = tomoyo_read_lock(); + file->private_data = head; + /* + * Call the handler now if the file is + * /sys/kernel/security/tomoyo/self_domain + * so that the user can use + * cat < /sys/kernel/security/tomoyo/self_domain" + * to know the current process's domainname. + */ + if (type == TOMOYO_SELFDOMAIN) + tomoyo_read_control(file, NULL, 0); + /* + * If the file is /sys/kernel/security/tomoyo/query , increment the + * observer counter. + * The obserber counter is used by tomoyo_supervisor() to see if + * there is some process monitoring /sys/kernel/security/tomoyo/query. + */ + else if (type == TOMOYO_QUERY) + atomic_inc(&tomoyo_query_observers); + return 0; +} + +/** + * tomoyo_poll_control - poll() for /sys/kernel/security/tomoyo/ interface. + * + * @file: Pointer to "struct file". + * @wait: Pointer to "poll_table". + * + * Waits for read readiness. + * /sys/kernel/security/tomoyo/query is handled by /usr/sbin/tomoyo-queryd . + */ +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); +} + +/** + * tomoyo_read_control - read() for /sys/kernel/security/tomoyo/ interface. + * + * @file: Pointer to "struct file". + * @buffer: Poiner to buffer to write to. + * @buffer_len: Size of @buffer. + * + * Returns bytes read on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_read_control(struct file *file, char __user *buffer, + const int buffer_len) +{ + int len; + struct tomoyo_io_buffer *head = file->private_data; + + if (!head->read) + return -ENOSYS; + if (mutex_lock_interruptible(&head->io_sem)) + return -EINTR; + head->read_user_buf = buffer; + head->read_user_buf_avail = buffer_len; + if (tomoyo_flush(head)) + /* Call the policy handler. */ + head->read(head); + tomoyo_flush(head); + len = head->read_user_buf - buffer; + mutex_unlock(&head->io_sem); + return len; +} + +/** + * tomoyo_write_control - write() for /sys/kernel/security/tomoyo/ interface. + * + * @file: Pointer to "struct file". + * @buffer: Pointer to buffer to read from. + * @buffer_len: Size of @buffer. + * + * Returns @buffer_len on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_write_control(struct file *file, const char __user *buffer, + const int buffer_len) +{ + struct tomoyo_io_buffer *head = file->private_data; + int error = buffer_len; + int avail_len = buffer_len; + char *cp0 = head->write_buf; + + if (!head->write) + return -ENOSYS; + if (!access_ok(VERIFY_READ, buffer, buffer_len)) + return -EFAULT; + /* Don't allow updating policies by non manager programs. */ + if (head->write != tomoyo_write_pid && + head->write != tomoyo_write_domain && !tomoyo_manager()) + return -EPERM; + if (mutex_lock_interruptible(&head->io_sem)) + return -EINTR; + /* Read a line and dispatch it to the policy handler. */ + while (avail_len > 0) { + char c; + if (head->write_avail >= head->writebuf_size - 1) { + error = -ENOMEM; + break; + } else if (get_user(c, buffer)) { + error = -EFAULT; + break; + } + buffer++; + avail_len--; + cp0[head->write_avail++] = c; + if (c != '\n') + continue; + cp0[head->write_avail - 1] = '\0'; + head->write_avail = 0; + tomoyo_normalize_line(cp0); + head->write(head); + } + mutex_unlock(&head->io_sem); + return error; +} + +/** + * tomoyo_close_control - close() for /sys/kernel/security/tomoyo/ interface. + * + * @file: Pointer to "struct file". + * + * Releases memory and returns 0. + * + * Caller looses tomoyo_read_lock(). + */ +int tomoyo_close_control(struct file *file) +{ + struct tomoyo_io_buffer *head = file->private_data; + const bool is_write = !!head->write_buf; + + /* + * If the file is /sys/kernel/security/tomoyo/query , decrement the + * observer counter. + */ + if (head->type == TOMOYO_QUERY) + atomic_dec(&tomoyo_query_observers); + else + tomoyo_read_unlock(head->reader_idx); + /* Release memory used for policy I/O. */ + kfree(head->read_buf); + head->read_buf = NULL; + kfree(head->write_buf); + head->write_buf = NULL; + kfree(head); + head = NULL; + file->private_data = NULL; + if (is_write) + tomoyo_run_gc(); + return 0; +} + +/** + * tomoyo_check_profile - Check all profiles currently assigned to domains are defined. + */ +void tomoyo_check_profile(void) +{ + struct tomoyo_domain_info *domain; + const int idx = tomoyo_read_lock(); + tomoyo_policy_loaded = true; + /* Check all profiles currently assigned to domains are defined. */ + list_for_each_entry_rcu(domain, &tomoyo_domain_list, list) { + const u8 profile = domain->profile; + if (tomoyo_profile_ptr[profile]) + continue; + printk(KERN_ERR "You need to define profile %u before using it.\n", + profile); + printk(KERN_ERR "Please see http://tomoyo.sourceforge.jp/2.3/ " + "for more information.\n"); + panic("Profile %u (used by '%s') not defined.\n", + profile, domain->domainname->name); + } + tomoyo_read_unlock(idx); + if (tomoyo_profile_version != 20090903) { + printk(KERN_ERR "You need to install userland programs for " + "TOMOYO 2.3 and initialize policy configuration.\n"); + printk(KERN_ERR "Please see http://tomoyo.sourceforge.jp/2.3/ " + "for more information.\n"); + panic("Profile version %u is not supported.\n", + tomoyo_profile_version); + } + printk(KERN_INFO "TOMOYO: 2.3.0\n"); + printk(KERN_INFO "Mandatory Access Control activated.\n"); +} diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h new file mode 100644 index 00000000..7c66bd89 --- /dev/null +++ b/security/tomoyo/common.h @@ -0,0 +1,1015 @@ +/* + * security/tomoyo/common.h + * + * Header file for TOMOYO. + * + * Copyright (C) 2005-2010 NTT DATA CORPORATION + */ + +#ifndef _SECURITY_TOMOYO_COMMON_H +#define _SECURITY_TOMOYO_COMMON_H + +#include <linux/ctype.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/file.h> +#include <linux/kmod.h> +#include <linux/fs.h> +#include <linux/sched.h> +#include <linux/namei.h> +#include <linux/mount.h> +#include <linux/list.h> +#include <linux/cred.h> +#include <linux/poll.h> +struct linux_binprm; + +/********** Constants definitions. **********/ + +/* + * TOMOYO uses this hash only when appending a string into the string + * table. Frequency of appending strings is very low. So we don't need + * large (e.g. 64k) hash size. 256 will be sufficient. + */ +#define TOMOYO_HASH_BITS 8 +#define TOMOYO_MAX_HASH (1u<<TOMOYO_HASH_BITS) + +#define TOMOYO_EXEC_TMPSIZE 4096 + +/* Profile number is an integer between 0 and 255. */ +#define TOMOYO_MAX_PROFILES 256 + +enum tomoyo_mode_index { + TOMOYO_CONFIG_DISABLED, + TOMOYO_CONFIG_LEARNING, + TOMOYO_CONFIG_PERMISSIVE, + TOMOYO_CONFIG_ENFORCING, + TOMOYO_CONFIG_USE_DEFAULT = 255 +}; + +enum tomoyo_policy_id { + TOMOYO_ID_GROUP, + TOMOYO_ID_PATH_GROUP, + TOMOYO_ID_NUMBER_GROUP, + TOMOYO_ID_TRANSITION_CONTROL, + TOMOYO_ID_AGGREGATOR, + TOMOYO_ID_GLOBALLY_READABLE, + TOMOYO_ID_PATTERN, + TOMOYO_ID_NO_REWRITE, + TOMOYO_ID_MANAGER, + TOMOYO_ID_NAME, + TOMOYO_ID_ACL, + TOMOYO_ID_DOMAIN, + TOMOYO_MAX_POLICY +}; + +enum tomoyo_group_id { + TOMOYO_PATH_GROUP, + TOMOYO_NUMBER_GROUP, + TOMOYO_MAX_GROUP +}; + +/* Keywords for ACLs. */ +#define TOMOYO_KEYWORD_AGGREGATOR "aggregator " +#define TOMOYO_KEYWORD_ALLOW_MOUNT "allow_mount " +#define TOMOYO_KEYWORD_ALLOW_READ "allow_read " +#define TOMOYO_KEYWORD_DELETE "delete " +#define TOMOYO_KEYWORD_DENY_REWRITE "deny_rewrite " +#define TOMOYO_KEYWORD_FILE_PATTERN "file_pattern " +#define TOMOYO_KEYWORD_INITIALIZE_DOMAIN "initialize_domain " +#define TOMOYO_KEYWORD_KEEP_DOMAIN "keep_domain " +#define TOMOYO_KEYWORD_NO_INITIALIZE_DOMAIN "no_initialize_domain " +#define TOMOYO_KEYWORD_NO_KEEP_DOMAIN "no_keep_domain " +#define TOMOYO_KEYWORD_PATH_GROUP "path_group " +#define TOMOYO_KEYWORD_NUMBER_GROUP "number_group " +#define TOMOYO_KEYWORD_SELECT "select " +#define TOMOYO_KEYWORD_USE_PROFILE "use_profile " +#define TOMOYO_KEYWORD_IGNORE_GLOBAL_ALLOW_READ "ignore_global_allow_read" +#define TOMOYO_KEYWORD_QUOTA_EXCEEDED "quota_exceeded" +#define TOMOYO_KEYWORD_TRANSITION_FAILED "transition_failed" +/* A domain definition starts with <kernel>. */ +#define TOMOYO_ROOT_NAME "<kernel>" +#define TOMOYO_ROOT_NAME_LEN (sizeof(TOMOYO_ROOT_NAME) - 1) + +/* Value type definition. */ +#define TOMOYO_VALUE_TYPE_INVALID 0 +#define TOMOYO_VALUE_TYPE_DECIMAL 1 +#define TOMOYO_VALUE_TYPE_OCTAL 2 +#define TOMOYO_VALUE_TYPE_HEXADECIMAL 3 + +enum tomoyo_transition_type { + /* Do not change this order, */ + TOMOYO_TRANSITION_CONTROL_NO_INITIALIZE, + TOMOYO_TRANSITION_CONTROL_INITIALIZE, + TOMOYO_TRANSITION_CONTROL_NO_KEEP, + TOMOYO_TRANSITION_CONTROL_KEEP, + TOMOYO_MAX_TRANSITION_TYPE +}; + +/* Index numbers for Access Controls. */ +enum tomoyo_acl_entry_type_index { + TOMOYO_TYPE_PATH_ACL, + TOMOYO_TYPE_PATH2_ACL, + TOMOYO_TYPE_PATH_NUMBER_ACL, + TOMOYO_TYPE_MKDEV_ACL, + TOMOYO_TYPE_MOUNT_ACL, +}; + +/* Index numbers for File Controls. */ + +/* + * TOMOYO_TYPE_READ_WRITE is special. TOMOYO_TYPE_READ_WRITE is automatically + * set if both TOMOYO_TYPE_READ and TOMOYO_TYPE_WRITE are set. + * Both TOMOYO_TYPE_READ and TOMOYO_TYPE_WRITE are automatically set if + * TOMOYO_TYPE_READ_WRITE is set. + * TOMOYO_TYPE_READ_WRITE is automatically cleared if either TOMOYO_TYPE_READ + * or TOMOYO_TYPE_WRITE is cleared. + * Both TOMOYO_TYPE_READ and TOMOYO_TYPE_WRITE are automatically cleared if + * TOMOYO_TYPE_READ_WRITE is cleared. + */ + +enum tomoyo_path_acl_index { + TOMOYO_TYPE_READ_WRITE, + TOMOYO_TYPE_EXECUTE, + TOMOYO_TYPE_READ, + TOMOYO_TYPE_WRITE, + TOMOYO_TYPE_UNLINK, + TOMOYO_TYPE_RMDIR, + TOMOYO_TYPE_TRUNCATE, + TOMOYO_TYPE_SYMLINK, + TOMOYO_TYPE_REWRITE, + TOMOYO_TYPE_CHROOT, + TOMOYO_TYPE_UMOUNT, + TOMOYO_MAX_PATH_OPERATION +}; + +#define TOMOYO_RW_MASK ((1 << TOMOYO_TYPE_READ) | (1 << TOMOYO_TYPE_WRITE)) + +enum tomoyo_mkdev_acl_index { + TOMOYO_TYPE_MKBLOCK, + TOMOYO_TYPE_MKCHAR, + TOMOYO_MAX_MKDEV_OPERATION +}; + +enum tomoyo_path2_acl_index { + TOMOYO_TYPE_LINK, + TOMOYO_TYPE_RENAME, + TOMOYO_TYPE_PIVOT_ROOT, + TOMOYO_MAX_PATH2_OPERATION +}; + +enum tomoyo_path_number_acl_index { + TOMOYO_TYPE_CREATE, + TOMOYO_TYPE_MKDIR, + TOMOYO_TYPE_MKFIFO, + TOMOYO_TYPE_MKSOCK, + TOMOYO_TYPE_IOCTL, + TOMOYO_TYPE_CHMOD, + TOMOYO_TYPE_CHOWN, + TOMOYO_TYPE_CHGRP, + TOMOYO_MAX_PATH_NUMBER_OPERATION +}; + +enum tomoyo_securityfs_interface_index { + TOMOYO_DOMAINPOLICY, + TOMOYO_EXCEPTIONPOLICY, + TOMOYO_DOMAIN_STATUS, + TOMOYO_PROCESS_STATUS, + TOMOYO_MEMINFO, + TOMOYO_SELFDOMAIN, + TOMOYO_VERSION, + TOMOYO_PROFILE, + TOMOYO_QUERY, + TOMOYO_MANAGER +}; + +enum tomoyo_mac_index { + TOMOYO_MAC_FILE_EXECUTE, + TOMOYO_MAC_FILE_OPEN, + TOMOYO_MAC_FILE_CREATE, + TOMOYO_MAC_FILE_UNLINK, + TOMOYO_MAC_FILE_MKDIR, + TOMOYO_MAC_FILE_RMDIR, + TOMOYO_MAC_FILE_MKFIFO, + TOMOYO_MAC_FILE_MKSOCK, + TOMOYO_MAC_FILE_TRUNCATE, + TOMOYO_MAC_FILE_SYMLINK, + TOMOYO_MAC_FILE_REWRITE, + TOMOYO_MAC_FILE_MKBLOCK, + TOMOYO_MAC_FILE_MKCHAR, + TOMOYO_MAC_FILE_LINK, + TOMOYO_MAC_FILE_RENAME, + TOMOYO_MAC_FILE_CHMOD, + TOMOYO_MAC_FILE_CHOWN, + TOMOYO_MAC_FILE_CHGRP, + TOMOYO_MAC_FILE_IOCTL, + TOMOYO_MAC_FILE_CHROOT, + TOMOYO_MAC_FILE_MOUNT, + TOMOYO_MAC_FILE_UMOUNT, + TOMOYO_MAC_FILE_PIVOT_ROOT, + TOMOYO_MAX_MAC_INDEX +}; + +enum tomoyo_mac_category_index { + TOMOYO_MAC_CATEGORY_FILE, + TOMOYO_MAX_MAC_CATEGORY_INDEX +}; + +#define TOMOYO_RETRY_REQUEST 1 /* Retry this request. */ + +/********** Structure definitions. **********/ + +/* + * tomoyo_acl_head is a structure which is used for holding elements not in + * domain policy. + * It has following fields. + * + * (1) "list" which is linked to tomoyo_policy_list[] . + * (2) "is_deleted" is a bool which is true if marked as deleted, false + * otherwise. + */ +struct tomoyo_acl_head { + struct list_head list; + bool is_deleted; +} __packed; + +/* + * tomoyo_request_info is a structure which is used for holding + * + * (1) Domain information of current process. + * (2) How many retries are made for this request. + * (3) Profile number used for this request. + * (4) Access control mode of the profile. + */ +struct tomoyo_request_info { + struct tomoyo_domain_info *domain; + /* For holding parameters. */ + union { + struct { + const struct tomoyo_path_info *filename; + /* For using wildcards at tomoyo_find_next_domain(). */ + const struct tomoyo_path_info *matched_path; + u8 operation; + } path; + struct { + const struct tomoyo_path_info *filename1; + const struct tomoyo_path_info *filename2; + u8 operation; + } path2; + struct { + const struct tomoyo_path_info *filename; + unsigned int mode; + unsigned int major; + unsigned int minor; + u8 operation; + } mkdev; + struct { + const struct tomoyo_path_info *filename; + unsigned long number; + u8 operation; + } path_number; + struct { + const struct tomoyo_path_info *type; + const struct tomoyo_path_info *dir; + const struct tomoyo_path_info *dev; + unsigned long flags; + int need_dev; + } mount; + } param; + u8 param_type; + bool granted; + u8 retry; + u8 profile; + u8 mode; /* One of tomoyo_mode_index . */ + u8 type; +}; + +/* + * tomoyo_path_info is a structure which is used for holding a string data + * used by TOMOYO. + * This structure has several fields for supporting pattern matching. + * + * (1) "name" is the '\0' terminated string data. + * (2) "hash" is full_name_hash(name, strlen(name)). + * This allows tomoyo_pathcmp() to compare by hash before actually compare + * using strcmp(). + * (3) "const_len" is the length of the initial segment of "name" which + * consists entirely of non wildcard characters. In other words, the length + * which we can compare two strings using strncmp(). + * (4) "is_dir" is a bool which is true if "name" ends with "/", + * false otherwise. + * TOMOYO distinguishes directory and non-directory. A directory ends with + * "/" and non-directory does not end with "/". + * (5) "is_patterned" is a bool which is true if "name" contains wildcard + * characters, false otherwise. This allows TOMOYO to use "hash" and + * strcmp() for string comparison if "is_patterned" is false. + */ +struct tomoyo_path_info { + const char *name; + u32 hash; /* = full_name_hash(name, strlen(name)) */ + u16 const_len; /* = tomoyo_const_part_length(name) */ + bool is_dir; /* = tomoyo_strendswith(name, "/") */ + bool is_patterned; /* = tomoyo_path_contains_pattern(name) */ +}; + +/* + * tomoyo_name is a structure which is used for linking + * "struct tomoyo_path_info" into tomoyo_name_list . + */ +struct tomoyo_name { + struct list_head list; + atomic_t users; + struct tomoyo_path_info entry; +}; + +struct tomoyo_name_union { + const struct tomoyo_path_info *filename; + struct tomoyo_group *group; + u8 is_group; +}; + +struct tomoyo_number_union { + unsigned long values[2]; + struct tomoyo_group *group; + u8 min_type; + u8 max_type; + u8 is_group; +}; + +/* Structure for "path_group"/"number_group" directive. */ +struct tomoyo_group { + struct list_head list; + const struct tomoyo_path_info *group_name; + struct list_head member_list; + atomic_t users; +}; + +/* Structure for "path_group" directive. */ +struct tomoyo_path_group { + struct tomoyo_acl_head head; + const struct tomoyo_path_info *member_name; +}; + +/* Structure for "number_group" directive. */ +struct tomoyo_number_group { + struct tomoyo_acl_head head; + struct tomoyo_number_union number; +}; + +/* + * tomoyo_acl_info is a structure which is used for holding + * + * (1) "list" which is linked to the ->acl_info_list of + * "struct tomoyo_domain_info" + * (2) "is_deleted" is a bool which is true if this domain is marked as + * "deleted", false otherwise. + * (3) "type" which tells type of the entry. + * + * Packing "struct tomoyo_acl_info" allows + * "struct tomoyo_path_acl" to embed "u16" and "struct tomoyo_path2_acl" + * "struct tomoyo_path_number_acl" "struct tomoyo_mkdev_acl" to embed + * "u8" without enlarging their structure size. + */ +struct tomoyo_acl_info { + struct list_head list; + bool is_deleted; + u8 type; /* = one of values in "enum tomoyo_acl_entry_type_index". */ +} __packed; + +/* + * tomoyo_domain_info is a structure which is used for holding permissions + * (e.g. "allow_read /lib/libc-2.5.so") given to each domain. + * It has following fields. + * + * (1) "list" which is linked to tomoyo_domain_list . + * (2) "acl_info_list" which is linked to "struct tomoyo_acl_info". + * (3) "domainname" which holds the name of the domain. + * (4) "profile" which remembers profile number assigned to this domain. + * (5) "is_deleted" is a bool which is true if this domain is marked as + * "deleted", false otherwise. + * (6) "quota_warned" is a bool which is used for suppressing warning message + * when learning mode learned too much entries. + * (7) "ignore_global_allow_read" is a bool which is true if this domain + * should ignore "allow_read" directive in exception policy. + * (8) "transition_failed" is a bool which is set to true when this domain was + * unable to create a new domain at tomoyo_find_next_domain() because the + * name of the domain to be created was too long or it could not allocate + * memory. If set to true, more than one process continued execve() + * without domain transition. + * (9) "users" is an atomic_t that holds how many "struct cred"->security + * are referring this "struct tomoyo_domain_info". If is_deleted == true + * and users == 0, this struct will be kfree()d upon next garbage + * collection. + * + * A domain's lifecycle is an analogy of files on / directory. + * Multiple domains with the same domainname cannot be created (as with + * creating files with the same filename fails with -EEXIST). + * If a process reached a domain, that process can reside in that domain after + * that domain is marked as "deleted" (as with a process can access an already + * open()ed file after that file was unlink()ed). + */ +struct tomoyo_domain_info { + struct list_head list; + struct list_head acl_info_list; + /* Name of this domain. Never NULL. */ + const struct tomoyo_path_info *domainname; + u8 profile; /* Profile number to use. */ + bool is_deleted; /* Delete flag. */ + bool quota_warned; /* Quota warnning flag. */ + bool ignore_global_allow_read; /* Ignore "allow_read" flag. */ + bool transition_failed; /* Domain transition failed flag. */ + atomic_t users; /* Number of referring credentials. */ +}; + +/* + * tomoyo_path_acl is a structure which is used for holding an + * entry with one pathname operation (e.g. open(), mkdir()). + * It has following fields. + * + * (1) "head" which is a "struct tomoyo_acl_info". + * (2) "perm" which is a bitmask of permitted operations. + * (3) "name" is the pathname. + * + * Directives held by this structure are "allow_read/write", "allow_execute", + * "allow_read", "allow_write", "allow_unlink", "allow_rmdir", + * "allow_truncate", "allow_symlink", "allow_rewrite", "allow_chroot" and + * "allow_unmount". + */ +struct tomoyo_path_acl { + struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_PATH_ACL */ + u16 perm; + struct tomoyo_name_union name; +}; + +/* + * tomoyo_path_number_acl is a structure which is used for holding an + * entry with one pathname and one number operation. + * It has following fields. + * + * (1) "head" which is a "struct tomoyo_acl_info". + * (2) "perm" which is a bitmask of permitted operations. + * (3) "name" is the pathname. + * (4) "number" is the numeric value. + * + * Directives held by this structure are "allow_create", "allow_mkdir", + * "allow_ioctl", "allow_mkfifo", "allow_mksock", "allow_chmod", "allow_chown" + * and "allow_chgrp". + * + */ +struct tomoyo_path_number_acl { + struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_PATH_NUMBER_ACL */ + u8 perm; + struct tomoyo_name_union name; + struct tomoyo_number_union number; +}; + +/* + * tomoyo_mkdev_acl is a structure which is used for holding an + * entry with one pathname and three numbers operation. + * It has following fields. + * + * (1) "head" which is a "struct tomoyo_acl_info". + * (2) "perm" which is a bitmask of permitted operations. + * (3) "mode" is the create mode. + * (4) "major" is the major number of device node. + * (5) "minor" is the minor number of device node. + * + * Directives held by this structure are "allow_mkchar", "allow_mkblock". + * + */ +struct tomoyo_mkdev_acl { + struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_MKDEV_ACL */ + u8 perm; + struct tomoyo_name_union name; + struct tomoyo_number_union mode; + struct tomoyo_number_union major; + struct tomoyo_number_union minor; +}; + +/* + * tomoyo_path2_acl is a structure which is used for holding an + * entry with two pathnames operation (i.e. link(), rename() and pivot_root()). + * It has following fields. + * + * (1) "head" which is a "struct tomoyo_acl_info". + * (2) "perm" which is a bitmask of permitted operations. + * (3) "name1" is the source/old pathname. + * (4) "name2" is the destination/new pathname. + * + * Directives held by this structure are "allow_rename", "allow_link" and + * "allow_pivot_root". + */ +struct tomoyo_path2_acl { + struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_PATH2_ACL */ + u8 perm; + struct tomoyo_name_union name1; + struct tomoyo_name_union name2; +}; + +/* + * tomoyo_mount_acl is a structure which is used for holding an + * entry for mount operation. + * It has following fields. + * + * (1) "head" which is a "struct tomoyo_acl_info". + * (2) "dev_name" is the device name. + * (3) "dir_name" is the mount point. + * (4) "fs_type" is the filesystem type. + * (5) "flags" is the mount flags. + * + * Directive held by this structure is "allow_mount". + */ +struct tomoyo_mount_acl { + struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_MOUNT_ACL */ + struct tomoyo_name_union dev_name; + struct tomoyo_name_union dir_name; + struct tomoyo_name_union fs_type; + struct tomoyo_number_union flags; +}; + +#define TOMOYO_MAX_IO_READ_QUEUE 32 + +/* + * Structure for reading/writing policy via /sys/kernel/security/tomoyo + * interfaces. + */ +struct tomoyo_io_buffer { + void (*read) (struct tomoyo_io_buffer *); + int (*write) (struct tomoyo_io_buffer *); + int (*poll) (struct file *file, poll_table *wait); + /* Exclusive lock for this structure. */ + struct mutex io_sem; + /* Index returned by tomoyo_read_lock(). */ + int reader_idx; + char __user *read_user_buf; + int read_user_buf_avail; + struct { + struct list_head *domain; + struct list_head *group; + struct list_head *acl; + int avail; + int step; + int query_index; + u16 index; + u8 bit; + u8 w_pos; + bool eof; + bool print_this_domain_only; + bool print_execute_only; + const char *w[TOMOYO_MAX_IO_READ_QUEUE]; + } r; + /* The position currently writing to. */ + struct tomoyo_domain_info *write_var1; + /* Buffer for reading. */ + char *read_buf; + /* Size of read buffer. */ + int readbuf_size; + /* Buffer for writing. */ + char *write_buf; + /* Bytes available for writing. */ + int write_avail; + /* Size of write buffer. */ + int writebuf_size; + /* Type of this interface. */ + u8 type; +}; + +/* + * tomoyo_readable_file is a structure which is used for holding + * "allow_read" entries. + * It has following fields. + * + * (1) "head" is "struct tomoyo_acl_head". + * (2) "filename" is a pathname which is allowed to open(O_RDONLY). + */ +struct tomoyo_readable_file { + struct tomoyo_acl_head head; + const struct tomoyo_path_info *filename; +}; + +/* + * tomoyo_no_pattern is a structure which is used for holding + * "file_pattern" entries. + * It has following fields. + * + * (1) "head" is "struct tomoyo_acl_head". + * (2) "pattern" is a pathname pattern which is used for converting pathnames + * to pathname patterns during learning mode. + */ +struct tomoyo_no_pattern { + struct tomoyo_acl_head head; + const struct tomoyo_path_info *pattern; +}; + +/* + * tomoyo_no_rewrite is a structure which is used for holding + * "deny_rewrite" entries. + * It has following fields. + * + * (1) "head" is "struct tomoyo_acl_head". + * (2) "pattern" is a pathname which is by default not permitted to modify + * already existing content. + */ +struct tomoyo_no_rewrite { + struct tomoyo_acl_head head; + const struct tomoyo_path_info *pattern; +}; + +/* + * tomoyo_transition_control is a structure which is used for holding + * "initialize_domain"/"no_initialize_domain"/"keep_domain"/"no_keep_domain" + * entries. + * It has following fields. + * + * (1) "head" is "struct tomoyo_acl_head". + * (2) "type" is type of this entry. + * (3) "is_last_name" is a bool which is true if "domainname" is "the last + * component of a domainname", false otherwise. + * (4) "domainname" which is "a domainname" or "the last component of a + * domainname". + * (5) "program" which is a program's pathname. + */ +struct tomoyo_transition_control { + struct tomoyo_acl_head head; + u8 type; /* One of values in "enum tomoyo_transition_type". */ + /* True if the domainname is tomoyo_get_last_name(). */ + bool is_last_name; + const struct tomoyo_path_info *domainname; /* Maybe NULL */ + const struct tomoyo_path_info *program; /* Maybe NULL */ +}; + +/* + * tomoyo_aggregator is a structure which is used for holding + * "aggregator" entries. + * It has following fields. + * + * (1) "head" is "struct tomoyo_acl_head". + * (2) "original_name" which is originally requested name. + * (3) "aggregated_name" which is name to rewrite. + */ +struct tomoyo_aggregator { + struct tomoyo_acl_head head; + const struct tomoyo_path_info *original_name; + const struct tomoyo_path_info *aggregated_name; +}; + +/* + * tomoyo_manager is a structure which is used for holding list of + * domainnames or programs which are permitted to modify configuration via + * /sys/kernel/security/tomoyo/ interface. + * It has following fields. + * + * (1) "head" is "struct tomoyo_acl_head". + * (2) "is_domain" is a bool which is true if "manager" is a domainname, false + * otherwise. + * (3) "manager" is a domainname or a program's pathname. + */ +struct tomoyo_manager { + struct tomoyo_acl_head head; + bool is_domain; /* True if manager is a domainname. */ + /* A path to program or a domainname. */ + const struct tomoyo_path_info *manager; +}; + +struct tomoyo_preference { + unsigned int learning_max_entry; + bool enforcing_verbose; + bool learning_verbose; + bool permissive_verbose; +}; + +struct tomoyo_profile { + const struct tomoyo_path_info *comment; + struct tomoyo_preference *learning; + struct tomoyo_preference *permissive; + struct tomoyo_preference *enforcing; + struct tomoyo_preference preference; + u8 default_config; + u8 config[TOMOYO_MAX_MAC_INDEX + TOMOYO_MAX_MAC_CATEGORY_INDEX]; +}; + +/********** Function prototypes. **********/ + +/* Check whether the given string starts with the given keyword. */ +bool tomoyo_str_starts(char **src, const char *find); +/* Get tomoyo_realpath() of current process. */ +const char *tomoyo_get_exe(void); +/* Format string. */ +void tomoyo_normalize_line(unsigned char *buffer); +/* Print warning or error message on console. */ +void tomoyo_warn_log(struct tomoyo_request_info *r, const char *fmt, ...) + __attribute__ ((format(printf, 2, 3))); +/* Check all profiles currently assigned to domains are defined. */ +void tomoyo_check_profile(void); +/* Open operation for /sys/kernel/security/tomoyo/ interface. */ +int tomoyo_open_control(const u8 type, struct file *file); +/* Close /sys/kernel/security/tomoyo/ interface. */ +int tomoyo_close_control(struct file *file); +/* Poll operation for /sys/kernel/security/tomoyo/ interface. */ +int tomoyo_poll_control(struct file *file, poll_table *wait); +/* Read operation for /sys/kernel/security/tomoyo/ interface. */ +int tomoyo_read_control(struct file *file, char __user *buffer, + const int buffer_len); +/* Write operation for /sys/kernel/security/tomoyo/ interface. */ +int tomoyo_write_control(struct file *file, const char __user *buffer, + const int buffer_len); +/* Check whether the domain has too many ACL entries to hold. */ +bool tomoyo_domain_quota_is_ok(struct tomoyo_request_info *r); +/* Print out of memory warning message. */ +void tomoyo_warn_oom(const char *function); +/* Check whether the given name matches the given name_union. */ +const struct tomoyo_path_info * +tomoyo_compare_name_union(const struct tomoyo_path_info *name, + const struct tomoyo_name_union *ptr); +/* Check whether the given number matches the given number_union. */ +bool tomoyo_compare_number_union(const unsigned long value, + const struct tomoyo_number_union *ptr); +int tomoyo_get_mode(const u8 profile, const u8 index); +void tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt, ...) + __attribute__ ((format(printf, 2, 3))); +/* Check whether the domainname is correct. */ +bool tomoyo_correct_domain(const unsigned char *domainname); +/* Check whether the token is correct. */ +bool tomoyo_correct_path(const char *filename); +bool tomoyo_correct_word(const char *string); +/* Check whether the token can be a domainname. */ +bool tomoyo_domain_def(const unsigned char *buffer); +bool tomoyo_parse_name_union(const char *filename, + struct tomoyo_name_union *ptr); +/* Check whether the given filename matches the given path_group. */ +const struct tomoyo_path_info * +tomoyo_path_matches_group(const struct tomoyo_path_info *pathname, + const struct tomoyo_group *group); +/* Check whether the given value matches the given number_group. */ +bool tomoyo_number_matches_group(const unsigned long min, + const unsigned long max, + const struct tomoyo_group *group); +/* Check whether the given filename matches the given pattern. */ +bool tomoyo_path_matches_pattern(const struct tomoyo_path_info *filename, + const struct tomoyo_path_info *pattern); + +bool tomoyo_parse_number_union(char *data, struct tomoyo_number_union *num); +/* Tokenize a line. */ +bool tomoyo_tokenize(char *buffer, char *w[], size_t size); +/* Write domain policy violation warning message to console? */ +bool tomoyo_verbose_mode(const struct tomoyo_domain_info *domain); +/* Fill "struct tomoyo_request_info". */ +int tomoyo_init_request_info(struct tomoyo_request_info *r, + struct tomoyo_domain_info *domain, + const u8 index); +/* Check permission for mount operation. */ +int tomoyo_mount_permission(char *dev_name, struct path *path, char *type, + unsigned long flags, void *data_page); +/* Create "aggregator" entry in exception policy. */ +int tomoyo_write_aggregator(char *data, const bool is_delete); +int tomoyo_write_transition_control(char *data, const bool is_delete, + const u8 type); +/* + * Create "allow_read/write", "allow_execute", "allow_read", "allow_write", + * "allow_create", "allow_unlink", "allow_mkdir", "allow_rmdir", + * "allow_mkfifo", "allow_mksock", "allow_mkblock", "allow_mkchar", + * "allow_truncate", "allow_symlink", "allow_rewrite", "allow_rename" and + * "allow_link" entry in domain policy. + */ +int tomoyo_write_file(char *data, struct tomoyo_domain_info *domain, + const bool is_delete); +/* Create "allow_read" entry in exception policy. */ +int tomoyo_write_globally_readable(char *data, const bool is_delete); +/* Create "allow_mount" entry in domain policy. */ +int tomoyo_write_mount(char *data, struct tomoyo_domain_info *domain, + const bool is_delete); +/* Create "deny_rewrite" entry in exception policy. */ +int tomoyo_write_no_rewrite(char *data, const bool is_delete); +/* Create "file_pattern" entry in exception policy. */ +int tomoyo_write_pattern(char *data, const bool is_delete); +/* Create "path_group"/"number_group" entry in exception policy. */ +int tomoyo_write_group(char *data, const bool is_delete, const u8 type); +int tomoyo_supervisor(struct tomoyo_request_info *r, const char *fmt, ...) + __attribute__ ((format(printf, 2, 3))); +/* Find a domain by the given name. */ +struct tomoyo_domain_info *tomoyo_find_domain(const char *domainname); +/* Find or create a domain by the given name. */ +struct tomoyo_domain_info *tomoyo_assign_domain(const char *domainname, + const u8 profile); +struct tomoyo_profile *tomoyo_profile(const u8 profile); +/* + * Allocate memory for "struct tomoyo_path_group"/"struct tomoyo_number_group". + */ +struct tomoyo_group *tomoyo_get_group(const char *group_name, const u8 type); + +/* Check mode for specified functionality. */ +unsigned int tomoyo_check_flags(const struct tomoyo_domain_info *domain, + const u8 index); +/* Fill in "struct tomoyo_path_info" members. */ +void tomoyo_fill_path_info(struct tomoyo_path_info *ptr); +/* Run policy loader when /sbin/init starts. */ +void tomoyo_load_policy(const char *filename); + +void tomoyo_put_number_union(struct tomoyo_number_union *ptr); + +/* Convert binary string to ascii string. */ +char *tomoyo_encode(const char *str); + +/* + * Returns realpath(3) of the given pathname except that + * ignores chroot'ed root and does not follow the final symlink. + */ +char *tomoyo_realpath_nofollow(const char *pathname); +/* + * Returns realpath(3) of the given pathname except that + * ignores chroot'ed root and the pathname is already solved. + */ +char *tomoyo_realpath_from_path(struct path *path); +/* Get patterned pathname. */ +const char *tomoyo_pattern(const struct tomoyo_path_info *filename); + +/* Check memory quota. */ +bool tomoyo_memory_ok(void *ptr); +void *tomoyo_commit_ok(void *data, const unsigned int size); + +/* + * Keep the given name on the RAM. + * The RAM is shared, so NEVER try to modify or kfree() the returned name. + */ +const struct tomoyo_path_info *tomoyo_get_name(const char *name); + +/* Check for memory usage. */ +void tomoyo_read_memory_counter(struct tomoyo_io_buffer *head); + +/* Set memory quota. */ +int tomoyo_write_memory_quota(struct tomoyo_io_buffer *head); + +/* Initialize mm related code. */ +void __init tomoyo_mm_init(void); +int tomoyo_path_permission(struct tomoyo_request_info *r, u8 operation, + const struct tomoyo_path_info *filename); +int tomoyo_check_open_permission(struct tomoyo_domain_info *domain, + struct path *path, const int flag); +int tomoyo_path_number_perm(const u8 operation, struct path *path, + unsigned long number); +int tomoyo_mkdev_perm(const u8 operation, struct path *path, + const unsigned int mode, unsigned int dev); +int tomoyo_path_perm(const u8 operation, struct path *path); +int tomoyo_path2_perm(const u8 operation, struct path *path1, + struct path *path2); +int tomoyo_find_next_domain(struct linux_binprm *bprm); + +void tomoyo_print_ulong(char *buffer, const int buffer_len, + const unsigned long value, const u8 type); + +/* Drop refcount on tomoyo_name_union. */ +void tomoyo_put_name_union(struct tomoyo_name_union *ptr); + +/* Run garbage collector. */ +void tomoyo_run_gc(void); + +void tomoyo_memory_free(void *ptr); + +int tomoyo_update_domain(struct tomoyo_acl_info *new_entry, const int size, + bool is_delete, struct tomoyo_domain_info *domain, + bool (*check_duplicate) (const struct tomoyo_acl_info + *, + const struct tomoyo_acl_info + *), + bool (*merge_duplicate) (struct tomoyo_acl_info *, + struct tomoyo_acl_info *, + const bool)); +int tomoyo_update_policy(struct tomoyo_acl_head *new_entry, const int size, + bool is_delete, struct list_head *list, + bool (*check_duplicate) (const struct tomoyo_acl_head + *, + const struct tomoyo_acl_head + *)); +void tomoyo_check_acl(struct tomoyo_request_info *r, + bool (*check_entry) (struct tomoyo_request_info *, + const struct tomoyo_acl_info *)); + +/********** External variable definitions. **********/ + +/* Lock for GC. */ +extern struct srcu_struct tomoyo_ss; + +/* The list for "struct tomoyo_domain_info". */ +extern struct list_head tomoyo_domain_list; + +extern struct list_head tomoyo_policy_list[TOMOYO_MAX_POLICY]; +extern struct list_head tomoyo_group_list[TOMOYO_MAX_GROUP]; +extern struct list_head tomoyo_name_list[TOMOYO_MAX_HASH]; + +/* Lock for protecting policy. */ +extern struct mutex tomoyo_policy_lock; + +/* Has /sbin/init started? */ +extern bool tomoyo_policy_loaded; + +/* The kernel's domain. */ +extern struct tomoyo_domain_info tomoyo_kernel_domain; + +extern const char *tomoyo_path_keyword[TOMOYO_MAX_PATH_OPERATION]; +extern const char *tomoyo_mkdev_keyword[TOMOYO_MAX_MKDEV_OPERATION]; +extern const char *tomoyo_path2_keyword[TOMOYO_MAX_PATH2_OPERATION]; +extern const char *tomoyo_path_number_keyword[TOMOYO_MAX_PATH_NUMBER_OPERATION]; + +extern unsigned int tomoyo_quota_for_query; +extern unsigned int tomoyo_query_memory_size; + +/********** Inlined functions. **********/ + +static inline int tomoyo_read_lock(void) +{ + return srcu_read_lock(&tomoyo_ss); +} + +static inline void tomoyo_read_unlock(int idx) +{ + srcu_read_unlock(&tomoyo_ss, idx); +} + +/* strcmp() for "struct tomoyo_path_info" structure. */ +static inline bool tomoyo_pathcmp(const struct tomoyo_path_info *a, + const struct tomoyo_path_info *b) +{ + return a->hash != b->hash || strcmp(a->name, b->name); +} + +/** + * tomoyo_valid - Check whether the character is a valid char. + * + * @c: The character to check. + * + * Returns true if @c is a valid character, false otherwise. + */ +static inline bool tomoyo_valid(const unsigned char c) +{ + return c > ' ' && c < 127; +} + +/** + * tomoyo_invalid - Check whether the character is an invalid char. + * + * @c: The character to check. + * + * Returns true if @c is an invalid character, false otherwise. + */ +static inline bool tomoyo_invalid(const unsigned char c) +{ + return c && (c <= ' ' || c >= 127); +} + +static inline void tomoyo_put_name(const struct tomoyo_path_info *name) +{ + if (name) { + struct tomoyo_name *ptr = + container_of(name, typeof(*ptr), entry); + atomic_dec(&ptr->users); + } +} + +static inline void tomoyo_put_group(struct tomoyo_group *group) +{ + if (group) + atomic_dec(&group->users); +} + +static inline struct tomoyo_domain_info *tomoyo_domain(void) +{ + return current_cred()->security; +} + +static inline struct tomoyo_domain_info *tomoyo_real_domain(struct task_struct + *task) +{ + return task_cred_xxx(task, security); +} + +static inline bool tomoyo_same_acl_head(const struct tomoyo_acl_info *p1, + const struct tomoyo_acl_info *p2) +{ + return p1->type == p2->type; +} + +static inline bool tomoyo_same_name_union +(const struct tomoyo_name_union *p1, const struct tomoyo_name_union *p2) +{ + return p1->filename == p2->filename && p1->group == p2->group && + p1->is_group == p2->is_group; +} + +static inline bool tomoyo_same_number_union +(const struct tomoyo_number_union *p1, const struct tomoyo_number_union *p2) +{ + return p1->values[0] == p2->values[0] && p1->values[1] == p2->values[1] + && p1->group == p2->group && p1->min_type == p2->min_type && + p1->max_type == p2->max_type && p1->is_group == p2->is_group; +} + +/** + * list_for_each_cookie - iterate over a list with cookie. + * @pos: the &struct list_head to use as a loop cursor. + * @head: the head for your list. + */ +#define list_for_each_cookie(pos, head) \ + if (!pos) \ + pos = srcu_dereference((head)->next, &tomoyo_ss); \ + for ( ; pos != (head); pos = srcu_dereference(pos->next, &tomoyo_ss)) + +#endif /* !defined(_SECURITY_TOMOYO_COMMON_H) */ diff --git a/security/tomoyo/domain.c b/security/tomoyo/domain.c new file mode 100644 index 00000000..35388408 --- /dev/null +++ b/security/tomoyo/domain.c @@ -0,0 +1,542 @@ +/* + * security/tomoyo/domain.c + * + * Domain transition functions for TOMOYO. + * + * Copyright (C) 2005-2010 NTT DATA CORPORATION + */ + +#include "common.h" +#include <linux/binfmts.h> +#include <linux/slab.h> + +/* Variables definitions.*/ + +/* The initial domain. */ +struct tomoyo_domain_info tomoyo_kernel_domain; + +/** + * tomoyo_update_policy - Update an entry for exception policy. + * + * @new_entry: Pointer to "struct tomoyo_acl_info". + * @size: Size of @new_entry in bytes. + * @is_delete: True if it is a delete request. + * @list: Pointer to "struct list_head". + * @check_duplicate: Callback function to find duplicated entry. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_update_policy(struct tomoyo_acl_head *new_entry, const int size, + bool is_delete, struct list_head *list, + bool (*check_duplicate) (const struct tomoyo_acl_head + *, + const struct tomoyo_acl_head + *)) +{ + int error = is_delete ? -ENOENT : -ENOMEM; + struct tomoyo_acl_head *entry; + + if (mutex_lock_interruptible(&tomoyo_policy_lock)) + return -ENOMEM; + list_for_each_entry_rcu(entry, list, list) { + if (!check_duplicate(entry, new_entry)) + continue; + entry->is_deleted = is_delete; + error = 0; + break; + } + if (error && !is_delete) { + entry = tomoyo_commit_ok(new_entry, size); + if (entry) { + list_add_tail_rcu(&entry->list, list); + error = 0; + } + } + mutex_unlock(&tomoyo_policy_lock); + return error; +} + +/** + * tomoyo_update_domain - Update an entry for domain policy. + * + * @new_entry: Pointer to "struct tomoyo_acl_info". + * @size: Size of @new_entry in bytes. + * @is_delete: True if it is a delete request. + * @domain: Pointer to "struct tomoyo_domain_info". + * @check_duplicate: Callback function to find duplicated entry. + * @merge_duplicate: Callback function to merge duplicated entry. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_update_domain(struct tomoyo_acl_info *new_entry, const int size, + bool is_delete, struct tomoyo_domain_info *domain, + bool (*check_duplicate) (const struct tomoyo_acl_info + *, + const struct tomoyo_acl_info + *), + bool (*merge_duplicate) (struct tomoyo_acl_info *, + struct tomoyo_acl_info *, + const bool)) +{ + int error = is_delete ? -ENOENT : -ENOMEM; + struct tomoyo_acl_info *entry; + + if (mutex_lock_interruptible(&tomoyo_policy_lock)) + return error; + list_for_each_entry_rcu(entry, &domain->acl_info_list, list) { + if (!check_duplicate(entry, new_entry)) + continue; + if (merge_duplicate) + entry->is_deleted = merge_duplicate(entry, new_entry, + is_delete); + else + entry->is_deleted = is_delete; + error = 0; + break; + } + if (error && !is_delete) { + entry = tomoyo_commit_ok(new_entry, size); + if (entry) { + list_add_tail_rcu(&entry->list, &domain->acl_info_list); + error = 0; + } + } + mutex_unlock(&tomoyo_policy_lock); + return error; +} + +void tomoyo_check_acl(struct tomoyo_request_info *r, + bool (*check_entry) (struct tomoyo_request_info *, + const struct tomoyo_acl_info *)) +{ + const struct tomoyo_domain_info *domain = r->domain; + struct tomoyo_acl_info *ptr; + + list_for_each_entry_rcu(ptr, &domain->acl_info_list, list) { + if (ptr->is_deleted || ptr->type != r->param_type) + continue; + if (check_entry(r, ptr)) { + r->granted = true; + return; + } + } + r->granted = false; +} + +/* The list for "struct tomoyo_domain_info". */ +LIST_HEAD(tomoyo_domain_list); + +struct list_head tomoyo_policy_list[TOMOYO_MAX_POLICY]; +struct list_head tomoyo_group_list[TOMOYO_MAX_GROUP]; + +/** + * tomoyo_last_word - Get last component of a domainname. + * + * @domainname: Domainname to check. + * + * Returns the last word of @domainname. + */ +static const char *tomoyo_last_word(const char *name) +{ + const char *cp = strrchr(name, ' '); + if (cp) + return cp + 1; + return name; +} + +static bool tomoyo_same_transition_control(const struct tomoyo_acl_head *a, + const struct tomoyo_acl_head *b) +{ + const struct tomoyo_transition_control *p1 = container_of(a, + typeof(*p1), + head); + const struct tomoyo_transition_control *p2 = container_of(b, + typeof(*p2), + head); + return p1->type == p2->type && p1->is_last_name == p2->is_last_name + && p1->domainname == p2->domainname + && p1->program == p2->program; +} + +/** + * tomoyo_update_transition_control_entry - Update "struct tomoyo_transition_control" list. + * + * @domainname: The name of domain. Maybe NULL. + * @program: The name of program. Maybe NULL. + * @type: Type of transition. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_update_transition_control_entry(const char *domainname, + const char *program, + const u8 type, + const bool is_delete) +{ + struct tomoyo_transition_control e = { .type = type }; + int error = is_delete ? -ENOENT : -ENOMEM; + if (program) { + if (!tomoyo_correct_path(program)) + return -EINVAL; + e.program = tomoyo_get_name(program); + if (!e.program) + goto out; + } + if (domainname) { + if (!tomoyo_correct_domain(domainname)) { + if (!tomoyo_correct_path(domainname)) + goto out; + e.is_last_name = true; + } + e.domainname = tomoyo_get_name(domainname); + if (!e.domainname) + goto out; + } + error = tomoyo_update_policy(&e.head, sizeof(e), is_delete, + &tomoyo_policy_list + [TOMOYO_ID_TRANSITION_CONTROL], + tomoyo_same_transition_control); + out: + tomoyo_put_name(e.domainname); + tomoyo_put_name(e.program); + return error; +} + +/** + * tomoyo_write_transition_control - Write "struct tomoyo_transition_control" list. + * + * @data: String to parse. + * @is_delete: True if it is a delete request. + * @type: Type of this entry. + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_write_transition_control(char *data, const bool is_delete, + const u8 type) +{ + char *domainname = strstr(data, " from "); + if (domainname) { + *domainname = '\0'; + domainname += 6; + } else if (type == TOMOYO_TRANSITION_CONTROL_NO_KEEP || + type == TOMOYO_TRANSITION_CONTROL_KEEP) { + domainname = data; + data = NULL; + } + return tomoyo_update_transition_control_entry(domainname, data, type, + is_delete); +} + +/** + * tomoyo_transition_type - Get domain transition type. + * + * @domainname: The name of domain. + * @program: The name of program. + * + * Returns TOMOYO_TRANSITION_CONTROL_INITIALIZE if executing @program + * reinitializes domain transition, TOMOYO_TRANSITION_CONTROL_KEEP if executing + * @program suppresses domain transition, others otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static u8 tomoyo_transition_type(const struct tomoyo_path_info *domainname, + const struct tomoyo_path_info *program) +{ + const struct tomoyo_transition_control *ptr; + const char *last_name = tomoyo_last_word(domainname->name); + u8 type; + for (type = 0; type < TOMOYO_MAX_TRANSITION_TYPE; type++) { + next: + list_for_each_entry_rcu(ptr, &tomoyo_policy_list + [TOMOYO_ID_TRANSITION_CONTROL], + head.list) { + if (ptr->head.is_deleted || ptr->type != type) + continue; + if (ptr->domainname) { + if (!ptr->is_last_name) { + if (ptr->domainname != domainname) + continue; + } else { + /* + * Use direct strcmp() since this is + * unlikely used. + */ + if (strcmp(ptr->domainname->name, + last_name)) + continue; + } + } + if (ptr->program && + tomoyo_pathcmp(ptr->program, program)) + continue; + if (type == TOMOYO_TRANSITION_CONTROL_NO_INITIALIZE) { + /* + * Do not check for initialize_domain if + * no_initialize_domain matched. + */ + type = TOMOYO_TRANSITION_CONTROL_NO_KEEP; + goto next; + } + goto done; + } + } + done: + return type; +} + +static bool tomoyo_same_aggregator(const struct tomoyo_acl_head *a, + const struct tomoyo_acl_head *b) +{ + const struct tomoyo_aggregator *p1 = container_of(a, typeof(*p1), head); + const struct tomoyo_aggregator *p2 = container_of(b, typeof(*p2), head); + return p1->original_name == p2->original_name && + p1->aggregated_name == p2->aggregated_name; +} + +/** + * tomoyo_update_aggregator_entry - Update "struct tomoyo_aggregator" list. + * + * @original_name: The original program's name. + * @aggregated_name: The program name to use. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_update_aggregator_entry(const char *original_name, + const char *aggregated_name, + const bool is_delete) +{ + struct tomoyo_aggregator e = { }; + int error = is_delete ? -ENOENT : -ENOMEM; + + if (!tomoyo_correct_path(original_name) || + !tomoyo_correct_path(aggregated_name)) + return -EINVAL; + e.original_name = tomoyo_get_name(original_name); + e.aggregated_name = tomoyo_get_name(aggregated_name); + if (!e.original_name || !e.aggregated_name || + e.aggregated_name->is_patterned) /* No patterns allowed. */ + goto out; + error = tomoyo_update_policy(&e.head, sizeof(e), is_delete, + &tomoyo_policy_list[TOMOYO_ID_AGGREGATOR], + tomoyo_same_aggregator); + out: + tomoyo_put_name(e.original_name); + tomoyo_put_name(e.aggregated_name); + return error; +} + +/** + * tomoyo_write_aggregator - Write "struct tomoyo_aggregator" list. + * + * @data: String to parse. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_write_aggregator(char *data, const bool is_delete) +{ + char *cp = strchr(data, ' '); + + if (!cp) + return -EINVAL; + *cp++ = '\0'; + return tomoyo_update_aggregator_entry(data, cp, is_delete); +} + +/** + * tomoyo_assign_domain - Create a domain. + * + * @domainname: The name of domain. + * @profile: Profile number to assign if the domain was newly created. + * + * Returns pointer to "struct tomoyo_domain_info" on success, NULL otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +struct tomoyo_domain_info *tomoyo_assign_domain(const char *domainname, + const u8 profile) +{ + struct tomoyo_domain_info *entry; + struct tomoyo_domain_info *domain = NULL; + const struct tomoyo_path_info *saved_domainname; + bool found = false; + + if (!tomoyo_correct_domain(domainname)) + return NULL; + saved_domainname = tomoyo_get_name(domainname); + if (!saved_domainname) + return NULL; + entry = kzalloc(sizeof(*entry), GFP_NOFS); + if (mutex_lock_interruptible(&tomoyo_policy_lock)) + goto out; + list_for_each_entry_rcu(domain, &tomoyo_domain_list, list) { + if (domain->is_deleted || + tomoyo_pathcmp(saved_domainname, domain->domainname)) + continue; + found = true; + break; + } + if (!found && tomoyo_memory_ok(entry)) { + INIT_LIST_HEAD(&entry->acl_info_list); + entry->domainname = saved_domainname; + saved_domainname = NULL; + entry->profile = profile; + list_add_tail_rcu(&entry->list, &tomoyo_domain_list); + domain = entry; + entry = NULL; + found = true; + } + mutex_unlock(&tomoyo_policy_lock); + out: + tomoyo_put_name(saved_domainname); + kfree(entry); + return found ? domain : NULL; +} + +/** + * tomoyo_find_next_domain - Find a domain. + * + * @bprm: Pointer to "struct linux_binprm". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_find_next_domain(struct linux_binprm *bprm) +{ + struct tomoyo_request_info r; + char *tmp = kzalloc(TOMOYO_EXEC_TMPSIZE, GFP_NOFS); + struct tomoyo_domain_info *old_domain = tomoyo_domain(); + struct tomoyo_domain_info *domain = NULL; + const char *original_name = bprm->filename; + u8 mode; + bool is_enforce; + int retval = -ENOMEM; + bool need_kfree = false; + struct tomoyo_path_info rn = { }; /* real name */ + + mode = tomoyo_init_request_info(&r, NULL, TOMOYO_MAC_FILE_EXECUTE); + is_enforce = (mode == TOMOYO_CONFIG_ENFORCING); + if (!tmp) + goto out; + + retry: + if (need_kfree) { + kfree(rn.name); + need_kfree = false; + } + /* Get symlink's pathname of program. */ + retval = -ENOENT; + rn.name = tomoyo_realpath_nofollow(original_name); + if (!rn.name) + goto out; + tomoyo_fill_path_info(&rn); + need_kfree = true; + + /* Check 'aggregator' directive. */ + { + struct tomoyo_aggregator *ptr; + list_for_each_entry_rcu(ptr, &tomoyo_policy_list + [TOMOYO_ID_AGGREGATOR], head.list) { + if (ptr->head.is_deleted || + !tomoyo_path_matches_pattern(&rn, + ptr->original_name)) + continue; + kfree(rn.name); + need_kfree = false; + /* This is OK because it is read only. */ + rn = *ptr->aggregated_name; + break; + } + } + + /* Check execute permission. */ + retval = tomoyo_path_permission(&r, TOMOYO_TYPE_EXECUTE, &rn); + if (retval == TOMOYO_RETRY_REQUEST) + goto retry; + if (retval < 0) + goto out; + /* + * To be able to specify domainnames with wildcards, use the + * pathname specified in the policy (which may contain + * wildcard) rather than the pathname passed to execve() + * (which never contains wildcard). + */ + if (r.param.path.matched_path) { + if (need_kfree) + kfree(rn.name); + need_kfree = false; + /* This is OK because it is read only. */ + rn = *r.param.path.matched_path; + } + + /* Calculate domain to transit to. */ + switch (tomoyo_transition_type(old_domain->domainname, &rn)) { + case TOMOYO_TRANSITION_CONTROL_INITIALIZE: + /* Transit to the child of tomoyo_kernel_domain domain. */ + snprintf(tmp, TOMOYO_EXEC_TMPSIZE - 1, TOMOYO_ROOT_NAME " " + "%s", rn.name); + break; + case TOMOYO_TRANSITION_CONTROL_KEEP: + /* Keep current domain. */ + domain = old_domain; + break; + default: + if (old_domain == &tomoyo_kernel_domain && + !tomoyo_policy_loaded) { + /* + * Needn't to transit from kernel domain before + * starting /sbin/init. But transit from kernel domain + * if executing initializers because they might start + * before /sbin/init. + */ + domain = old_domain; + } else { + /* Normal domain transition. */ + snprintf(tmp, TOMOYO_EXEC_TMPSIZE - 1, "%s %s", + old_domain->domainname->name, rn.name); + } + break; + } + if (domain || strlen(tmp) >= TOMOYO_EXEC_TMPSIZE - 10) + goto done; + domain = tomoyo_find_domain(tmp); + if (domain) + goto done; + if (is_enforce) { + int error = tomoyo_supervisor(&r, "# wants to create domain\n" + "%s\n", tmp); + if (error == TOMOYO_RETRY_REQUEST) + goto retry; + if (error < 0) + goto done; + } + domain = tomoyo_assign_domain(tmp, old_domain->profile); + done: + if (domain) + goto out; + printk(KERN_WARNING "TOMOYO-ERROR: Domain '%s' not defined.\n", tmp); + if (is_enforce) + retval = -EPERM; + else + old_domain->transition_failed = true; + out: + if (!domain) + domain = old_domain; + /* Update reference count on "struct tomoyo_domain_info". */ + atomic_inc(&domain->users); + bprm->cred->security = domain; + if (need_kfree) + kfree(rn.name); + kfree(tmp); + return retval; +} diff --git a/security/tomoyo/file.c b/security/tomoyo/file.c new file mode 100644 index 00000000..d64e8ecb --- /dev/null +++ b/security/tomoyo/file.c @@ -0,0 +1,1176 @@ +/* + * security/tomoyo/file.c + * + * Pathname restriction functions. + * + * Copyright (C) 2005-2010 NTT DATA CORPORATION + */ + +#include "common.h" +#include <linux/slab.h> + +/* Keyword array for operations with one pathname. */ +const char *tomoyo_path_keyword[TOMOYO_MAX_PATH_OPERATION] = { + [TOMOYO_TYPE_READ_WRITE] = "read/write", + [TOMOYO_TYPE_EXECUTE] = "execute", + [TOMOYO_TYPE_READ] = "read", + [TOMOYO_TYPE_WRITE] = "write", + [TOMOYO_TYPE_UNLINK] = "unlink", + [TOMOYO_TYPE_RMDIR] = "rmdir", + [TOMOYO_TYPE_TRUNCATE] = "truncate", + [TOMOYO_TYPE_SYMLINK] = "symlink", + [TOMOYO_TYPE_REWRITE] = "rewrite", + [TOMOYO_TYPE_CHROOT] = "chroot", + [TOMOYO_TYPE_UMOUNT] = "unmount", +}; + +/* Keyword array for operations with one pathname and three numbers. */ +const char *tomoyo_mkdev_keyword[TOMOYO_MAX_MKDEV_OPERATION] = { + [TOMOYO_TYPE_MKBLOCK] = "mkblock", + [TOMOYO_TYPE_MKCHAR] = "mkchar", +}; + +/* Keyword array for operations with two pathnames. */ +const char *tomoyo_path2_keyword[TOMOYO_MAX_PATH2_OPERATION] = { + [TOMOYO_TYPE_LINK] = "link", + [TOMOYO_TYPE_RENAME] = "rename", + [TOMOYO_TYPE_PIVOT_ROOT] = "pivot_root", +}; + +/* Keyword array for operations with one pathname and one number. */ +const char *tomoyo_path_number_keyword[TOMOYO_MAX_PATH_NUMBER_OPERATION] = { + [TOMOYO_TYPE_CREATE] = "create", + [TOMOYO_TYPE_MKDIR] = "mkdir", + [TOMOYO_TYPE_MKFIFO] = "mkfifo", + [TOMOYO_TYPE_MKSOCK] = "mksock", + [TOMOYO_TYPE_IOCTL] = "ioctl", + [TOMOYO_TYPE_CHMOD] = "chmod", + [TOMOYO_TYPE_CHOWN] = "chown", + [TOMOYO_TYPE_CHGRP] = "chgrp", +}; + +static const u8 tomoyo_p2mac[TOMOYO_MAX_PATH_OPERATION] = { + [TOMOYO_TYPE_READ_WRITE] = TOMOYO_MAC_FILE_OPEN, + [TOMOYO_TYPE_EXECUTE] = TOMOYO_MAC_FILE_EXECUTE, + [TOMOYO_TYPE_READ] = TOMOYO_MAC_FILE_OPEN, + [TOMOYO_TYPE_WRITE] = TOMOYO_MAC_FILE_OPEN, + [TOMOYO_TYPE_UNLINK] = TOMOYO_MAC_FILE_UNLINK, + [TOMOYO_TYPE_RMDIR] = TOMOYO_MAC_FILE_RMDIR, + [TOMOYO_TYPE_TRUNCATE] = TOMOYO_MAC_FILE_TRUNCATE, + [TOMOYO_TYPE_SYMLINK] = TOMOYO_MAC_FILE_SYMLINK, + [TOMOYO_TYPE_REWRITE] = TOMOYO_MAC_FILE_REWRITE, + [TOMOYO_TYPE_CHROOT] = TOMOYO_MAC_FILE_CHROOT, + [TOMOYO_TYPE_UMOUNT] = TOMOYO_MAC_FILE_UMOUNT, +}; + +static const u8 tomoyo_pnnn2mac[TOMOYO_MAX_MKDEV_OPERATION] = { + [TOMOYO_TYPE_MKBLOCK] = TOMOYO_MAC_FILE_MKBLOCK, + [TOMOYO_TYPE_MKCHAR] = TOMOYO_MAC_FILE_MKCHAR, +}; + +static const u8 tomoyo_pp2mac[TOMOYO_MAX_PATH2_OPERATION] = { + [TOMOYO_TYPE_LINK] = TOMOYO_MAC_FILE_LINK, + [TOMOYO_TYPE_RENAME] = TOMOYO_MAC_FILE_RENAME, + [TOMOYO_TYPE_PIVOT_ROOT] = TOMOYO_MAC_FILE_PIVOT_ROOT, +}; + +static const u8 tomoyo_pn2mac[TOMOYO_MAX_PATH_NUMBER_OPERATION] = { + [TOMOYO_TYPE_CREATE] = TOMOYO_MAC_FILE_CREATE, + [TOMOYO_TYPE_MKDIR] = TOMOYO_MAC_FILE_MKDIR, + [TOMOYO_TYPE_MKFIFO] = TOMOYO_MAC_FILE_MKFIFO, + [TOMOYO_TYPE_MKSOCK] = TOMOYO_MAC_FILE_MKSOCK, + [TOMOYO_TYPE_IOCTL] = TOMOYO_MAC_FILE_IOCTL, + [TOMOYO_TYPE_CHMOD] = TOMOYO_MAC_FILE_CHMOD, + [TOMOYO_TYPE_CHOWN] = TOMOYO_MAC_FILE_CHOWN, + [TOMOYO_TYPE_CHGRP] = TOMOYO_MAC_FILE_CHGRP, +}; + +void tomoyo_put_name_union(struct tomoyo_name_union *ptr) +{ + if (!ptr) + return; + if (ptr->is_group) + tomoyo_put_group(ptr->group); + else + tomoyo_put_name(ptr->filename); +} + +const struct tomoyo_path_info * +tomoyo_compare_name_union(const struct tomoyo_path_info *name, + const struct tomoyo_name_union *ptr) +{ + if (ptr->is_group) + return tomoyo_path_matches_group(name, ptr->group); + if (tomoyo_path_matches_pattern(name, ptr->filename)) + return ptr->filename; + return NULL; +} + +void tomoyo_put_number_union(struct tomoyo_number_union *ptr) +{ + if (ptr && ptr->is_group) + tomoyo_put_group(ptr->group); +} + +bool tomoyo_compare_number_union(const unsigned long value, + const struct tomoyo_number_union *ptr) +{ + if (ptr->is_group) + return tomoyo_number_matches_group(value, value, ptr->group); + return value >= ptr->values[0] && value <= ptr->values[1]; +} + +static void tomoyo_add_slash(struct tomoyo_path_info *buf) +{ + if (buf->is_dir) + return; + /* + * This is OK because tomoyo_encode() reserves space for appending "/". + */ + strcat((char *) buf->name, "/"); + tomoyo_fill_path_info(buf); +} + +/** + * tomoyo_strendswith - Check whether the token ends with the given token. + * + * @name: The token to check. + * @tail: The token to find. + * + * Returns true if @name ends with @tail, false otherwise. + */ +static bool tomoyo_strendswith(const char *name, const char *tail) +{ + int len; + + if (!name || !tail) + return false; + len = strlen(name) - strlen(tail); + return len >= 0 && !strcmp(name + len, tail); +} + +/** + * tomoyo_get_realpath - Get realpath. + * + * @buf: Pointer to "struct tomoyo_path_info". + * @path: Pointer to "struct path". + * + * Returns true on success, false otherwise. + */ +static bool tomoyo_get_realpath(struct tomoyo_path_info *buf, struct path *path) +{ + buf->name = tomoyo_realpath_from_path(path); + if (buf->name) { + tomoyo_fill_path_info(buf); + return true; + } + return false; +} + +/** + * tomoyo_audit_path_log - Audit path request log. + * + * @r: Pointer to "struct tomoyo_request_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_audit_path_log(struct tomoyo_request_info *r) +{ + const char *operation = tomoyo_path_keyword[r->param.path.operation]; + const struct tomoyo_path_info *filename = r->param.path.filename; + if (r->granted) + return 0; + tomoyo_warn_log(r, "%s %s", operation, filename->name); + return tomoyo_supervisor(r, "allow_%s %s\n", operation, + tomoyo_pattern(filename)); +} + +/** + * tomoyo_audit_path2_log - Audit path/path request log. + * + * @r: Pointer to "struct tomoyo_request_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_audit_path2_log(struct tomoyo_request_info *r) +{ + const char *operation = tomoyo_path2_keyword[r->param.path2.operation]; + const struct tomoyo_path_info *filename1 = r->param.path2.filename1; + const struct tomoyo_path_info *filename2 = r->param.path2.filename2; + if (r->granted) + return 0; + tomoyo_warn_log(r, "%s %s %s", operation, filename1->name, + filename2->name); + return tomoyo_supervisor(r, "allow_%s %s %s\n", operation, + tomoyo_pattern(filename1), + tomoyo_pattern(filename2)); +} + +/** + * tomoyo_audit_mkdev_log - Audit path/number/number/number request log. + * + * @r: Pointer to "struct tomoyo_request_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_audit_mkdev_log(struct tomoyo_request_info *r) +{ + const char *operation = tomoyo_mkdev_keyword[r->param.mkdev.operation]; + const struct tomoyo_path_info *filename = r->param.mkdev.filename; + const unsigned int major = r->param.mkdev.major; + const unsigned int minor = r->param.mkdev.minor; + const unsigned int mode = r->param.mkdev.mode; + if (r->granted) + return 0; + tomoyo_warn_log(r, "%s %s 0%o %u %u", operation, filename->name, mode, + major, minor); + return tomoyo_supervisor(r, "allow_%s %s 0%o %u %u\n", operation, + tomoyo_pattern(filename), mode, major, minor); +} + +/** + * tomoyo_audit_path_number_log - Audit path/number request log. + * + * @r: Pointer to "struct tomoyo_request_info". + * @error: Error code. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_audit_path_number_log(struct tomoyo_request_info *r) +{ + const u8 type = r->param.path_number.operation; + u8 radix; + const struct tomoyo_path_info *filename = r->param.path_number.filename; + const char *operation = tomoyo_path_number_keyword[type]; + char buffer[64]; + if (r->granted) + return 0; + switch (type) { + case TOMOYO_TYPE_CREATE: + case TOMOYO_TYPE_MKDIR: + case TOMOYO_TYPE_MKFIFO: + case TOMOYO_TYPE_MKSOCK: + case TOMOYO_TYPE_CHMOD: + radix = TOMOYO_VALUE_TYPE_OCTAL; + break; + case TOMOYO_TYPE_IOCTL: + radix = TOMOYO_VALUE_TYPE_HEXADECIMAL; + break; + default: + radix = TOMOYO_VALUE_TYPE_DECIMAL; + break; + } + tomoyo_print_ulong(buffer, sizeof(buffer), r->param.path_number.number, + radix); + tomoyo_warn_log(r, "%s %s %s", operation, filename->name, buffer); + return tomoyo_supervisor(r, "allow_%s %s %s\n", operation, + tomoyo_pattern(filename), buffer); +} + +static bool tomoyo_same_globally_readable(const struct tomoyo_acl_head *a, + const struct tomoyo_acl_head *b) +{ + return container_of(a, struct tomoyo_readable_file, + head)->filename == + container_of(b, struct tomoyo_readable_file, + head)->filename; +} + +/** + * tomoyo_update_globally_readable_entry - Update "struct tomoyo_readable_file" list. + * + * @filename: Filename unconditionally permitted to open() for reading. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_update_globally_readable_entry(const char *filename, + const bool is_delete) +{ + struct tomoyo_readable_file e = { }; + int error; + + if (!tomoyo_correct_word(filename)) + return -EINVAL; + e.filename = tomoyo_get_name(filename); + if (!e.filename) + return -ENOMEM; + error = tomoyo_update_policy(&e.head, sizeof(e), is_delete, + &tomoyo_policy_list + [TOMOYO_ID_GLOBALLY_READABLE], + tomoyo_same_globally_readable); + tomoyo_put_name(e.filename); + return error; +} + +/** + * tomoyo_globally_readable_file - Check if the file is unconditionnaly permitted to be open()ed for reading. + * + * @filename: The filename to check. + * + * Returns true if any domain can open @filename for reading, false otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static bool tomoyo_globally_readable_file(const struct tomoyo_path_info * + filename) +{ + struct tomoyo_readable_file *ptr; + bool found = false; + + list_for_each_entry_rcu(ptr, &tomoyo_policy_list + [TOMOYO_ID_GLOBALLY_READABLE], head.list) { + if (!ptr->head.is_deleted && + tomoyo_path_matches_pattern(filename, ptr->filename)) { + found = true; + break; + } + } + return found; +} + +/** + * tomoyo_write_globally_readable - Write "struct tomoyo_readable_file" list. + * + * @data: String to parse. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_write_globally_readable(char *data, const bool is_delete) +{ + return tomoyo_update_globally_readable_entry(data, is_delete); +} + +static bool tomoyo_same_pattern(const struct tomoyo_acl_head *a, + const struct tomoyo_acl_head *b) +{ + return container_of(a, struct tomoyo_no_pattern, head)->pattern == + container_of(b, struct tomoyo_no_pattern, head)->pattern; +} + +/** + * tomoyo_update_file_pattern_entry - Update "struct tomoyo_no_pattern" list. + * + * @pattern: Pathname pattern. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_update_file_pattern_entry(const char *pattern, + const bool is_delete) +{ + struct tomoyo_no_pattern e = { }; + int error; + + if (!tomoyo_correct_word(pattern)) + return -EINVAL; + e.pattern = tomoyo_get_name(pattern); + if (!e.pattern) + return -ENOMEM; + error = tomoyo_update_policy(&e.head, sizeof(e), is_delete, + &tomoyo_policy_list[TOMOYO_ID_PATTERN], + tomoyo_same_pattern); + tomoyo_put_name(e.pattern); + return error; +} + +/** + * tomoyo_pattern - Get patterned pathname. + * + * @filename: The filename to find patterned pathname. + * + * Returns pointer to pathname pattern if matched, @filename otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +const char *tomoyo_pattern(const struct tomoyo_path_info *filename) +{ + struct tomoyo_no_pattern *ptr; + const struct tomoyo_path_info *pattern = NULL; + + list_for_each_entry_rcu(ptr, &tomoyo_policy_list[TOMOYO_ID_PATTERN], + head.list) { + if (ptr->head.is_deleted) + continue; + if (!tomoyo_path_matches_pattern(filename, ptr->pattern)) + continue; + pattern = ptr->pattern; + if (tomoyo_strendswith(pattern->name, "/\\*")) { + /* Do nothing. Try to find the better match. */ + } else { + /* This would be the better match. Use this. */ + break; + } + } + if (pattern) + filename = pattern; + return filename->name; +} + +/** + * tomoyo_write_pattern - Write "struct tomoyo_no_pattern" list. + * + * @data: String to parse. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_write_pattern(char *data, const bool is_delete) +{ + return tomoyo_update_file_pattern_entry(data, is_delete); +} + +static bool tomoyo_same_no_rewrite(const struct tomoyo_acl_head *a, + const struct tomoyo_acl_head *b) +{ + return container_of(a, struct tomoyo_no_rewrite, head)->pattern + == container_of(b, struct tomoyo_no_rewrite, head) + ->pattern; +} + +/** + * tomoyo_update_no_rewrite_entry - Update "struct tomoyo_no_rewrite" list. + * + * @pattern: Pathname pattern that are not rewritable by default. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_update_no_rewrite_entry(const char *pattern, + const bool is_delete) +{ + struct tomoyo_no_rewrite e = { }; + int error; + + if (!tomoyo_correct_word(pattern)) + return -EINVAL; + e.pattern = tomoyo_get_name(pattern); + if (!e.pattern) + return -ENOMEM; + error = tomoyo_update_policy(&e.head, sizeof(e), is_delete, + &tomoyo_policy_list[TOMOYO_ID_NO_REWRITE], + tomoyo_same_no_rewrite); + tomoyo_put_name(e.pattern); + return error; +} + +/** + * tomoyo_no_rewrite_file - Check if the given pathname is not permitted to be rewrited. + * + * @filename: Filename to check. + * + * Returns true if @filename is specified by "deny_rewrite" directive, + * false otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static bool tomoyo_no_rewrite_file(const struct tomoyo_path_info *filename) +{ + struct tomoyo_no_rewrite *ptr; + bool found = false; + + list_for_each_entry_rcu(ptr, &tomoyo_policy_list[TOMOYO_ID_NO_REWRITE], + head.list) { + if (ptr->head.is_deleted) + continue; + if (!tomoyo_path_matches_pattern(filename, ptr->pattern)) + continue; + found = true; + break; + } + return found; +} + +/** + * tomoyo_write_no_rewrite - Write "struct tomoyo_no_rewrite" list. + * + * @data: String to parse. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_write_no_rewrite(char *data, const bool is_delete) +{ + return tomoyo_update_no_rewrite_entry(data, is_delete); +} + +static bool tomoyo_check_path_acl(struct tomoyo_request_info *r, + const struct tomoyo_acl_info *ptr) +{ + const struct tomoyo_path_acl *acl = container_of(ptr, typeof(*acl), + head); + if (acl->perm & (1 << r->param.path.operation)) { + r->param.path.matched_path = + tomoyo_compare_name_union(r->param.path.filename, + &acl->name); + return r->param.path.matched_path != NULL; + } + return false; +} + +static bool tomoyo_check_path_number_acl(struct tomoyo_request_info *r, + const struct tomoyo_acl_info *ptr) +{ + const struct tomoyo_path_number_acl *acl = + container_of(ptr, typeof(*acl), head); + return (acl->perm & (1 << r->param.path_number.operation)) && + tomoyo_compare_number_union(r->param.path_number.number, + &acl->number) && + tomoyo_compare_name_union(r->param.path_number.filename, + &acl->name); +} + +static bool tomoyo_check_path2_acl(struct tomoyo_request_info *r, + const struct tomoyo_acl_info *ptr) +{ + const struct tomoyo_path2_acl *acl = + container_of(ptr, typeof(*acl), head); + return (acl->perm & (1 << r->param.path2.operation)) && + tomoyo_compare_name_union(r->param.path2.filename1, &acl->name1) + && tomoyo_compare_name_union(r->param.path2.filename2, + &acl->name2); +} + +static bool tomoyo_check_mkdev_acl(struct tomoyo_request_info *r, + const struct tomoyo_acl_info *ptr) +{ + const struct tomoyo_mkdev_acl *acl = + container_of(ptr, typeof(*acl), head); + return (acl->perm & (1 << r->param.mkdev.operation)) && + tomoyo_compare_number_union(r->param.mkdev.mode, + &acl->mode) && + tomoyo_compare_number_union(r->param.mkdev.major, + &acl->major) && + tomoyo_compare_number_union(r->param.mkdev.minor, + &acl->minor) && + tomoyo_compare_name_union(r->param.mkdev.filename, + &acl->name); +} + +static bool tomoyo_same_path_acl(const struct tomoyo_acl_info *a, + const struct tomoyo_acl_info *b) +{ + const struct tomoyo_path_acl *p1 = container_of(a, typeof(*p1), head); + const struct tomoyo_path_acl *p2 = container_of(b, typeof(*p2), head); + return tomoyo_same_acl_head(&p1->head, &p2->head) && + tomoyo_same_name_union(&p1->name, &p2->name); +} + +static bool tomoyo_merge_path_acl(struct tomoyo_acl_info *a, + struct tomoyo_acl_info *b, + const bool is_delete) +{ + u16 * const a_perm = &container_of(a, struct tomoyo_path_acl, head) + ->perm; + u16 perm = *a_perm; + const u16 b_perm = container_of(b, struct tomoyo_path_acl, head)->perm; + if (is_delete) { + perm &= ~b_perm; + if ((perm & TOMOYO_RW_MASK) != TOMOYO_RW_MASK) + perm &= ~(1 << TOMOYO_TYPE_READ_WRITE); + else if (!(perm & (1 << TOMOYO_TYPE_READ_WRITE))) + perm &= ~TOMOYO_RW_MASK; + } else { + perm |= b_perm; + if ((perm & TOMOYO_RW_MASK) == TOMOYO_RW_MASK) + perm |= (1 << TOMOYO_TYPE_READ_WRITE); + else if (perm & (1 << TOMOYO_TYPE_READ_WRITE)) + perm |= TOMOYO_RW_MASK; + } + *a_perm = perm; + return !perm; +} + +/** + * tomoyo_update_path_acl - Update "struct tomoyo_path_acl" list. + * + * @type: Type of operation. + * @filename: Filename. + * @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(). + */ +static int tomoyo_update_path_acl(const u8 type, const char *filename, + struct tomoyo_domain_info * const domain, + const bool is_delete) +{ + struct tomoyo_path_acl e = { + .head.type = TOMOYO_TYPE_PATH_ACL, + .perm = 1 << type + }; + int error; + if (e.perm == (1 << TOMOYO_TYPE_READ_WRITE)) + e.perm |= TOMOYO_RW_MASK; + if (!tomoyo_parse_name_union(filename, &e.name)) + return -EINVAL; + error = tomoyo_update_domain(&e.head, sizeof(e), is_delete, domain, + tomoyo_same_path_acl, + tomoyo_merge_path_acl); + tomoyo_put_name_union(&e.name); + return error; +} + +static bool tomoyo_same_mkdev_acl(const struct tomoyo_acl_info *a, + const struct tomoyo_acl_info *b) +{ + const struct tomoyo_mkdev_acl *p1 = container_of(a, typeof(*p1), + head); + const struct tomoyo_mkdev_acl *p2 = container_of(b, typeof(*p2), + head); + return tomoyo_same_acl_head(&p1->head, &p2->head) + && tomoyo_same_name_union(&p1->name, &p2->name) + && tomoyo_same_number_union(&p1->mode, &p2->mode) + && tomoyo_same_number_union(&p1->major, &p2->major) + && tomoyo_same_number_union(&p1->minor, &p2->minor); +} + +static bool tomoyo_merge_mkdev_acl(struct tomoyo_acl_info *a, + struct tomoyo_acl_info *b, + const bool is_delete) +{ + u8 *const a_perm = &container_of(a, struct tomoyo_mkdev_acl, + head)->perm; + u8 perm = *a_perm; + const u8 b_perm = container_of(b, struct tomoyo_mkdev_acl, head) + ->perm; + if (is_delete) + perm &= ~b_perm; + else + perm |= b_perm; + *a_perm = perm; + return !perm; +} + +/** + * tomoyo_update_mkdev_acl - Update "struct tomoyo_mkdev_acl" list. + * + * @type: Type of operation. + * @filename: Filename. + * @mode: Create mode. + * @major: Device major number. + * @minor: Device minor number. + * @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(). + */ +static int tomoyo_update_mkdev_acl(const u8 type, const char *filename, + char *mode, char *major, char *minor, + struct tomoyo_domain_info * const + domain, const bool is_delete) +{ + struct tomoyo_mkdev_acl e = { + .head.type = TOMOYO_TYPE_MKDEV_ACL, + .perm = 1 << type + }; + int error = is_delete ? -ENOENT : -ENOMEM; + if (!tomoyo_parse_name_union(filename, &e.name) || + !tomoyo_parse_number_union(mode, &e.mode) || + !tomoyo_parse_number_union(major, &e.major) || + !tomoyo_parse_number_union(minor, &e.minor)) + goto out; + error = tomoyo_update_domain(&e.head, sizeof(e), is_delete, domain, + tomoyo_same_mkdev_acl, + tomoyo_merge_mkdev_acl); + out: + tomoyo_put_name_union(&e.name); + tomoyo_put_number_union(&e.mode); + tomoyo_put_number_union(&e.major); + tomoyo_put_number_union(&e.minor); + return error; +} + +static bool tomoyo_same_path2_acl(const struct tomoyo_acl_info *a, + const struct tomoyo_acl_info *b) +{ + const struct tomoyo_path2_acl *p1 = container_of(a, typeof(*p1), head); + const struct tomoyo_path2_acl *p2 = container_of(b, typeof(*p2), head); + return tomoyo_same_acl_head(&p1->head, &p2->head) + && tomoyo_same_name_union(&p1->name1, &p2->name1) + && tomoyo_same_name_union(&p1->name2, &p2->name2); +} + +static bool tomoyo_merge_path2_acl(struct tomoyo_acl_info *a, + struct tomoyo_acl_info *b, + const bool is_delete) +{ + u8 * const a_perm = &container_of(a, struct tomoyo_path2_acl, head) + ->perm; + u8 perm = *a_perm; + const u8 b_perm = container_of(b, struct tomoyo_path2_acl, head)->perm; + if (is_delete) + perm &= ~b_perm; + else + perm |= b_perm; + *a_perm = perm; + return !perm; +} + +/** + * tomoyo_update_path2_acl - Update "struct tomoyo_path2_acl" list. + * + * @type: Type of operation. + * @filename1: First filename. + * @filename2: Second filename. + * @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(). + */ +static int tomoyo_update_path2_acl(const u8 type, const char *filename1, + const char *filename2, + struct tomoyo_domain_info * const domain, + const bool is_delete) +{ + struct tomoyo_path2_acl e = { + .head.type = TOMOYO_TYPE_PATH2_ACL, + .perm = 1 << type + }; + int error = is_delete ? -ENOENT : -ENOMEM; + if (!tomoyo_parse_name_union(filename1, &e.name1) || + !tomoyo_parse_name_union(filename2, &e.name2)) + goto out; + error = tomoyo_update_domain(&e.head, sizeof(e), is_delete, domain, + tomoyo_same_path2_acl, + tomoyo_merge_path2_acl); + out: + tomoyo_put_name_union(&e.name1); + tomoyo_put_name_union(&e.name2); + return error; +} + +/** + * tomoyo_path_permission - Check permission for single path operation. + * + * @r: Pointer to "struct tomoyo_request_info". + * @operation: Type of operation. + * @filename: Filename to check. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_path_permission(struct tomoyo_request_info *r, u8 operation, + const struct tomoyo_path_info *filename) +{ + int error; + + next: + r->type = tomoyo_p2mac[operation]; + r->mode = tomoyo_get_mode(r->profile, r->type); + if (r->mode == TOMOYO_CONFIG_DISABLED) + return 0; + r->param_type = TOMOYO_TYPE_PATH_ACL; + r->param.path.filename = filename; + r->param.path.operation = operation; + do { + tomoyo_check_acl(r, tomoyo_check_path_acl); + if (!r->granted && operation == TOMOYO_TYPE_READ && + !r->domain->ignore_global_allow_read && + tomoyo_globally_readable_file(filename)) + r->granted = true; + error = tomoyo_audit_path_log(r); + /* + * Do not retry for execute request, for alias may have + * changed. + */ + } while (error == TOMOYO_RETRY_REQUEST && + operation != TOMOYO_TYPE_EXECUTE); + /* + * Since "allow_truncate" doesn't imply "allow_rewrite" permission, + * we need to check "allow_rewrite" permission if the filename is + * specified by "deny_rewrite" keyword. + */ + if (!error && operation == TOMOYO_TYPE_TRUNCATE && + tomoyo_no_rewrite_file(filename)) { + operation = TOMOYO_TYPE_REWRITE; + goto next; + } + return error; +} + +static bool tomoyo_same_path_number_acl(const struct tomoyo_acl_info *a, + const struct tomoyo_acl_info *b) +{ + const struct tomoyo_path_number_acl *p1 = container_of(a, typeof(*p1), + head); + const struct tomoyo_path_number_acl *p2 = container_of(b, typeof(*p2), + head); + return tomoyo_same_acl_head(&p1->head, &p2->head) + && tomoyo_same_name_union(&p1->name, &p2->name) + && tomoyo_same_number_union(&p1->number, &p2->number); +} + +static bool tomoyo_merge_path_number_acl(struct tomoyo_acl_info *a, + struct tomoyo_acl_info *b, + const bool is_delete) +{ + u8 * const a_perm = &container_of(a, struct tomoyo_path_number_acl, + head)->perm; + u8 perm = *a_perm; + const u8 b_perm = container_of(b, struct tomoyo_path_number_acl, head) + ->perm; + if (is_delete) + perm &= ~b_perm; + else + perm |= b_perm; + *a_perm = perm; + return !perm; +} + +/** + * tomoyo_update_path_number_acl - Update ioctl/chmod/chown/chgrp ACL. + * + * @type: Type of operation. + * @filename: Filename. + * @number: Number. + * @domain: Pointer to "struct tomoyo_domain_info". + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_update_path_number_acl(const u8 type, const char *filename, + char *number, + struct tomoyo_domain_info * const + domain, + const bool is_delete) +{ + struct tomoyo_path_number_acl e = { + .head.type = TOMOYO_TYPE_PATH_NUMBER_ACL, + .perm = 1 << type + }; + int error = is_delete ? -ENOENT : -ENOMEM; + if (!tomoyo_parse_name_union(filename, &e.name)) + return -EINVAL; + if (!tomoyo_parse_number_union(number, &e.number)) + goto out; + error = tomoyo_update_domain(&e.head, sizeof(e), is_delete, domain, + tomoyo_same_path_number_acl, + tomoyo_merge_path_number_acl); + out: + tomoyo_put_name_union(&e.name); + tomoyo_put_number_union(&e.number); + return error; +} + +/** + * tomoyo_path_number_perm - Check permission for "create", "mkdir", "mkfifo", "mksock", "ioctl", "chmod", "chown", "chgrp". + * + * @type: Type of operation. + * @path: Pointer to "struct path". + * @number: Number. + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_path_number_perm(const u8 type, struct path *path, + unsigned long number) +{ + struct tomoyo_request_info r; + int error = -ENOMEM; + struct tomoyo_path_info buf; + int idx; + + if (tomoyo_init_request_info(&r, NULL, tomoyo_pn2mac[type]) + == TOMOYO_CONFIG_DISABLED || !path->mnt || !path->dentry) + return 0; + idx = tomoyo_read_lock(); + if (!tomoyo_get_realpath(&buf, path)) + goto out; + if (type == TOMOYO_TYPE_MKDIR) + tomoyo_add_slash(&buf); + r.param_type = TOMOYO_TYPE_PATH_NUMBER_ACL; + r.param.path_number.operation = type; + r.param.path_number.filename = &buf; + r.param.path_number.number = number; + do { + tomoyo_check_acl(&r, tomoyo_check_path_number_acl); + error = tomoyo_audit_path_number_log(&r); + } while (error == TOMOYO_RETRY_REQUEST); + kfree(buf.name); + out: + tomoyo_read_unlock(idx); + if (r.mode != TOMOYO_CONFIG_ENFORCING) + error = 0; + return error; +} + +/** + * tomoyo_check_open_permission - Check permission for "read" and "write". + * + * @domain: Pointer to "struct tomoyo_domain_info". + * @path: Pointer to "struct path". + * @flag: Flags for open(). + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_check_open_permission(struct tomoyo_domain_info *domain, + struct path *path, const int flag) +{ + const u8 acc_mode = ACC_MODE(flag); + int error = 0; + struct tomoyo_path_info buf; + struct tomoyo_request_info r; + int idx; + + if (!path->mnt || + (path->dentry->d_inode && S_ISDIR(path->dentry->d_inode->i_mode))) + return 0; + buf.name = NULL; + r.mode = TOMOYO_CONFIG_DISABLED; + idx = tomoyo_read_lock(); + /* + * If the filename is specified by "deny_rewrite" keyword, + * we need to check "allow_rewrite" permission when the filename is not + * opened for append mode or the filename is truncated at open time. + */ + if ((acc_mode & MAY_WRITE) && !(flag & O_APPEND) + && tomoyo_init_request_info(&r, domain, TOMOYO_MAC_FILE_REWRITE) + != TOMOYO_CONFIG_DISABLED) { + if (!tomoyo_get_realpath(&buf, path)) { + error = -ENOMEM; + goto out; + } + if (tomoyo_no_rewrite_file(&buf)) + error = tomoyo_path_permission(&r, TOMOYO_TYPE_REWRITE, + &buf); + } + if (!error && acc_mode && + tomoyo_init_request_info(&r, domain, TOMOYO_MAC_FILE_OPEN) + != TOMOYO_CONFIG_DISABLED) { + u8 operation; + if (!buf.name && !tomoyo_get_realpath(&buf, path)) { + error = -ENOMEM; + goto out; + } + if (acc_mode == (MAY_READ | MAY_WRITE)) + operation = TOMOYO_TYPE_READ_WRITE; + else if (acc_mode == MAY_READ) + operation = TOMOYO_TYPE_READ; + else + operation = TOMOYO_TYPE_WRITE; + error = tomoyo_path_permission(&r, operation, &buf); + } + out: + kfree(buf.name); + tomoyo_read_unlock(idx); + if (r.mode != TOMOYO_CONFIG_ENFORCING) + error = 0; + return error; +} + +/** + * tomoyo_path_perm - Check permission for "unlink", "rmdir", "truncate", "symlink", "rewrite", "chroot" and "unmount". + * + * @operation: Type of operation. + * @path: Pointer to "struct path". + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_path_perm(const u8 operation, struct path *path) +{ + int error = -ENOMEM; + struct tomoyo_path_info buf; + struct tomoyo_request_info r; + int idx; + + if (!path->mnt) + return 0; + if (tomoyo_init_request_info(&r, NULL, tomoyo_p2mac[operation]) + == TOMOYO_CONFIG_DISABLED) + return 0; + buf.name = NULL; + idx = tomoyo_read_lock(); + if (!tomoyo_get_realpath(&buf, path)) + goto out; + switch (operation) { + case TOMOYO_TYPE_REWRITE: + if (!tomoyo_no_rewrite_file(&buf)) { + error = 0; + goto out; + } + break; + case TOMOYO_TYPE_RMDIR: + case TOMOYO_TYPE_CHROOT: + tomoyo_add_slash(&buf); + break; + } + error = tomoyo_path_permission(&r, operation, &buf); + out: + kfree(buf.name); + tomoyo_read_unlock(idx); + if (r.mode != TOMOYO_CONFIG_ENFORCING) + error = 0; + return error; +} + +/** + * tomoyo_mkdev_perm - Check permission for "mkblock" and "mkchar". + * + * @operation: Type of operation. (TOMOYO_TYPE_MKCHAR or TOMOYO_TYPE_MKBLOCK) + * @path: Pointer to "struct path". + * @mode: Create mode. + * @dev: Device number. + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_mkdev_perm(const u8 operation, struct path *path, + const unsigned int mode, unsigned int dev) +{ + struct tomoyo_request_info r; + int error = -ENOMEM; + struct tomoyo_path_info buf; + int idx; + + if (!path->mnt || + tomoyo_init_request_info(&r, NULL, tomoyo_pnnn2mac[operation]) + == TOMOYO_CONFIG_DISABLED) + return 0; + idx = tomoyo_read_lock(); + error = -ENOMEM; + if (tomoyo_get_realpath(&buf, path)) { + dev = new_decode_dev(dev); + r.param_type = TOMOYO_TYPE_MKDEV_ACL; + r.param.mkdev.filename = &buf; + r.param.mkdev.operation = operation; + r.param.mkdev.mode = mode; + r.param.mkdev.major = MAJOR(dev); + r.param.mkdev.minor = MINOR(dev); + tomoyo_check_acl(&r, tomoyo_check_mkdev_acl); + error = tomoyo_audit_mkdev_log(&r); + kfree(buf.name); + } + tomoyo_read_unlock(idx); + if (r.mode != TOMOYO_CONFIG_ENFORCING) + error = 0; + return error; +} + +/** + * tomoyo_path2_perm - Check permission for "rename", "link" and "pivot_root". + * + * @operation: Type of operation. + * @path1: Pointer to "struct path". + * @path2: Pointer to "struct path". + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_path2_perm(const u8 operation, struct path *path1, + struct path *path2) +{ + int error = -ENOMEM; + struct tomoyo_path_info buf1; + struct tomoyo_path_info buf2; + struct tomoyo_request_info r; + int idx; + + if (!path1->mnt || !path2->mnt || + tomoyo_init_request_info(&r, NULL, tomoyo_pp2mac[operation]) + == TOMOYO_CONFIG_DISABLED) + return 0; + buf1.name = NULL; + buf2.name = NULL; + idx = tomoyo_read_lock(); + if (!tomoyo_get_realpath(&buf1, path1) || + !tomoyo_get_realpath(&buf2, path2)) + goto out; + switch (operation) { + struct dentry *dentry; + case TOMOYO_TYPE_RENAME: + case TOMOYO_TYPE_LINK: + dentry = path1->dentry; + if (!dentry->d_inode || !S_ISDIR(dentry->d_inode->i_mode)) + break; + /* fall through */ + case TOMOYO_TYPE_PIVOT_ROOT: + tomoyo_add_slash(&buf1); + tomoyo_add_slash(&buf2); + break; + } + r.param_type = TOMOYO_TYPE_PATH2_ACL; + r.param.path2.operation = operation; + r.param.path2.filename1 = &buf1; + r.param.path2.filename2 = &buf2; + do { + tomoyo_check_acl(&r, tomoyo_check_path2_acl); + error = tomoyo_audit_path2_log(&r); + } while (error == TOMOYO_RETRY_REQUEST); + out: + kfree(buf1.name); + kfree(buf2.name); + tomoyo_read_unlock(idx); + if (r.mode != TOMOYO_CONFIG_ENFORCING) + error = 0; + return error; +} + +/** + * tomoyo_write_file - Update file related 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_file(char *data, struct tomoyo_domain_info *domain, + const bool is_delete) +{ + char *w[5]; + u8 type; + if (!tomoyo_tokenize(data, w, sizeof(w)) || !w[1][0]) + return -EINVAL; + if (strncmp(w[0], "allow_", 6)) + goto out; + w[0] += 6; + for (type = 0; type < TOMOYO_MAX_PATH_OPERATION; type++) { + if (strcmp(w[0], tomoyo_path_keyword[type])) + continue; + return tomoyo_update_path_acl(type, w[1], domain, is_delete); + } + if (!w[2][0]) + goto out; + for (type = 0; type < TOMOYO_MAX_PATH2_OPERATION; type++) { + if (strcmp(w[0], tomoyo_path2_keyword[type])) + continue; + return tomoyo_update_path2_acl(type, w[1], w[2], domain, + is_delete); + } + for (type = 0; type < TOMOYO_MAX_PATH_NUMBER_OPERATION; type++) { + if (strcmp(w[0], tomoyo_path_number_keyword[type])) + continue; + return tomoyo_update_path_number_acl(type, w[1], w[2], domain, + is_delete); + } + if (!w[3][0] || !w[4][0]) + goto out; + for (type = 0; type < TOMOYO_MAX_MKDEV_OPERATION; type++) { + if (strcmp(w[0], tomoyo_mkdev_keyword[type])) + continue; + return tomoyo_update_mkdev_acl(type, w[1], w[2], w[3], + w[4], domain, is_delete); + } + out: + return -EINVAL; +} diff --git a/security/tomoyo/gc.c b/security/tomoyo/gc.c new file mode 100644 index 00000000..a877e4c3 --- /dev/null +++ b/security/tomoyo/gc.c @@ -0,0 +1,354 @@ +/* + * security/tomoyo/gc.c + * + * Implementation of the Domain-Based Mandatory Access Control. + * + * Copyright (C) 2005-2010 NTT DATA CORPORATION + * + */ + +#include "common.h" +#include <linux/kthread.h> +#include <linux/slab.h> + +struct tomoyo_gc { + struct list_head list; + int type; + 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, struct list_head *element) +{ + 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 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 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 list_head *element) +{ + struct tomoyo_no_rewrite *ptr = + container_of(element, typeof(*ptr), head.list); + tomoyo_put_name(ptr->pattern); +} + +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_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->aggregated_name); +} + +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 list_head *element) +{ + struct tomoyo_acl_info *acl = + container_of(element, typeof(*acl), list); + switch (acl->type) { + case TOMOYO_TYPE_PATH_ACL: + { + struct tomoyo_path_acl *entry + = container_of(acl, typeof(*entry), head); + tomoyo_put_name_union(&entry->name); + } + break; + case TOMOYO_TYPE_PATH2_ACL: + { + struct tomoyo_path2_acl *entry + = container_of(acl, typeof(*entry), head); + tomoyo_put_name_union(&entry->name1); + tomoyo_put_name_union(&entry->name2); + } + break; + 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 list_head *element) +{ + struct tomoyo_domain_info *domain = + container_of(element, typeof(*domain), list); + struct tomoyo_acl_info *acl; + struct tomoyo_acl_info *tmp; + /* + * Since we don't protect whole execve() operation using SRCU, + * we need to recheck domain->users at this point. + * + * (1) Reader starts SRCU section upon execve(). + * (2) Reader traverses tomoyo_domain_list and finds this domain. + * (3) Writer marks this domain as deleted. + * (4) Garbage collector removes this domain from tomoyo_domain_list + * because this domain is marked as deleted and used by nobody. + * (5) Reader saves reference to this domain into + * "struct linux_binprm"->cred->security . + * (6) Reader finishes SRCU section, although execve() operation has + * not finished yet. + * (7) Garbage collector waits for SRCU synchronization. + * (8) Garbage collector kfree() this domain because this domain is + * used by nobody. + * (9) Reader finishes execve() operation and restores this domain from + * "struct linux_binprm"->cred->security. + * + * By updating domain->users at (5), we can solve this race problem + * by rechecking domain->users at (8). + */ + if (atomic_read(&domain->users)) + return false; + list_for_each_entry_safe(acl, tmp, &domain->acl_info_list, list) { + tomoyo_del_acl(&acl->list); + tomoyo_memory_free(acl); + } + tomoyo_put_name(domain->domainname); + return true; +} + + +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(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_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; + 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) { + if (!tomoyo_collect_acl(domain)) + goto unlock; + if (!domain->is_deleted || atomic_read(&domain->users)) + continue; + /* + * Nobody is referring this domain. But somebody may + * refer this domain after successful execve(). + * We recheck domain->users after SRCU synchronization. + */ + if (!tomoyo_add_to_gc(TOMOYO_ID_DOMAIN, &domain->list)) + goto unlock; + } + } + 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; + } + } + 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_GROUP, &group->list)) + goto unlock; + } + } + unlock: + mutex_unlock(&tomoyo_policy_lock); +} + +static void tomoyo_kfree_entry(void) +{ + 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_TRANSITION_CONTROL: + tomoyo_del_transition_control(element); + break; + case TOMOYO_ID_AGGREGATOR: + tomoyo_del_aggregator(element); + break; + case TOMOYO_ID_GLOBALLY_READABLE: + tomoyo_del_allow_read(element); + break; + case TOMOYO_ID_PATTERN: + tomoyo_del_file_pattern(element); + break; + case TOMOYO_ID_NO_REWRITE: + tomoyo_del_no_rewrite(element); + break; + case TOMOYO_ID_MANAGER: + tomoyo_del_manager(element); + break; + case TOMOYO_ID_NAME: + tomoyo_del_name(element); + break; + case TOMOYO_ID_ACL: + tomoyo_del_acl(element); + break; + case TOMOYO_ID_DOMAIN: + if (!tomoyo_del_domain(element)) + continue; + break; + case TOMOYO_ID_PATH_GROUP: + tomoyo_del_path_group(element); + break; + case TOMOYO_ID_GROUP: + tomoyo_del_group(element); + break; + case TOMOYO_ID_NUMBER_GROUP: + tomoyo_del_number_group(element); + break; + } + tomoyo_memory_free(element); + list_del(&p->list); + kfree(p); + } +} + +static int tomoyo_gc_thread(void *unused) +{ + daemonize("GC for TOMOYO"); + if (mutex_trylock(&tomoyo_gc_mutex)) { + int i; + for (i = 0; i < 10; i++) { + tomoyo_collect_entry(); + if (list_empty(&tomoyo_gc_queue)) + break; + synchronize_srcu(&tomoyo_ss); + tomoyo_kfree_entry(); + } + mutex_unlock(&tomoyo_gc_mutex); + } + do_exit(0); +} + +void tomoyo_run_gc(void) +{ + struct task_struct *task = kthread_create(tomoyo_gc_thread, NULL, + "GC for TOMOYO"); + if (!IS_ERR(task)) + wake_up_process(task); +} diff --git a/security/tomoyo/group.c b/security/tomoyo/group.c new file mode 100644 index 00000000..e94352ce --- /dev/null +++ b/security/tomoyo/group.c @@ -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; +} diff --git a/security/tomoyo/load_policy.c b/security/tomoyo/load_policy.c new file mode 100644 index 00000000..3312e562 --- /dev/null +++ b/security/tomoyo/load_policy.c @@ -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 every time. + */ + 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(); +} diff --git a/security/tomoyo/memory.c b/security/tomoyo/memory.c new file mode 100644 index 00000000..42a7b1ba --- /dev/null +++ b/security/tomoyo/memory.c @@ -0,0 +1,283 @@ +/* + * 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; + } + kfree(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; +} diff --git a/security/tomoyo/mount.c b/security/tomoyo/mount.c new file mode 100644 index 00000000..892494ac --- /dev/null +++ b/security/tomoyo/mount.c @@ -0,0 +1,287 @@ +/* + * 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 (!dev_name || kern_path(dev_name, LOOKUP_FOLLOW, &path)) { + error = -ENOENT; + goto out; + } + requested_dev_name = tomoyo_realpath_from_path(&path); + path_put(&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; + } else if (flags & MS_BIND) { + type = TOMOYO_MOUNT_BIND_KEYWORD; + flags &= ~MS_BIND; + } else if (flags & MS_SHARED) { + if (flags & (MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE)) + return -EINVAL; + type = TOMOYO_MOUNT_MAKE_SHARED_KEYWORD; + flags &= ~MS_SHARED; + } else if (flags & MS_PRIVATE) { + if (flags & (MS_SHARED | MS_SLAVE | MS_UNBINDABLE)) + return -EINVAL; + type = TOMOYO_MOUNT_MAKE_PRIVATE_KEYWORD; + flags &= ~MS_PRIVATE; + } else if (flags & MS_SLAVE) { + if (flags & (MS_SHARED | MS_PRIVATE | MS_UNBINDABLE)) + return -EINVAL; + type = TOMOYO_MOUNT_MAKE_SLAVE_KEYWORD; + flags &= ~MS_SLAVE; + } else if (flags & MS_UNBINDABLE) { + if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE)) + return -EINVAL; + type = TOMOYO_MOUNT_MAKE_UNBINDABLE_KEYWORD; + flags &= ~MS_UNBINDABLE; + } else if (flags & MS_MOVE) { + type = TOMOYO_MOUNT_MOVE_KEYWORD; + flags &= ~MS_MOVE; + } + 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; +} diff --git a/security/tomoyo/realpath.c b/security/tomoyo/realpath.c new file mode 100644 index 00000000..a339187c --- /dev/null +++ b/security/tomoyo/realpath.c @@ -0,0 +1,183 @@ +/* + * security/tomoyo/realpath.c + * + * 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/magic.h> +#include <linux/slab.h> +#include <net/sock.h> +#include "common.h" +#include "../../fs/internal.h" + +/** + * tomoyo_encode: Convert binary string to ascii string. + * + * @str: String in binary format. + * + * 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. + */ +char *tomoyo_encode(const char *str) +{ + int len = 0; + const char *p = str; + char *cp; + char *cp0; + + if (!p) + return NULL; + while (*p) { + const unsigned char c = *p++; + if (c == '\\') + len += 2; + else if (c > ' ' && c < 127) + len++; + else + len += 4; + } + 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++; + + 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'; + } + } + return cp0; +} + +/** + * tomoyo_realpath_from_path - Returns realpath(3) of the given pathname but ignores chroot'ed root. + * + * @path: Pointer to "struct path". + * + * 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 = NULL; + char *name = NULL; + unsigned int buf_len = PAGE_SIZE / 2; + struct dentry *dentry = path->dentry; + bool is_dir; + if (!dentry) + return NULL; + is_dir = dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode); + while (1) { + 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; + pos = d_absolute_path(path, buf, buf_len - 1); + /* If path is disconnected, use "[unknown]" instead. */ + if (pos == ERR_PTR(-EINVAL)) { + name = tomoyo_encode("[unknown]"); + break; + } + /* 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; + } + 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; +} + +/** + * tomoyo_realpath_nofollow - Get realpath of a pathname. + * + * @pathname: The pathname to solve. + * + * Returns the realpath of @pathname on success, NULL otherwise. + */ +char *tomoyo_realpath_nofollow(const char *pathname) +{ + struct path path; + + if (pathname && kern_path(pathname, 0, &path) == 0) { + char *buf = tomoyo_realpath_from_path(&path); + path_put(&path); + return buf; + } + return NULL; +} diff --git a/security/tomoyo/securityfs_if.c b/security/tomoyo/securityfs_if.c new file mode 100644 index 00000000..e43d5554 --- /dev/null +++ b/security/tomoyo/securityfs_if.c @@ -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); diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c new file mode 100644 index 00000000..95d3f957 --- /dev/null +++ b/security/tomoyo/tomoyo.c @@ -0,0 +1,289 @@ +/* + * security/tomoyo/tomoyo.c + * + * LSM hooks for TOMOYO Linux. + * + * Copyright (C) 2005-2010 NTT DATA CORPORATION + */ + +#include <linux/security.h> +#include "common.h" + +static int tomoyo_cred_alloc_blank(struct cred *new, gfp_t gfp) +{ + new->security = NULL; + return 0; +} + +static int tomoyo_cred_prepare(struct cred *new, const struct cred *old, + gfp_t gfp) +{ + struct tomoyo_domain_info *domain = old->security; + new->security = domain; + if (domain) + atomic_inc(&domain->users); + return 0; +} + +static void tomoyo_cred_transfer(struct cred *new, const struct cred *old) +{ + tomoyo_cred_prepare(new, old, 0); +} + +static void tomoyo_cred_free(struct cred *cred) +{ + struct tomoyo_domain_info *domain = cred->security; + if (domain) + atomic_dec(&domain->users); +} + +static int tomoyo_bprm_set_creds(struct linux_binprm *bprm) +{ + int rc; + + rc = cap_bprm_set_creds(bprm); + if (rc) + return rc; + + /* + * Do only if this function is called for the first time of an execve + * operation. + */ + if (bprm->cred_prepared) + return 0; + /* + * Load policy if /sbin/tomoyo-init exists and /sbin/init is requested + * for the first time. + */ + if (!tomoyo_policy_loaded) + tomoyo_load_policy(bprm->filename); + /* + * Release reference to "struct tomoyo_domain_info" stored inside + * "bprm->cred->security". New reference to "struct tomoyo_domain_info" + * stored inside "bprm->cred->security" will be acquired later inside + * tomoyo_find_next_domain(). + */ + atomic_dec(&((struct tomoyo_domain_info *) + bprm->cred->security)->users); + /* + * Tell tomoyo_bprm_check_security() is called for the first time of an + * execve operation. + */ + bprm->cred->security = NULL; + return 0; +} + +static int tomoyo_bprm_check_security(struct linux_binprm *bprm) +{ + struct tomoyo_domain_info *domain = bprm->cred->security; + + /* + * Execute permission is checked against pathname passed to do_execve() + * using current domain. + */ + if (!domain) { + const int idx = tomoyo_read_lock(); + const int err = tomoyo_find_next_domain(bprm); + tomoyo_read_unlock(idx); + return err; + } + /* + * Read permission is checked against interpreters using next domain. + */ + return tomoyo_check_open_permission(domain, &bprm->file->f_path, O_RDONLY); +} + +static int tomoyo_path_truncate(struct path *path) +{ + return tomoyo_path_perm(TOMOYO_TYPE_TRUNCATE, path); +} + +static int tomoyo_path_unlink(struct path *parent, struct dentry *dentry) +{ + struct path path = { parent->mnt, dentry }; + return tomoyo_path_perm(TOMOYO_TYPE_UNLINK, &path); +} + +static int tomoyo_path_mkdir(struct path *parent, struct dentry *dentry, + int mode) +{ + struct path path = { parent->mnt, dentry }; + return tomoyo_path_number_perm(TOMOYO_TYPE_MKDIR, &path, + mode & S_IALLUGO); +} + +static int tomoyo_path_rmdir(struct path *parent, struct dentry *dentry) +{ + struct path path = { parent->mnt, dentry }; + return tomoyo_path_perm(TOMOYO_TYPE_RMDIR, &path); +} + +static int tomoyo_path_symlink(struct path *parent, struct dentry *dentry, + const char *old_name) +{ + struct path path = { parent->mnt, dentry }; + return tomoyo_path_perm(TOMOYO_TYPE_SYMLINK, &path); +} + +static int tomoyo_path_mknod(struct path *parent, struct dentry *dentry, + int mode, unsigned int dev) +{ + 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: + type = TOMOYO_TYPE_MKCHAR; + break; + 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; + case S_IFSOCK: + type = TOMOYO_TYPE_MKSOCK; + break; + } + return tomoyo_path_number_perm(type, &path, perm); +} + +static int tomoyo_path_link(struct dentry *old_dentry, struct path *new_dir, + struct dentry *new_dentry) +{ + struct path path1 = { new_dir->mnt, old_dentry }; + struct path path2 = { new_dir->mnt, new_dentry }; + return tomoyo_path2_perm(TOMOYO_TYPE_LINK, &path1, &path2); +} + +static int tomoyo_path_rename(struct path *old_parent, + struct dentry *old_dentry, + struct path *new_parent, + struct dentry *new_dentry) +{ + struct path path1 = { old_parent->mnt, old_dentry }; + struct path path2 = { new_parent->mnt, new_dentry }; + return tomoyo_path2_perm(TOMOYO_TYPE_RENAME, &path1, &path2); +} + +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_path_perm(TOMOYO_TYPE_REWRITE, &file->f_path); + return 0; +} + +static int tomoyo_dentry_open(struct file *f, const struct cred *cred) +{ + int flags = f->f_flags; + /* Don't check read permission here if called from do_execve(). */ + if (current->in_execve) + return 0; + return tomoyo_check_open_permission(tomoyo_domain(), &f->f_path, flags); +} + +static int tomoyo_file_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + 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_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_number_perm(TOMOYO_TYPE_CHOWN, path, uid); + if (!error && gid != (gid_t) -1) + error = tomoyo_path_number_perm(TOMOYO_TYPE_CHGRP, path, gid); + return error; +} + +static int tomoyo_path_chroot(struct path *path) +{ + return tomoyo_path_perm(TOMOYO_TYPE_CHROOT, path); +} + +static int tomoyo_sb_mount(char *dev_name, struct path *path, + char *type, unsigned long flags, void *data) +{ + return tomoyo_mount_permission(dev_name, path, type, flags, data); +} + +static int tomoyo_sb_umount(struct vfsmount *mnt, int flags) +{ + struct path path = { mnt, mnt->mnt_root }; + return tomoyo_path_perm(TOMOYO_TYPE_UMOUNT, &path); +} + +static int tomoyo_sb_pivotroot(struct path *old_path, struct path *new_path) +{ + return tomoyo_path2_perm(TOMOYO_TYPE_PIVOT_ROOT, new_path, old_path); +} + +/* + * tomoyo_security_ops is a "struct security_operations" which is used for + * registering TOMOYO. + */ +static struct security_operations tomoyo_security_ops = { + .name = "tomoyo", + .cred_alloc_blank = tomoyo_cred_alloc_blank, + .cred_prepare = tomoyo_cred_prepare, + .cred_transfer = tomoyo_cred_transfer, + .cred_free = tomoyo_cred_free, + .bprm_set_creds = tomoyo_bprm_set_creds, + .bprm_check_security = tomoyo_bprm_check_security, + .file_fcntl = tomoyo_file_fcntl, + .dentry_open = tomoyo_dentry_open, + .path_truncate = tomoyo_path_truncate, + .path_unlink = tomoyo_path_unlink, + .path_mkdir = tomoyo_path_mkdir, + .path_rmdir = tomoyo_path_rmdir, + .path_symlink = tomoyo_path_symlink, + .path_mknod = tomoyo_path_mknod, + .path_link = tomoyo_path_link, + .path_rename = tomoyo_path_rename, + .file_ioctl = tomoyo_file_ioctl, + .path_chmod = tomoyo_path_chmod, + .path_chown = tomoyo_path_chown, + .path_chroot = tomoyo_path_chroot, + .sb_mount = tomoyo_sb_mount, + .sb_umount = tomoyo_sb_umount, + .sb_pivotroot = tomoyo_sb_pivotroot, +}; + +/* Lock for GC. */ +struct srcu_struct tomoyo_ss; + +static int __init tomoyo_init(void) +{ + struct cred *cred = (struct cred *) current_cred(); + + if (!security_module_enable(&tomoyo_security_ops)) + return 0; + /* register ourselves with the security framework */ + if (register_security(&tomoyo_security_ops) || + init_srcu_struct(&tomoyo_ss)) + panic("Failure registering TOMOYO Linux"); + printk(KERN_INFO "TOMOYO Linux initialized\n"); + cred->security = &tomoyo_kernel_domain; + tomoyo_mm_init(); + return 0; +} + +security_initcall(tomoyo_init); diff --git a/security/tomoyo/util.c b/security/tomoyo/util.c new file mode 100644 index 00000000..6d539320 --- /dev/null +++ b/security/tomoyo/util.c @@ -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)) + 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; +} |