summaryrefslogtreecommitdiffstats
path: root/security
diff options
context:
space:
mode:
Diffstat (limited to 'security')
-rw-r--r--security/Kconfig6
-rw-r--r--security/Makefile2
-rw-r--r--security/apparmor/.gitignore6
-rw-r--r--security/apparmor/Kconfig31
-rw-r--r--security/apparmor/Makefile24
-rw-r--r--security/apparmor/apparmorfs.c244
-rw-r--r--security/apparmor/audit.c215
-rw-r--r--security/apparmor/capability.c141
-rw-r--r--security/apparmor/context.c216
-rw-r--r--security/apparmor/domain.c823
-rw-r--r--security/apparmor/file.c457
-rw-r--r--security/apparmor/include/apparmor.h92
-rw-r--r--security/apparmor/include/apparmorfs.h20
-rw-r--r--security/apparmor/include/audit.h123
-rw-r--r--security/apparmor/include/capability.h45
-rw-r--r--security/apparmor/include/context.h154
-rw-r--r--security/apparmor/include/domain.h36
-rw-r--r--security/apparmor/include/file.h217
-rw-r--r--security/apparmor/include/ipc.h28
-rw-r--r--security/apparmor/include/match.h132
-rw-r--r--security/apparmor/include/path.h31
-rw-r--r--security/apparmor/include/policy.h305
-rw-r--r--security/apparmor/include/policy_unpack.h20
-rw-r--r--security/apparmor/include/procattr.h26
-rw-r--r--security/apparmor/include/resource.h46
-rw-r--r--security/apparmor/include/sid.h24
-rw-r--r--security/apparmor/ipc.c114
-rw-r--r--security/apparmor/lib.c133
-rw-r--r--security/apparmor/lsm.c950
-rw-r--r--security/apparmor/match.c353
-rw-r--r--security/apparmor/path.c212
-rw-r--r--security/apparmor/policy.c1186
-rw-r--r--security/apparmor/policy_unpack.c703
-rw-r--r--security/apparmor/procattr.c170
-rw-r--r--security/apparmor/resource.c138
-rw-r--r--security/apparmor/sid.c55
-rw-r--r--security/capability.c25
-rw-r--r--security/commoncap.c7
-rw-r--r--security/inode.c6
-rw-r--r--security/integrity/ima/ima.h19
-rw-r--r--security/integrity/ima/ima_api.c2
-rw-r--r--security/integrity/ima/ima_fs.c9
-rw-r--r--security/integrity/ima/ima_iint.c162
-rw-r--r--security/integrity/ima/ima_main.c188
-rw-r--r--security/keys/internal.h10
-rw-r--r--security/keys/keyctl.c35
-rw-r--r--security/keys/proc.c20
-rw-r--r--security/keys/process_keys.c66
-rw-r--r--security/keys/request_key.c3
-rw-r--r--security/security.c71
-rw-r--r--security/selinux/Makefile23
-rw-r--r--security/selinux/avc.c25
-rw-r--r--security/selinux/exports.c49
-rw-r--r--security/selinux/hooks.c341
-rw-r--r--security/selinux/include/classmap.h18
-rw-r--r--security/selinux/include/security.h23
-rw-r--r--security/selinux/netnode.c2
-rw-r--r--security/selinux/selinuxfs.c212
-rw-r--r--security/selinux/ss/Makefile9
-rw-r--r--security/selinux/ss/avtab.c85
-rw-r--r--security/selinux/ss/avtab.h3
-rw-r--r--security/selinux/ss/conditional.c188
-rw-r--r--security/selinux/ss/conditional.h2
-rw-r--r--security/selinux/ss/ebitmap.c81
-rw-r--r--security/selinux/ss/ebitmap.h1
-rw-r--r--security/selinux/ss/policydb.c1493
-rw-r--r--security/selinux/ss/policydb.h24
-rw-r--r--security/selinux/ss/services.c70
-rw-r--r--security/selinux/ss/status.c126
-rw-r--r--security/selinux/ss/symtab.c2
-rw-r--r--security/smack/smack.h10
-rw-r--r--security/smack/smack_lsm.c26
-rw-r--r--security/smack/smackfs.c5
-rw-r--r--security/tomoyo/Makefile2
-rw-r--r--security/tomoyo/common.c2851
-rw-r--r--security/tomoyo/common.h847
-rw-r--r--security/tomoyo/domain.c855
-rw-r--r--security/tomoyo/file.c1533
-rw-r--r--security/tomoyo/gc.c360
-rw-r--r--security/tomoyo/group.c130
-rw-r--r--security/tomoyo/load_policy.c81
-rw-r--r--security/tomoyo/memory.c282
-rw-r--r--security/tomoyo/mount.c284
-rw-r--r--security/tomoyo/path_group.c172
-rw-r--r--security/tomoyo/realpath.c428
-rw-r--r--security/tomoyo/securityfs_if.c155
-rw-r--r--security/tomoyo/tomoyo.c35
-rw-r--r--security/tomoyo/util.c963
88 files changed, 15032 insertions, 4865 deletions
diff --git a/security/Kconfig b/security/Kconfig
index 226b955..bd72ae6 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -140,6 +140,7 @@ config LSM_MMAP_MIN_ADDR
source security/selinux/Kconfig
source security/smack/Kconfig
source security/tomoyo/Kconfig
+source security/apparmor/Kconfig
source security/integrity/ima/Kconfig
@@ -148,6 +149,7 @@ choice
default DEFAULT_SECURITY_SELINUX if SECURITY_SELINUX
default DEFAULT_SECURITY_SMACK if SECURITY_SMACK
default DEFAULT_SECURITY_TOMOYO if SECURITY_TOMOYO
+ default DEFAULT_SECURITY_APPARMOR if SECURITY_APPARMOR
default DEFAULT_SECURITY_DAC
help
@@ -163,6 +165,9 @@ choice
config DEFAULT_SECURITY_TOMOYO
bool "TOMOYO" if SECURITY_TOMOYO=y
+ config DEFAULT_SECURITY_APPARMOR
+ bool "AppArmor" if SECURITY_APPARMOR=y
+
config DEFAULT_SECURITY_DAC
bool "Unix Discretionary Access Controls"
@@ -173,6 +178,7 @@ config DEFAULT_SECURITY
default "selinux" if DEFAULT_SECURITY_SELINUX
default "smack" if DEFAULT_SECURITY_SMACK
default "tomoyo" if DEFAULT_SECURITY_TOMOYO
+ default "apparmor" if DEFAULT_SECURITY_APPARMOR
default "" if DEFAULT_SECURITY_DAC
endmenu
diff --git a/security/Makefile b/security/Makefile
index da20a19..8bb0fe9 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -6,6 +6,7 @@ obj-$(CONFIG_KEYS) += keys/
subdir-$(CONFIG_SECURITY_SELINUX) += selinux
subdir-$(CONFIG_SECURITY_SMACK) += smack
subdir-$(CONFIG_SECURITY_TOMOYO) += tomoyo
+subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor
# always enable default capabilities
obj-y += commoncap.o
@@ -19,6 +20,7 @@ obj-$(CONFIG_SECURITY_SELINUX) += selinux/built-in.o
obj-$(CONFIG_SECURITY_SMACK) += smack/built-in.o
obj-$(CONFIG_AUDIT) += lsm_audit.o
obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/built-in.o
+obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/built-in.o
obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o
# Object integrity file lists
diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore
new file mode 100644
index 0000000..4d995ae
--- /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 0000000..9b9013b
--- /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 0000000..f204869
--- /dev/null
+++ b/security/apparmor/Makefile
@@ -0,0 +1,24 @@
+# Makefile for AppArmor Linux Security Module
+#
+obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
+
+apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \
+ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \
+ resource.o sid.o file.o
+
+clean-files: capability_names.h af_names.h
+
+quiet_cmd_make-caps = GEN $@
+cmd_make-caps = echo "static const char *capability_names[] = {" > $@ ; sed -n -e "/CAP_FS_MASK/d" -e "s/^\#define[ \\t]\\+CAP_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\$$/[\\2] = \"\\1\",/p" $< | tr A-Z a-z >> $@ ; echo "};" >> $@
+
+quiet_cmd_make-rlim = GEN $@
+cmd_make-rlim = echo "static const char *rlim_names[] = {" > $@ ; sed -n --e "/AF_MAX/d" -e "s/^\# \\?define[ \\t]\\+RLIMIT_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\\(.*\\)\$$/[\\2] = \"\\1\",/p" $< | tr A-Z a-z >> $@ ; echo "};" >> $@ ; echo "static const int rlim_map[] = {" >> $@ ; sed -n -e "/AF_MAX/d" -e "s/^\# \\?define[ \\t]\\+\\(RLIMIT_[A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\\(.*\\)\$$/\\1,/p" $< >> $@ ; echo "};" >> $@
+
+$(obj)/capability.o : $(obj)/capability_names.h
+$(obj)/resource.o : $(obj)/rlim_names.h
+$(obj)/capability_names.h : $(srctree)/include/linux/capability.h
+ $(call cmd,make-caps)
+$(obj)/af_names.h : $(srctree)/include/linux/socket.h
+ $(call cmd,make-af)
+$(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h
+ $(call cmd,make-rlim)
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
new file mode 100644
index 0000000..0848292
--- /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 0000000..96502b2
--- /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 0000000..9982c48
--- /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 0000000..8a9b502
--- /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 0000000..c825c6e
--- /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);
+ }
+ rcu_read_unlock();
+
+ /* not ptraced */
+ if (!tracer || unconfined(tracerp))
+ goto out;
+
+ error = aa_may_ptrace(tracer, tracerp, to_profile, PTRACE_MODE_ATTACH);
+
+out:
+ if (cred)
+ put_cred(cred);
+
+ return error;
+}
+
+/**
+ * change_profile_perms - find permissions for change_profile
+ * @profile: the current profile (NOT NULL)
+ * @ns: the namespace being switched to (NOT NULL)
+ * @name: the name of the profile to change to (NOT NULL)
+ * @request: requested perms
+ * @start: state to start matching in
+ *
+ * Returns: permission set
+ */
+static struct file_perms change_profile_perms(struct aa_profile *profile,
+ struct aa_namespace *ns,
+ const char *name, u32 request,
+ unsigned int start)
+{
+ struct file_perms perms;
+ struct path_cond cond = { };
+ unsigned int state;
+
+ if (unconfined(profile)) {
+ perms.allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC;
+ perms.audit = perms.quiet = perms.kill = 0;
+ return perms;
+ } else if (!profile->file.dfa) {
+ return nullperms;
+ } else if ((ns == profile->ns)) {
+ /* try matching against rules with out namespace prepended */
+ aa_str_perms(profile->file.dfa, start, name, &cond, &perms);
+ if (COMBINED_PERM_MASK(perms) & request)
+ return perms;
+ }
+
+ /* try matching with namespace name and then profile */
+ state = aa_dfa_match(profile->file.dfa, start, ns->base.name);
+ state = aa_dfa_match_len(profile->file.dfa, state, ":", 1);
+ aa_str_perms(profile->file.dfa, state, name, &cond, &perms);
+
+ return perms;
+}
+
+/**
+ * __attach_match_ - find an attachment match
+ * @name - to match against (NOT NULL)
+ * @head - profile list to walk (NOT NULL)
+ *
+ * Do a linear search on the profiles in the list. There is a matching
+ * preference where an exact match is preferred over a name which uses
+ * expressions to match, and matching expressions with the greatest
+ * xmatch_len are preferred.
+ *
+ * Requires: @head not be shared or have appropriate locks held
+ *
+ * Returns: profile or NULL if no match found
+ */
+static struct aa_profile *__attach_match(const char *name,
+ struct list_head *head)
+{
+ int len = 0;
+ struct aa_profile *profile, *candidate = NULL;
+
+ list_for_each_entry(profile, head, base.list) {
+ if (profile->flags & PFLAG_NULL)
+ continue;
+ if (profile->xmatch && profile->xmatch_len > len) {
+ unsigned int state = aa_dfa_match(profile->xmatch,
+ DFA_START, name);
+ u32 perm = dfa_user_allow(profile->xmatch, state);
+ /* any accepting state means a valid match. */
+ if (perm & MAY_EXEC) {
+ candidate = profile;
+ len = profile->xmatch_len;
+ }
+ } else if (!strcmp(profile->base.name, name))
+ /* exact non-re match, no more searching required */
+ return profile;
+ }
+
+ return candidate;
+}
+
+/**
+ * find_attach - do attachment search for unconfined processes
+ * @ns: the current namespace (NOT NULL)
+ * @list: list to search (NOT NULL)
+ * @name: the executable name to match against (NOT NULL)
+ *
+ * Returns: profile or NULL if no match found
+ */
+static struct aa_profile *find_attach(struct aa_namespace *ns,
+ struct list_head *list, const char *name)
+{
+ struct aa_profile *profile;
+
+ read_lock(&ns->lock);
+ profile = aa_get_profile(__attach_match(name, list));
+ read_unlock(&ns->lock);
+
+ return profile;
+}
+
+/**
+ * separate_fqname - separate the namespace and profile names
+ * @fqname: the fqname name to split (NOT NULL)
+ * @ns_name: the namespace name if it exists (NOT NULL)
+ *
+ * This is the xtable equivalent routine of aa_split_fqname. It finds the
+ * split in an xtable fqname which contains an embedded \0 instead of a :
+ * if a namespace is specified. This is done so the xtable is constant and
+ * isn't re-split on every lookup.
+ *
+ * Either the profile or namespace name may be optional but if the namespace
+ * is specified the profile name termination must be present. This results
+ * in the following possible encodings:
+ * profile_name\0
+ * :ns_name\0profile_name\0
+ * :ns_name\0\0
+ *
+ * NOTE: the xtable fqname is pre-validated at load time in unpack_trans_table
+ *
+ * Returns: profile name if it is specified else NULL
+ */
+static const char *separate_fqname(const char *fqname, const char **ns_name)
+{
+ const char *name;
+
+ if (fqname[0] == ':') {
+ /* In this case there is guaranteed to be two \0 terminators
+ * in the string. They are verified at load time by
+ * by unpack_trans_table
+ */
+ *ns_name = fqname + 1; /* skip : */
+ name = *ns_name + strlen(*ns_name) + 1;
+ if (!*name)
+ name = NULL;
+ } else {
+ *ns_name = NULL;
+ name = fqname;
+ }
+
+ return name;
+}
+
+static const char *next_name(int xtype, const char *name)
+{
+ return NULL;
+}
+
+/**
+ * x_table_lookup - lookup an x transition name via transition table
+ * @profile: current profile (NOT NULL)
+ * @xindex: index into x transition table
+ *
+ * Returns: refcounted profile, or NULL on failure (MAYBE NULL)
+ */
+static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex)
+{
+ struct aa_profile *new_profile = NULL;
+ struct aa_namespace *ns = profile->ns;
+ u32 xtype = xindex & AA_X_TYPE_MASK;
+ int index = xindex & AA_X_INDEX_MASK;
+ const char *name;
+
+ /* index is guaranteed to be in range, validated at load time */
+ for (name = profile->file.trans.table[index]; !new_profile && name;
+ name = next_name(xtype, name)) {
+ struct aa_namespace *new_ns;
+ const char *xname = NULL;
+
+ new_ns = NULL;
+ if (xindex & AA_X_CHILD) {
+ /* release by caller */
+ new_profile = aa_find_child(profile, name);
+ continue;
+ } else if (*name == ':') {
+ /* switching namespace */
+ const char *ns_name;
+ xname = name = separate_fqname(name, &ns_name);
+ if (!xname)
+ /* no name so use profile name */
+ xname = profile->base.hname;
+ if (*ns_name == '@') {
+ /* TODO: variable support */
+ ;
+ }
+ /* released below */
+ new_ns = aa_find_namespace(ns, ns_name);
+ if (!new_ns)
+ continue;
+ } else if (*name == '@') {
+ /* TODO: variable support */
+ continue;
+ } else {
+ /* basic namespace lookup */
+ xname = name;
+ }
+
+ /* released by caller */
+ new_profile = aa_lookup_profile(new_ns ? new_ns : ns, xname);
+ aa_put_namespace(new_ns);
+ }
+
+ /* released by caller */
+ return new_profile;
+}
+
+/**
+ * x_to_profile - get target profile for a given xindex
+ * @profile: current profile (NOT NULL)
+ * @name: name to lookup (NOT NULL)
+ * @xindex: index into x transition table
+ *
+ * find profile for a transition index
+ *
+ * Returns: refcounted profile or NULL if not found available
+ */
+static struct aa_profile *x_to_profile(struct aa_profile *profile,
+ const char *name, u32 xindex)
+{
+ struct aa_profile *new_profile = NULL;
+ struct aa_namespace *ns = profile->ns;
+ u32 xtype = xindex & AA_X_TYPE_MASK;
+
+ switch (xtype) {
+ case AA_X_NONE:
+ /* fail exec unless ix || ux fallback - handled by caller */
+ return NULL;
+ case AA_X_NAME:
+ if (xindex & AA_X_CHILD)
+ /* released by caller */
+ new_profile = find_attach(ns, &profile->base.profiles,
+ name);
+ else
+ /* released by caller */
+ new_profile = find_attach(ns, &ns->base.profiles,
+ name);
+ break;
+ case AA_X_TABLE:
+ /* released by caller */
+ new_profile = x_table_lookup(profile, xindex);
+ break;
+ }
+
+ /* released by caller */
+ return new_profile;
+}
+
+/**
+ * apparmor_bprm_set_creds - set the new creds on the bprm struct
+ * @bprm: binprm for the exec (NOT NULL)
+ *
+ * Returns: %0 or error on failure
+ */
+int apparmor_bprm_set_creds(struct linux_binprm *bprm)
+{
+ struct aa_task_cxt *cxt;
+ struct aa_profile *profile, *new_profile = NULL;
+ struct aa_namespace *ns;
+ char *buffer = NULL;
+ unsigned int state;
+ struct file_perms perms = {};
+ struct path_cond cond = {
+ bprm->file->f_path.dentry->d_inode->i_uid,
+ bprm->file->f_path.dentry->d_inode->i_mode
+ };
+ const char *name = NULL, *target = NULL, *info = NULL;
+ int error = cap_bprm_set_creds(bprm);
+ if (error)
+ return error;
+
+ if (bprm->cred_prepared)
+ return 0;
+
+ cxt = bprm->cred->security;
+ BUG_ON(!cxt);
+
+ profile = aa_get_profile(aa_newest_version(cxt->profile));
+ /*
+ * get the namespace from the replacement profile as replacement
+ * can change the namespace
+ */
+ ns = profile->ns;
+ state = profile->file.start;
+
+ /* buffer freed below, name is pointer into buffer */
+ error = aa_get_name(&bprm->file->f_path, profile->path_flags, &buffer,
+ &name);
+ if (error) {
+ if (profile->flags &
+ (PFLAG_IX_ON_NAME_ERROR | PFLAG_UNCONFINED))
+ error = 0;
+ info = "Exec failed name resolution";
+ name = bprm->filename;
+ goto audit;
+ }
+
+ /* Test for onexec first as onexec directives override other
+ * x transitions.
+ */
+ if (unconfined(profile)) {
+ /* unconfined task */
+ if (cxt->onexec)
+ /* change_profile on exec already been granted */
+ new_profile = aa_get_profile(cxt->onexec);
+ else
+ new_profile = find_attach(ns, &ns->base.profiles, name);
+ if (!new_profile)
+ goto cleanup;
+ goto apply;
+ }
+
+ /* find exec permissions for name */
+ state = aa_str_perms(profile->file.dfa, state, name, &cond, &perms);
+ if (cxt->onexec) {
+ struct file_perms cp;
+ info = "change_profile onexec";
+ if (!(perms.allow & AA_MAY_ONEXEC))
+ goto audit;
+
+ /* test if this exec can be paired with change_profile onexec.
+ * onexec permission is linked to exec with a standard pairing
+ * exec\0change_profile
+ */
+ state = aa_dfa_null_transition(profile->file.dfa, state);
+ cp = change_profile_perms(profile, cxt->onexec->ns, name,
+ AA_MAY_ONEXEC, state);
+
+ if (!(cp.allow & AA_MAY_ONEXEC))
+ goto audit;
+ new_profile = aa_get_profile(aa_newest_version(cxt->onexec));
+ goto apply;
+ }
+
+ if (perms.allow & MAY_EXEC) {
+ /* exec permission determine how to transition */
+ new_profile = x_to_profile(profile, name, perms.xindex);
+ if (!new_profile) {
+ if (perms.xindex & AA_X_INHERIT) {
+ /* (p|c|n)ix - don't change profile but do
+ * use the newest version, which was picked
+ * up above when getting profile
+ */
+ info = "ix fallback";
+ new_profile = aa_get_profile(profile);
+ goto x_clear;
+ } else if (perms.xindex & AA_X_UNCONFINED) {
+ new_profile = aa_get_profile(ns->unconfined);
+ info = "ux fallback";
+ } else {
+ error = -ENOENT;
+ info = "profile not found";
+ }
+ }
+ } else if (COMPLAIN_MODE(profile)) {
+ /* no exec permission - are we in learning mode */
+ new_profile = aa_new_null_profile(profile, 0);
+ if (!new_profile) {
+ error = -ENOMEM;
+ info = "could not create null profile";
+ } else {
+ error = -EACCES;
+ target = new_profile->base.hname;
+ }
+ perms.xindex |= AA_X_UNSAFE;
+ } else
+ /* fail exec */
+ error = -EACCES;
+
+ if (!new_profile)
+ goto audit;
+
+ if (bprm->unsafe & LSM_UNSAFE_SHARE) {
+ /* FIXME: currently don't mediate shared state */
+ ;
+ }
+
+ if (bprm->unsafe & (LSM_UNSAFE_PTRACE | LSM_UNSAFE_PTRACE_CAP)) {
+ error = may_change_ptraced_domain(current, new_profile);
+ if (error) {
+ aa_put_profile(new_profile);
+ goto audit;
+ }
+ }
+
+ /* Determine if secure exec is needed.
+ * Can be at this point for the following reasons:
+ * 1. unconfined switching to confined
+ * 2. confined switching to different confinement
+ * 3. confined switching to unconfined
+ *
+ * Cases 2 and 3 are marked as requiring secure exec
+ * (unless policy specified "unsafe exec")
+ *
+ * bprm->unsafe is used to cache the AA_X_UNSAFE permission
+ * to avoid having to recompute in secureexec
+ */
+ if (!(perms.xindex & AA_X_UNSAFE)) {
+ AA_DEBUG("scrubbing environment variables for %s profile=%s\n",
+ name, new_profile->base.hname);
+ bprm->unsafe |= AA_SECURE_X_NEEDED;
+ }
+apply:
+ target = new_profile->base.hname;
+ /* when transitioning profiles clear unsafe personality bits */
+ bprm->per_clear |= PER_CLEAR_ON_SETID;
+
+x_clear:
+ aa_put_profile(cxt->profile);
+ /* transfer new profile reference will be released when cxt is freed */
+ cxt->profile = new_profile;
+
+ /* clear out all temporary/transitional state from the context */
+ aa_put_profile(cxt->previous);
+ aa_put_profile(cxt->onexec);
+ cxt->previous = NULL;
+ cxt->onexec = NULL;
+ cxt->token = 0;
+
+audit:
+ error = aa_audit_file(profile, &perms, GFP_KERNEL, OP_EXEC, MAY_EXEC,
+ name, target, cond.uid, info, error);
+
+cleanup:
+ aa_put_profile(profile);
+ kfree(buffer);
+
+ return error;
+}
+
+/**
+ * apparmor_bprm_secureexec - determine if secureexec is needed
+ * @bprm: binprm for exec (NOT NULL)
+ *
+ * Returns: %1 if secureexec is needed else %0
+ */
+int apparmor_bprm_secureexec(struct linux_binprm *bprm)
+{
+ int ret = cap_bprm_secureexec(bprm);
+
+ /* the decision to use secure exec is computed in set_creds
+ * and stored in bprm->unsafe.
+ */
+ if (!ret && (bprm->unsafe & AA_SECURE_X_NEEDED))
+ ret = 1;
+
+ return ret;
+}
+
+/**
+ * apparmor_bprm_committing_creds - do task cleanup on committing new creds
+ * @bprm: binprm for the exec (NOT NULL)
+ */
+void apparmor_bprm_committing_creds(struct linux_binprm *bprm)
+{
+ struct aa_profile *profile = __aa_current_profile();
+ struct aa_task_cxt *new_cxt = bprm->cred->security;
+
+ /* bail out if unconfined or not changing profile */
+ if ((new_cxt->profile == profile) ||
+ (unconfined(new_cxt->profile)))
+ return;
+
+ current->pdeath_signal = 0;
+
+ /* reset soft limits and set hard limits for the new profile */
+ __aa_transition_rlimits(profile, new_cxt->profile);
+}
+
+/**
+ * apparmor_bprm_commited_cred - do cleanup after new creds committed
+ * @bprm: binprm for the exec (NOT NULL)
+ */
+void apparmor_bprm_committed_creds(struct linux_binprm *bprm)
+{
+ /* TODO: cleanup signals - ipc mediation */
+ return;
+}
+
+/*
+ * Functions for self directed profile change
+ */
+
+/**
+ * new_compound_name - create an hname with @n2 appended to @n1
+ * @n1: base of hname (NOT NULL)
+ * @n2: name to append (NOT NULL)
+ *
+ * Returns: new name or NULL on error
+ */
+static char *new_compound_name(const char *n1, const char *n2)
+{
+ char *name = kmalloc(strlen(n1) + strlen(n2) + 3, GFP_KERNEL);
+ if (name)
+ sprintf(name, "%s//%s", n1, n2);
+ return name;
+}
+
+/**
+ * aa_change_hat - change hat to/from subprofile
+ * @hats: vector of hat names to try changing into (MAYBE NULL if @count == 0)
+ * @count: number of hat names in @hats
+ * @token: magic value to validate the hat change
+ * @permtest: true if this is just a permission test
+ *
+ * Change to the first profile specified in @hats that exists, and store
+ * the @hat_magic in the current task context. If the count == 0 and the
+ * @token matches that stored in the current task context, return to the
+ * top level profile.
+ *
+ * Returns %0 on success, error otherwise.
+ */
+int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
+{
+ const struct cred *cred;
+ struct aa_task_cxt *cxt;
+ struct aa_profile *profile, *previous_profile, *hat = NULL;
+ char *name = NULL;
+ int i;
+ struct file_perms perms = {};
+ const char *target = NULL, *info = NULL;
+ int error = 0;
+
+ /* released below */
+ cred = get_current_cred();
+ cxt = cred->security;
+ profile = aa_cred_profile(cred);
+ previous_profile = cxt->previous;
+
+ if (unconfined(profile)) {
+ info = "unconfined";
+ error = -EPERM;
+ goto audit;
+ }
+
+ if (count) {
+ /* attempting to change into a new hat or switch to a sibling */
+ struct aa_profile *root;
+ root = PROFILE_IS_HAT(profile) ? profile->parent : profile;
+
+ /* find first matching hat */
+ for (i = 0; i < count && !hat; i++)
+ /* released below */
+ hat = aa_find_child(root, hats[i]);
+ if (!hat) {
+ if (!COMPLAIN_MODE(root) || permtest) {
+ if (list_empty(&root->base.profiles))
+ error = -ECHILD;
+ else
+ error = -ENOENT;
+ goto out;
+ }
+
+ /*
+ * In complain mode and failed to match any hats.
+ * Audit the failure is based off of the first hat
+ * supplied. This is done due how userspace
+ * interacts with change_hat.
+ *
+ * TODO: Add logging of all failed hats
+ */
+
+ /* freed below */
+ name = new_compound_name(root->base.hname, hats[0]);
+ target = name;
+ /* released below */
+ hat = aa_new_null_profile(profile, 1);
+ if (!hat) {
+ info = "failed null profile create";
+ error = -ENOMEM;
+ goto audit;
+ }
+ } else {
+ target = hat->base.hname;
+ if (!PROFILE_IS_HAT(hat)) {
+ info = "target not hat";
+ error = -EPERM;
+ goto audit;
+ }
+ }
+
+ error = may_change_ptraced_domain(current, hat);
+ if (error) {
+ info = "ptraced";
+ error = -EPERM;
+ goto audit;
+ }
+
+ if (!permtest) {
+ error = aa_set_current_hat(hat, token);
+ if (error == -EACCES)
+ /* kill task in case of brute force attacks */
+ perms.kill = AA_MAY_CHANGEHAT;
+ else if (name && !error)
+ /* reset error for learning of new hats */
+ error = -ENOENT;
+ }
+ } else if (previous_profile) {
+ /* Return to saved profile. Kill task if restore fails
+ * to avoid brute force attacks
+ */
+ target = previous_profile->base.hname;
+ error = aa_restore_previous_profile(token);
+ perms.kill = AA_MAY_CHANGEHAT;
+ } else
+ /* ignore restores when there is no saved profile */
+ goto out;
+
+audit:
+ if (!permtest)
+ error = aa_audit_file(profile, &perms, GFP_KERNEL,
+ OP_CHANGE_HAT, AA_MAY_CHANGEHAT, NULL,
+ target, 0, info, error);
+
+out:
+ aa_put_profile(hat);
+ kfree(name);
+ put_cred(cred);
+
+ return error;
+}
+
+/**
+ * aa_change_profile - perform a one-way profile transition
+ * @ns_name: name of the profile namespace to change to (MAYBE NULL)
+ * @hname: name of profile to change to (MAYBE NULL)
+ * @onexec: whether this transition is to take place immediately or at exec
+ * @permtest: true if this is just a permission test
+ *
+ * Change to new profile @name. Unlike with hats, there is no way
+ * to change back. If @name isn't specified the current profile name is
+ * used.
+ * If @onexec then the transition is delayed until
+ * the next exec.
+ *
+ * Returns %0 on success, error otherwise.
+ */
+int aa_change_profile(const char *ns_name, const char *hname, bool onexec,
+ bool permtest)
+{
+ const struct cred *cred;
+ struct aa_task_cxt *cxt;
+ struct aa_profile *profile, *target = NULL;
+ struct aa_namespace *ns = NULL;
+ struct file_perms perms = {};
+ const char *name = NULL, *info = NULL;
+ int op, error = 0;
+ u32 request;
+
+ if (!hname && !ns_name)
+ return -EINVAL;
+
+ if (onexec) {
+ request = AA_MAY_ONEXEC;
+ op = OP_CHANGE_ONEXEC;
+ } else {
+ request = AA_MAY_CHANGE_PROFILE;
+ op = OP_CHANGE_PROFILE;
+ }
+
+ cred = get_current_cred();
+ cxt = cred->security;
+ profile = aa_cred_profile(cred);
+
+ if (ns_name) {
+ /* released below */
+ ns = aa_find_namespace(profile->ns, ns_name);
+ if (!ns) {
+ /* we don't create new namespace in complain mode */
+ name = ns_name;
+ info = "namespace not found";
+ error = -ENOENT;
+ goto audit;
+ }
+ } else
+ /* released below */
+ ns = aa_get_namespace(profile->ns);
+
+ /* if the name was not specified, use the name of the current profile */
+ if (!hname) {
+ if (unconfined(profile))
+ hname = ns->unconfined->base.hname;
+ else
+ hname = profile->base.hname;
+ }
+
+ perms = change_profile_perms(profile, ns, hname, request,
+ profile->file.start);
+ if (!(perms.allow & request)) {
+ error = -EACCES;
+ goto audit;
+ }
+
+ /* released below */
+ target = aa_lookup_profile(ns, hname);
+ if (!target) {
+ info = "profile not found";
+ error = -ENOENT;
+ if (permtest || !COMPLAIN_MODE(profile))
+ goto audit;
+ /* released below */
+ target = aa_new_null_profile(profile, 0);
+ if (!target) {
+ info = "failed null profile create";
+ error = -ENOMEM;
+ goto audit;
+ }
+ }
+
+ /* check if tracing task is allowed to trace target domain */
+ error = may_change_ptraced_domain(current, target);
+ if (error) {
+ info = "ptrace prevents transition";
+ goto audit;
+ }
+
+ if (permtest)
+ goto audit;
+
+ if (onexec)
+ error = aa_set_current_onexec(target);
+ else
+ error = aa_replace_current_profile(target);
+
+audit:
+ if (!permtest)
+ error = aa_audit_file(profile, &perms, GFP_KERNEL, op, request,
+ name, hname, 0, info, error);
+
+ aa_put_namespace(ns);
+ aa_put_profile(target);
+ put_cred(cred);
+
+ return error;
+}
diff --git a/security/apparmor/file.c b/security/apparmor/file.c
new file mode 100644
index 0000000..7312db7
--- /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 0000000..38ccaea
--- /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 0000000..cb1e93a
--- /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 0000000..1951786
--- /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 0000000..c24d295
--- /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 0000000..a9cbee4
--- /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 0000000..de04464
--- /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 0000000..be36fea
--- /dev/null
+++ b/security/apparmor/include/file.h
@@ -0,0 +1,217 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor file mediation function definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+
+#ifndef __AA_FILE_H
+#define __AA_FILE_H
+
+#include <linux/path.h>
+
+#include "domain.h"
+#include "match.h"
+
+struct aa_profile;
+
+/*
+ * We use MAY_EXEC, MAY_WRITE, MAY_READ, MAY_APPEND and the following flags
+ * for profile permissions
+ */
+#define AA_MAY_CREATE 0x0010
+#define AA_MAY_DELETE 0x0020
+#define AA_MAY_META_WRITE 0x0040
+#define AA_MAY_META_READ 0x0080
+
+#define AA_MAY_CHMOD 0x0100
+#define AA_MAY_CHOWN 0x0200
+#define AA_MAY_LOCK 0x0400
+#define AA_EXEC_MMAP 0x0800
+
+#define AA_MAY_LINK 0x1000
+#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */
+#define AA_MAY_ONEXEC 0x40000000 /* exec allows onexec */
+#define AA_MAY_CHANGE_PROFILE 0x80000000
+#define AA_MAY_CHANGEHAT 0x80000000 /* ctrl auditing only */
+
+#define AA_AUDIT_FILE_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | MAY_APPEND |\
+ AA_MAY_CREATE | AA_MAY_DELETE | \
+ AA_MAY_META_READ | AA_MAY_META_WRITE | \
+ AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_LOCK | \
+ AA_EXEC_MMAP | AA_MAY_LINK)
+
+/*
+ * The xindex is broken into 3 parts
+ * - index - an index into either the exec name table or the variable table
+ * - exec type - which determines how the executable name and index are used
+ * - flags - which modify how the destination name is applied
+ */
+#define AA_X_INDEX_MASK 0x03ff
+
+#define AA_X_TYPE_MASK 0x0c00
+#define AA_X_TYPE_SHIFT 10
+#define AA_X_NONE 0x0000
+#define AA_X_NAME 0x0400 /* use executable name px */
+#define AA_X_TABLE 0x0800 /* use a specified name ->n# */
+
+#define AA_X_UNSAFE 0x1000
+#define AA_X_CHILD 0x2000 /* make >AA_X_NONE apply to children */
+#define AA_X_INHERIT 0x4000
+#define AA_X_UNCONFINED 0x8000
+
+/* AA_SECURE_X_NEEDED - is passed in the bprm->unsafe field */
+#define AA_SECURE_X_NEEDED 0x8000
+
+/* need to make conditional which ones are being set */
+struct path_cond {
+ uid_t uid;
+ umode_t mode;
+};
+
+/* struct file_perms - file permission
+ * @allow: mask of permissions that are allowed
+ * @audit: mask of permissions to force an audit message for
+ * @quiet: mask of permissions to quiet audit messages for
+ * @kill: mask of permissions that when matched will kill the task
+ * @xindex: exec transition index if @allow contains MAY_EXEC
+ *
+ * The @audit and @queit mask should be mutually exclusive.
+ */
+struct file_perms {
+ u32 allow;
+ u32 audit;
+ u32 quiet;
+ u32 kill;
+ u16 xindex;
+};
+
+extern struct file_perms nullperms;
+
+#define COMBINED_PERM_MASK(X) ((X).allow | (X).audit | (X).quiet | (X).kill)
+
+/* FIXME: split perms from dfa and match this to description
+ * also add delegation info.
+ */
+static inline u16 dfa_map_xindex(u16 mask)
+{
+ u16 old_index = (mask >> 10) & 0xf;
+ u16 index = 0;
+
+ if (mask & 0x100)
+ index |= AA_X_UNSAFE;
+ if (mask & 0x200)
+ index |= AA_X_INHERIT;
+ if (mask & 0x80)
+ index |= AA_X_UNCONFINED;
+
+ if (old_index == 1) {
+ index |= AA_X_UNCONFINED;
+ } else if (old_index == 2) {
+ index |= AA_X_NAME;
+ } else if (old_index == 3) {
+ index |= AA_X_NAME | AA_X_CHILD;
+ } else {
+ index |= AA_X_TABLE;
+ index |= old_index - 4;
+ }
+
+ return index;
+}
+
+/*
+ * map old dfa inline permissions to new format
+ */
+#define dfa_user_allow(dfa, state) (((ACCEPT_TABLE(dfa)[state]) & 0x7f) | \
+ ((ACCEPT_TABLE(dfa)[state]) & 0x80000000))
+#define dfa_user_audit(dfa, state) ((ACCEPT_TABLE2(dfa)[state]) & 0x7f)
+#define dfa_user_quiet(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 7) & 0x7f)
+#define dfa_user_xindex(dfa, state) \
+ (dfa_map_xindex(ACCEPT_TABLE(dfa)[state] & 0x3fff))
+
+#define dfa_other_allow(dfa, state) ((((ACCEPT_TABLE(dfa)[state]) >> 14) & \
+ 0x7f) | \
+ ((ACCEPT_TABLE(dfa)[state]) & 0x80000000))
+#define dfa_other_audit(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 14) & 0x7f)
+#define dfa_other_quiet(dfa, state) \
+ ((((ACCEPT_TABLE2(dfa)[state]) >> 7) >> 14) & 0x7f)
+#define dfa_other_xindex(dfa, state) \
+ dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff)
+
+int aa_audit_file(struct aa_profile *profile, struct file_perms *perms,
+ gfp_t gfp, int op, u32 request, const char *name,
+ const char *target, uid_t ouid, const char *info, int error);
+
+/**
+ * struct aa_file_rules - components used for file rule permissions
+ * @dfa: dfa to match path names and conditionals against
+ * @perms: permission table indexed by the matched state accept entry of @dfa
+ * @trans: transition table for indexed by named x transitions
+ *
+ * File permission are determined by matching a path against @dfa and then
+ * then using the value of the accept entry for the matching state as
+ * an index into @perms. If a named exec transition is required it is
+ * looked up in the transition table.
+ */
+struct aa_file_rules {
+ unsigned int start;
+ struct aa_dfa *dfa;
+ /* struct perms perms; */
+ struct aa_domain trans;
+ /* TODO: add delegate table */
+};
+
+unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
+ const char *name, struct path_cond *cond,
+ struct file_perms *perms);
+
+int aa_path_perm(int op, struct aa_profile *profile, struct path *path,
+ int flags, u32 request, struct path_cond *cond);
+
+int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
+ struct path *new_dir, struct dentry *new_dentry);
+
+int aa_file_perm(int op, struct aa_profile *profile, struct file *file,
+ u32 request);
+
+static inline void aa_free_file_rules(struct aa_file_rules *rules)
+{
+ aa_put_dfa(rules->dfa);
+ aa_free_domain_entries(&rules->trans);
+}
+
+#define ACC_FMODE(x) (("\000\004\002\006"[(x)&O_ACCMODE]) | (((x) << 1) & 0x40))
+
+/* from namei.c */
+#define MAP_OPEN_FLAGS(x) ((((x) + 1) & O_ACCMODE) ? (x) + 1 : (x))
+
+/**
+ * aa_map_file_perms - map file flags to AppArmor permissions
+ * @file: open file to map flags to AppArmor permissions
+ *
+ * Returns: apparmor permission set for the file
+ */
+static inline u32 aa_map_file_to_perms(struct file *file)
+{
+ int flags = MAP_OPEN_FLAGS(file->f_flags);
+ u32 perms = ACC_FMODE(file->f_mode);
+
+ if ((flags & O_APPEND) && (perms & MAY_WRITE))
+ perms = (perms & ~MAY_WRITE) | MAY_APPEND;
+ /* trunc implies write permission */
+ if (flags & O_TRUNC)
+ perms |= MAY_WRITE;
+ if (flags & O_CREAT)
+ perms |= AA_MAY_CREATE;
+
+ return perms;
+}
+
+#endif /* __AA_FILE_H */
diff --git a/security/apparmor/include/ipc.h b/security/apparmor/include/ipc.h
new file mode 100644
index 0000000..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 0000000..734a6d3
--- /dev/null
+++ b/security/apparmor/include/match.h
@@ -0,0 +1,132 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor policy dfa matching engine definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+
+#ifndef __AA_MATCH_H
+#define __AA_MATCH_H
+
+#include <linux/workqueue.h>
+
+#define DFA_NOMATCH 0
+#define DFA_START 1
+
+#define DFA_VALID_PERM_MASK 0xffffffff
+#define DFA_VALID_PERM2_MASK 0xffffffff
+
+/**
+ * The format used for transition tables is based on the GNU flex table
+ * file format (--tables-file option; see Table File Format in the flex
+ * info pages and the flex sources for documentation). The magic number
+ * used in the header is 0x1B5E783D insted of 0xF13C57B1 though, because
+ * the YY_ID_CHK (check) and YY_ID_DEF (default) tables are used
+ * slightly differently (see the apparmor-parser package).
+ */
+
+#define YYTH_MAGIC 0x1B5E783D
+#define YYTH_DEF_RECURSE 0x1 /* DEF Table is recursive */
+
+struct table_set_header {
+ u32 th_magic; /* YYTH_MAGIC */
+ u32 th_hsize;
+ u32 th_ssize;
+ u16 th_flags;
+ char th_version[];
+};
+
+/* The YYTD_ID are one less than flex table mappings. The flex id
+ * has 1 subtracted at table load time, this allows us to directly use the
+ * ID's as indexes.
+ */
+#define YYTD_ID_ACCEPT 0
+#define YYTD_ID_BASE 1
+#define YYTD_ID_CHK 2
+#define YYTD_ID_DEF 3
+#define YYTD_ID_EC 4
+#define YYTD_ID_META 5
+#define YYTD_ID_ACCEPT2 6
+#define YYTD_ID_NXT 7
+#define YYTD_ID_TSIZE 8
+
+#define YYTD_DATA8 1
+#define YYTD_DATA16 2
+#define YYTD_DATA32 4
+#define YYTD_DATA64 8
+
+/* Each ACCEPT2 table gets 6 dedicated flags, YYTD_DATAX define the
+ * first flags
+ */
+#define ACCEPT1_FLAGS(X) ((X) & 0x3f)
+#define ACCEPT2_FLAGS(X) ACCEPT1_FLAGS((X) >> YYTD_ID_ACCEPT2)
+#define TO_ACCEPT1_FLAG(X) ACCEPT1_FLAGS(X)
+#define TO_ACCEPT2_FLAG(X) (ACCEPT1_FLAGS(X) << YYTD_ID_ACCEPT2)
+#define DFA_FLAG_VERIFY_STATES 0x1000
+
+struct table_header {
+ u16 td_id;
+ u16 td_flags;
+ u32 td_hilen;
+ u32 td_lolen;
+ char td_data[];
+};
+
+#define DEFAULT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_DEF]->td_data))
+#define BASE_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_BASE]->td_data))
+#define NEXT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_NXT]->td_data))
+#define CHECK_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_CHK]->td_data))
+#define EQUIV_TABLE(DFA) ((u8 *)((DFA)->tables[YYTD_ID_EC]->td_data))
+#define ACCEPT_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT]->td_data))
+#define ACCEPT_TABLE2(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT2]->td_data))
+
+struct aa_dfa {
+ struct kref count;
+ u16 flags;
+ struct table_header *tables[YYTD_ID_TSIZE];
+};
+
+#define byte_to_byte(X) (X)
+
+#define UNPACK_ARRAY(TABLE, BLOB, LEN, TYPE, NTOHX) \
+ do { \
+ typeof(LEN) __i; \
+ TYPE *__t = (TYPE *) TABLE; \
+ TYPE *__b = (TYPE *) BLOB; \
+ for (__i = 0; __i < LEN; __i++) { \
+ __t[__i] = NTOHX(__b[__i]); \
+ } \
+ } while (0)
+
+static inline size_t table_size(size_t len, size_t el_size)
+{
+ return ALIGN(sizeof(struct table_header) + len * el_size, 8);
+}
+
+struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags);
+unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
+ const char *str, int len);
+unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start,
+ const char *str);
+void aa_dfa_free_kref(struct kref *kref);
+
+/**
+ * aa_put_dfa - put a dfa refcount
+ * @dfa: dfa to put refcount (MAYBE NULL)
+ *
+ * Requires: if @dfa != NULL that a valid refcount be held
+ */
+static inline void aa_put_dfa(struct aa_dfa *dfa)
+{
+ if (dfa)
+ kref_put(&dfa->count, aa_dfa_free_kref);
+}
+
+#endif /* __AA_MATCH_H */
diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h
new file mode 100644
index 0000000..27b327a
--- /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 0000000..aeda5cf
--- /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 0000000..a2dccca
--- /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 0000000..544aa6b
--- /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 0000000..02baec7
--- /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 0000000..020db35
--- /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 0000000..649fad8
--- /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 0000000..506d2ba
--- /dev/null
+++ b/security/apparmor/lib.c
@@ -0,0 +1,133 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains basic common functions used in AppArmor
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/vmalloc.h>
+
+#include "include/audit.h"
+
+
+/**
+ * aa_split_fqname - split a fqname into a profile and namespace name
+ * @fqname: a full qualified name in namespace profile format (NOT NULL)
+ * @ns_name: pointer to portion of the string containing the ns name (NOT NULL)
+ *
+ * Returns: profile name or NULL if one is not specified
+ *
+ * Split a namespace name from a profile name (see policy.c for naming
+ * description). If a portion of the name is missing it returns NULL for
+ * that portion.
+ *
+ * NOTE: may modify the @fqname string. The pointers returned point
+ * into the @fqname string.
+ */
+char *aa_split_fqname(char *fqname, char **ns_name)
+{
+ char *name = strim(fqname);
+
+ *ns_name = NULL;
+ if (name[0] == ':') {
+ char *split = strchr(&name[1], ':');
+ *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 0000000..cf1de44
--- /dev/null
+++ b/security/apparmor/lsm.c
@@ -0,0 +1,950 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor LSM hooks.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+
+#include <linux/security.h>
+#include <linux/moduleparam.h>
+#include <linux/mm.h>
+#include <linux/mman.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+#include <linux/ptrace.h>
+#include <linux/ctype.h>
+#include <linux/sysctl.h>
+#include <linux/audit.h>
+#include <net/sock.h>
+
+#include "include/apparmor.h"
+#include "include/apparmorfs.h"
+#include "include/audit.h"
+#include "include/capability.h"
+#include "include/context.h"
+#include "include/file.h"
+#include "include/ipc.h"
+#include "include/path.h"
+#include "include/policy.h"
+#include "include/procattr.h"
+
+/* Flag indicating whether initialization completed */
+int apparmor_initialized __initdata;
+
+/*
+ * LSM hook functions
+ */
+
+/*
+ * free the associated aa_task_cxt and put its profiles
+ */
+static void apparmor_cred_free(struct cred *cred)
+{
+ aa_free_task_context(cred->security);
+ cred->security = NULL;
+}
+
+/*
+ * allocate the apparmor part of blank credentials
+ */
+static int apparmor_cred_alloc_blank(struct cred *cred, gfp_t gfp)
+{
+ /* freed by apparmor_cred_free */
+ struct aa_task_cxt *cxt = aa_alloc_task_context(gfp);
+ if (!cxt)
+ return -ENOMEM;
+
+ cred->security = cxt;
+ return 0;
+}
+
+/*
+ * prepare new aa_task_cxt for modification by prepare_cred block
+ */
+static int apparmor_cred_prepare(struct cred *new, const struct cred *old,
+ gfp_t gfp)
+{
+ /* freed by apparmor_cred_free */
+ struct aa_task_cxt *cxt = aa_alloc_task_context(gfp);
+ if (!cxt)
+ return -ENOMEM;
+
+ aa_dup_task_context(cxt, old->security);
+ new->security = cxt;
+ return 0;
+}
+
+/*
+ * transfer the apparmor data to a blank set of creds
+ */
+static void apparmor_cred_transfer(struct cred *new, const struct cred *old)
+{
+ const struct aa_task_cxt *old_cxt = old->security;
+ struct aa_task_cxt *new_cxt = new->security;
+
+ aa_dup_task_context(new_cxt, old_cxt);
+}
+
+static int apparmor_ptrace_access_check(struct task_struct *child,
+ unsigned int mode)
+{
+ int error = cap_ptrace_access_check(child, mode);
+ if (error)
+ return error;
+
+ return aa_ptrace(current, child, mode);
+}
+
+static int apparmor_ptrace_traceme(struct task_struct *parent)
+{
+ int error = cap_ptrace_traceme(parent);
+ if (error)
+ return error;
+
+ return aa_ptrace(parent, current, PTRACE_MODE_ATTACH);
+}
+
+/* Derived from security/commoncap.c:cap_capget */
+static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective,
+ kernel_cap_t *inheritable, kernel_cap_t *permitted)
+{
+ struct aa_profile *profile;
+ const struct cred *cred;
+
+ rcu_read_lock();
+ cred = __task_cred(target);
+ profile = aa_cred_profile(cred);
+
+ *effective = cred->cap_effective;
+ *inheritable = cred->cap_inheritable;
+ *permitted = cred->cap_permitted;
+
+ if (!unconfined(profile)) {
+ *effective = cap_intersect(*effective, profile->caps.allow);
+ *permitted = cap_intersect(*permitted, profile->caps.allow);
+ }
+ rcu_read_unlock();
+
+ return 0;
+}
+
+static int apparmor_capable(struct task_struct *task, const struct cred *cred,
+ int cap, int audit)
+{
+ struct aa_profile *profile;
+ /* cap_capable returns 0 on success, else -EPERM */
+ int error = cap_capable(task, cred, cap, audit);
+ if (!error) {
+ profile = aa_cred_profile(cred);
+ if (!unconfined(profile))
+ error = aa_capable(task, profile, cap, audit);
+ }
+ return error;
+}
+
+/**
+ * common_perm - basic common permission check wrapper fn for paths
+ * @op: operation being checked
+ * @path: path to check permission of (NOT NULL)
+ * @mask: requested permissions mask
+ * @cond: conditional info for the permission request (NOT NULL)
+ *
+ * Returns: %0 else error code if error or permission denied
+ */
+static int common_perm(int op, struct path *path, u32 mask,
+ struct path_cond *cond)
+{
+ struct aa_profile *profile;
+ int error = 0;
+
+ profile = __aa_current_profile();
+ if (!unconfined(profile))
+ error = aa_path_perm(op, profile, path, 0, mask, cond);
+
+ return error;
+}
+
+/**
+ * common_perm_dir_dentry - common permission wrapper when path is dir, dentry
+ * @op: operation being checked
+ * @dir: directory of the dentry (NOT NULL)
+ * @dentry: dentry to check (NOT NULL)
+ * @mask: requested permissions mask
+ * @cond: conditional info for the permission request (NOT NULL)
+ *
+ * Returns: %0 else error code if error or permission denied
+ */
+static int common_perm_dir_dentry(int op, struct path *dir,
+ struct dentry *dentry, u32 mask,
+ struct path_cond *cond)
+{
+ struct path path = { dir->mnt, dentry };
+
+ return common_perm(op, &path, mask, cond);
+}
+
+/**
+ * common_perm_mnt_dentry - common permission wrapper when mnt, dentry
+ * @op: operation being checked
+ * @mnt: mount point of dentry (NOT NULL)
+ * @dentry: dentry to check (NOT NULL)
+ * @mask: requested permissions mask
+ *
+ * Returns: %0 else error code if error or permission denied
+ */
+static int common_perm_mnt_dentry(int op, struct vfsmount *mnt,
+ struct dentry *dentry, u32 mask)
+{
+ struct path path = { mnt, dentry };
+ struct path_cond cond = { dentry->d_inode->i_uid,
+ dentry->d_inode->i_mode
+ };
+
+ return common_perm(op, &path, mask, &cond);
+}
+
+/**
+ * common_perm_rm - common permission wrapper for operations doing rm
+ * @op: operation being checked
+ * @dir: directory that the dentry is in (NOT NULL)
+ * @dentry: dentry being rm'd (NOT NULL)
+ * @mask: requested permission mask
+ *
+ * Returns: %0 else error code if error or permission denied
+ */
+static int common_perm_rm(int op, struct path *dir,
+ struct dentry *dentry, u32 mask)
+{
+ struct inode *inode = dentry->d_inode;
+ struct path_cond cond = { };
+
+ if (!inode || !dir->mnt || !mediated_filesystem(inode))
+ return 0;
+
+ cond.uid = inode->i_uid;
+ cond.mode = inode->i_mode;
+
+ return common_perm_dir_dentry(op, dir, dentry, mask, &cond);
+}
+
+/**
+ * common_perm_create - common permission wrapper for operations doing create
+ * @op: operation being checked
+ * @dir: directory that dentry will be created in (NOT NULL)
+ * @dentry: dentry to create (NOT NULL)
+ * @mask: request permission mask
+ * @mode: created file mode
+ *
+ * Returns: %0 else error code if error or permission denied
+ */
+static int common_perm_create(int op, struct path *dir, struct dentry *dentry,
+ u32 mask, umode_t mode)
+{
+ struct path_cond cond = { current_fsuid(), mode };
+
+ if (!dir->mnt || !mediated_filesystem(dir->dentry->d_inode))
+ return 0;
+
+ return common_perm_dir_dentry(op, dir, dentry, mask, &cond);
+}
+
+static int apparmor_path_unlink(struct path *dir, struct dentry *dentry)
+{
+ return common_perm_rm(OP_UNLINK, dir, dentry, AA_MAY_DELETE);
+}
+
+static int apparmor_path_mkdir(struct path *dir, struct dentry *dentry,
+ int mode)
+{
+ return common_perm_create(OP_MKDIR, dir, dentry, AA_MAY_CREATE,
+ S_IFDIR);
+}
+
+static int apparmor_path_rmdir(struct path *dir, struct dentry *dentry)
+{
+ return common_perm_rm(OP_RMDIR, dir, dentry, AA_MAY_DELETE);
+}
+
+static int apparmor_path_mknod(struct path *dir, struct dentry *dentry,
+ int mode, unsigned int dev)
+{
+ return common_perm_create(OP_MKNOD, dir, dentry, AA_MAY_CREATE, mode);
+}
+
+static int apparmor_path_truncate(struct path *path)
+{
+ struct path_cond cond = { path->dentry->d_inode->i_uid,
+ path->dentry->d_inode->i_mode
+ };
+
+ if (!path->mnt || !mediated_filesystem(path->dentry->d_inode))
+ return 0;
+
+ return common_perm(OP_TRUNC, path, MAY_WRITE | AA_MAY_META_WRITE,
+ &cond);
+}
+
+static int apparmor_path_symlink(struct path *dir, struct dentry *dentry,
+ const char *old_name)
+{
+ return common_perm_create(OP_SYMLINK, dir, dentry, AA_MAY_CREATE,
+ S_IFLNK);
+}
+
+static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir,
+ struct dentry *new_dentry)
+{
+ struct aa_profile *profile;
+ int error = 0;
+
+ if (!mediated_filesystem(old_dentry->d_inode))
+ return 0;
+
+ profile = aa_current_profile();
+ if (!unconfined(profile))
+ error = aa_path_link(profile, old_dentry, new_dir, new_dentry);
+ return error;
+}
+
+static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
+ struct path *new_dir, struct dentry *new_dentry)
+{
+ struct aa_profile *profile;
+ int error = 0;
+
+ if (!mediated_filesystem(old_dentry->d_inode))
+ return 0;
+
+ profile = aa_current_profile();
+ if (!unconfined(profile)) {
+ struct path old_path = { old_dir->mnt, old_dentry };
+ struct path new_path = { new_dir->mnt, new_dentry };
+ struct path_cond cond = { old_dentry->d_inode->i_uid,
+ old_dentry->d_inode->i_mode
+ };
+
+ error = aa_path_perm(OP_RENAME_SRC, profile, &old_path, 0,
+ MAY_READ | AA_MAY_META_READ | MAY_WRITE |
+ AA_MAY_META_WRITE | AA_MAY_DELETE,
+ &cond);
+ if (!error)
+ error = aa_path_perm(OP_RENAME_DEST, profile, &new_path,
+ 0, MAY_WRITE | AA_MAY_META_WRITE |
+ AA_MAY_CREATE, &cond);
+
+ }
+ return error;
+}
+
+static int apparmor_path_chmod(struct dentry *dentry, struct vfsmount *mnt,
+ mode_t mode)
+{
+ if (!mediated_filesystem(dentry->d_inode))
+ return 0;
+
+ return common_perm_mnt_dentry(OP_CHMOD, mnt, dentry, AA_MAY_CHMOD);
+}
+
+static int apparmor_path_chown(struct path *path, uid_t uid, gid_t gid)
+{
+ struct path_cond cond = { path->dentry->d_inode->i_uid,
+ path->dentry->d_inode->i_mode
+ };
+
+ if (!mediated_filesystem(path->dentry->d_inode))
+ return 0;
+
+ return common_perm(OP_CHOWN, path, AA_MAY_CHOWN, &cond);
+}
+
+static int apparmor_inode_getattr(struct vfsmount *mnt, struct dentry *dentry)
+{
+ if (!mediated_filesystem(dentry->d_inode))
+ return 0;
+
+ return common_perm_mnt_dentry(OP_GETATTR, mnt, dentry,
+ AA_MAY_META_READ);
+}
+
+static int apparmor_dentry_open(struct file *file, const struct cred *cred)
+{
+ struct aa_file_cxt *fcxt = file->f_security;
+ struct aa_profile *profile;
+ int error = 0;
+
+ if (!mediated_filesystem(file->f_path.dentry->d_inode))
+ return 0;
+
+ /* If in exec, permission is handled by bprm hooks.
+ * Cache permissions granted by the previous exec check, with
+ * implicit read and executable mmap which are required to
+ * actually execute the image.
+ */
+ if (current->in_execve) {
+ fcxt->allow = MAY_EXEC | MAY_READ | AA_EXEC_MMAP;
+ return 0;
+ }
+
+ profile = aa_cred_profile(cred);
+ if (!unconfined(profile)) {
+ struct inode *inode = file->f_path.dentry->d_inode;
+ struct path_cond cond = { inode->i_uid, inode->i_mode };
+
+ error = aa_path_perm(OP_OPEN, profile, &file->f_path, 0,
+ aa_map_file_to_perms(file), &cond);
+ /* todo cache full allowed permissions set and state */
+ fcxt->allow = aa_map_file_to_perms(file);
+ }
+
+ return error;
+}
+
+static int apparmor_file_alloc_security(struct file *file)
+{
+ /* freed by apparmor_file_free_security */
+ file->f_security = aa_alloc_file_context(GFP_KERNEL);
+ if (!file->f_security)
+ return -ENOMEM;
+ return 0;
+
+}
+
+static void apparmor_file_free_security(struct file *file)
+{
+ struct aa_file_cxt *cxt = file->f_security;
+
+ aa_free_file_context(cxt);
+}
+
+static int common_file_perm(int op, struct file *file, u32 mask)
+{
+ struct aa_file_cxt *fcxt = file->f_security;
+ struct aa_profile *profile, *fprofile = aa_cred_profile(file->f_cred);
+ int error = 0;
+
+ BUG_ON(!fprofile);
+
+ if (!file->f_path.mnt ||
+ !mediated_filesystem(file->f_path.dentry->d_inode))
+ return 0;
+
+ profile = __aa_current_profile();
+
+ /* revalidate access, if task is unconfined, or the cached cred
+ * doesn't match or if the request is for more permissions than
+ * was granted.
+ *
+ * Note: the test for !unconfined(fprofile) is to handle file
+ * delegation from unconfined tasks
+ */
+ if (!unconfined(profile) && !unconfined(fprofile) &&
+ ((fprofile != profile) || (mask & ~fcxt->allow)))
+ error = aa_file_perm(op, profile, file, mask);
+
+ return error;
+}
+
+static int apparmor_file_permission(struct file *file, int mask)
+{
+ return common_file_perm(OP_FPERM, file, mask);
+}
+
+static int apparmor_file_lock(struct file *file, unsigned int cmd)
+{
+ u32 mask = AA_MAY_LOCK;
+
+ if (cmd == F_WRLCK)
+ mask |= MAY_WRITE;
+
+ return common_file_perm(OP_FLOCK, file, mask);
+}
+
+static int common_mmap(int op, struct file *file, unsigned long prot,
+ unsigned long flags)
+{
+ struct dentry *dentry;
+ int mask = 0;
+
+ if (!file || !file->f_security)
+ return 0;
+
+ if (prot & PROT_READ)
+ mask |= MAY_READ;
+ /*
+ * Private mappings don't require write perms since they don't
+ * write back to the files
+ */
+ if ((prot & PROT_WRITE) && !(flags & MAP_PRIVATE))
+ mask |= MAY_WRITE;
+ if (prot & PROT_EXEC)
+ mask |= AA_EXEC_MMAP;
+
+ dentry = file->f_path.dentry;
+ return common_file_perm(op, file, mask);
+}
+
+static int apparmor_file_mmap(struct file *file, unsigned long reqprot,
+ unsigned long prot, unsigned long flags,
+ unsigned long addr, unsigned long addr_only)
+{
+ int rc = 0;
+
+ /* do DAC check */
+ rc = cap_file_mmap(file, reqprot, prot, flags, addr, addr_only);
+ if (rc || addr_only)
+ return rc;
+
+ return common_mmap(OP_FMMAP, file, prot, flags);
+}
+
+static int apparmor_file_mprotect(struct vm_area_struct *vma,
+ unsigned long reqprot, unsigned long prot)
+{
+ return common_mmap(OP_FMPROT, vma->vm_file, prot,
+ !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0);
+}
+
+static int apparmor_getprocattr(struct task_struct *task, char *name,
+ char **value)
+{
+ int error = -ENOENT;
+ struct aa_profile *profile;
+ /* released below */
+ const struct cred *cred = get_task_cred(task);
+ struct aa_task_cxt *cxt = cred->security;
+ profile = aa_cred_profile(cred);
+
+ if (strcmp(name, "current") == 0)
+ error = aa_getprocattr(aa_newest_version(cxt->profile),
+ value);
+ else if (strcmp(name, "prev") == 0 && cxt->previous)
+ error = aa_getprocattr(aa_newest_version(cxt->previous),
+ value);
+ else if (strcmp(name, "exec") == 0 && cxt->onexec)
+ error = aa_getprocattr(aa_newest_version(cxt->onexec),
+ value);
+ else
+ error = -EINVAL;
+
+ put_cred(cred);
+
+ return error;
+}
+
+static int apparmor_setprocattr(struct task_struct *task, char *name,
+ void *value, size_t size)
+{
+ char *command, *args = value;
+ size_t arg_size;
+ int error;
+
+ if (size == 0)
+ return -EINVAL;
+ /* args points to a PAGE_SIZE buffer, AppArmor requires that
+ * the buffer must be null terminated or have size <= PAGE_SIZE -1
+ * so that AppArmor can null terminate them
+ */
+ if (args[size - 1] != '\0') {
+ if (size == PAGE_SIZE)
+ return -EINVAL;
+ args[size] = '\0';
+ }
+
+ /* task can only write its own attributes */
+ if (current != task)
+ return -EACCES;
+
+ args = value;
+ args = strim(args);
+ command = strsep(&args, " ");
+ if (!args)
+ return -EINVAL;
+ args = skip_spaces(args);
+ if (!*args)
+ return -EINVAL;
+
+ arg_size = size - (args - (char *) value);
+ if (strcmp(name, "current") == 0) {
+ if (strcmp(command, "changehat") == 0) {
+ error = aa_setprocattr_changehat(args, arg_size,
+ !AA_DO_TEST);
+ } else if (strcmp(command, "permhat") == 0) {
+ error = aa_setprocattr_changehat(args, arg_size,
+ AA_DO_TEST);
+ } else if (strcmp(command, "changeprofile") == 0) {
+ error = aa_setprocattr_changeprofile(args, !AA_ONEXEC,
+ !AA_DO_TEST);
+ } else if (strcmp(command, "permprofile") == 0) {
+ error = aa_setprocattr_changeprofile(args, !AA_ONEXEC,
+ AA_DO_TEST);
+ } else if (strcmp(command, "permipc") == 0) {
+ error = aa_setprocattr_permipc(args);
+ } else {
+ struct common_audit_data sa;
+ COMMON_AUDIT_DATA_INIT(&sa, NONE);
+ sa.aad.op = OP_SETPROCATTR;
+ sa.aad.info = name;
+ sa.aad.error = -EINVAL;
+ return aa_audit(AUDIT_APPARMOR_DENIED, NULL, GFP_KERNEL,
+ &sa, NULL);
+ }
+ } else if (strcmp(name, "exec") == 0) {
+ error = aa_setprocattr_changeprofile(args, AA_ONEXEC,
+ !AA_DO_TEST);
+ } else {
+ /* only support the "current" and "exec" process attributes */
+ return -EINVAL;
+ }
+ if (!error)
+ error = size;
+ return error;
+}
+
+static int apparmor_task_setrlimit(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);
+#define param_check_audit(name, p) __param_check(name, p, int)
+
+static int param_set_mode(const char *val, struct kernel_param *kp);
+static int param_get_mode(char *buffer, struct kernel_param *kp);
+#define param_check_mode(name, p) __param_check(name, p, int)
+
+/* Flag values, also controllable via /sys/module/apparmor/parameters
+ * We define special types as we want to do additional mediation.
+ */
+
+/* AppArmor global enforcement switch - complain, enforce, kill */
+enum profile_mode aa_g_profile_mode = APPARMOR_ENFORCE;
+module_param_call(mode, param_set_mode, param_get_mode,
+ &aa_g_profile_mode, S_IRUSR | S_IWUSR);
+
+/* Debug mode */
+int aa_g_debug;
+module_param_named(debug, aa_g_debug, aabool, S_IRUSR | S_IWUSR);
+
+/* Audit mode */
+enum audit_mode aa_g_audit;
+module_param_call(audit, param_set_audit, param_get_audit,
+ &aa_g_audit, S_IRUSR | S_IWUSR);
+
+/* Determines if audit header is included in audited messages. This
+ * provides more context if the audit daemon is not running
+ */
+int aa_g_audit_header = 1;
+module_param_named(audit_header, aa_g_audit_header, aabool,
+ S_IRUSR | S_IWUSR);
+
+/* lock out loading/removal of policy
+ * TODO: add in at boot loading of policy, which is the only way to
+ * load policy, if lock_policy is set
+ */
+int aa_g_lock_policy;
+module_param_named(lock_policy, aa_g_lock_policy, aalockpolicy,
+ S_IRUSR | S_IWUSR);
+
+/* Syscall logging mode */
+int aa_g_logsyscall;
+module_param_named(logsyscall, aa_g_logsyscall, aabool, S_IRUSR | S_IWUSR);
+
+/* Maximum pathname length before accesses will start getting rejected */
+unsigned int aa_g_path_max = 2 * PATH_MAX;
+module_param_named(path_max, aa_g_path_max, aauint, S_IRUSR | S_IWUSR);
+
+/* Determines how paranoid loading of policy is and how much verification
+ * on the loaded policy is done.
+ */
+int aa_g_paranoid_load = 1;
+module_param_named(paranoid_load, aa_g_paranoid_load, aabool,
+ S_IRUSR | S_IWUSR);
+
+/* Boot time disable flag */
+static unsigned int apparmor_enabled = CONFIG_SECURITY_APPARMOR_BOOTPARAM_VALUE;
+module_param_named(enabled, apparmor_enabled, aabool, S_IRUSR);
+
+static int __init apparmor_enabled_setup(char *str)
+{
+ unsigned long enabled;
+ int error = strict_strtoul(str, 0, &enabled);
+ if (!error)
+ apparmor_enabled = enabled ? 1 : 0;
+ return 1;
+}
+
+__setup("apparmor=", apparmor_enabled_setup);
+
+/* set global flag turning off the ability to load policy */
+static int param_set_aalockpolicy(const char *val, 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 register_security_out;
+ }
+
+ /* Report that AppArmor successfully initialized */
+ apparmor_initialized = 1;
+ if (aa_g_profile_mode == APPARMOR_COMPLAIN)
+ aa_info_message("AppArmor initialized: complain mode enabled");
+ else if (aa_g_profile_mode == APPARMOR_KILL)
+ aa_info_message("AppArmor initialized: kill mode enabled");
+ else
+ aa_info_message("AppArmor initialized");
+
+ return error;
+
+register_security_out:
+ aa_free_root_ns();
+
+alloc_out:
+ aa_destroy_aafs();
+
+ apparmor_enabled = 0;
+ return error;
+
+}
+
+security_initcall(apparmor_init);
diff --git a/security/apparmor/match.c b/security/apparmor/match.c
new file mode 100644
index 0000000..5cb4dc1
--- /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/apparmor.txt
+ * Assumes the dfa @blob stream has been aligned on a 8 byte boundry
+ *
+ * Returns: an unpacked dfa ready for matching or ERR_PTR on failure
+ */
+struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags)
+{
+ int hsize;
+ int error = -ENOMEM;
+ char *data = blob;
+ struct table_header *table = NULL;
+ struct aa_dfa *dfa = kzalloc(sizeof(struct aa_dfa), GFP_KERNEL);
+ if (!dfa)
+ goto fail;
+
+ kref_init(&dfa->count);
+
+ error = -EPROTO;
+
+ /* get dfa table set header */
+ if (size < sizeof(struct table_set_header))
+ goto fail;
+
+ if (ntohl(*(u32 *) data) != YYTH_MAGIC)
+ goto fail;
+
+ hsize = ntohl(*(u32 *) (data + 4));
+ if (size < hsize)
+ goto fail;
+
+ dfa->flags = ntohs(*(u16 *) (data + 12));
+ data += hsize;
+ size -= hsize;
+
+ while (size > 0) {
+ table = unpack_table(data, size);
+ if (!table)
+ goto fail;
+
+ switch (table->td_id) {
+ case YYTD_ID_ACCEPT:
+ if (!(table->td_flags & ACCEPT1_FLAGS(flags)))
+ goto fail;
+ break;
+ case YYTD_ID_ACCEPT2:
+ if (!(table->td_flags & ACCEPT2_FLAGS(flags)))
+ goto fail;
+ break;
+ case YYTD_ID_BASE:
+ if (table->td_flags != YYTD_DATA32)
+ goto fail;
+ break;
+ case YYTD_ID_DEF:
+ case YYTD_ID_NXT:
+ case YYTD_ID_CHK:
+ if (table->td_flags != YYTD_DATA16)
+ goto fail;
+ break;
+ case YYTD_ID_EC:
+ if (table->td_flags != YYTD_DATA8)
+ goto fail;
+ break;
+ default:
+ goto fail;
+ }
+ /* check for duplicate table entry */
+ if (dfa->tables[table->td_id])
+ goto fail;
+ dfa->tables[table->td_id] = table;
+ data += table_size(table->td_lolen, table->td_flags);
+ size -= table_size(table->td_lolen, table->td_flags);
+ table = NULL;
+ }
+
+ error = verify_dfa(dfa, flags);
+ if (error)
+ goto fail;
+
+ return dfa;
+
+fail:
+ kvfree(table);
+ dfa_free(dfa);
+ return ERR_PTR(error);
+}
+
+/**
+ * aa_dfa_match_len - traverse @dfa to find state @str stops at
+ * @dfa: the dfa to match @str against (NOT NULL)
+ * @start: the state of the dfa to start matching in
+ * @str: the string of bytes to match against the dfa (NOT NULL)
+ * @len: length of the string of bytes to match
+ *
+ * aa_dfa_match_len will match @str against the dfa and return the state it
+ * finished matching in. The final state can be used to look up the accepting
+ * label, or as the start state of a continuing match.
+ *
+ * This function will happily match again the 0 byte and only finishes
+ * when @len input is consumed.
+ *
+ * Returns: final state reached after input is consumed
+ */
+unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
+ const char *str, int len)
+{
+ u16 *def = DEFAULT_TABLE(dfa);
+ u32 *base = BASE_TABLE(dfa);
+ u16 *next = NEXT_TABLE(dfa);
+ u16 *check = CHECK_TABLE(dfa);
+ unsigned int state = start, pos;
+
+ if (state == 0)
+ return 0;
+
+ /* current state is <state>, matching character *str */
+ if (dfa->tables[YYTD_ID_EC]) {
+ /* Equivalence class table defined */
+ u8 *equiv = EQUIV_TABLE(dfa);
+ /* default is direct to next state */
+ for (; len; len--) {
+ pos = base[state] + equiv[(u8) *str++];
+ if (check[pos] == state)
+ state = next[pos];
+ else
+ state = def[state];
+ }
+ } else {
+ /* default is direct to next state */
+ for (; len; len--) {
+ pos = base[state] + (u8) *str++;
+ if (check[pos] == state)
+ state = next[pos];
+ else
+ state = def[state];
+ }
+ }
+
+ return state;
+}
+
+/**
+ * aa_dfa_next_state - traverse @dfa to find state @str stops at
+ * @dfa: the dfa to match @str against (NOT NULL)
+ * @start: the state of the dfa to start matching in
+ * @str: the null terminated string of bytes to match against the dfa (NOT NULL)
+ *
+ * aa_dfa_next_state will match @str against the dfa and return the state it
+ * finished matching in. The final state can be used to look up the accepting
+ * label, or as the start state of a continuing match.
+ *
+ * Returns: final state reached after input is consumed
+ */
+unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start,
+ const char *str)
+{
+ return aa_dfa_match_len(dfa, start, str, strlen(str));
+}
diff --git a/security/apparmor/path.c b/security/apparmor/path.c
new file mode 100644
index 0000000..36cc0cc
--- /dev/null
+++ b/security/apparmor/path.c
@@ -0,0 +1,212 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor function for pathnames
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+
+#include <linux/magic.h>
+#include <linux/mnt_namespace.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+#include <linux/nsproxy.h>
+#include <linux/path.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/fs_struct.h>
+
+#include "include/apparmor.h"
+#include "include/path.h"
+#include "include/policy.h"
+
+
+/* modified from dcache.c */
+static int prepend(char **buffer, int buflen, const char *str, int namelen)
+{
+ buflen -= namelen;
+ if (buflen < 0)
+ return -ENAMETOOLONG;
+ *buffer -= namelen;
+ memcpy(*buffer, str, namelen);
+ return 0;
+}
+
+#define CHROOT_NSCONNECT (PATH_CHROOT_REL | PATH_CHROOT_NSCONNECT)
+
+/**
+ * d_namespace_path - lookup a name associated with a given path
+ * @path: path to lookup (NOT NULL)
+ * @buf: buffer to store path to (NOT NULL)
+ * @buflen: length of @buf
+ * @name: Returns - pointer for start of path name with in @buf (NOT NULL)
+ * @flags: flags controlling path lookup
+ *
+ * Handle path name lookup.
+ *
+ * Returns: %0 else error code if path lookup fails
+ * When no error the path name is returned in @name which points to
+ * to a position in @buf
+ */
+static int d_namespace_path(struct path *path, char *buf, int buflen,
+ char **name, int flags)
+{
+ struct path root, tmp;
+ char *res;
+ int connected, error = 0;
+
+ /* Get the root we want to resolve too, released below */
+ if (flags & PATH_CHROOT_REL) {
+ /* resolve paths relative to chroot */
+ get_fs_root(current->fs, &root);
+ } else {
+ /* resolve paths relative to namespace */
+ root.mnt = current->nsproxy->mnt_ns->root;
+ root.dentry = root.mnt->mnt_root;
+ path_get(&root);
+ }
+
+ tmp = root;
+ res = __d_path(path, &tmp, 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;
+ }
+
+ /* 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;
+ }
+
+ /* Determine if the path is connected to the expected root */
+ connected = tmp.dentry == root.dentry && tmp.mnt == root.mnt;
+
+ /* If the path is not connected,
+ * check if it is a sysctl and handle specially else remove any
+ * leading / that __d_path may have returned.
+ * Unless
+ * specifically directed to connect the path,
+ * OR
+ * if in a chroot and doing chroot relative paths and the path
+ * resolves to the namespace root (would be connected outside
+ * of chroot) and specifically directed to connect paths to
+ * namespace root.
+ */
+ if (!connected) {
+ /* is the disconnect path a sysctl? */
+ if (tmp.dentry->d_sb->s_magic == PROC_SUPER_MAGIC &&
+ strncmp(*name, "/sys/", 5) == 0) {
+ /* TODO: convert over to using a per namespace
+ * control instead of hard coded /proc
+ */
+ error = prepend(name, *name - buf, "/proc", 5);
+ } else if (!(flags & PATH_CONNECT_PATH) &&
+ !(((flags & CHROOT_NSCONNECT) == CHROOT_NSCONNECT) &&
+ (tmp.mnt == current->nsproxy->mnt_ns->root &&
+ tmp.dentry == tmp.mnt->mnt_root))) {
+ /* disconnected path, don't return pathname starting
+ * with '/'
+ */
+ error = -ESTALE;
+ if (*res == '/')
+ *name = res + 1;
+ }
+ }
+
+out:
+ path_put(&root);
+
+ return error;
+}
+
+/**
+ * get_name_to_buffer - get the pathname to a buffer ensure dir / is appended
+ * @path: path to get name for (NOT NULL)
+ * @flags: flags controlling path lookup
+ * @buffer: buffer to put name in (NOT NULL)
+ * @size: size of buffer
+ * @name: Returns - contains position of path name in @buffer (NOT NULL)
+ *
+ * Returns: %0 else error on failure
+ */
+static int get_name_to_buffer(struct path *path, int flags, char *buffer,
+ int size, char **name)
+{
+ int adjust = (flags & PATH_IS_DIR) ? 1 : 0;
+ int error = d_namespace_path(path, buffer, size - adjust, name, flags);
+
+ if (!error && (flags & PATH_IS_DIR) && (*name)[1] != '\0')
+ /*
+ * Append "/" to the pathname. The root directory is a special
+ * case; it already ends in slash.
+ */
+ strcpy(&buffer[size - 2], "/");
+
+ return error;
+}
+
+/**
+ * aa_get_name - compute the pathname of a file
+ * @path: path the file (NOT NULL)
+ * @flags: flags controlling path name generation
+ * @buffer: buffer that aa_get_name() allocated (NOT NULL)
+ * @name: Returns - the generated path name if !error (NOT NULL)
+ *
+ * @name is a pointer to the beginning of the pathname (which usually differs
+ * from the beginning of the buffer), or NULL. If there is an error @name
+ * may contain a partial or invalid name that can be used for audit purposes,
+ * but it can not be used for mediation.
+ *
+ * We need PATH_IS_DIR to indicate whether the file is a directory or not
+ * because the file may not yet exist, and so we cannot check the inode's
+ * file type.
+ *
+ * Returns: %0 else error code if could retrieve name
+ */
+int aa_get_name(struct path *path, int flags, char **buffer, const char **name)
+{
+ char *buf, *str = NULL;
+ int size = 256;
+ int error;
+
+ *name = NULL;
+ *buffer = NULL;
+ for (;;) {
+ /* freed by caller */
+ buf = kmalloc(size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ error = get_name_to_buffer(path, flags, buf, size, &str);
+ if (error != -ENAMETOOLONG)
+ break;
+
+ kfree(buf);
+ size <<= 1;
+ if (size > aa_g_path_max)
+ return -ENAMETOOLONG;
+ }
+ *buffer = buf;
+ *name = str;
+
+ return error;
+}
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c
new file mode 100644
index 0000000..52cc865
--- /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.name);
+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 0000000..eb3700e
--- /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/apparmor.txt
+ * All policy is validated before it is used.
+ */
+
+#include <asm/unaligned.h>
+#include <linux/ctype.h>
+#include <linux/errno.h>
+
+#include "include/apparmor.h"
+#include "include/audit.h"
+#include "include/context.h"
+#include "include/match.h"
+#include "include/policy.h"
+#include "include/policy_unpack.h"
+#include "include/sid.h"
+
+/*
+ * The AppArmor interface treats data as a type byte followed by the
+ * actual data. The interface has the notion of a a named entry
+ * which has a name (AA_NAME typecode followed by name string) followed by
+ * the entries typecode and data. Named types allow for optional
+ * elements and extensions to be added and tested for without breaking
+ * backwards compatibility.
+ */
+
+enum aa_code {
+ AA_U8,
+ AA_U16,
+ AA_U32,
+ AA_U64,
+ AA_NAME, /* same as string except it is items name */
+ AA_STRING,
+ AA_BLOB,
+ AA_STRUCT,
+ AA_STRUCTEND,
+ AA_LIST,
+ AA_LISTEND,
+ AA_ARRAY,
+ AA_ARRAYEND,
+};
+
+/*
+ * aa_ext is the read of the buffer containing the serialized profile. The
+ * data is copied into a kernel buffer in apparmorfs and then handed off to
+ * the unpack routines.
+ */
+struct aa_ext {
+ void *start;
+ void *end;
+ void *pos; /* pointer to current position in the buffer */
+ u32 version;
+};
+
+/* audit callback for unpack fields */
+static void audit_cb(struct audit_buffer *ab, void *va)
+{
+ struct common_audit_data *sa = va;
+ if (sa->aad.iface.target) {
+ struct aa_profile *name = sa->aad.iface.target;
+ audit_log_format(ab, " name=");
+ audit_log_untrustedstring(ab, name->base.hname);
+ }
+ if (sa->aad.iface.pos)
+ audit_log_format(ab, " offset=%ld", sa->aad.iface.pos);
+}
+
+/**
+ * audit_iface - do audit message for policy unpacking/load/replace/remove
+ * @new: profile if it has been allocated (MAYBE NULL)
+ * @name: name of the profile being manipulated (MAYBE NULL)
+ * @info: any extra info about the failure (MAYBE NULL)
+ * @e: buffer position info (NOT NULL)
+ * @error: error code
+ *
+ * Returns: %0 or error
+ */
+static int audit_iface(struct aa_profile *new, const char *name,
+ const char *info, struct aa_ext *e, int error)
+{
+ struct aa_profile *profile = __aa_current_profile();
+ struct common_audit_data sa;
+ COMMON_AUDIT_DATA_INIT(&sa, NONE);
+ sa.aad.iface.pos = e->pos - e->start;
+ sa.aad.iface.target = new;
+ sa.aad.name = name;
+ sa.aad.info = info;
+ sa.aad.error = error;
+
+ return aa_audit(AUDIT_APPARMOR_STATUS, profile, GFP_KERNEL, &sa,
+ audit_cb);
+}
+
+/* test if read will be in packed data bounds */
+static bool inbounds(struct aa_ext *e, size_t size)
+{
+ return (size <= e->end - e->pos);
+}
+
+/**
+ * aa_u16_chunck - test and do bounds checking for a u16 size based chunk
+ * @e: serialized data read head (NOT NULL)
+ * @chunk: start address for chunk of data (NOT NULL)
+ *
+ * Returns: the size of chunk found with the read head at the end of the chunk.
+ */
+static size_t unpack_u16_chunk(struct aa_ext *e, char **chunk)
+{
+ size_t size = 0;
+
+ if (!inbounds(e, sizeof(u16)))
+ return 0;
+ size = le16_to_cpu(get_unaligned((u16 *) e->pos));
+ e->pos += sizeof(u16);
+ if (!inbounds(e, size))
+ return 0;
+ *chunk = e->pos;
+ e->pos += size;
+ return size;
+}
+
+/* unpack control byte */
+static bool unpack_X(struct aa_ext *e, enum aa_code code)
+{
+ if (!inbounds(e, 1))
+ return 0;
+ if (*(u8 *) e->pos != code)
+ return 0;
+ e->pos++;
+ return 1;
+}
+
+/**
+ * unpack_nameX - check is the next element is of type X with a name of @name
+ * @e: serialized data extent information (NOT NULL)
+ * @code: type code
+ * @name: name to match to the serialized element. (MAYBE NULL)
+ *
+ * check that the next serialized data element is of type X and has a tag
+ * name @name. If @name is specified then there must be a matching
+ * name element in the stream. If @name is NULL any name element will be
+ * skipped and only the typecode will be tested.
+ *
+ * Returns 1 on success (both type code and name tests match) and the read
+ * head is advanced past the headers
+ *
+ * Returns: 0 if either match fails, the read head does not move
+ */
+static bool unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name)
+{
+ /*
+ * May need to reset pos if name or type doesn't match
+ */
+ void *pos = e->pos;
+ /*
+ * Check for presence of a tagname, and if present name size
+ * AA_NAME tag value is a u16.
+ */
+ if (unpack_X(e, AA_NAME)) {
+ char *tag = NULL;
+ size_t size = unpack_u16_chunk(e, &tag);
+ /* if a name is specified it must match. otherwise skip tag */
+ if (name && (!size || strcmp(name, tag)))
+ goto fail;
+ } else if (name) {
+ /* if a name is specified and there is no name tag fail */
+ goto fail;
+ }
+
+ /* now check if type code matches */
+ if (unpack_X(e, code))
+ return 1;
+
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name)
+{
+ if (unpack_nameX(e, AA_U32, name)) {
+ if (!inbounds(e, sizeof(u32)))
+ return 0;
+ if (data)
+ *data = le32_to_cpu(get_unaligned((u32 *) e->pos));
+ e->pos += sizeof(u32);
+ return 1;
+ }
+ return 0;
+}
+
+static bool unpack_u64(struct aa_ext *e, u64 *data, const char *name)
+{
+ if (unpack_nameX(e, AA_U64, name)) {
+ if (!inbounds(e, sizeof(u64)))
+ return 0;
+ if (data)
+ *data = le64_to_cpu(get_unaligned((u64 *) e->pos));
+ e->pos += sizeof(u64);
+ return 1;
+ }
+ return 0;
+}
+
+static size_t unpack_array(struct aa_ext *e, const char *name)
+{
+ if (unpack_nameX(e, AA_ARRAY, name)) {
+ int size;
+ if (!inbounds(e, sizeof(u16)))
+ return 0;
+ size = (int)le16_to_cpu(get_unaligned((u16 *) e->pos));
+ e->pos += sizeof(u16);
+ return size;
+ }
+ return 0;
+}
+
+static size_t unpack_blob(struct aa_ext *e, char **blob, const char *name)
+{
+ if (unpack_nameX(e, AA_BLOB, name)) {
+ u32 size;
+ if (!inbounds(e, sizeof(u32)))
+ return 0;
+ size = le32_to_cpu(get_unaligned((u32 *) e->pos));
+ e->pos += sizeof(u32);
+ if (inbounds(e, (size_t) size)) {
+ *blob = e->pos;
+ e->pos += size;
+ return size;
+ }
+ }
+ return 0;
+}
+
+static int unpack_str(struct aa_ext *e, const char **string, const char *name)
+{
+ char *src_str;
+ size_t size = 0;
+ void *pos = e->pos;
+ *string = NULL;
+ if (unpack_nameX(e, AA_STRING, name)) {
+ size = unpack_u16_chunk(e, &src_str);
+ if (size) {
+ /* strings are null terminated, length is size - 1 */
+ if (src_str[size - 1] != 0)
+ goto fail;
+ *string = src_str;
+ }
+ }
+ return size;
+
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+static int unpack_strdup(struct aa_ext *e, char **string, const char *name)
+{
+ const char *tmp;
+ void *pos = e->pos;
+ int res = unpack_str(e, &tmp, name);
+ *string = NULL;
+
+ if (!res)
+ return 0;
+
+ *string = kmemdup(tmp, res, GFP_KERNEL);
+ if (!*string) {
+ e->pos = pos;
+ return 0;
+ }
+
+ return res;
+}
+
+/**
+ * verify_accept - verify the accept tables of a dfa
+ * @dfa: dfa to verify accept tables of (NOT NULL)
+ * @flags: flags governing dfa
+ *
+ * Returns: 1 if valid accept tables else 0 if error
+ */
+static bool verify_accept(struct aa_dfa *dfa, int flags)
+{
+ int i;
+
+ /* verify accept permissions */
+ for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) {
+ int mode = ACCEPT_TABLE(dfa)[i];
+
+ if (mode & ~DFA_VALID_PERM_MASK)
+ return 0;
+
+ if (ACCEPT_TABLE2(dfa)[i] & ~DFA_VALID_PERM2_MASK)
+ return 0;
+ }
+ return 1;
+}
+
+/**
+ * unpack_dfa - unpack a file rule dfa
+ * @e: serialized data extent information (NOT NULL)
+ *
+ * returns dfa or ERR_PTR or NULL if no dfa
+ */
+static struct aa_dfa *unpack_dfa(struct aa_ext *e)
+{
+ char *blob = NULL;
+ size_t size;
+ struct aa_dfa *dfa = NULL;
+
+ size = unpack_blob(e, &blob, "aadfa");
+ if (size) {
+ /*
+ * The dfa is aligned with in the blob to 8 bytes
+ * from the beginning of the stream.
+ */
+ size_t sz = blob - (char *)e->start;
+ size_t pad = ALIGN(sz, 8) - sz;
+ int flags = TO_ACCEPT1_FLAG(YYTD_DATA32) |
+ TO_ACCEPT2_FLAG(YYTD_DATA32);
+
+
+ if (aa_g_paranoid_load)
+ flags |= DFA_FLAG_VERIFY_STATES;
+
+ dfa = aa_dfa_unpack(blob + pad, size - pad, flags);
+
+ if (IS_ERR(dfa))
+ return dfa;
+
+ if (!verify_accept(dfa, flags))
+ goto fail;
+ }
+
+ return dfa;
+
+fail:
+ aa_put_dfa(dfa);
+ return ERR_PTR(-EPROTO);
+}
+
+/**
+ * unpack_trans_table - unpack a profile transition table
+ * @e: serialized data extent information (NOT NULL)
+ * @profile: profile to add the accept table to (NOT NULL)
+ *
+ * Returns: 1 if table succesfully unpacked
+ */
+static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
+{
+ void *pos = e->pos;
+
+ /* exec table is optional */
+ if (unpack_nameX(e, AA_STRUCT, "xtable")) {
+ int i, size;
+
+ size = unpack_array(e, NULL);
+ /* currently 4 exec bits and entries 0-3 are reserved iupcx */
+ if (size > 16 - 4)
+ goto fail;
+ profile->file.trans.table = kzalloc(sizeof(char *) * size,
+ GFP_KERNEL);
+ if (!profile->file.trans.table)
+ goto fail;
+
+ profile->file.trans.size = size;
+ for (i = 0; i < size; i++) {
+ char *str;
+ int c, j, size = unpack_strdup(e, &str, NULL);
+ /* unpack_strdup verifies that the last character is
+ * null termination byte.
+ */
+ if (!size)
+ goto fail;
+ profile->file.trans.table[i] = str;
+ /* verify that name doesn't start with space */
+ if (isspace(*str))
+ goto fail;
+
+ /* count internal # of internal \0 */
+ for (c = j = 0; j < size - 2; j++) {
+ if (!str[j])
+ c++;
+ }
+ if (*str == ':') {
+ /* beginning with : requires an embedded \0,
+ * verify that exactly 1 internal \0 exists
+ * trailing \0 already verified by unpack_strdup
+ */
+ if (c != 1)
+ goto fail;
+ /* first character after : must be valid */
+ if (!str[1])
+ goto fail;
+ } else if (c)
+ /* fail - all other cases with embedded \0 */
+ goto fail;
+ }
+ if (!unpack_nameX(e, AA_ARRAYEND, NULL))
+ goto fail;
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+ }
+ return 1;
+
+fail:
+ aa_free_domain_entries(&profile->file.trans);
+ e->pos = pos;
+ return 0;
+}
+
+static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile)
+{
+ void *pos = e->pos;
+
+ /* rlimits are optional */
+ if (unpack_nameX(e, AA_STRUCT, "rlimits")) {
+ int i, size;
+ u32 tmp = 0;
+ if (!unpack_u32(e, &tmp, NULL))
+ goto fail;
+ profile->rlimits.mask = tmp;
+
+ size = unpack_array(e, NULL);
+ if (size > RLIM_NLIMITS)
+ goto fail;
+ for (i = 0; i < size; i++) {
+ u64 tmp = 0;
+ int a = aa_map_resource(i);
+ if (!unpack_u64(e, &tmp, NULL))
+ goto fail;
+ profile->rlimits.limits[a].rlim_max = tmp;
+ }
+ if (!unpack_nameX(e, AA_ARRAYEND, NULL))
+ goto fail;
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+ }
+ return 1;
+
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+/**
+ * unpack_profile - unpack a serialized profile
+ * @e: serialized data extent information (NOT NULL)
+ *
+ * NOTE: unpack profile sets audit struct if there is a failure
+ */
+static struct aa_profile *unpack_profile(struct aa_ext *e)
+{
+ struct aa_profile *profile = NULL;
+ const char *name = NULL;
+ int error = -EPROTO;
+ kernel_cap_t tmpcap;
+ u32 tmp;
+
+ /* check that we have the right struct being passed */
+ if (!unpack_nameX(e, AA_STRUCT, "profile"))
+ goto fail;
+ if (!unpack_str(e, &name, NULL))
+ goto fail;
+
+ profile = aa_alloc_profile(name);
+ if (!profile)
+ return ERR_PTR(-ENOMEM);
+
+ /* profile renaming is optional */
+ (void) unpack_str(e, &profile->rename, "rename");
+
+ /* xmatch is optional and may be NULL */
+ profile->xmatch = unpack_dfa(e);
+ if (IS_ERR(profile->xmatch)) {
+ error = PTR_ERR(profile->xmatch);
+ profile->xmatch = NULL;
+ goto fail;
+ }
+ /* xmatch_len is not optional if xmatch is set */
+ if (profile->xmatch) {
+ if (!unpack_u32(e, &tmp, NULL))
+ goto fail;
+ profile->xmatch_len = tmp;
+ }
+
+ /* per profile debug flags (complain, audit) */
+ if (!unpack_nameX(e, AA_STRUCT, "flags"))
+ goto fail;
+ if (!unpack_u32(e, &tmp, NULL))
+ goto fail;
+ if (tmp)
+ profile->flags |= PFLAG_HAT;
+ if (!unpack_u32(e, &tmp, NULL))
+ goto fail;
+ if (tmp)
+ profile->mode = APPARMOR_COMPLAIN;
+ if (!unpack_u32(e, &tmp, NULL))
+ goto fail;
+ if (tmp)
+ profile->audit = AUDIT_ALL;
+
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+
+ /* path_flags is optional */
+ if (unpack_u32(e, &profile->path_flags, "path_flags"))
+ profile->path_flags |= profile->flags & PFLAG_MEDIATE_DELETED;
+ else
+ /* set a default value if path_flags field is not present */
+ profile->path_flags = PFLAG_MEDIATE_DELETED;
+
+ if (!unpack_u32(e, &(profile->caps.allow.cap[0]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.audit.cap[0]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.quiet.cap[0]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &tmpcap.cap[0], NULL))
+ goto fail;
+
+ if (unpack_nameX(e, AA_STRUCT, "caps64")) {
+ /* optional upper half of 64 bit caps */
+ if (!unpack_u32(e, &(profile->caps.allow.cap[1]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.audit.cap[1]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.quiet.cap[1]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(tmpcap.cap[1]), NULL))
+ goto fail;
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+ }
+
+ if (unpack_nameX(e, AA_STRUCT, "capsx")) {
+ /* optional extended caps mediation mask */
+ if (!unpack_u32(e, &(profile->caps.extended.cap[0]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.extended.cap[1]), NULL))
+ goto fail;
+ }
+
+ if (!unpack_rlimits(e, profile))
+ goto fail;
+
+ /* get file rules */
+ profile->file.dfa = unpack_dfa(e);
+ if (IS_ERR(profile->file.dfa)) {
+ error = PTR_ERR(profile->file.dfa);
+ profile->file.dfa = NULL;
+ goto fail;
+ }
+
+ if (!unpack_u32(e, &profile->file.start, "dfa_start"))
+ /* default start state */
+ profile->file.start = DFA_START;
+
+ if (!unpack_trans_table(e, profile))
+ goto fail;
+
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+
+ return profile;
+
+fail:
+ if (profile)
+ name = NULL;
+ else if (!name)
+ name = "unknown";
+ audit_iface(profile, name, "failed to unpack profile", e, error);
+ aa_put_profile(profile);
+
+ return ERR_PTR(error);
+}
+
+/**
+ * verify_head - unpack serialized stream header
+ * @e: serialized data read head (NOT NULL)
+ * @ns: Returns - namespace if one is specified else NULL (NOT NULL)
+ *
+ * Returns: error or 0 if header is good
+ */
+static int verify_header(struct aa_ext *e, const char **ns)
+{
+ int error = -EPROTONOSUPPORT;
+ /* get the interface version */
+ if (!unpack_u32(e, &e->version, "version")) {
+ audit_iface(NULL, NULL, "invalid profile format", e, error);
+ return error;
+ }
+
+ /* check that the interface version is currently supported */
+ if (e->version != 5) {
+ audit_iface(NULL, NULL, "unsupported interface version", e,
+ error);
+ return error;
+ }
+
+ /* read the namespace if present */
+ if (!unpack_str(e, ns, "namespace"))
+ *ns = NULL;
+
+ return 0;
+}
+
+static bool verify_xindex(int xindex, int table_size)
+{
+ int index, xtype;
+ xtype = xindex & AA_X_TYPE_MASK;
+ index = xindex & AA_X_INDEX_MASK;
+ if (xtype == AA_X_TABLE && index > table_size)
+ return 0;
+ return 1;
+}
+
+/* verify dfa xindexes are in range of transition tables */
+static bool verify_dfa_xindex(struct aa_dfa *dfa, int table_size)
+{
+ int i;
+ for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) {
+ if (!verify_xindex(dfa_user_xindex(dfa, i), table_size))
+ return 0;
+ if (!verify_xindex(dfa_other_xindex(dfa, i), table_size))
+ return 0;
+ }
+ return 1;
+}
+
+/**
+ * verify_profile - Do post unpack analysis to verify profile consistency
+ * @profile: profile to verify (NOT NULL)
+ *
+ * Returns: 0 if passes verification else error
+ */
+static int verify_profile(struct aa_profile *profile)
+{
+ if (aa_g_paranoid_load) {
+ if (profile->file.dfa &&
+ !verify_dfa_xindex(profile->file.dfa,
+ profile->file.trans.size)) {
+ audit_iface(profile, NULL, "Invalid named transition",
+ NULL, -EPROTO);
+ return -EPROTO;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * aa_unpack - unpack packed binary profile data loaded from user space
+ * @udata: user data copied to kmem (NOT NULL)
+ * @size: the size of the user data
+ * @ns: Returns namespace profile is in if specified else NULL (NOT NULL)
+ *
+ * Unpack user data and return refcounted allocated profile or ERR_PTR
+ *
+ * Returns: profile else error pointer if fails to unpack
+ */
+struct aa_profile *aa_unpack(void *udata, size_t size, const char **ns)
+{
+ struct aa_profile *profile = NULL;
+ int error;
+ struct aa_ext e = {
+ .start = udata,
+ .end = udata + size,
+ .pos = udata,
+ };
+
+ error = verify_header(&e, ns);
+ if (error)
+ return ERR_PTR(error);
+
+ profile = unpack_profile(&e);
+ if (IS_ERR(profile))
+ return profile;
+
+ error = verify_profile(profile);
+ if (error) {
+ aa_put_profile(profile);
+ profile = ERR_PTR(error);
+ }
+
+ /* return refcount */
+ return profile;
+}
diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c
new file mode 100644
index 0000000..04a2cf8
--- /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 0000000..a4136c1
--- /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 0000000..f0b34f7
--- /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
index 8168e3e..30ae00f 100644
--- a/security/capability.c
+++ b/security/capability.c
@@ -27,7 +27,7 @@ static int cap_quota_on(struct dentry *dentry)
return 0;
}
-static int cap_bprm_check_security (struct linux_binprm *bprm)
+static int cap_bprm_check_security(struct linux_binprm *bprm)
{
return 0;
}
@@ -268,8 +268,7 @@ static int cap_path_rename(struct path *old_path, struct dentry *old_dentry,
return 0;
}
-static int cap_path_truncate(struct path *path, loff_t length,
- unsigned int time_attrs)
+static int cap_path_truncate(struct path *path)
{
return 0;
}
@@ -412,7 +411,8 @@ static int cap_task_getioprio(struct task_struct *p)
return 0;
}
-static int cap_task_setrlimit(unsigned int resource, struct rlimit *new_rlim)
+static int cap_task_setrlimit(struct task_struct *p, unsigned int resource,
+ struct rlimit *new_rlim)
{
return 0;
}
@@ -677,7 +677,18 @@ 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)
@@ -777,7 +788,8 @@ static int cap_secid_to_secctx(u32 secid, char **secdata, u32 *seclen)
static int cap_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid)
{
- return -EOPNOTSUPP;
+ *secid = 0;
+ return 0;
}
static void cap_release_secctx(char *secdata, u32 seclen)
@@ -1018,6 +1030,9 @@ void __init security_fixup_ops(struct security_operations *ops)
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);
diff --git a/security/commoncap.c b/security/commoncap.c
index 4e01599..5e632b4 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -40,7 +40,7 @@
*
* Warn if that happens, once per boot.
*/
-static void warn_setuid_and_fcaps_mixed(char *fname)
+static void warn_setuid_and_fcaps_mixed(const char *fname)
{
static int warned;
if (!warned) {
@@ -719,14 +719,11 @@ static int cap_safe_nice(struct task_struct *p)
/**
* cap_task_setscheduler - Detemine if scheduler policy change is permitted
* @p: The task to affect
- * @policy: The policy to effect
- * @lp: The parameters to the scheduling policy
*
* 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, int policy,
- struct sched_param *lp)
+int cap_task_setscheduler(struct task_struct *p)
{
return cap_safe_nice(p);
}
diff --git a/security/inode.c b/security/inode.c
index 1c812e8..cb8f47c 100644
--- a/security/inode.c
+++ b/security/inode.c
@@ -53,6 +53,7 @@ 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)
@@ -60,6 +61,7 @@ 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) {
@@ -86,7 +88,7 @@ static int mknod(struct inode *dir, struct dentry *dentry,
int mode, dev_t dev)
{
struct inode *inode;
- int error = -EPERM;
+ int error = -ENOMEM;
if (dentry->d_inode)
return -EEXIST;
@@ -166,6 +168,8 @@ static int create_by_name(const char *name, mode_t mode,
error = mkdir(parent->d_inode, *dentry, mode);
else
error = create(parent->d_inode, *dentry, mode);
+ if (error)
+ dput(*dentry);
} else
error = PTR_ERR(*dentry);
mutex_unlock(&parent->d_inode->i_mutex);
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 16d100d3..ac79032 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -35,6 +35,7 @@ enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8 };
#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;
@@ -69,6 +70,7 @@ 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);
@@ -95,19 +97,16 @@ static inline unsigned long ima_hash_key(u8 *digest)
}
/* iint cache flags */
-#define IMA_MEASURED 1
+#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 long flags;
+ unsigned char flags;
u8 digest[IMA_DIGEST_SIZE];
struct mutex mutex; /* protects: version, flags, digest */
- long readcount; /* measured files readcount */
- long writecount; /* measured files writecount */
- long opencount; /* opens reference count */
- struct kref refcount; /* ima_iint_cache reference count */
- struct rcu_head rcu;
};
/* LIM API function definitions */
@@ -121,13 +120,11 @@ int ima_store_template(struct ima_template_entry *entry, int violation,
void ima_template_show(struct seq_file *m, void *e,
enum ima_show_type show);
-/* radix tree calls to lookup, insert, delete
+/* 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_get(struct inode *inode);
-void iint_free(struct kref *kref);
-void iint_rcu_free(struct rcu_head *rcu);
+struct ima_iint_cache *ima_iint_find(struct inode *inode);
/* IMA policy related functions */
enum ima_hooks { FILE_CHECK = 1, FILE_MMAP, BPRM_CHECK };
diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c
index 52015d0..d3963de 100644
--- a/security/integrity/ima/ima_api.c
+++ b/security/integrity/ima/ima_api.c
@@ -116,7 +116,7 @@ int ima_must_measure(struct ima_iint_cache *iint, struct inode *inode,
{
int must_measure;
- if (iint->flags & IMA_MEASURED)
+ if (iint && iint->flags & IMA_MEASURED)
return 1;
must_measure = ima_match_policy(inode, function, mask);
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index 8fe736a..ef21b96 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -45,7 +45,8 @@ static ssize_t ima_show_htable_violations(struct file *filp,
}
static const struct file_operations ima_htable_violations_ops = {
- .read = ima_show_htable_violations
+ .read = ima_show_htable_violations,
+ .llseek = generic_file_llseek,
};
static ssize_t ima_show_measurements_count(struct file *filp,
@@ -57,7 +58,8 @@ static ssize_t ima_show_measurements_count(struct file *filp,
}
static const struct file_operations ima_measurements_count_ops = {
- .read = ima_show_measurements_count
+ .read = ima_show_measurements_count,
+ .llseek = generic_file_llseek,
};
/* returns pointer to hlist_node */
@@ -319,7 +321,8 @@ static int ima_release_policy(struct inode *inode, struct file *file)
static const struct file_operations ima_measure_policy_ops = {
.open = ima_open_policy,
.write = ima_write_policy,
- .release = ima_release_policy
+ .release = ima_release_policy,
+ .llseek = generic_file_llseek,
};
int __init ima_fs_init(void)
diff --git a/security/integrity/ima/ima_iint.c b/security/integrity/ima/ima_iint.c
index 7625b85..c442e47 100644
--- a/security/integrity/ima/ima_iint.c
+++ b/security/integrity/ima/ima_iint.c
@@ -12,97 +12,119 @@
* File: ima_iint.c
* - implements the IMA hooks: ima_inode_alloc, ima_inode_free
* - cache integrity information associated with an inode
- * using a radix tree.
+ * using a rbtree tree.
*/
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/spinlock.h>
-#include <linux/radix-tree.h>
+#include <linux/rbtree.h>
#include "ima.h"
-RADIX_TREE(ima_iint_store, GFP_ATOMIC);
-DEFINE_SPINLOCK(ima_iint_lock);
-
+static struct rb_root ima_iint_tree = RB_ROOT;
+static DEFINE_SPINLOCK(ima_iint_lock);
static struct kmem_cache *iint_cache __read_mostly;
-/* ima_iint_find_get - return the iint associated with an inode
- *
- * ima_iint_find_get gets a reference to the iint. Caller must
- * remember to put the iint reference.
+int iint_initialized = 0;
+
+/*
+ * __ima_iint_find - return the iint associated with an inode
*/
-struct ima_iint_cache *ima_iint_find_get(struct inode *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;
- rcu_read_lock();
- iint = radix_tree_lookup(&ima_iint_store, (unsigned long)inode);
- if (!iint)
- goto out;
- kref_get(&iint->refcount);
-out:
- rcu_read_unlock();
return iint;
}
-/**
- * ima_inode_alloc - allocate an iint associated with an inode
- * @inode: pointer to the inode
+/*
+ * ima_iint_find - return the iint associated with an inode
*/
-int ima_inode_alloc(struct inode *inode)
+struct ima_iint_cache *ima_iint_find(struct inode *inode)
{
- struct ima_iint_cache *iint = NULL;
- int rc = 0;
-
- iint = kmem_cache_alloc(iint_cache, GFP_NOFS);
- if (!iint)
- return -ENOMEM;
+ struct ima_iint_cache *iint;
- rc = radix_tree_preload(GFP_NOFS);
- if (rc < 0)
- goto out;
+ if (!IS_IMA(inode))
+ return NULL;
spin_lock(&ima_iint_lock);
- rc = radix_tree_insert(&ima_iint_store, (unsigned long)inode, iint);
+ iint = __ima_iint_find(inode);
spin_unlock(&ima_iint_lock);
- radix_tree_preload_end();
-out:
- if (rc < 0)
- kmem_cache_free(iint_cache, iint);
- return rc;
+ return iint;
}
-/* iint_free - called when the iint refcount goes to zero */
-void iint_free(struct kref *kref)
+static void iint_free(struct ima_iint_cache *iint)
{
- struct ima_iint_cache *iint = container_of(kref, struct ima_iint_cache,
- refcount);
iint->version = 0;
iint->flags = 0UL;
- if (iint->readcount != 0) {
- printk(KERN_INFO "%s: readcount: %ld\n", __func__,
- iint->readcount);
- iint->readcount = 0;
- }
- if (iint->writecount != 0) {
- printk(KERN_INFO "%s: writecount: %ld\n", __func__,
- iint->writecount);
- iint->writecount = 0;
- }
- if (iint->opencount != 0) {
- printk(KERN_INFO "%s: opencount: %ld\n", __func__,
- iint->opencount);
- iint->opencount = 0;
- }
- kref_init(&iint->refcount);
kmem_cache_free(iint_cache, iint);
}
-void iint_rcu_free(struct rcu_head *rcu_head)
+/**
+ * ima_inode_alloc - allocate an iint associated with an inode
+ * @inode: pointer to the inode
+ */
+int ima_inode_alloc(struct inode *inode)
{
- struct ima_iint_cache *iint = container_of(rcu_head,
- struct ima_iint_cache, rcu);
- kref_put(&iint->refcount, iint_free);
+ 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;
}
/**
@@ -115,11 +137,20 @@ void ima_inode_free(struct inode *inode)
{
struct ima_iint_cache *iint;
+ if (inode->i_readcount)
+ printk(KERN_INFO "%s: readcount: %u\n", __func__, inode->i_readcount);
+
+ inode->i_readcount = 0;
+
+ if (!IS_IMA(inode))
+ return;
+
spin_lock(&ima_iint_lock);
- iint = radix_tree_delete(&ima_iint_store, (unsigned long)inode);
+ iint = __ima_iint_find(inode);
+ rb_erase(&iint->rb_node, &ima_iint_tree);
spin_unlock(&ima_iint_lock);
- if (iint)
- call_rcu(&iint->rcu, iint_rcu_free);
+
+ iint_free(iint);
}
static void init_once(void *foo)
@@ -130,10 +161,6 @@ static void init_once(void *foo)
iint->version = 0;
iint->flags = 0UL;
mutex_init(&iint->mutex);
- iint->readcount = 0;
- iint->writecount = 0;
- iint->opencount = 0;
- kref_init(&iint->refcount);
}
static int __init ima_iintcache_init(void)
@@ -141,6 +168,7 @@ 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_main.c b/security/integrity/ima/ima_main.c
index f936413..203de97 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -85,50 +85,6 @@ out:
return found;
}
-/* ima_read_write_check - reflect possible reading/writing errors in the PCR.
- *
- * When opening a file for read, if the file is already open for write,
- * the file could change, resulting in a file measurement error.
- *
- * Opening a file for write, if the file is already open for read, results
- * in a time of measure, time of use (ToMToU) error.
- *
- * In either case invalidate the PCR.
- */
-enum iint_pcr_error { TOMTOU, OPEN_WRITERS };
-static void ima_read_write_check(enum iint_pcr_error error,
- struct ima_iint_cache *iint,
- struct inode *inode,
- const unsigned char *filename)
-{
- switch (error) {
- case TOMTOU:
- if (iint->readcount > 0)
- ima_add_violation(inode, filename, "invalid_pcr",
- "ToMToU");
- break;
- case OPEN_WRITERS:
- if (iint->writecount > 0)
- ima_add_violation(inode, filename, "invalid_pcr",
- "open_writers");
- break;
- }
-}
-
-/*
- * Update the counts given an fmode_t
- */
-static void ima_inc_counts(struct ima_iint_cache *iint, fmode_t mode)
-{
- BUG_ON(!mutex_is_locked(&iint->mutex));
-
- iint->opencount++;
- if ((mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ)
- iint->readcount++;
- if (mode & FMODE_WRITE)
- iint->writecount++;
-}
-
/*
* ima_counts_get - increment file counts
*
@@ -145,60 +101,101 @@ void ima_counts_get(struct file *file)
struct dentry *dentry = file->f_path.dentry;
struct inode *inode = dentry->d_inode;
fmode_t mode = file->f_mode;
- struct ima_iint_cache *iint;
int rc;
+ bool send_tomtou = false, send_writers = false;
- if (!ima_initialized || !S_ISREG(inode->i_mode))
+ if (!S_ISREG(inode->i_mode))
return;
- iint = ima_iint_find_get(inode);
- if (!iint)
- return;
- mutex_lock(&iint->mutex);
- rc = ima_must_measure(iint, inode, MAY_READ, FILE_CHECK);
- if (rc < 0)
+
+ spin_lock(&inode->i_lock);
+
+ if (!ima_initialized)
goto out;
if (mode & FMODE_WRITE) {
- ima_read_write_check(TOMTOU, iint, inode, dentry->d_name.name);
+ if (inode->i_readcount && IS_IMA(inode))
+ send_tomtou = true;
goto out;
}
- ima_read_write_check(OPEN_WRITERS, iint, inode, dentry->d_name.name);
+
+ rc = ima_must_measure(NULL, inode, MAY_READ, FILE_CHECK);
+ if (rc < 0)
+ goto out;
+
+ if (atomic_read(&inode->i_writecount) > 0)
+ send_writers = true;
out:
- ima_inc_counts(iint, file->f_mode);
- mutex_unlock(&iint->mutex);
+ /* remember the vfs deals with i_writecount */
+ if ((mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ)
+ inode->i_readcount++;
+
+ spin_unlock(&inode->i_lock);
- kref_put(&iint->refcount, iint_free);
+ 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");
}
/*
* Decrement ima counts
*/
-static void ima_dec_counts(struct ima_iint_cache *iint, struct inode *inode,
- struct file *file)
+static void ima_dec_counts(struct inode *inode, struct file *file)
{
mode_t mode = file->f_mode;
- BUG_ON(!mutex_is_locked(&iint->mutex));
- iint->opencount--;
- if ((mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ)
- iint->readcount--;
- if (mode & FMODE_WRITE) {
- iint->writecount--;
- if (iint->writecount == 0) {
- if (iint->version != inode->i_version)
- iint->flags &= ~IMA_MEASURED;
+ assert_spin_locked(&inode->i_lock);
+
+ if ((mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ) {
+ if (unlikely(inode->i_readcount == 0)) {
+ if (!ima_limit_imbalance(file)) {
+ printk(KERN_INFO "%s: open/free imbalance (r:%u)\n",
+ __func__, inode->i_readcount);
+ dump_stack();
+ }
+ return;
}
+ inode->i_readcount--;
}
+}
- if (((iint->opencount < 0) ||
- (iint->readcount < 0) ||
- (iint->writecount < 0)) &&
- !ima_limit_imbalance(file)) {
- printk(KERN_INFO "%s: open/free imbalance (r:%ld w:%ld o:%ld)\n",
- __func__, iint->readcount, iint->writecount,
- iint->opencount);
- dump_stack();
- }
+static void ima_check_last_writer(struct ima_iint_cache *iint,
+ struct inode *inode,
+ struct file *file)
+{
+ mode_t mode = file->f_mode;
+
+ BUG_ON(!mutex_is_locked(&iint->mutex));
+ assert_spin_locked(&inode->i_lock);
+
+ if (mode & FMODE_WRITE &&
+ atomic_read(&inode->i_writecount) == 1 &&
+ iint->version != inode->i_version)
+ iint->flags &= ~IMA_MEASURED;
+}
+
+static void ima_file_free_iint(struct ima_iint_cache *iint, struct inode *inode,
+ struct file *file)
+{
+ mutex_lock(&iint->mutex);
+ spin_lock(&inode->i_lock);
+
+ ima_dec_counts(inode, file);
+ ima_check_last_writer(iint, inode, file);
+
+ spin_unlock(&inode->i_lock);
+ mutex_unlock(&iint->mutex);
+}
+
+static void ima_file_free_noiint(struct inode *inode, struct file *file)
+{
+ spin_lock(&inode->i_lock);
+
+ ima_dec_counts(inode, file);
+
+ spin_unlock(&inode->i_lock);
}
/**
@@ -206,23 +203,23 @@ static void ima_dec_counts(struct ima_iint_cache *iint, struct inode *inode,
* @file: pointer to file structure being freed
*
* Flag files that changed, based on i_version;
- * and decrement the iint readcount/writecount.
+ * and decrement the i_readcount.
*/
void ima_file_free(struct file *file)
{
struct inode *inode = file->f_dentry->d_inode;
struct ima_iint_cache *iint;
- if (!ima_initialized || !S_ISREG(inode->i_mode))
- return;
- iint = ima_iint_find_get(inode);
- if (!iint)
+ if (!iint_initialized || !S_ISREG(inode->i_mode))
return;
- mutex_lock(&iint->mutex);
- ima_dec_counts(iint, inode, file);
- mutex_unlock(&iint->mutex);
- kref_put(&iint->refcount, iint_free);
+ iint = ima_iint_find(inode);
+
+ if (iint)
+ ima_file_free_iint(iint, inode, file);
+ else
+ ima_file_free_noiint(inode, file);
+
}
static int process_measurement(struct file *file, const unsigned char *filename,
@@ -230,15 +227,25 @@ static int process_measurement(struct file *file, const unsigned char *filename,
{
struct inode *inode = file->f_dentry->d_inode;
struct ima_iint_cache *iint;
- int rc;
+ int rc = 0;
if (!ima_initialized || !S_ISREG(inode->i_mode))
return 0;
- iint = ima_iint_find_get(inode);
- if (!iint)
- return -ENOMEM;
+
+ rc = ima_must_measure(NULL, 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 = ima_must_measure(iint, inode, mask, function);
if (rc != 0)
goto out;
@@ -248,7 +255,6 @@ static int process_measurement(struct file *file, const unsigned char *filename,
ima_store_measurement(iint, file, filename);
out:
mutex_unlock(&iint->mutex);
- kref_put(&iint->refcount, iint_free);
return rc;
}
diff --git a/security/keys/internal.h b/security/keys/internal.h
index 38783dc..56a133d 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -15,11 +15,6 @@
#include <linux/sched.h>
#include <linux/key-type.h>
-static inline __attribute__((format(printf, 1, 2)))
-void no_printk(const char *fmt, ...)
-{
-}
-
#ifdef __KDEBUG
#define kenter(FMT, ...) \
printk(KERN_DEBUG "==> %s("FMT")\n", __func__, ##__VA_ARGS__)
@@ -114,6 +109,10 @@ extern key_ref_t keyring_search_aux(key_ref_t keyring_ref,
const void *description,
key_match_func_t match);
+extern key_ref_t search_my_process_keyrings(struct key_type *type,
+ const void *description,
+ key_match_func_t match,
+ const struct cred *cred);
extern key_ref_t search_process_keyrings(struct key_type *type,
const void *description,
key_match_func_t match,
@@ -134,6 +133,7 @@ extern struct key *request_key_and_link(struct key_type *type,
struct key *dest_keyring,
unsigned long flags);
+extern int lookup_user_key_possessed(const struct key *key, const void *target);
extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags,
key_perm_t perm);
#define KEY_LOOKUP_CREATE 0x01
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 6261745..60924f6 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -505,13 +505,11 @@ okay:
ret = snprintf(tmpbuf, PAGE_SIZE - 1,
"%s;%d;%d;%08x;%s",
- key_ref_to_ptr(key_ref)->type->name,
- key_ref_to_ptr(key_ref)->uid,
- key_ref_to_ptr(key_ref)->gid,
- key_ref_to_ptr(key_ref)->perm,
- key_ref_to_ptr(key_ref)->description ?
- key_ref_to_ptr(key_ref)->description : ""
- );
+ key->type->name,
+ key->uid,
+ key->gid,
+ key->perm,
+ key->description ?: "");
/* include a NUL char at the end of the data */
if (ret > PAGE_SIZE - 1)
@@ -1091,7 +1089,7 @@ error:
long keyctl_set_timeout(key_serial_t id, unsigned timeout)
{
struct timespec now;
- struct key *key;
+ struct key *key, *instkey;
key_ref_t key_ref;
time_t expiry;
long ret;
@@ -1099,10 +1097,25 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout)
key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
KEY_SETATTR);
if (IS_ERR(key_ref)) {
+ /* setting the timeout on a key under construction is permitted
+ * if we have the authorisation token handy */
+ if (PTR_ERR(key_ref) == -EACCES) {
+ instkey = key_get_instantiation_authkey(id);
+ if (!IS_ERR(instkey)) {
+ key_put(instkey);
+ key_ref = lookup_user_key(id,
+ KEY_LOOKUP_PARTIAL,
+ 0);
+ if (!IS_ERR(key_ref))
+ goto okay;
+ }
+ }
+
ret = PTR_ERR(key_ref);
goto error;
}
+okay:
key = key_ref_to_ptr(key_ref);
/* make the changes with the locks held to prevent races */
@@ -1259,6 +1272,7 @@ long keyctl_session_to_parent(void)
keyring_r = NULL;
me = current;
+ rcu_read_lock();
write_lock_irq(&tasklist_lock);
parent = me->real_parent;
@@ -1291,7 +1305,8 @@ long keyctl_session_to_parent(void)
goto not_permitted;
/* the keyrings must have the same UID */
- if (pcred->tgcred->session_keyring->uid != mycred->euid ||
+ if ((pcred->tgcred->session_keyring &&
+ pcred->tgcred->session_keyring->uid != mycred->euid) ||
mycred->tgcred->session_keyring->uid != mycred->euid)
goto not_permitted;
@@ -1306,6 +1321,7 @@ long keyctl_session_to_parent(void)
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;
@@ -1314,6 +1330,7 @@ already_same:
ret = 0;
not_permitted:
write_unlock_irq(&tasklist_lock);
+ rcu_read_unlock();
put_cred(cred);
return ret;
diff --git a/security/keys/proc.c b/security/keys/proc.c
index 068b66e..70373966 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -184,20 +184,36 @@ static void proc_keys_stop(struct seq_file *p, void *v)
static int proc_keys_show(struct seq_file *m, void *v)
{
+ const struct cred *cred = current_cred();
struct rb_node *_p = v;
struct key *key = rb_entry(_p, struct key, serial_node);
struct timespec now;
unsigned long timo;
+ key_ref_t key_ref, skey_ref;
char xbuf[12];
int rc;
+ key_ref = make_key_ref(key, 0);
+
+ /* determine if the key is possessed by this process (a test we can
+ * skip if the key does not indicate the possessor can view it
+ */
+ if (key->perm & KEY_POS_VIEW) {
+ skey_ref = search_my_process_keyrings(key->type, key,
+ lookup_user_key_possessed,
+ cred);
+ if (!IS_ERR(skey_ref)) {
+ key_ref_put(skey_ref);
+ key_ref = make_key_ref(key, 1);
+ }
+ }
+
/* check whether the current task is allowed to view the key (assuming
* non-possession)
* - the caller holds a spinlock, and thus the RCU read lock, making our
* access to __current_cred() safe
*/
- rc = key_task_permission(make_key_ref(key, 0), current_cred(),
- KEY_VIEW);
+ rc = key_task_permission(key_ref, cred, KEY_VIEW);
if (rc < 0)
return 0;
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index 6b8e4ff..504bdd2 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -207,7 +207,7 @@ static int install_process_keyring(void)
ret = install_process_keyring_to_cred(new);
if (ret < 0) {
abort_creds(new);
- return ret != -EEXIST ?: 0;
+ return ret != -EEXIST ? ret : 0;
}
return commit_creds(new);
@@ -309,22 +309,19 @@ void key_fsgid_changed(struct task_struct *tsk)
/*****************************************************************************/
/*
- * search the process keyrings for the first matching key
+ * search only my process keyrings for the first matching key
* - we use the supplied match function to see if the description (or other
* feature of interest) matches
* - we return -EAGAIN if we didn't find any matching key
* - we return -ENOKEY if we found only negative matching keys
*/
-key_ref_t search_process_keyrings(struct key_type *type,
- const void *description,
- key_match_func_t match,
- const struct cred *cred)
+key_ref_t search_my_process_keyrings(struct key_type *type,
+ const void *description,
+ key_match_func_t match,
+ const struct cred *cred)
{
- struct request_key_auth *rka;
key_ref_t key_ref, ret, err;
- might_sleep();
-
/* we want to return -EAGAIN or -ENOKEY if any of the keyrings were
* searchable, but we failed to find a key or we found a negative key;
* otherwise we want to return a sample error (probably -EACCES) if
@@ -424,6 +421,36 @@ key_ref_t search_process_keyrings(struct key_type *type,
}
}
+ /* no key - decide on the error we're going to go for */
+ key_ref = ret ? ret : err;
+
+found:
+ return key_ref;
+}
+
+/*****************************************************************************/
+/*
+ * search the process keyrings for the first matching key
+ * - we use the supplied match function to see if the description (or other
+ * feature of interest) matches
+ * - we return -EAGAIN if we didn't find any matching key
+ * - we return -ENOKEY if we found only negative matching keys
+ */
+key_ref_t search_process_keyrings(struct key_type *type,
+ const void *description,
+ key_match_func_t match,
+ const struct cred *cred)
+{
+ struct request_key_auth *rka;
+ key_ref_t key_ref, ret = ERR_PTR(-EACCES), err;
+
+ might_sleep();
+
+ key_ref = search_my_process_keyrings(type, description, match, cred);
+ if (!IS_ERR(key_ref))
+ goto found;
+ err = key_ref;
+
/* if this process has an instantiation authorisation key, then we also
* search the keyrings of the process mentioned there
* - we don't permit access to request_key auth keys via this method
@@ -446,24 +473,19 @@ key_ref_t search_process_keyrings(struct key_type *type,
if (!IS_ERR(key_ref))
goto found;
- switch (PTR_ERR(key_ref)) {
- case -EAGAIN: /* no key */
- if (ret)
- break;
- case -ENOKEY: /* negative key */
- ret = key_ref;
- break;
- default:
- err = key_ref;
- break;
- }
+ ret = key_ref;
} else {
up_read(&cred->request_key_auth->sem);
}
}
/* no key - decide on the error we're going to go for */
- key_ref = ret ? ret : err;
+ if (err == ERR_PTR(-ENOKEY) || ret == ERR_PTR(-ENOKEY))
+ key_ref = ERR_PTR(-ENOKEY);
+ else if (err == ERR_PTR(-EACCES))
+ key_ref = ret;
+ else
+ key_ref = err;
found:
return key_ref;
@@ -474,7 +496,7 @@ found:
/*
* see if the key we're looking at is the target key
*/
-static int lookup_user_key_possessed(const struct key *key, const void *target)
+int lookup_user_key_possessed(const struct key *key, const void *target)
{
return key == target;
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index f5ec9ac..0088dd8 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -144,6 +144,7 @@ static int call_sbin_request_key(struct key_construction *cons,
prkey = 0;
if (cred->tgcred->process_keyring)
prkey = cred->tgcred->process_keyring->serial;
+ sprintf(keyring_str[1], "%d", prkey);
rcu_read_lock();
session = rcu_dereference(cred->tgcred->session_keyring);
@@ -536,6 +537,8 @@ int wait_for_key_construction(struct key *key, bool intr)
intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE);
if (ret < 0)
return ret;
+ if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+ return -ENOKEY;
return key_validate(key);
}
EXPORT_SYMBOL(wait_for_key_construction);
diff --git a/security/security.c b/security/security.c
index 351942a..3ef5e2a 100644
--- a/security/security.c
+++ b/security/security.c
@@ -89,20 +89,12 @@ __setup("security=", choose_lsm);
* 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,
- * -or there is no default LSM set and the user didn't specify a
- * specific LSM and we're the first to ask for registration permission,
- * -or the passed LSM is currently loaded.
+ * choose an alternate LSM at boot time.
* Otherwise, return false.
*/
int __init security_module_enable(struct security_operations *ops)
{
- if (!*chosen_lsm)
- strncpy(chosen_lsm, ops->name, SECURITY_NAME_MAX);
- else if (strncmp(ops->name, chosen_lsm, SECURITY_NAME_MAX))
- return 0;
-
- return 1;
+ return !strcmp(ops->name, chosen_lsm);
}
/**
@@ -333,16 +325,8 @@ EXPORT_SYMBOL(security_sb_parse_opts_str);
int security_inode_alloc(struct inode *inode)
{
- int ret;
-
inode->i_security = NULL;
- ret = security_ops->inode_alloc_security(inode);
- if (ret)
- return ret;
- ret = ima_inode_alloc(inode);
- if (ret)
- security_inode_free(inode);
- return ret;
+ return security_ops->inode_alloc_security(inode);
}
void security_inode_free(struct inode *inode)
@@ -417,12 +401,11 @@ int security_path_rename(struct path *old_dir, struct dentry *old_dentry,
new_dentry);
}
-int security_path_truncate(struct path *path, loff_t length,
- unsigned int time_attrs)
+int security_path_truncate(struct path *path)
{
if (unlikely(IS_PRIVATE(path->dentry->d_inode)))
return 0;
- return security_ops->path_truncate(path, length, time_attrs);
+ return security_ops->path_truncate(path);
}
int security_path_chmod(struct dentry *dentry, struct vfsmount *mnt,
@@ -620,7 +603,13 @@ void security_inode_getsecid(const struct inode *inode, u32 *secid)
int security_file_permission(struct file *file, int mask)
{
- return security_ops->file_permission(file, 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)
@@ -684,7 +673,13 @@ int security_file_receive(struct file *file)
int security_dentry_open(struct file *file, const struct cred *cred)
{
- return security_ops->dentry_open(file, 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)
@@ -769,15 +764,15 @@ int security_task_getioprio(struct task_struct *p)
return security_ops->task_getioprio(p);
}
-int security_task_setrlimit(unsigned int resource, struct rlimit *new_rlim)
+int security_task_setrlimit(struct task_struct *p, unsigned int resource,
+ struct rlimit *new_rlim)
{
- return security_ops->task_setrlimit(resource, new_rlim);
+ return security_ops->task_setrlimit(p, resource, new_rlim);
}
-int security_task_setscheduler(struct task_struct *p,
- int policy, struct sched_param *lp)
+int security_task_setscheduler(struct task_struct *p)
{
- return security_ops->task_setscheduler(p, policy, lp);
+ return security_ops->task_setscheduler(p);
}
int security_task_getscheduler(struct task_struct *p)
@@ -1133,6 +1128,24 @@ void security_inet_conn_established(struct sock *sk,
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();
diff --git a/security/selinux/Makefile b/security/selinux/Makefile
index f013982..ad5cd76 100644
--- a/security/selinux/Makefile
+++ b/security/selinux/Makefile
@@ -2,29 +2,24 @@
# Makefile for building the SELinux module as part of the kernel tree.
#
-obj-$(CONFIG_SECURITY_SELINUX) := selinux.o ss/
-
-selinux-y := avc.o \
- hooks.o \
- selinuxfs.o \
- netlink.o \
- nlmsgtab.o \
- netif.o \
- netnode.o \
- netport.o \
- exports.o
+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
-EXTRA_CFLAGS += -Isecurity/selinux -Isecurity/selinux/include
+ccflags-y := -Isecurity/selinux -Isecurity/selinux/include
-$(obj)/avc.o: $(obj)/flask.h
+$(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
+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
index 7f1a304..9da6420 100644
--- a/security/selinux/avc.c
+++ b/security/selinux/avc.c
@@ -288,7 +288,6 @@ static struct avc_node *avc_alloc_node(void)
if (!node)
goto out;
- INIT_RCU_HEAD(&node->rhead);
INIT_HLIST_NODE(&node->list);
avc_cache_stats_incr(allocations);
@@ -489,9 +488,29 @@ void avc_audit(u32 ssid, u32 tsid,
struct common_audit_data stack_data;
u32 denied, audited;
denied = requested & ~avd->allowed;
- if (denied)
+ if (denied) {
audited = denied & avd->auditdeny;
- else if (result)
+ /*
+ * a->selinux_audit_data.auditdeny is TRICKY! Setting a bit in
+ * this field means that ANY denials should NOT be audited if
+ * the policy contains an explicit dontaudit rule for that
+ * permission. Take notice that this is unrelated to the
+ * actual permissions that were denied. As an example lets
+ * assume:
+ *
+ * denied == READ
+ * avd.auditdeny & ACCESS == 0 (not set means explicit rule)
+ * selinux_audit_data.auditdeny & ACCESS == 1
+ *
+ * We will NOT audit the denial even though the denied
+ * permission was READ and the auditdeny checks were for
+ * ACCESS
+ */
+ if (a &&
+ a->selinux_audit_data.auditdeny &&
+ !(a->selinux_audit_data.auditdeny & avd->auditdeny))
+ audited = 0;
+ } else if (result)
audited = denied = requested;
else
audited = requested & avd->auditallow;
diff --git a/security/selinux/exports.c b/security/selinux/exports.c
index c0a454a..9066438 100644
--- a/security/selinux/exports.c
+++ b/security/selinux/exports.c
@@ -11,58 +11,9 @@
* 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/module.h>
-#include <linux/selinux.h>
-#include <linux/fs.h>
-#include <linux/ipc.h>
-#include <asm/atomic.h>
#include "security.h"
-#include "objsec.h"
-
-/* SECMARK reference count */
-extern atomic_t selinux_secmark_refcount;
-
-int selinux_string_to_sid(char *str, u32 *sid)
-{
- if (selinux_enabled)
- return security_context_to_sid(str, strlen(str), sid);
- else {
- *sid = 0;
- return 0;
- }
-}
-EXPORT_SYMBOL_GPL(selinux_string_to_sid);
-
-int selinux_secmark_relabel_packet_permission(u32 sid)
-{
- if (selinux_enabled) {
- 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);
- }
- return 0;
-}
-EXPORT_SYMBOL_GPL(selinux_secmark_relabel_packet_permission);
-
-void selinux_secmark_refcount_inc(void)
-{
- atomic_inc(&selinux_secmark_refcount);
-}
-EXPORT_SYMBOL_GPL(selinux_secmark_refcount_inc);
-
-void selinux_secmark_refcount_dec(void)
-{
- atomic_dec(&selinux_secmark_refcount);
-}
-EXPORT_SYMBOL_GPL(selinux_secmark_refcount_dec);
bool selinux_is_enabled(void)
{
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 5c9f25b..d9154cf 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -87,9 +87,6 @@
#include "netlabel.h"
#include "audit.h"
-#define XATTR_SELINUX_SUFFIX "selinux"
-#define XATTR_NAME_SELINUX XATTR_SECURITY_PREFIX XATTR_SELINUX_SUFFIX
-
#define NUM_SEL_MNT_OPTS 5
extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm);
@@ -188,7 +185,7 @@ static inline u32 task_sid(const struct task_struct *task)
*/
static inline u32 current_sid(void)
{
- const struct task_security_struct *tsec = current_cred()->security;
+ const struct task_security_struct *tsec = current_security();
return tsec->sid;
}
@@ -279,32 +276,6 @@ static void superblock_free_security(struct super_block *sb)
kfree(sbsec);
}
-static int sk_alloc_security(struct sock *sk, int family, gfp_t priority)
-{
- struct sk_security_struct *sksec;
-
- sksec = kzalloc(sizeof(*sksec), priority);
- if (!sksec)
- return -ENOMEM;
-
- sksec->peer_sid = SECINITSID_UNLABELED;
- sksec->sid = SECINITSID_UNLABELED;
- sk->sk_security = sksec;
-
- selinux_netlbl_sk_security_reset(sksec);
-
- return 0;
-}
-
-static void sk_free_security(struct sock *sk)
-{
- struct sk_security_struct *sksec = sk->sk_security;
-
- sk->sk_security = NULL;
- selinux_netlbl_sk_security_free(sksec);
- kfree(sksec);
-}
-
/* The security server must be initialized before
any labeling or access decisions can be provided. */
extern int ss_initialized;
@@ -1584,8 +1555,7 @@ static int may_create(struct inode *dir,
struct dentry *dentry,
u16 tclass)
{
- const struct cred *cred = current_cred();
- const struct task_security_struct *tsec = cred->security;
+ const struct task_security_struct *tsec = current_security();
struct inode_security_struct *dsec;
struct superblock_security_struct *sbsec;
u32 sid, newsid;
@@ -1806,27 +1776,9 @@ static inline u32 open_file_to_av(struct file *file)
{
u32 av = file_to_av(file);
- if (selinux_policycap_openperm) {
- mode_t mode = file->f_path.dentry->d_inode->i_mode;
- /*
- * lnk files and socks do not really have an 'open'
- */
- if (S_ISREG(mode))
- av |= FILE__OPEN;
- else if (S_ISCHR(mode))
- av |= CHR_FILE__OPEN;
- else if (S_ISBLK(mode))
- av |= BLK_FILE__OPEN;
- else if (S_ISFIFO(mode))
- av |= FIFO_FILE__OPEN;
- else if (S_ISDIR(mode))
- av |= DIR__OPEN;
- else if (S_ISSOCK(mode))
- av |= SOCK_FILE__OPEN;
- else
- printk(KERN_ERR "SELinux: WARNING: inside %s with "
- "unknown mode:%o\n", __func__, mode);
- }
+ if (selinux_policycap_openperm)
+ av |= FILE__OPEN;
+
return av;
}
@@ -2183,8 +2135,7 @@ static int selinux_bprm_set_creds(struct linux_binprm *bprm)
static int selinux_bprm_secureexec(struct linux_binprm *bprm)
{
- const struct cred *cred = current_cred();
- const struct task_security_struct *tsec = cred->security;
+ const struct task_security_struct *tsec = current_security();
u32 sid, osid;
int atsecure = 0;
@@ -2219,8 +2170,9 @@ static inline void flush_unauthorized_files(const struct cred *cred,
tty = get_current_tty();
if (tty) {
- file_list_lock();
+ 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.
@@ -2228,14 +2180,16 @@ static inline void flush_unauthorized_files(const struct cred *cred,
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 = list_first_entry(&tty->tty_files, struct file, f_u.fu_list);
+ 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(cred, inode,
FILE__READ | FILE__WRITE, NULL)) {
drop_tty = 1;
}
}
- file_list_unlock();
+ spin_unlock(&tty_files_lock);
tty_kref_put(tty);
}
/* Reset controlling tty. */
@@ -2333,12 +2287,15 @@ static void selinux_bprm_committing_creds(struct linux_binprm *bprm)
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);
}
- update_rlimit_cpu(current->signal->rlim[RLIMIT_CPU].rlim_cur);
+ task_unlock(current);
+ update_rlimit_cpu(current, rlimit(RLIMIT_CPU));
}
}
@@ -2559,8 +2516,7 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir,
char **name, void **value,
size_t *len)
{
- const struct cred *cred = current_cred();
- const struct task_security_struct *tsec = cred->security;
+ const struct task_security_struct *tsec = current_security();
struct inode_security_struct *dsec;
struct superblock_security_struct *sbsec;
u32 sid, newsid, clen;
@@ -2676,14 +2632,26 @@ static int selinux_inode_follow_link(struct dentry *dentry, struct nameidata *na
static int selinux_inode_permission(struct inode *inode, int mask)
{
const struct cred *cred = current_cred();
+ struct common_audit_data ad;
+ u32 perms;
+ bool from_access;
- if (!mask) {
- /* No permission to check. Existence test. */
+ from_access = mask & MAY_ACCESS;
+ mask &= (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND);
+
+ /* No permission to check. Existence test. */
+ if (!mask)
return 0;
- }
- return inode_has_perm(cred, inode,
- file_mask_to_av(inode->i_mode, mask), NULL);
+ COMMON_AUDIT_DATA_INIT(&ad, FS);
+ ad.u.fs.inode = inode;
+
+ if (from_access)
+ ad.selinux_audit_data.auditdeny |= FILE__AUDIT_ACCESS;
+
+ perms = file_mask_to_av(inode->i_mode, mask);
+
+ return inode_has_perm(cred, inode, perms, &ad);
}
static int selinux_inode_setattr(struct dentry *dentry, struct iattr *iattr)
@@ -3371,25 +3339,26 @@ static int selinux_task_getioprio(struct task_struct *p)
return current_has_perm(p, PROCESS__GETSCHED);
}
-static int selinux_task_setrlimit(unsigned int resource, struct rlimit *new_rlim)
+static int selinux_task_setrlimit(struct task_struct *p, unsigned int resource,
+ struct rlimit *new_rlim)
{
- struct rlimit *old_rlim = current->signal->rlim + resource;
+ 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(current, PROCESS__SETRLIMIT);
+ return current_has_perm(p, PROCESS__SETRLIMIT);
return 0;
}
-static int selinux_task_setscheduler(struct task_struct *p, int policy, struct sched_param *lp)
+static int selinux_task_setscheduler(struct task_struct *p)
{
int rc;
- rc = cap_task_setscheduler(p, policy, lp);
+ rc = cap_task_setscheduler(p);
if (rc)
return rc;
@@ -3671,71 +3640,54 @@ static int selinux_skb_peerlbl_sid(struct sk_buff *skb, u16 family, u32 *sid)
}
/* socket security operations */
-static int socket_has_perm(struct task_struct *task, struct socket *sock,
- u32 perms)
+
+static u32 socket_sockcreate_sid(const struct task_security_struct *tsec)
{
- struct inode_security_struct *isec;
- struct common_audit_data ad;
- u32 sid;
- int err = 0;
+ return tsec->sockcreate_sid ? : tsec->sid;
+}
- isec = SOCK_INODE(sock)->i_security;
+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 (isec->sid == SECINITSID_KERNEL)
- goto out;
- sid = task_sid(task);
+ if (sksec->sid == SECINITSID_KERNEL)
+ return 0;
COMMON_AUDIT_DATA_INIT(&ad, NET);
- ad.u.net.sk = sock->sk;
- err = avc_has_perm(sid, isec->sid, isec->sclass, perms, &ad);
+ ad.u.net.sk = sk;
-out:
- return err;
+ return avc_has_perm(tsid, sksec->sid, sksec->sclass, perms, &ad);
}
static int selinux_socket_create(int family, int type,
int protocol, int kern)
{
- const struct cred *cred = current_cred();
- const struct task_security_struct *tsec = cred->security;
- u32 sid, newsid;
+ const struct task_security_struct *tsec = current_security();
+ u32 newsid;
u16 secclass;
- int err = 0;
if (kern)
- goto out;
-
- sid = tsec->sid;
- newsid = tsec->sockcreate_sid ?: sid;
+ return 0;
+ newsid = socket_sockcreate_sid(tsec);
secclass = socket_type_to_security_class(family, type, protocol);
- err = avc_has_perm(sid, newsid, secclass, SOCKET__CREATE, NULL);
-
-out:
- return err;
+ return avc_has_perm(tsec->sid, newsid, secclass, SOCKET__CREATE, NULL);
}
static int selinux_socket_post_create(struct socket *sock, int family,
int type, int protocol, int kern)
{
- const struct cred *cred = current_cred();
- const struct task_security_struct *tsec = cred->security;
- struct inode_security_struct *isec;
+ const struct task_security_struct *tsec = current_security();
+ struct inode_security_struct *isec = SOCK_INODE(sock)->i_security;
struct sk_security_struct *sksec;
- u32 sid, newsid;
int err = 0;
- sid = tsec->sid;
- newsid = tsec->sockcreate_sid;
-
- isec = SOCK_INODE(sock)->i_security;
-
if (kern)
isec->sid = SECINITSID_KERNEL;
- else if (newsid)
- isec->sid = newsid;
else
- isec->sid = sid;
+ isec->sid = socket_sockcreate_sid(tsec);
isec->sclass = socket_type_to_security_class(family, type, protocol);
isec->initialized = 1;
@@ -3756,10 +3708,11 @@ static int selinux_socket_post_create(struct socket *sock, int family,
static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen)
{
+ struct sock *sk = sock->sk;
u16 family;
int err;
- err = socket_has_perm(current, sock, SOCKET__BIND);
+ err = sock_has_perm(current, sk, SOCKET__BIND);
if (err)
goto out;
@@ -3768,19 +3721,16 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in
* Multiple address binding for SCTP is not supported yet: we just
* check the first address now.
*/
- family = sock->sk->sk_family;
+ family = sk->sk_family;
if (family == PF_INET || family == PF_INET6) {
char *addrp;
- struct inode_security_struct *isec;
+ struct sk_security_struct *sksec = sk->sk_security;
struct common_audit_data ad;
struct sockaddr_in *addr4 = NULL;
struct sockaddr_in6 *addr6 = NULL;
unsigned short snum;
- struct sock *sk = sock->sk;
u32 sid, node_perm;
- isec = SOCK_INODE(sock)->i_security;
-
if (family == PF_INET) {
addr4 = (struct sockaddr_in *)address;
snum = ntohs(addr4->sin_port);
@@ -3804,15 +3754,15 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in
COMMON_AUDIT_DATA_INIT(&ad, NET);
ad.u.net.sport = htons(snum);
ad.u.net.family = family;
- err = avc_has_perm(isec->sid, sid,
- isec->sclass,
+ err = avc_has_perm(sksec->sid, sid,
+ sksec->sclass,
SOCKET__NAME_BIND, &ad);
if (err)
goto out;
}
}
- switch (isec->sclass) {
+ switch (sksec->sclass) {
case SECCLASS_TCP_SOCKET:
node_perm = TCP_SOCKET__NODE_BIND;
break;
@@ -3843,8 +3793,8 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in
else
ipv6_addr_copy(&ad.u.net.v6info.saddr, &addr6->sin6_addr);
- err = avc_has_perm(isec->sid, sid,
- isec->sclass, node_perm, &ad);
+ err = avc_has_perm(sksec->sid, sid,
+ sksec->sclass, node_perm, &ad);
if (err)
goto out;
}
@@ -3855,19 +3805,18 @@ out:
static int selinux_socket_connect(struct socket *sock, struct sockaddr *address, int addrlen)
{
struct sock *sk = sock->sk;
- struct inode_security_struct *isec;
+ struct sk_security_struct *sksec = sk->sk_security;
int err;
- err = socket_has_perm(current, sock, SOCKET__CONNECT);
+ err = sock_has_perm(current, sk, SOCKET__CONNECT);
if (err)
return err;
/*
* If a TCP or DCCP socket, check name_connect permission for the port.
*/
- isec = SOCK_INODE(sock)->i_security;
- if (isec->sclass == SECCLASS_TCP_SOCKET ||
- isec->sclass == SECCLASS_DCCP_SOCKET) {
+ if (sksec->sclass == SECCLASS_TCP_SOCKET ||
+ sksec->sclass == SECCLASS_DCCP_SOCKET) {
struct common_audit_data ad;
struct sockaddr_in *addr4 = NULL;
struct sockaddr_in6 *addr6 = NULL;
@@ -3890,13 +3839,13 @@ static int selinux_socket_connect(struct socket *sock, struct sockaddr *address,
if (err)
goto out;
- perm = (isec->sclass == SECCLASS_TCP_SOCKET) ?
+ perm = (sksec->sclass == SECCLASS_TCP_SOCKET) ?
TCP_SOCKET__NAME_CONNECT : DCCP_SOCKET__NAME_CONNECT;
COMMON_AUDIT_DATA_INIT(&ad, NET);
ad.u.net.dport = htons(snum);
ad.u.net.family = sk->sk_family;
- err = avc_has_perm(isec->sid, sid, isec->sclass, perm, &ad);
+ err = avc_has_perm(sksec->sid, sid, sksec->sclass, perm, &ad);
if (err)
goto out;
}
@@ -3909,7 +3858,7 @@ out:
static int selinux_socket_listen(struct socket *sock, int backlog)
{
- return socket_has_perm(current, sock, SOCKET__LISTEN);
+ return sock_has_perm(current, sock->sk, SOCKET__LISTEN);
}
static int selinux_socket_accept(struct socket *sock, struct socket *newsock)
@@ -3918,7 +3867,7 @@ static int selinux_socket_accept(struct socket *sock, struct socket *newsock)
struct inode_security_struct *isec;
struct inode_security_struct *newisec;
- err = socket_has_perm(current, sock, SOCKET__ACCEPT);
+ err = sock_has_perm(current, sock->sk, SOCKET__ACCEPT);
if (err)
return err;
@@ -3935,30 +3884,30 @@ static int selinux_socket_accept(struct socket *sock, struct socket *newsock)
static int selinux_socket_sendmsg(struct socket *sock, struct msghdr *msg,
int size)
{
- return socket_has_perm(current, sock, SOCKET__WRITE);
+ return sock_has_perm(current, sock->sk, SOCKET__WRITE);
}
static int selinux_socket_recvmsg(struct socket *sock, struct msghdr *msg,
int size, int flags)
{
- return socket_has_perm(current, sock, SOCKET__READ);
+ return sock_has_perm(current, sock->sk, SOCKET__READ);
}
static int selinux_socket_getsockname(struct socket *sock)
{
- return socket_has_perm(current, sock, SOCKET__GETATTR);
+ return sock_has_perm(current, sock->sk, SOCKET__GETATTR);
}
static int selinux_socket_getpeername(struct socket *sock)
{
- return socket_has_perm(current, sock, SOCKET__GETATTR);
+ return sock_has_perm(current, sock->sk, SOCKET__GETATTR);
}
static int selinux_socket_setsockopt(struct socket *sock, int level, int optname)
{
int err;
- err = socket_has_perm(current, sock, SOCKET__SETOPT);
+ err = sock_has_perm(current, sock->sk, SOCKET__SETOPT);
if (err)
return err;
@@ -3968,68 +3917,58 @@ static int selinux_socket_setsockopt(struct socket *sock, int level, int optname
static int selinux_socket_getsockopt(struct socket *sock, int level,
int optname)
{
- return socket_has_perm(current, sock, SOCKET__GETOPT);
+ return sock_has_perm(current, sock->sk, SOCKET__GETOPT);
}
static int selinux_socket_shutdown(struct socket *sock, int how)
{
- return socket_has_perm(current, sock, SOCKET__SHUTDOWN);
+ return sock_has_perm(current, sock->sk, SOCKET__SHUTDOWN);
}
static int selinux_socket_unix_stream_connect(struct socket *sock,
struct socket *other,
struct sock *newsk)
{
- struct sk_security_struct *sksec;
- struct inode_security_struct *isec;
- struct inode_security_struct *other_isec;
+ struct sk_security_struct *sksec_sock = sock->sk->sk_security;
+ struct sk_security_struct *sksec_other = other->sk->sk_security;
+ struct sk_security_struct *sksec_new = newsk->sk_security;
struct common_audit_data ad;
int err;
- isec = SOCK_INODE(sock)->i_security;
- other_isec = SOCK_INODE(other)->i_security;
-
COMMON_AUDIT_DATA_INIT(&ad, NET);
ad.u.net.sk = other->sk;
- err = avc_has_perm(isec->sid, other_isec->sid,
- isec->sclass,
+ err = avc_has_perm(sksec_sock->sid, sksec_other->sid,
+ sksec_other->sclass,
UNIX_STREAM_SOCKET__CONNECTTO, &ad);
if (err)
return err;
- /* connecting socket */
- sksec = sock->sk->sk_security;
- sksec->peer_sid = other_isec->sid;
-
/* server child socket */
- sksec = newsk->sk_security;
- sksec->peer_sid = isec->sid;
- err = security_sid_mls_copy(other_isec->sid, sksec->peer_sid, &sksec->sid);
+ sksec_new->peer_sid = sksec_sock->sid;
+ err = security_sid_mls_copy(sksec_other->sid, sksec_sock->sid,
+ &sksec_new->sid);
+ if (err)
+ return err;
- return err;
+ /* connecting socket */
+ sksec_sock->peer_sid = sksec_new->sid;
+
+ return 0;
}
static int selinux_socket_unix_may_send(struct socket *sock,
struct socket *other)
{
- struct inode_security_struct *isec;
- struct inode_security_struct *other_isec;
+ struct sk_security_struct *ssec = sock->sk->sk_security;
+ struct sk_security_struct *osec = other->sk->sk_security;
struct common_audit_data ad;
- int err;
-
- isec = SOCK_INODE(sock)->i_security;
- other_isec = SOCK_INODE(other)->i_security;
COMMON_AUDIT_DATA_INIT(&ad, NET);
ad.u.net.sk = other->sk;
- err = avc_has_perm(isec->sid, other_isec->sid,
- isec->sclass, SOCKET__SENDTO, &ad);
- if (err)
- return err;
-
- return 0;
+ return avc_has_perm(ssec->sid, osec->sid, osec->sclass, SOCKET__SENDTO,
+ &ad);
}
static int selinux_inet_sys_rcv_skb(int ifindex, char *addrp, u16 family,
@@ -4168,26 +4107,18 @@ static int selinux_socket_getpeersec_stream(struct socket *sock, char __user *op
int err = 0;
char *scontext;
u32 scontext_len;
- struct sk_security_struct *sksec;
- struct inode_security_struct *isec;
+ struct sk_security_struct *sksec = sock->sk->sk_security;
u32 peer_sid = SECSID_NULL;
- isec = SOCK_INODE(sock)->i_security;
-
- if (isec->sclass == SECCLASS_UNIX_STREAM_SOCKET ||
- isec->sclass == SECCLASS_TCP_SOCKET) {
- sksec = sock->sk->sk_security;
+ if (sksec->sclass == SECCLASS_UNIX_STREAM_SOCKET ||
+ sksec->sclass == SECCLASS_TCP_SOCKET)
peer_sid = sksec->peer_sid;
- }
- if (peer_sid == SECSID_NULL) {
- err = -ENOPROTOOPT;
- goto out;
- }
+ if (peer_sid == SECSID_NULL)
+ return -ENOPROTOOPT;
err = security_sid_to_context(peer_sid, &scontext, &scontext_len);
-
if (err)
- goto out;
+ return err;
if (scontext_len > len) {
err = -ERANGE;
@@ -4200,9 +4131,7 @@ static int selinux_socket_getpeersec_stream(struct socket *sock, char __user *op
out_len:
if (put_user(scontext_len, optlen))
err = -EFAULT;
-
kfree(scontext);
-out:
return err;
}
@@ -4234,12 +4163,27 @@ out:
static int selinux_sk_alloc_security(struct sock *sk, int family, gfp_t priority)
{
- return sk_alloc_security(sk, family, priority);
+ struct sk_security_struct *sksec;
+
+ sksec = kzalloc(sizeof(*sksec), priority);
+ if (!sksec)
+ return -ENOMEM;
+
+ sksec->peer_sid = SECINITSID_UNLABELED;
+ sksec->sid = SECINITSID_UNLABELED;
+ selinux_netlbl_sk_security_reset(sksec);
+ sk->sk_security = sksec;
+
+ return 0;
}
static void selinux_sk_free_security(struct sock *sk)
{
- sk_free_security(sk);
+ struct sk_security_struct *sksec = sk->sk_security;
+
+ sk->sk_security = NULL;
+ selinux_netlbl_sk_security_free(sksec);
+ kfree(sksec);
}
static void selinux_sk_clone_security(const struct sock *sk, struct sock *newsk)
@@ -4335,6 +4279,27 @@ static void selinux_inet_conn_established(struct sock *sk, struct sk_buff *skb)
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)
{
@@ -4399,8 +4364,7 @@ static int selinux_nlmsg_perm(struct sock *sk, struct sk_buff *skb)
int err = 0;
u32 perm;
struct nlmsghdr *nlh;
- struct socket *sock = sk->sk_socket;
- struct inode_security_struct *isec = SOCK_INODE(sock)->i_security;
+ struct sk_security_struct *sksec = sk->sk_security;
if (skb->len < NLMSG_SPACE(0)) {
err = -EINVAL;
@@ -4408,13 +4372,13 @@ static int selinux_nlmsg_perm(struct sock *sk, struct sk_buff *skb)
}
nlh = nlmsg_hdr(skb);
- err = selinux_nlmsg_lookup(isec->sclass, nlh->nlmsg_type, &perm);
+ err = selinux_nlmsg_lookup(sksec->sclass, nlh->nlmsg_type, &perm);
if (err) {
if (err == -EINVAL) {
audit_log(current->audit_context, GFP_KERNEL, AUDIT_SELINUX_ERR,
"SELinux: unrecognized netlink message"
" type=%hu for sclass=%hu\n",
- nlh->nlmsg_type, isec->sclass);
+ nlh->nlmsg_type, sksec->sclass);
if (!selinux_enforcing || security_get_allow_unknown())
err = 0;
}
@@ -4425,7 +4389,7 @@ static int selinux_nlmsg_perm(struct sock *sk, struct sk_buff *skb)
goto out;
}
- err = socket_has_perm(current, sock, perm);
+ err = sock_has_perm(current, sk, perm);
out:
return err;
}
@@ -5590,6 +5554,9 @@ static struct security_operations selinux_ops = {
.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,
diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h
index 8b32e95..8858d2b 100644
--- a/security/selinux/include/classmap.h
+++ b/security/selinux/include/classmap.h
@@ -2,7 +2,8 @@
"getattr", "setattr", "lock", "relabelfrom", "relabelto", "append"
#define COMMON_FILE_PERMS COMMON_FILE_SOCK_PERMS, "unlink", "link", \
- "rename", "execute", "swapon", "quotaon", "mounton"
+ "rename", "execute", "swapon", "quotaon", "mounton", "audit_access", \
+ "open", "execmod"
#define COMMON_SOCK_PERMS COMMON_FILE_SOCK_PERMS, "bind", "connect", \
"listen", "accept", "getopt", "setopt", "shutdown", "recvfrom", \
@@ -16,7 +17,7 @@ struct security_class_mapping secclass_map[] = {
{ "compute_av", "compute_create", "compute_member",
"check_context", "load_policy", "compute_relabel",
"compute_user", "setenforce", "setbool", "setsecparam",
- "setcheckreqprot", NULL } },
+ "setcheckreqprot", "read_policy", NULL } },
{ "process",
{ "fork", "transition", "sigchld", "sigkill",
"sigstop", "signull", "signal", "ptrace", "getsched", "setsched",
@@ -43,22 +44,21 @@ struct security_class_mapping secclass_map[] = {
"quotaget", NULL } },
{ "file",
{ COMMON_FILE_PERMS,
- "execute_no_trans", "entrypoint", "execmod", "open", NULL } },
+ "execute_no_trans", "entrypoint", NULL } },
{ "dir",
{ COMMON_FILE_PERMS, "add_name", "remove_name",
- "reparent", "search", "rmdir", "open", NULL } },
+ "reparent", "search", "rmdir", NULL } },
{ "fd", { "use", NULL } },
{ "lnk_file",
{ COMMON_FILE_PERMS, NULL } },
{ "chr_file",
- { COMMON_FILE_PERMS,
- "execute_no_trans", "entrypoint", "execmod", "open", NULL } },
+ { COMMON_FILE_PERMS, NULL } },
{ "blk_file",
- { COMMON_FILE_PERMS, "open", NULL } },
+ { COMMON_FILE_PERMS, NULL } },
{ "sock_file",
- { COMMON_FILE_PERMS, "open", NULL } },
+ { COMMON_FILE_PERMS, NULL } },
{ "fifo_file",
- { COMMON_FILE_PERMS, "open", NULL } },
+ { COMMON_FILE_PERMS, NULL } },
{ "socket",
{ COMMON_SOCK_PERMS, NULL } },
{ "tcp_socket",
diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h
index 1f7c249..671273e 100644
--- a/security/selinux/include/security.h
+++ b/security/selinux/include/security.h
@@ -9,6 +9,7 @@
#define _SELINUX_SECURITY_H_
#include <linux/magic.h>
+#include <linux/types.h>
#include "flask.h"
#define SECSID_NULL 0x00000000 /* unspecified SID */
@@ -82,6 +83,8 @@ extern int selinux_policycap_openperm;
int security_mls_enabled(void);
int security_load_policy(void *data, size_t len);
+int security_read_policy(void **data, ssize_t *len);
+size_t security_policydb_len(void);
int security_policycap_supported(unsigned int req_cap);
@@ -191,5 +194,25 @@ static inline int security_netlbl_sid_to_secattr(u32 sid,
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/netnode.c b/security/selinux/netnode.c
index dc92792..65ebfe9 100644
--- a/security/selinux/netnode.c
+++ b/security/selinux/netnode.c
@@ -183,8 +183,6 @@ static void sel_netnode_insert(struct sel_netnode *node)
BUG();
}
- INIT_RCU_HEAD(&node->rcu);
-
/* we need to impose a limit on the growth of the hash table so check
* this bucket to make sure it is within the specified bounds */
list_add_rcu(&node->list, &sel_netnode_hash[idx].list);
diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
index 0293843..55a755c 100644
--- a/security/selinux/selinuxfs.c
+++ b/security/selinux/selinuxfs.c
@@ -68,6 +68,8 @@ static int *bool_pending_values;
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;
@@ -110,6 +112,8 @@ enum sel_inos {
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 */
};
@@ -171,6 +175,7 @@ static ssize_t sel_write_enforce(struct file *file, const char __user *buf,
if (selinux_enforcing)
avc_ss_reset(0);
selnl_notify_setenforce(selinux_enforcing);
+ selinux_status_update_setenforce(selinux_enforcing);
}
length = count;
out:
@@ -184,6 +189,7 @@ out:
static const struct file_operations sel_enforce_ops = {
.read = sel_read_enforce,
.write = sel_write_enforce,
+ .llseek = generic_file_llseek,
};
static ssize_t sel_read_handle_unknown(struct file *filp, char __user *buf,
@@ -201,6 +207,60 @@ static ssize_t sel_read_handle_unknown(struct file *filp, char __user *buf,
static const struct file_operations sel_handle_unknown_ops = {
.read = sel_read_handle_unknown,
+ .llseek = generic_file_llseek,
+};
+
+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
@@ -251,6 +311,7 @@ out:
static const struct file_operations sel_disable_ops = {
.write = sel_write_disable,
+ .llseek = generic_file_llseek,
};
static ssize_t sel_read_policyvers(struct file *filp, char __user *buf,
@@ -265,6 +326,7 @@ static ssize_t sel_read_policyvers(struct file *filp, char __user *buf,
static const struct file_operations sel_policyvers_ops = {
.read = sel_read_policyvers,
+ .llseek = generic_file_llseek,
};
/* declaration for sel_write_load */
@@ -289,6 +351,142 @@ static ssize_t sel_read_mls(struct file *filp, char __user *buf,
static const struct file_operations sel_mls_ops = {
.read = sel_read_mls,
+ .llseek = generic_file_llseek,
+};
+
+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,
@@ -356,6 +554,7 @@ out:
static const struct file_operations sel_load_ops = {
.write = sel_write_load,
+ .llseek = generic_file_llseek,
};
static ssize_t sel_write_context(struct file *file, char *buf, size_t size)
@@ -437,6 +636,7 @@ out:
static const struct file_operations sel_checkreqprot_ops = {
.read = sel_read_checkreqprot,
.write = sel_write_checkreqprot,
+ .llseek = generic_file_llseek,
};
/*
@@ -482,6 +682,7 @@ static const struct file_operations transaction_ops = {
.write = selinux_transaction_write,
.read = simple_transaction_read,
.release = simple_transaction_release,
+ .llseek = generic_file_llseek,
};
/*
@@ -777,6 +978,7 @@ static struct inode *sel_make_inode(struct super_block *sb, int mode)
struct inode *ret = new_inode(sb);
if (ret) {
+ ret->i_ino = get_next_ino();
ret->i_mode = mode;
ret->i_atime = ret->i_mtime = ret->i_ctime = CURRENT_TIME;
}
@@ -883,6 +1085,7 @@ out:
static const struct file_operations sel_bool_ops = {
.read = sel_read_bool,
.write = sel_write_bool,
+ .llseek = generic_file_llseek,
};
static ssize_t sel_commit_bools_write(struct file *filep,
@@ -935,6 +1138,7 @@ out:
static const struct file_operations sel_commit_bools_ops = {
.write = sel_commit_bools_write,
+ .llseek = generic_file_llseek,
};
static void sel_remove_entries(struct dentry *de)
@@ -1127,10 +1331,12 @@ out:
static const struct file_operations sel_avc_cache_threshold_ops = {
.read = sel_read_avc_cache_threshold,
.write = sel_write_avc_cache_threshold,
+ .llseek = generic_file_llseek,
};
static const struct file_operations sel_avc_hash_stats_ops = {
.read = sel_read_avc_hash_stats,
+ .llseek = generic_file_llseek,
};
#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS
@@ -1255,6 +1461,7 @@ static ssize_t sel_read_initcon(struct file *file, char __user *buf,
static const struct file_operations sel_initcon_ops = {
.read = sel_read_initcon,
+ .llseek = generic_file_llseek,
};
static int sel_make_initcon_files(struct dentry *dir)
@@ -1330,6 +1537,7 @@ out:
static const struct file_operations sel_class_ops = {
.read = sel_read_class,
+ .llseek = generic_file_llseek,
};
static ssize_t sel_read_perm(struct file *file, char __user *buf,
@@ -1354,6 +1562,7 @@ out:
static const struct file_operations sel_perm_ops = {
.read = sel_read_perm,
+ .llseek = generic_file_llseek,
};
static ssize_t sel_read_policycap(struct file *file, char __user *buf,
@@ -1372,6 +1581,7 @@ static ssize_t sel_read_policycap(struct file *file, char __user *buf,
static const struct file_operations sel_policycap_ops = {
.read = sel_read_policycap,
+ .llseek = generic_file_llseek,
};
static int sel_make_perm_files(char *objclass, int classvalue,
@@ -1596,6 +1806,8 @@ static int sel_fill_super(struct super_block *sb, void *data, int silent)
[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);
diff --git a/security/selinux/ss/Makefile b/security/selinux/ss/Makefile
deleted file mode 100644
index 15d4e62..0000000
--- a/security/selinux/ss/Makefile
+++ /dev/null
@@ -1,9 +0,0 @@
-#
-# Makefile for building the SELinux security server as part of the kernel tree.
-#
-
-EXTRA_CFLAGS += -Isecurity/selinux -Isecurity/selinux/include
-obj-y := ss.o
-
-ss-y := ebitmap.o hashtab.o symtab.o sidtab.o avtab.o policydb.o services.o conditional.o mls.o
-
diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c
index 1215b8e..a3dd9fa 100644
--- a/security/selinux/ss/avtab.c
+++ b/security/selinux/ss/avtab.c
@@ -266,8 +266,8 @@ int avtab_alloc(struct avtab *h, u32 nrules)
if (shift > 2)
shift = shift - 2;
nslot = 1 << shift;
- if (nslot > MAX_AVTAB_SIZE)
- nslot = MAX_AVTAB_SIZE;
+ if (nslot > MAX_AVTAB_HASH_BUCKETS)
+ nslot = MAX_AVTAB_HASH_BUCKETS;
mask = nslot - 1;
h->htable = kcalloc(nslot, sizeof(*(h->htable)), GFP_KERNEL);
@@ -342,20 +342,20 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol,
if (vers < POLICYDB_VERSION_AVTAB) {
rc = next_entry(buf32, fp, sizeof(u32));
- if (rc < 0) {
+ if (rc) {
printk(KERN_ERR "SELinux: avtab: truncated entry\n");
- return -1;
+ return rc;
}
items2 = le32_to_cpu(buf32[0]);
if (items2 > ARRAY_SIZE(buf32)) {
printk(KERN_ERR "SELinux: avtab: entry overflow\n");
- return -1;
+ return -EINVAL;
}
rc = next_entry(buf32, fp, sizeof(u32)*items2);
- if (rc < 0) {
+ if (rc) {
printk(KERN_ERR "SELinux: avtab: truncated entry\n");
- return -1;
+ return rc;
}
items = 0;
@@ -363,19 +363,19 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol,
key.source_type = (u16)val;
if (key.source_type != val) {
printk(KERN_ERR "SELinux: avtab: truncated source type\n");
- return -1;
+ return -EINVAL;
}
val = le32_to_cpu(buf32[items++]);
key.target_type = (u16)val;
if (key.target_type != val) {
printk(KERN_ERR "SELinux: avtab: truncated target type\n");
- return -1;
+ return -EINVAL;
}
val = le32_to_cpu(buf32[items++]);
key.target_class = (u16)val;
if (key.target_class != val) {
printk(KERN_ERR "SELinux: avtab: truncated target class\n");
- return -1;
+ return -EINVAL;
}
val = le32_to_cpu(buf32[items++]);
@@ -383,12 +383,12 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol,
if (!(val & (AVTAB_AV | AVTAB_TYPE))) {
printk(KERN_ERR "SELinux: avtab: null entry\n");
- return -1;
+ return -EINVAL;
}
if ((val & AVTAB_AV) &&
(val & AVTAB_TYPE)) {
printk(KERN_ERR "SELinux: avtab: entry has both access vectors and types\n");
- return -1;
+ return -EINVAL;
}
for (i = 0; i < ARRAY_SIZE(spec_order); i++) {
@@ -403,15 +403,15 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol,
if (items != items2) {
printk(KERN_ERR "SELinux: avtab: entry only had %d items, expected %d\n", items2, items);
- return -1;
+ return -EINVAL;
}
return 0;
}
rc = next_entry(buf16, fp, sizeof(u16)*4);
- if (rc < 0) {
+ if (rc) {
printk(KERN_ERR "SELinux: avtab: truncated entry\n");
- return -1;
+ return rc;
}
items = 0;
@@ -424,7 +424,7 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol,
!policydb_type_isvalid(pol, key.target_type) ||
!policydb_class_isvalid(pol, key.target_class)) {
printk(KERN_ERR "SELinux: avtab: invalid type or class\n");
- return -1;
+ return -EINVAL;
}
set = 0;
@@ -434,19 +434,19 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol,
}
if (!set || set > 1) {
printk(KERN_ERR "SELinux: avtab: more than one specifier\n");
- return -1;
+ return -EINVAL;
}
rc = next_entry(buf32, fp, sizeof(u32));
- if (rc < 0) {
+ if (rc) {
printk(KERN_ERR "SELinux: avtab: truncated entry\n");
- return -1;
+ return rc;
}
datum.data = le32_to_cpu(*buf32);
if ((key.specified & AVTAB_TYPE) &&
!policydb_type_isvalid(pol, datum.data)) {
printk(KERN_ERR "SELinux: avtab: invalid type\n");
- return -1;
+ return -EINVAL;
}
return insertf(a, &key, &datum, p);
}
@@ -487,8 +487,7 @@ int avtab_read(struct avtab *a, void *fp, struct policydb *pol)
printk(KERN_ERR "SELinux: avtab: out of memory\n");
else if (rc == -EEXIST)
printk(KERN_ERR "SELinux: avtab: duplicate entry\n");
- else
- rc = -EINVAL;
+
goto bad;
}
}
@@ -502,6 +501,48 @@ bad:
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",
diff --git a/security/selinux/ss/avtab.h b/security/selinux/ss/avtab.h
index cd4f734..dff0c75 100644
--- a/security/selinux/ss/avtab.h
+++ b/security/selinux/ss/avtab.h
@@ -71,6 +71,8 @@ int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol,
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);
@@ -85,7 +87,6 @@ void avtab_cache_destroy(void);
#define MAX_AVTAB_HASH_BITS 11
#define MAX_AVTAB_HASH_BUCKETS (1 << MAX_AVTAB_HASH_BITS)
#define MAX_AVTAB_HASH_MASK (MAX_AVTAB_HASH_BUCKETS-1)
-#define MAX_AVTAB_SIZE MAX_AVTAB_HASH_BUCKETS
#endif /* _SS_AVTAB_H_ */
diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c
index 4a4e35c..655fe1c 100644
--- a/security/selinux/ss/conditional.c
+++ b/security/selinux/ss/conditional.c
@@ -117,10 +117,14 @@ int evaluate_cond_node(struct policydb *p, struct cond_node *node)
int cond_policydb_init(struct policydb *p)
{
+ int rc;
+
p->bool_val_to_struct = NULL;
p->cond_list = NULL;
- if (avtab_init(&p->te_cond_avtab))
- return -1;
+
+ rc = avtab_init(&p->te_cond_avtab);
+ if (rc)
+ return rc;
return 0;
}
@@ -219,34 +223,37 @@ int cond_read_bool(struct policydb *p, struct hashtab *h, void *fp)
booldatum = kzalloc(sizeof(struct cond_bool_datum), GFP_KERNEL);
if (!booldatum)
- return -1;
+ return -ENOMEM;
rc = next_entry(buf, fp, sizeof buf);
- if (rc < 0)
+ if (rc)
goto err;
booldatum->value = le32_to_cpu(buf[0]);
booldatum->state = le32_to_cpu(buf[1]);
+ rc = -EINVAL;
if (!bool_isvalid(booldatum))
goto err;
len = le32_to_cpu(buf[2]);
+ rc = -ENOMEM;
key = kmalloc(len + 1, GFP_KERNEL);
if (!key)
goto err;
rc = next_entry(key, fp, len);
- if (rc < 0)
+ if (rc)
goto err;
key[len] = '\0';
- if (hashtab_insert(h, key, booldatum))
+ rc = hashtab_insert(h, key, booldatum);
+ if (rc)
goto err;
return 0;
err:
cond_destroy_bool(key, booldatum, NULL);
- return -1;
+ return rc;
}
struct cond_insertf_data {
@@ -263,7 +270,7 @@ static int cond_insertf(struct avtab *a, struct avtab_key *k, struct avtab_datum
struct cond_av_list *other = data->other, *list, *cur;
struct avtab_node *node_ptr;
u8 found;
-
+ int rc = -EINVAL;
/*
* For type rules we have to make certain there aren't any
@@ -313,12 +320,15 @@ static int cond_insertf(struct avtab *a, struct avtab_key *k, struct avtab_datum
node_ptr = avtab_insert_nonunique(&p->te_cond_avtab, k, d);
if (!node_ptr) {
printk(KERN_ERR "SELinux: could not insert rule.\n");
+ rc = -ENOMEM;
goto err;
}
list = kzalloc(sizeof(struct cond_av_list), GFP_KERNEL);
- if (!list)
+ if (!list) {
+ rc = -ENOMEM;
goto err;
+ }
list->node = node_ptr;
if (!data->head)
@@ -331,7 +341,7 @@ static int cond_insertf(struct avtab *a, struct avtab_key *k, struct avtab_datum
err:
cond_av_list_destroy(data->head);
data->head = NULL;
- return -1;
+ return rc;
}
static int cond_read_av_list(struct policydb *p, void *fp, struct cond_av_list **ret_list, struct cond_av_list *other)
@@ -345,8 +355,8 @@ static int cond_read_av_list(struct policydb *p, void *fp, struct cond_av_list *
len = 0;
rc = next_entry(buf, fp, sizeof(u32));
- if (rc < 0)
- return -1;
+ if (rc)
+ return rc;
len = le32_to_cpu(buf[0]);
if (len == 0)
@@ -361,7 +371,6 @@ static int cond_read_av_list(struct policydb *p, void *fp, struct cond_av_list *
&data);
if (rc)
return rc;
-
}
*ret_list = data.head;
@@ -390,24 +399,25 @@ static int cond_read_node(struct policydb *p, struct cond_node *node, void *fp)
struct cond_expr *expr = NULL, *last = NULL;
rc = next_entry(buf, fp, sizeof(u32));
- if (rc < 0)
- return -1;
+ if (rc)
+ return rc;
node->cur_state = le32_to_cpu(buf[0]);
len = 0;
rc = next_entry(buf, fp, sizeof(u32));
- if (rc < 0)
- return -1;
+ if (rc)
+ return rc;
/* expr */
len = le32_to_cpu(buf[0]);
for (i = 0; i < len; i++) {
rc = next_entry(buf, fp, sizeof(u32) * 2);
- if (rc < 0)
+ if (rc)
goto err;
+ rc = -ENOMEM;
expr = kzalloc(sizeof(struct cond_expr), GFP_KERNEL);
if (!expr)
goto err;
@@ -416,6 +426,7 @@ static int cond_read_node(struct policydb *p, struct cond_node *node, void *fp)
expr->bool = le32_to_cpu(buf[1]);
if (!expr_isvalid(p, expr)) {
+ rc = -EINVAL;
kfree(expr);
goto err;
}
@@ -427,14 +438,16 @@ static int cond_read_node(struct policydb *p, struct cond_node *node, void *fp)
last = expr;
}
- if (cond_read_av_list(p, fp, &node->true_list, NULL) != 0)
+ rc = cond_read_av_list(p, fp, &node->true_list, NULL);
+ if (rc)
goto err;
- if (cond_read_av_list(p, fp, &node->false_list, node->true_list) != 0)
+ rc = cond_read_av_list(p, fp, &node->false_list, node->true_list);
+ if (rc)
goto err;
return 0;
err:
cond_node_destroy(node);
- return -1;
+ return rc;
}
int cond_read_list(struct policydb *p, void *fp)
@@ -445,8 +458,8 @@ int cond_read_list(struct policydb *p, void *fp)
int rc;
rc = next_entry(buf, fp, sizeof buf);
- if (rc < 0)
- return -1;
+ if (rc)
+ return rc;
len = le32_to_cpu(buf[0]);
@@ -455,11 +468,13 @@ int cond_read_list(struct policydb *p, void *fp)
goto err;
for (i = 0; i < len; i++) {
+ rc = -ENOMEM;
node = kzalloc(sizeof(struct cond_node), GFP_KERNEL);
if (!node)
goto err;
- if (cond_read_node(p, node, fp) != 0)
+ rc = cond_read_node(p, node, fp);
+ if (rc)
goto err;
if (i == 0)
@@ -472,9 +487,132 @@ int cond_read_list(struct policydb *p, void *fp)
err:
cond_list_destroy(p->cond_list);
p->cond_list = NULL;
- return -1;
+ 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
*/
diff --git a/security/selinux/ss/conditional.h b/security/selinux/ss/conditional.h
index 53ddb01..3f209c6 100644
--- a/security/selinux/ss/conditional.h
+++ b/security/selinux/ss/conditional.h
@@ -69,6 +69,8 @@ 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);
diff --git a/security/selinux/ss/ebitmap.c b/security/selinux/ss/ebitmap.c
index 04b6145..d42951f 100644
--- a/security/selinux/ss/ebitmap.c
+++ b/security/selinux/ss/ebitmap.c
@@ -22,6 +22,8 @@
#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;
@@ -363,10 +365,10 @@ int ebitmap_read(struct ebitmap *e, void *fp)
e->highbit = le32_to_cpu(buf[1]);
count = le32_to_cpu(buf[2]);
- if (mapunit != sizeof(u64) * 8) {
+ 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, sizeof(u64) * 8, e->highbit);
+ mapunit, BITS_PER_U64, e->highbit);
goto bad;
}
@@ -446,3 +448,78 @@ bad:
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
index f283b43..1f4e93c 100644
--- a/security/selinux/ss/ebitmap.h
+++ b/security/selinux/ss/ebitmap.h
@@ -123,6 +123,7 @@ 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,
diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
index c57802a..94f630d 100644
--- a/security/selinux/ss/policydb.c
+++ b/security/selinux/ss/policydb.c
@@ -31,11 +31,13 @@
#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
@@ -184,9 +186,19 @@ static u32 rangetr_hash(struct hashtab *h, const void *k)
static int rangetr_cmp(struct hashtab *h, const void *k1, const void *k2)
{
const struct range_trans *key1 = k1, *key2 = k2;
- return (key1->source_type != key2->source_type ||
- key1->target_type != key2->target_type ||
- key1->target_class != key2->target_class);
+ 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;
}
/*
@@ -655,6 +667,9 @@ static int range_tr_destroy(void *key, void *datum, void *p)
static void ocontext_destroy(struct ocontext *c, int i)
{
+ if (!c)
+ return;
+
context_destroy(&c->context[0]);
context_destroy(&c->context[1]);
if (i == OCON_ISID || i == OCON_FS ||
@@ -736,11 +751,17 @@ void policydb_destroy(struct policydb *p)
hashtab_map(p->range_tr, range_tr_destroy, NULL);
hashtab_destroy(p->range_tr);
- if (p->type_attr_map) {
- for (i = 0; i < p->p_types.nprim; i++)
- ebitmap_destroy(&p->type_attr_map[i]);
+ if (p->type_attr_map_array) {
+ for (i = 0; i < p->p_types.nprim; i++) {
+ struct ebitmap *e;
+
+ e = flex_array_get(p->type_attr_map_array, i);
+ if (!e)
+ continue;
+ ebitmap_destroy(e);
+ }
+ flex_array_free(p->type_attr_map_array);
}
- kfree(p->type_attr_map);
ebitmap_destroy(&p->policycaps);
ebitmap_destroy(&p->permissive_map);
@@ -1614,11 +1635,11 @@ static int role_bounds_sanity_check(void *key, void *datum, void *datap)
static int type_bounds_sanity_check(void *key, void *datum, void *datap)
{
- struct type_datum *upper, *type;
+ struct type_datum *upper;
struct policydb *p = datap;
int depth = 0;
- upper = type = datum;
+ upper = datum;
while (upper->bounds) {
if (++depth == POLICYDB_BOUNDS_MAXDEPTH) {
printk(KERN_ERR "SELinux: type %s: "
@@ -1701,6 +1722,333 @@ u32 string_to_av_perm(struct policydb *p, u16 tclass, const char *name)
return 1U << (perdatum->value-1);
}
+static int range_read(struct policydb *p, void *fp)
+{
+ struct range_trans *rt = NULL;
+ struct mls_range *r = NULL;
+ int i, rc;
+ __le32 buf[2];
+ u32 nel;
+
+ if (p->policyvers < POLICYDB_VERSION_MLS)
+ return 0;
+
+ rc = next_entry(buf, fp, sizeof(u32));
+ if (rc)
+ goto out;
+
+ nel = le32_to_cpu(buf[0]);
+ for (i = 0; i < nel; i++) {
+ rc = -ENOMEM;
+ rt = kzalloc(sizeof(*rt), GFP_KERNEL);
+ if (!rt)
+ goto out;
+
+ rc = next_entry(buf, fp, (sizeof(u32) * 2));
+ if (rc)
+ goto out;
+
+ rt->source_type = le32_to_cpu(buf[0]);
+ rt->target_type = le32_to_cpu(buf[1]);
+ if (p->policyvers >= POLICYDB_VERSION_RANGETRANS) {
+ rc = next_entry(buf, fp, sizeof(u32));
+ if (rc)
+ goto out;
+ rt->target_class = le32_to_cpu(buf[0]);
+ } else
+ rt->target_class = p->process_class;
+
+ rc = -EINVAL;
+ if (!policydb_type_isvalid(p, rt->source_type) ||
+ !policydb_type_isvalid(p, rt->target_type) ||
+ !policydb_class_isvalid(p, rt->target_class))
+ goto out;
+
+ rc = -ENOMEM;
+ r = kzalloc(sizeof(*r), GFP_KERNEL);
+ if (!r)
+ goto out;
+
+ rc = mls_read_range_helper(r, fp);
+ if (rc)
+ goto out;
+
+ rc = -EINVAL;
+ if (!mls_range_isvalid(p, r)) {
+ printk(KERN_WARNING "SELinux: rangetrans: invalid range\n");
+ goto out;
+ }
+
+ rc = hashtab_insert(p->range_tr, rt, r);
+ if (rc)
+ goto out;
+
+ rt = NULL;
+ r = NULL;
+ }
+ rangetr_hash_eval(p->range_tr);
+ rc = 0;
+out:
+ kfree(rt);
+ kfree(r);
+ return rc;
+}
+
+static int genfs_read(struct policydb *p, void *fp)
+{
+ int i, j, rc;
+ u32 nel, nel2, len, len2;
+ __le32 buf[1];
+ struct ocontext *l, *c;
+ struct ocontext *newc = NULL;
+ struct genfs *genfs_p, *genfs;
+ struct genfs *newgenfs = NULL;
+
+ rc = next_entry(buf, fp, sizeof(u32));
+ if (rc)
+ goto out;
+ nel = le32_to_cpu(buf[0]);
+
+ for (i = 0; i < nel; i++) {
+ rc = next_entry(buf, fp, sizeof(u32));
+ if (rc)
+ goto out;
+ len = le32_to_cpu(buf[0]);
+
+ rc = -ENOMEM;
+ newgenfs = kzalloc(sizeof(*newgenfs), GFP_KERNEL);
+ if (!newgenfs)
+ goto out;
+
+ rc = -ENOMEM;
+ newgenfs->fstype = kmalloc(len + 1, GFP_KERNEL);
+ if (!newgenfs->fstype)
+ goto out;
+
+ rc = next_entry(newgenfs->fstype, fp, len);
+ if (rc)
+ goto out;
+
+ newgenfs->fstype[len] = 0;
+
+ for (genfs_p = NULL, genfs = p->genfs; genfs;
+ genfs_p = genfs, genfs = genfs->next) {
+ rc = -EINVAL;
+ if (strcmp(newgenfs->fstype, genfs->fstype) == 0) {
+ printk(KERN_ERR "SELinux: dup genfs fstype %s\n",
+ newgenfs->fstype);
+ goto out;
+ }
+ if (strcmp(newgenfs->fstype, genfs->fstype) < 0)
+ break;
+ }
+ newgenfs->next = genfs;
+ if (genfs_p)
+ genfs_p->next = newgenfs;
+ else
+ p->genfs = newgenfs;
+ genfs = newgenfs;
+ newgenfs = NULL;
+
+ rc = next_entry(buf, fp, sizeof(u32));
+ if (rc)
+ goto out;
+
+ nel2 = le32_to_cpu(buf[0]);
+ for (j = 0; j < nel2; j++) {
+ rc = next_entry(buf, fp, sizeof(u32));
+ if (rc)
+ goto out;
+ len = le32_to_cpu(buf[0]);
+
+ rc = -ENOMEM;
+ newc = kzalloc(sizeof(*newc), GFP_KERNEL);
+ if (!newc)
+ goto out;
+
+ rc = -ENOMEM;
+ newc->u.name = kmalloc(len + 1, GFP_KERNEL);
+ if (!newc->u.name)
+ goto out;
+
+ rc = next_entry(newc->u.name, fp, len);
+ if (rc)
+ goto out;
+ newc->u.name[len] = 0;
+
+ rc = next_entry(buf, fp, sizeof(u32));
+ if (rc)
+ goto out;
+
+ newc->v.sclass = le32_to_cpu(buf[0]);
+ rc = context_read_and_validate(&newc->context[0], p, fp);
+ if (rc)
+ goto out;
+
+ for (l = NULL, c = genfs->head; c;
+ l = c, c = c->next) {
+ rc = -EINVAL;
+ if (!strcmp(newc->u.name, c->u.name) &&
+ (!c->v.sclass || !newc->v.sclass ||
+ newc->v.sclass == c->v.sclass)) {
+ printk(KERN_ERR "SELinux: dup genfs entry (%s,%s)\n",
+ genfs->fstype, c->u.name);
+ goto out;
+ }
+ len = strlen(newc->u.name);
+ len2 = strlen(c->u.name);
+ if (len > len2)
+ break;
+ }
+
+ newc->next = c;
+ if (l)
+ l->next = newc;
+ else
+ genfs->head = newc;
+ newc = NULL;
+ }
+ }
+ rc = 0;
+out:
+ if (newgenfs)
+ kfree(newgenfs->fstype);
+ kfree(newgenfs);
+ ocontext_destroy(newc, OCON_FSUSE);
+
+ return rc;
+}
+
+static int ocontext_read(struct policydb *p, struct policydb_compat_info *info,
+ void *fp)
+{
+ int i, j, rc;
+ u32 nel, len;
+ __le32 buf[3];
+ struct ocontext *l, *c;
+ u32 nodebuf[8];
+
+ for (i = 0; i < info->ocon_num; i++) {
+ rc = next_entry(buf, fp, sizeof(u32));
+ if (rc)
+ goto out;
+ nel = le32_to_cpu(buf[0]);
+
+ l = NULL;
+ for (j = 0; j < nel; j++) {
+ rc = -ENOMEM;
+ c = kzalloc(sizeof(*c), GFP_KERNEL);
+ if (!c)
+ goto out;
+ if (l)
+ l->next = c;
+ else
+ p->ocontexts[i] = c;
+ l = c;
+
+ switch (i) {
+ case OCON_ISID:
+ rc = next_entry(buf, fp, sizeof(u32));
+ if (rc)
+ goto out;
+
+ c->sid[0] = le32_to_cpu(buf[0]);
+ rc = context_read_and_validate(&c->context[0], p, fp);
+ if (rc)
+ goto out;
+ break;
+ case OCON_FS:
+ case OCON_NETIF:
+ rc = next_entry(buf, fp, sizeof(u32));
+ if (rc)
+ goto out;
+ len = le32_to_cpu(buf[0]);
+
+ rc = -ENOMEM;
+ c->u.name = kmalloc(len + 1, GFP_KERNEL);
+ if (!c->u.name)
+ goto out;
+
+ rc = next_entry(c->u.name, fp, len);
+ if (rc)
+ goto out;
+
+ c->u.name[len] = 0;
+ rc = context_read_and_validate(&c->context[0], p, fp);
+ if (rc)
+ goto out;
+ rc = context_read_and_validate(&c->context[1], p, fp);
+ if (rc)
+ goto out;
+ break;
+ case OCON_PORT:
+ rc = next_entry(buf, fp, sizeof(u32)*3);
+ if (rc)
+ goto out;
+ c->u.port.protocol = le32_to_cpu(buf[0]);
+ c->u.port.low_port = le32_to_cpu(buf[1]);
+ c->u.port.high_port = le32_to_cpu(buf[2]);
+ rc = context_read_and_validate(&c->context[0], p, fp);
+ if (rc)
+ goto out;
+ break;
+ case OCON_NODE:
+ rc = next_entry(nodebuf, fp, sizeof(u32) * 2);
+ if (rc)
+ goto out;
+ c->u.node.addr = nodebuf[0]; /* network order */
+ c->u.node.mask = nodebuf[1]; /* network order */
+ rc = context_read_and_validate(&c->context[0], p, fp);
+ if (rc)
+ goto out;
+ break;
+ case OCON_FSUSE:
+ rc = next_entry(buf, fp, sizeof(u32)*2);
+ if (rc)
+ goto out;
+
+ rc = -EINVAL;
+ c->v.behavior = le32_to_cpu(buf[0]);
+ if (c->v.behavior > SECURITY_FS_USE_NONE)
+ goto out;
+
+ rc = -ENOMEM;
+ len = le32_to_cpu(buf[1]);
+ c->u.name = kmalloc(len + 1, GFP_KERNEL);
+ if (!c->u.name)
+ goto out;
+
+ rc = next_entry(c->u.name, fp, len);
+ if (rc)
+ goto out;
+ c->u.name[len] = 0;
+ rc = context_read_and_validate(&c->context[0], p, fp);
+ if (rc)
+ goto out;
+ break;
+ case OCON_NODE6: {
+ int k;
+
+ rc = next_entry(nodebuf, fp, sizeof(u32) * 8);
+ if (rc)
+ goto out;
+ for (k = 0; k < 4; k++)
+ c->u.node6.addr[k] = nodebuf[k];
+ for (k = 0; k < 4; k++)
+ c->u.node6.mask[k] = nodebuf[k+4];
+ rc = context_read_and_validate(&c->context[0], p, fp);
+ if (rc)
+ goto out;
+ break;
+ }
+ }
+ }
+ }
+ rc = 0;
+out:
+ return rc;
+}
+
/*
* Read the configuration data from a policy database binary
* representation file into a policy database structure.
@@ -1709,16 +2057,12 @@ int policydb_read(struct policydb *p, void *fp)
{
struct role_allow *ra, *lra;
struct role_trans *tr, *ltr;
- struct ocontext *l, *c, *newc;
- struct genfs *genfs_p, *genfs, *newgenfs;
int i, j, rc;
__le32 buf[4];
- u32 nodebuf[8];
- u32 len, len2, nprim, nel, nel2;
+ u32 len, nprim, nel;
+
char *policydb_str;
struct policydb_compat_info *info;
- struct range_trans *rt;
- struct mls_range *r;
rc = policydb_init(p);
if (rc)
@@ -1919,308 +2263,897 @@ int policydb_read(struct policydb *p, void *fp)
if (!p->process_trans_perms)
goto bad;
- for (i = 0; i < info->ocon_num; i++) {
- rc = next_entry(buf, fp, sizeof(u32));
- if (rc < 0)
- goto bad;
- nel = le32_to_cpu(buf[0]);
- l = NULL;
- for (j = 0; j < nel; j++) {
- c = kzalloc(sizeof(*c), GFP_KERNEL);
- if (!c) {
- rc = -ENOMEM;
+ 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 - 1,
+ 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:
+ if (!rc)
+ rc = -EINVAL;
+ 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 role_trans *r, void *fp)
+{
+ 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;
+ }
+
+ 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;
}
- if (l)
- l->next = c;
- else
- p->ocontexts[i] = c;
- l = c;
- rc = -EINVAL;
+ }
+ }
+
+ 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:
- rc = next_entry(buf, fp, sizeof(u32));
- if (rc < 0)
- goto bad;
- c->sid[0] = le32_to_cpu(buf[0]);
- rc = context_read_and_validate(&c->context[0], p, fp);
+ 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)
- goto bad;
+ return rc;
break;
case OCON_FS:
case OCON_NETIF:
- rc = next_entry(buf, fp, sizeof(u32));
- if (rc < 0)
- goto bad;
- len = le32_to_cpu(buf[0]);
- c->u.name = kmalloc(len + 1, GFP_KERNEL);
- if (!c->u.name) {
- rc = -ENOMEM;
- goto bad;
- }
- rc = next_entry(c->u.name, fp, len);
- if (rc < 0)
- goto bad;
- c->u.name[len] = 0;
- rc = context_read_and_validate(&c->context[0], p, fp);
+ len = strlen(c->u.name);
+ buf[0] = cpu_to_le32(len);
+ rc = put_entry(buf, sizeof(u32), 1, fp);
if (rc)
- goto bad;
- rc = context_read_and_validate(&c->context[1], p, fp);
+ return rc;
+ rc = put_entry(c->u.name, 1, len, fp);
if (rc)
- goto bad;
+ 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:
- rc = next_entry(buf, fp, sizeof(u32)*3);
- if (rc < 0)
- goto bad;
- c->u.port.protocol = le32_to_cpu(buf[0]);
- c->u.port.low_port = le32_to_cpu(buf[1]);
- c->u.port.high_port = le32_to_cpu(buf[2]);
- rc = context_read_and_validate(&c->context[0], p, fp);
+ 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)
- goto bad;
+ return rc;
+ rc = context_write(p, &c->context[0], fp);
+ if (rc)
+ return rc;
break;
case OCON_NODE:
- rc = next_entry(nodebuf, fp, sizeof(u32) * 2);
- if (rc < 0)
- goto bad;
- c->u.node.addr = nodebuf[0]; /* network order */
- c->u.node.mask = nodebuf[1]; /* network order */
- rc = context_read_and_validate(&c->context[0], p, fp);
+ 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)
- goto bad;
+ return rc;
+ rc = context_write(p, &c->context[0], fp);
+ if (rc)
+ return rc;
break;
case OCON_FSUSE:
- rc = next_entry(buf, fp, sizeof(u32)*2);
- if (rc < 0)
- goto bad;
- c->v.behavior = le32_to_cpu(buf[0]);
- if (c->v.behavior > SECURITY_FS_USE_NONE)
- goto bad;
- len = le32_to_cpu(buf[1]);
- c->u.name = kmalloc(len + 1, GFP_KERNEL);
- if (!c->u.name) {
- rc = -ENOMEM;
- goto bad;
- }
- rc = next_entry(c->u.name, fp, len);
- if (rc < 0)
- goto bad;
- c->u.name[len] = 0;
- rc = context_read_and_validate(&c->context[0], p, fp);
+ 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)
- goto bad;
+ return rc;
break;
- case OCON_NODE6: {
- int k;
-
- rc = next_entry(nodebuf, fp, sizeof(u32) * 8);
- if (rc < 0)
- goto bad;
- for (k = 0; k < 4; k++)
- c->u.node6.addr[k] = nodebuf[k];
- for (k = 0; k < 4; k++)
- c->u.node6.mask[k] = nodebuf[k+4];
- if (context_read_and_validate(&c->context[0], p, fp))
- goto bad;
+ 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;
+}
- rc = next_entry(buf, fp, sizeof(u32));
- if (rc < 0)
- goto bad;
- nel = le32_to_cpu(buf[0]);
- genfs_p = NULL;
- rc = -EINVAL;
- for (i = 0; i < nel; i++) {
- rc = next_entry(buf, fp, sizeof(u32));
- if (rc < 0)
- goto bad;
- len = le32_to_cpu(buf[0]);
- newgenfs = kzalloc(sizeof(*newgenfs), GFP_KERNEL);
- if (!newgenfs) {
- rc = -ENOMEM;
- goto bad;
- }
+static int genfs_write(struct policydb *p, void *fp)
+{
+ struct genfs *genfs;
+ struct ocontext *c;
+ size_t len;
+ __le32 buf[1];
+ int rc;
- newgenfs->fstype = kmalloc(len + 1, GFP_KERNEL);
- if (!newgenfs->fstype) {
- rc = -ENOMEM;
- kfree(newgenfs);
- goto bad;
- }
- rc = next_entry(newgenfs->fstype, fp, len);
- if (rc < 0) {
- kfree(newgenfs->fstype);
- kfree(newgenfs);
- goto bad;
- }
- newgenfs->fstype[len] = 0;
- for (genfs_p = NULL, genfs = p->genfs; genfs;
- genfs_p = genfs, genfs = genfs->next) {
- if (strcmp(newgenfs->fstype, genfs->fstype) == 0) {
- printk(KERN_ERR "SELinux: dup genfs "
- "fstype %s\n", newgenfs->fstype);
- kfree(newgenfs->fstype);
- kfree(newgenfs);
- goto bad;
- }
- if (strcmp(newgenfs->fstype, genfs->fstype) < 0)
- break;
+ 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;
}
- newgenfs->next = genfs;
- if (genfs_p)
- genfs_p->next = newgenfs;
- else
- p->genfs = newgenfs;
- rc = next_entry(buf, fp, sizeof(u32));
- if (rc < 0)
- goto bad;
- nel2 = le32_to_cpu(buf[0]);
- for (j = 0; j < nel2; j++) {
- rc = next_entry(buf, fp, sizeof(u32));
- if (rc < 0)
- goto bad;
- len = le32_to_cpu(buf[0]);
+ }
+ return 0;
+}
- newc = kzalloc(sizeof(*newc), GFP_KERNEL);
- if (!newc) {
- rc = -ENOMEM;
- goto bad;
- }
+static int range_count(void *key, void *data, void *ptr)
+{
+ int *cnt = ptr;
+ *cnt = *cnt + 1;
- newc->u.name = kmalloc(len + 1, GFP_KERNEL);
- if (!newc->u.name) {
- rc = -ENOMEM;
- goto bad_newc;
- }
- rc = next_entry(newc->u.name, fp, len);
- if (rc < 0)
- goto bad_newc;
- newc->u.name[len] = 0;
- rc = next_entry(buf, fp, sizeof(u32));
- if (rc < 0)
- goto bad_newc;
- newc->v.sclass = le32_to_cpu(buf[0]);
- if (context_read_and_validate(&newc->context[0], p, fp))
- goto bad_newc;
- for (l = NULL, c = newgenfs->head; c;
- l = c, c = c->next) {
- if (!strcmp(newc->u.name, c->u.name) &&
- (!c->v.sclass || !newc->v.sclass ||
- newc->v.sclass == c->v.sclass)) {
- printk(KERN_ERR "SELinux: dup genfs "
- "entry (%s,%s)\n",
- newgenfs->fstype, c->u.name);
- goto bad_newc;
- }
- len = strlen(newc->u.name);
- len2 = strlen(c->u.name);
- if (len > len2)
- break;
- }
+ return 0;
+}
- newc->next = c;
- if (l)
- l->next = newc;
- else
- newgenfs->head = newc;
- }
+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;
- if (p->policyvers >= POLICYDB_VERSION_MLS) {
- int new_rangetr = p->policyvers >= POLICYDB_VERSION_RANGETRANS;
- rc = next_entry(buf, fp, sizeof(u32));
- if (rc < 0)
- goto bad;
- nel = le32_to_cpu(buf[0]);
- for (i = 0; i < nel; i++) {
- rt = kzalloc(sizeof(*rt), GFP_KERNEL);
- if (!rt) {
- rc = -ENOMEM;
- goto bad;
- }
- rc = next_entry(buf, fp, (sizeof(u32) * 2));
- if (rc < 0) {
- kfree(rt);
- goto bad;
- }
- rt->source_type = le32_to_cpu(buf[0]);
- rt->target_type = le32_to_cpu(buf[1]);
- if (new_rangetr) {
- rc = next_entry(buf, fp, sizeof(u32));
- if (rc < 0) {
- kfree(rt);
- goto bad;
- }
- rt->target_class = le32_to_cpu(buf[0]);
- } else
- rt->target_class = p->process_class;
- if (!policydb_type_isvalid(p, rt->source_type) ||
- !policydb_type_isvalid(p, rt->target_type) ||
- !policydb_class_isvalid(p, rt->target_class)) {
- kfree(rt);
- rc = -EINVAL;
- goto bad;
- }
- r = kzalloc(sizeof(*r), GFP_KERNEL);
- if (!r) {
- kfree(rt);
- rc = -ENOMEM;
- goto bad;
- }
- rc = mls_read_range_helper(r, fp);
- if (rc) {
- kfree(rt);
- kfree(r);
- goto bad;
- }
- if (!mls_range_isvalid(p, r)) {
- printk(KERN_WARNING "SELinux: rangetrans: invalid range\n");
- kfree(rt);
- kfree(r);
- goto bad;
- }
- rc = hashtab_insert(p->range_tr, rt, r);
- if (rc) {
- kfree(rt);
- kfree(r);
- goto bad;
- }
- }
- rangetr_hash_eval(p->range_tr);
+ 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, range_count, &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;
+}
+
+/*
+ * 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;
}
- p->type_attr_map = kmalloc(p->p_types.nprim * sizeof(struct ebitmap), GFP_KERNEL);
- if (!p->type_attr_map)
- goto bad;
+ 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;
- for (i = 0; i < p->p_types.nprim; i++) {
- ebitmap_init(&p->type_attr_map[i]);
- if (p->policyvers >= POLICYDB_VERSION_AVTAB) {
- if (ebitmap_read(&p->type_attr_map[i], fp))
- goto bad;
- }
- /* add the type itself as the degenerate case */
- if (ebitmap_set_bit(&p->type_attr_map[i], i, 1))
- goto bad;
+ /* 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 rc;
}
- rc = policydb_bounds_sanity_check(p);
+ 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)
- goto bad;
+ return rc;
- rc = 0;
-out:
- return rc;
-bad_newc:
- ocontext_destroy(newc, OCON_FSUSE);
-bad:
- if (!rc)
- rc = -EINVAL;
- policydb_destroy(p);
- goto out;
+ 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->role_tr, fp);
+ if (rc)
+ return rc;
+
+ rc = role_allow_write(p->role_allow, 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
index 26d9adf..95d3d7d 100644
--- a/security/selinux/ss/policydb.h
+++ b/security/selinux/ss/policydb.h
@@ -24,6 +24,8 @@
#ifndef _SS_POLICYDB_H_
#define _SS_POLICYDB_H_
+#include <linux/flex_array.h>
+
#include "symtab.h"
#include "avtab.h"
#include "sidtab.h"
@@ -246,12 +248,15 @@ struct policydb {
struct hashtab *range_tr;
/* type -> attribute reverse mapping */
- struct ebitmap *type_attr_map;
+ struct flex_array *type_attr_map_array;
struct ebitmap policycaps;
struct ebitmap permissive_map;
+ /* length of this policy when it was loaded */
+ size_t len;
+
unsigned int policyvers;
unsigned int reject_unknown : 1;
@@ -268,6 +273,7 @@ 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
@@ -288,6 +294,11 @@ struct policy_file {
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)
@@ -299,6 +310,17 @@ static inline int next_entry(void *buf, struct policy_file *fp, size_t bytes)
return 0;
}
+static inline int put_entry(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;
+}
+
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);
diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
index 1de60ce9..223c1ff 100644
--- a/security/selinux/ss/services.c
+++ b/security/selinux/ss/services.c
@@ -50,6 +50,8 @@
#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"
@@ -626,8 +628,10 @@ static void context_struct_compute_av(struct context *scontext,
*/
avkey.target_class = tclass;
avkey.specified = AVTAB_AV;
- sattr = &policydb.type_attr_map[scontext->type - 1];
- tattr = &policydb.type_attr_map[tcontext->type - 1];
+ sattr = flex_array_get(policydb.type_attr_map_array, scontext->type - 1);
+ BUG_ON(!sattr);
+ tattr = flex_array_get(policydb.type_attr_map_array, tcontext->type - 1);
+ BUG_ON(!tattr);
ebitmap_for_each_positive_bit(sattr, snode, i) {
ebitmap_for_each_positive_bit(tattr, tnode, j) {
avkey.source_type = i + 1;
@@ -988,7 +992,8 @@ static int context_struct_to_string(struct context *context, char **scontext, u3
{
char *scontextp;
- *scontext = NULL;
+ if (scontext)
+ *scontext = NULL;
*scontext_len = 0;
if (context->len) {
@@ -1005,6 +1010,9 @@ static int context_struct_to_string(struct context *context, char **scontext, u3
*scontext_len += strlen(policydb.p_type_val_to_name[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)
@@ -1044,7 +1052,8 @@ static int security_sid_to_context_core(u32 sid, char **scontext,
struct context *context;
int rc = 0;
- *scontext = NULL;
+ if (scontext)
+ *scontext = NULL;
*scontext_len = 0;
if (!ss_initialized) {
@@ -1052,6 +1061,8 @@ static int security_sid_to_context_core(u32 sid, char **scontext,
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;
@@ -1766,6 +1777,7 @@ int security_load_policy(void *data, size_t len)
return rc;
}
+ policydb.len = len;
rc = selinux_set_mapping(&policydb, secclass_map,
&current_mapping,
&current_mapping_size);
@@ -1788,6 +1800,7 @@ int security_load_policy(void *data, size_t len)
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;
@@ -1801,6 +1814,7 @@ int security_load_policy(void *data, size_t len)
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");
@@ -1867,6 +1881,7 @@ int security_load_policy(void *data, size_t len)
avc_ss_reset(seqno);
selnl_notify_policyload(seqno);
+ selinux_status_update_policyload(seqno);
selinux_netlbl_cache_invalidate();
selinux_xfrm_notify_policyload();
@@ -1880,6 +1895,17 @@ err:
}
+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
@@ -2371,6 +2397,7 @@ out:
if (!rc) {
avc_ss_reset(seqno);
selnl_notify_policyload(seqno);
+ selinux_status_update_policyload(seqno);
selinux_xfrm_notify_policyload();
}
return rc;
@@ -3126,3 +3153,38 @@ netlbl_sid_to_secattr_failure:
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, ssize_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/status.c b/security/selinux/ss/status.c
new file mode 100644
index 0000000..d982365
--- /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
index bcf9f62..160326e 100644
--- a/security/selinux/ss/symtab.c
+++ b/security/selinux/ss/symtab.c
@@ -36,7 +36,7 @@ int symtab_init(struct symtab *s, unsigned int size)
{
s->table = hashtab_create(symhash, symcmp, size);
if (!s->table)
- return -1;
+ return -ENOMEM;
s->nprim = 0;
return 0;
}
diff --git a/security/smack/smack.h b/security/smack/smack.h
index c6e9aca..43ae747 100644
--- a/security/smack/smack.h
+++ b/security/smack/smack.h
@@ -123,16 +123,6 @@ struct smack_known {
#define SMK_FSHAT "smackfshat="
#define SMK_FSROOT "smackfsroot="
-/*
- * xattr names
- */
-#define XATTR_SMACK_SUFFIX "SMACK64"
-#define XATTR_SMACK_IPIN "SMACK64IPIN"
-#define XATTR_SMACK_IPOUT "SMACK64IPOUT"
-#define XATTR_NAME_SMACK XATTR_SECURITY_PREFIX XATTR_SMACK_SUFFIX
-#define XATTR_NAME_SMACKIPIN XATTR_SECURITY_PREFIX XATTR_SMACK_IPIN
-#define XATTR_NAME_SMACKIPOUT XATTR_SECURITY_PREFIX XATTR_SMACK_IPOUT
-
#define SMACK_CIPSO_OPTION "-CIPSO"
/*
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index 0f2fc48..bc39f40 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -598,6 +598,8 @@ static int smack_inode_rename(struct inode *old_inode,
static int smack_inode_permission(struct inode *inode, int mask)
{
struct smk_audit_info ad;
+
+ mask &= (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND);
/*
* No permission to check. Existence test. Yup, it's there.
*/
@@ -1279,12 +1281,11 @@ static int smack_task_getioprio(struct task_struct *p)
*
* Return 0 if read access is permitted
*/
-static int smack_task_setscheduler(struct task_struct *p, int policy,
- struct sched_param *lp)
+static int smack_task_setscheduler(struct task_struct *p)
{
int rc;
- rc = cap_task_setscheduler(p, policy, lp);
+ rc = cap_task_setscheduler(p);
if (rc == 0)
rc = smk_curacc_on_task(p, MAY_WRITE);
return rc;
@@ -2191,7 +2192,7 @@ static void smack_ipc_getsecid(struct kern_ipc_perm *ipp, u32 *secid)
/**
* smack_d_instantiate - Make sure the blob is correct on an inode
- * @opt_dentry: unused
+ * @opt_dentry: dentry where inode will be attached
* @inode: the object
*
* Set the inode's security blob if it hasn't been done already.
@@ -2310,20 +2311,10 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode)
/*
* Get the dentry for xattr.
*/
- if (opt_dentry == NULL) {
- dp = d_find_alias(inode);
- if (dp == NULL)
- break;
- } else {
- dp = dget(opt_dentry);
- if (dp == NULL)
- break;
- }
-
+ dp = dget(opt_dentry);
fetched = smk_fetch(inode, dp);
if (fetched != NULL)
final = fetched;
-
dput(dp);
break;
}
@@ -3013,7 +3004,8 @@ static int smack_secid_to_secctx(u32 secid, char **secdata, u32 *seclen)
{
char *sp = smack_from_secid(secid);
- *secdata = sp;
+ if (secdata)
+ *secdata = sp;
*seclen = strlen(sp);
return 0;
}
@@ -3227,7 +3219,7 @@ static __init int smack_init(void)
cred = (struct cred *) current->cred;
cred->security = &smack_known_floor.smk_known;
- /* initilize the smack_know_list */
+ /* initialize the smack_know_list */
init_smack_know_list();
/*
* Initialize locks
diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c
index a2b72d7..7512502 100644
--- a/security/smack/smackfs.c
+++ b/security/smack/smackfs.c
@@ -968,6 +968,7 @@ static ssize_t smk_write_doi(struct file *file, const char __user *buf,
static const struct file_operations smk_doi_ops = {
.read = smk_read_doi,
.write = smk_write_doi,
+ .llseek = default_llseek,
};
/**
@@ -1031,6 +1032,7 @@ static ssize_t smk_write_direct(struct file *file, const char __user *buf,
static const struct file_operations smk_direct_ops = {
.read = smk_read_direct,
.write = smk_write_direct,
+ .llseek = default_llseek,
};
/**
@@ -1112,6 +1114,7 @@ static ssize_t smk_write_ambient(struct file *file, const char __user *buf,
static const struct file_operations smk_ambient_ops = {
.read = smk_read_ambient,
.write = smk_write_ambient,
+ .llseek = default_llseek,
};
/**
@@ -1191,6 +1194,7 @@ static ssize_t smk_write_onlycap(struct file *file, const char __user *buf,
static const struct file_operations smk_onlycap_ops = {
.read = smk_read_onlycap,
.write = smk_write_onlycap,
+ .llseek = default_llseek,
};
/**
@@ -1255,6 +1259,7 @@ static ssize_t smk_write_logging(struct file *file, const char __user *buf,
static const struct file_operations smk_logging_ops = {
.read = smk_read_logging,
.write = smk_write_logging,
+ .llseek = default_llseek,
};
/**
* smk_fill_super - fill the /smackfs superblock
diff --git a/security/tomoyo/Makefile b/security/tomoyo/Makefile
index 4fb3903..91640e9 100644
--- a/security/tomoyo/Makefile
+++ b/security/tomoyo/Makefile
@@ -1 +1 @@
-obj-y = common.o realpath.o tomoyo.o domain.o file.o gc.o path_group.o
+obj-y = common.o domain.o file.o gc.o group.o load_policy.o memory.o mount.o realpath.o securityfs_if.o tomoyo.o util.o
diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c
index b5dbdc9..7556315 100644
--- a/security/tomoyo/common.c
+++ b/security/tomoyo/common.c
@@ -3,974 +3,424 @@
*
* Common functions for TOMOYO.
*
- * Copyright (C) 2005-2009 NTT DATA CORPORATION
- *
- * Version: 2.2.0 2009/04/01
- *
+ * Copyright (C) 2005-2010 NTT DATA CORPORATION
*/
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/security.h>
-#include <linux/hardirq.h>
#include "common.h"
-/* Lock for protecting policy. */
-DEFINE_MUTEX(tomoyo_policy_lock);
+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;
-/* Has loading policy done? */
-bool tomoyo_policy_loaded;
+/* 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[4] = {
+static const char *tomoyo_mode[4] = {
"disabled", "learning", "permissive", "enforcing"
};
-/* String table for functionality that takes 2 modes. */
-static const char *tomoyo_mode_2[4] = {
- "disabled", "enabled", "enabled", "enabled"
-};
-/*
- * tomoyo_control_array is a static data which contains
- *
- * (1) functionality name used by /sys/kernel/security/tomoyo/profile .
- * (2) initial values for "struct tomoyo_profile".
- * (3) max values for "struct tomoyo_profile".
- */
-static struct {
- const char *keyword;
- unsigned int current_value;
- const unsigned int max_value;
-} tomoyo_control_array[TOMOYO_MAX_CONTROL_INDEX] = {
- [TOMOYO_MAC_FOR_FILE] = { "MAC_FOR_FILE", 0, 3 },
- [TOMOYO_MAX_ACCEPT_ENTRY] = { "MAX_ACCEPT_ENTRY", 2048, INT_MAX },
- [TOMOYO_VERBOSE] = { "TOMOYO_VERBOSE", 1, 1 },
+/* 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",
};
-/*
- * tomoyo_profile is a structure which is used for holding the mode of access
- * controls. TOMOYO has 4 modes: disabled, learning, permissive, enforcing.
- * An administrator can define up to 256 profiles.
- * The ->profile of "struct tomoyo_domain_info" is used for remembering
- * the profile's number (0 - 255) assigned to that domain.
- */
-static struct tomoyo_profile {
- unsigned int value[TOMOYO_MAX_CONTROL_INDEX];
- const struct tomoyo_path_info *comment;
-} *tomoyo_profile_ptr[TOMOYO_MAX_PROFILES];
-
/* Permit policy management by non-root user? */
static bool tomoyo_manage_by_non_root;
/* Utility functions. */
-/* Open operation for /sys/kernel/security/tomoyo/ interface. */
-static int tomoyo_open_control(const u8 type, struct file *file);
-/* Close /sys/kernel/security/tomoyo/ interface. */
-static int tomoyo_close_control(struct file *file);
-/* Read operation for /sys/kernel/security/tomoyo/ interface. */
-static int tomoyo_read_control(struct file *file, char __user *buffer,
- const int buffer_len);
-/* Write operation for /sys/kernel/security/tomoyo/ interface. */
-static int tomoyo_write_control(struct file *file, const char __user *buffer,
- const int buffer_len);
-
/**
- * tomoyo_parse_name_union - Parse a tomoyo_name_union.
+ * tomoyo_yesno - Return "yes" or "no".
*
- * @filename: Name or name group.
- * @ptr: Pointer to "struct tomoyo_name_union".
- *
- * Returns true on success, false otherwise.
+ * @value: Bool value.
*/
-bool tomoyo_parse_name_union(const char *filename,
- struct tomoyo_name_union *ptr)
+static const char *tomoyo_yesno(const unsigned int value)
{
- if (!tomoyo_is_correct_path(filename, 0, 0, 0))
- return false;
- if (filename[0] == '@') {
- ptr->group = tomoyo_get_path_group(filename + 1);
- ptr->is_group = true;
- return ptr->group != NULL;
- }
- ptr->filename = tomoyo_get_name(filename);
- ptr->is_group = false;
- return ptr->filename != NULL;
+ return value ? "yes" : "no";
}
-/**
- * tomoyo_print_name_union - Print a tomoyo_name_union.
- *
- * @head: Pointer to "struct tomoyo_io_buffer".
- * @ptr: Pointer to "struct tomoyo_name_union".
- *
- * Returns true on success, false otherwise.
- */
-static bool tomoyo_print_name_union(struct tomoyo_io_buffer *head,
- const struct tomoyo_name_union *ptr)
+static void tomoyo_addprintf(char *buffer, int len, const char *fmt, ...)
{
- int pos = head->read_avail;
- if (pos && head->read_buf[pos - 1] == ' ')
- head->read_avail--;
- if (ptr->is_group)
- return tomoyo_io_printf(head, " @%s",
- ptr->group->group_name->name);
- return tomoyo_io_printf(head, " %s", ptr->filename->name);
+ va_list args;
+ const int pos = strlen(buffer);
+ va_start(args, fmt);
+ vsnprintf(buffer + pos, len - pos - 1, fmt, args);
+ va_end(args);
}
/**
- * tomoyo_is_byte_range - Check whether the string isa \ooo style octal value.
- *
- * @str: Pointer to the string.
+ * tomoyo_flush - Flush queued string to userspace's buffer.
*
- * Returns true if @str is a \ooo style octal value, false otherwise.
+ * @head: Pointer to "struct tomoyo_io_buffer".
*
- * TOMOYO uses \ooo style representation for 0x01 - 0x20 and 0x7F - 0xFF.
- * This function verifies that \ooo is in valid range.
+ * Returns true if all data was flushed, false otherwise.
*/
-static inline bool tomoyo_is_byte_range(const char *str)
+static bool tomoyo_flush(struct tomoyo_io_buffer *head)
{
- return *str >= '0' && *str++ <= '3' &&
- *str >= '0' && *str++ <= '7' &&
- *str >= '0' && *str <= '7';
+ 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;
+ }
+ if (*w) {
+ head->r.w[0] = 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_is_alphabet_char - Check whether the character is an alphabet.
+ * tomoyo_set_string - Queue string to "struct tomoyo_io_buffer" structure.
*
- * @c: The character to check.
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ * @string: String to print.
*
- * Returns true if @c is an alphabet character, false otherwise.
+ * 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 inline bool tomoyo_is_alphabet_char(const char c)
+static void tomoyo_set_string(struct tomoyo_io_buffer *head, const char *string)
{
- return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
+ 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_make_byte - Make byte value from three octal characters.
+ * tomoyo_io_printf - printf() to "struct tomoyo_io_buffer" structure.
*
- * @c1: The first character.
- * @c2: The second character.
- * @c3: The third character.
- *
- * Returns byte value.
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ * @fmt: The printf()'s format string, followed by parameters.
*/
-static inline u8 tomoyo_make_byte(const u8 c1, const u8 c2, const u8 c3)
+void tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt, ...)
{
- return ((c1 - '0') << 6) + ((c2 - '0') << 3) + (c3 - '0');
+ 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);
}
-/**
- * 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.
- */
-static bool tomoyo_str_starts(char **src, const char *find)
+static void tomoyo_set_space(struct tomoyo_io_buffer *head)
{
- const int len = strlen(find);
- char *tmp = *src;
-
- if (strncmp(tmp, find, len))
- return false;
- tmp += len;
- *src = tmp;
- return true;
+ tomoyo_set_string(head, " ");
}
-/**
- * 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.
- */
-static void tomoyo_normalize_line(unsigned char *buffer)
+static bool tomoyo_set_lf(struct tomoyo_io_buffer *head)
{
- unsigned char *sp = buffer;
- unsigned char *dp = buffer;
- bool first = true;
-
- while (tomoyo_is_invalid(*sp))
- sp++;
- while (*sp) {
- if (!first)
- *dp++ = ' ';
- first = false;
- while (tomoyo_is_valid(*sp))
- *dp++ = *sp++;
- while (tomoyo_is_invalid(*sp))
- sp++;
- }
- *dp = '\0';
+ tomoyo_set_string(head, "\n");
+ return !head->r.w_pos;
}
/**
- * tomoyo_tokenize - Tokenize string.
- *
- * @buffer: The line to tokenize.
- * @w: Pointer to "char *".
- * @size: Sizeof @w .
+ * tomoyo_print_name_union - Print a tomoyo_name_union.
*
- * Returns true on success, false otherwise.
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ * @ptr: Pointer to "struct tomoyo_name_union".
*/
-bool tomoyo_tokenize(char *buffer, char *w[], size_t size)
+static void tomoyo_print_name_union(struct tomoyo_io_buffer *head,
+ const struct tomoyo_name_union *ptr)
{
- 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;
+ 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);
}
- return i < count || !*buffer;
}
/**
- * tomoyo_is_correct_path - Validate a pathname.
- * @filename: The pathname to check.
- * @start_type: Should the pathname start with '/'?
- * 1 = must / -1 = must not / 0 = don't care
- * @pattern_type: Can the pathname contain a wildcard?
- * 1 = must / -1 = must not / 0 = don't care
- * @end_type: Should the pathname end with '/'?
- * 1 = must / -1 = must not / 0 = don't care
- *
- * Check whether the given filename follows the naming rules.
- * Returns true if @filename follows the naming rules, false otherwise.
+ * tomoyo_print_number_union - Print a tomoyo_number_union.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ * @ptr: Pointer to "struct tomoyo_number_union".
*/
-bool tomoyo_is_correct_path(const char *filename, const s8 start_type,
- const s8 pattern_type, const s8 end_type)
+static void tomoyo_print_number_union(struct tomoyo_io_buffer *head,
+ const struct tomoyo_number_union *ptr)
{
- const char *const start = filename;
- bool in_repetition = false;
- bool contains_pattern = false;
- unsigned char c;
- unsigned char d;
- unsigned char e;
-
- if (!filename)
- goto out;
- c = *filename;
- if (start_type == 1) { /* Must start with '/' */
- if (c != '/')
- goto out;
- } else if (start_type == -1) { /* Must not start with '/' */
- if (c == '/')
- goto out;
- }
- if (c)
- c = *(filename + strlen(filename) - 1);
- if (end_type == 1) { /* Must end with '/' */
- if (c != '/')
- goto out;
- } else if (end_type == -1) { /* Must not end with '/' */
- if (c == '/')
- goto out;
- }
- while (1) {
- c = *filename++;
- if (!c)
- break;
- if (c == '\\') {
- c = *filename++;
- switch (c) {
- case '\\': /* "\\" */
- continue;
- case '$': /* "\$" */
- case '+': /* "\+" */
- case '?': /* "\?" */
- case '*': /* "\*" */
- case '@': /* "\@" */
- case 'x': /* "\x" */
- case 'X': /* "\X" */
- case 'a': /* "\a" */
- case 'A': /* "\A" */
- case '-': /* "\-" */
- if (pattern_type == -1)
- break; /* Must not contain pattern */
- contains_pattern = true;
- continue;
- case '{': /* "/\{" */
- if (filename - 3 < start ||
- *(filename - 3) != '/')
- break;
- if (pattern_type == -1)
- break; /* Must not contain pattern */
- contains_pattern = true;
- in_repetition = true;
- continue;
- case '}': /* "\}/" */
- if (*filename != '/')
- break;
- if (!in_repetition)
- break;
- in_repetition = false;
- continue;
- case '0': /* "\ooo" */
- case '1':
- case '2':
- case '3':
- d = *filename++;
- if (d < '0' || d > '7')
- break;
- e = *filename++;
- if (e < '0' || e > '7')
- break;
- c = tomoyo_make_byte(c, d, e);
- if (tomoyo_is_invalid(c))
- continue; /* pattern is not \000 */
+ 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;
}
- goto out;
- } else if (in_repetition && c == '/') {
- goto out;
- } else if (tomoyo_is_invalid(c)) {
- goto out;
+ 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);
}
- if (pattern_type == 1) { /* Must contain pattern */
- if (!contains_pattern)
- goto out;
- }
- if (in_repetition)
- goto out;
- return true;
- out:
- return false;
}
/**
- * tomoyo_is_correct_domain - Check whether the given domainname follows the naming rules.
- * @domainname: The domainname to check.
+ * tomoyo_assign_profile - Create a new profile.
+ *
+ * @profile: Profile number to create.
*
- * Returns true if @domainname follows the naming rules, false otherwise.
+ * Returns pointer to "struct tomoyo_profile" on success, NULL otherwise.
*/
-bool tomoyo_is_correct_domain(const unsigned char *domainname)
+static struct tomoyo_profile *tomoyo_assign_profile(const unsigned int profile)
{
- unsigned char c;
- unsigned char d;
- unsigned char e;
-
- if (!domainname || strncmp(domainname, TOMOYO_ROOT_NAME,
- TOMOYO_ROOT_NAME_LEN))
+ 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;
- domainname += TOMOYO_ROOT_NAME_LEN;
- if (!*domainname)
- return true;
- do {
- if (*domainname++ != ' ')
- goto out;
- if (*domainname++ != '/')
- goto out;
- while ((c = *domainname) != '\0' && c != ' ') {
- domainname++;
- if (c == '\\') {
- c = *domainname++;
- switch ((c)) {
- case '\\': /* "\\" */
- continue;
- case '0': /* "\ooo" */
- case '1':
- case '2':
- case '3':
- d = *domainname++;
- if (d < '0' || d > '7')
- break;
- e = *domainname++;
- if (e < '0' || e > '7')
- break;
- c = tomoyo_make_byte(c, d, e);
- if (tomoyo_is_invalid(c))
- /* pattern is not \000 */
- continue;
- }
- goto out;
- } else if (tomoyo_is_invalid(c)) {
- goto out;
- }
- }
- } while (*domainname);
- return true;
+ 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:
- return false;
+ kfree(entry);
+ return ptr;
}
/**
- * tomoyo_is_domain_def - Check whether the given token can be a domainname.
+ * tomoyo_profile - Find a profile.
*
- * @buffer: The token to check.
+ * @profile: Profile number to find.
*
- * Returns true if @buffer possibly be a domainname, false otherwise.
+ * Returns pointer to "struct tomoyo_profile".
*/
-bool tomoyo_is_domain_def(const unsigned char *buffer)
+struct tomoyo_profile *tomoyo_profile(const u8 profile)
{
- return !strncmp(buffer, TOMOYO_ROOT_NAME, TOMOYO_ROOT_NAME_LEN);
+ struct tomoyo_profile *ptr = tomoyo_profile_ptr[profile];
+ if (!tomoyo_policy_loaded)
+ return &tomoyo_default_profile;
+ BUG_ON(!ptr);
+ return ptr;
}
-/**
- * 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)
+static s8 tomoyo_find_yesno(const char *string, const char *find)
{
- 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;
+ 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 NULL;
+ return -1;
}
-/**
- * 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)
+static void tomoyo_set_bool(bool *b, const char *string, const char *find)
{
- 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;
- }
+ switch (tomoyo_find_yesno(string, find)) {
+ case 1:
+ *b = true;
+ break;
+ case 0:
+ *b = false;
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)
+static void tomoyo_set_uint(unsigned int *i, const char *string,
+ const char *find)
{
- 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);
+ const char *cp = strstr(string, find);
+ if (cp)
+ sscanf(cp + strlen(find), "=%u", i);
}
-/**
- * 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)
+static void tomoyo_set_pref(const char *name, const char *value,
+ const bool use_default,
+ struct tomoyo_profile *profile)
{
- while (filename < filename_end && pattern < pattern_end) {
- char c;
- if (*pattern != '\\') {
- if (*filename++ != *pattern++)
- return false;
- continue;
+ struct tomoyo_preference **pref;
+ bool *verbose;
+ if (!strcmp(name, "enforcing")) {
+ if (use_default) {
+ pref = &profile->enforcing;
+ goto set_default;
}
- c = *filename;
- pattern++;
- switch (*pattern) {
- int i;
- int j;
- case '?':
- if (c == '/') {
- return false;
- } else if (c == '\\') {
- if (filename[1] == '\\')
- filename++;
- else if (tomoyo_is_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_is_alphabet_char(c))
- return false;
- break;
- case '0':
- case '1':
- case '2':
- case '3':
- if (c == '\\' && tomoyo_is_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_is_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_is_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 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;
+ profile->enforcing = &profile->preference;
+ verbose = &profile->preference.enforcing_verbose;
+ goto set_verbose;
}
- 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++;
+ 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;
}
- /* 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_io_printf - Transactional printf() to "struct tomoyo_io_buffer" structure.
- *
- * @head: Pointer to "struct tomoyo_io_buffer".
- * @fmt: The printf()'s format string, followed by parameters.
- *
- * Returns true if output was written, false otherwise.
- *
- * The snprintf() will truncate, but tomoyo_io_printf() won't.
- */
-bool tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt, ...)
-{
- va_list args;
- int len;
- int pos = head->read_avail;
- int size = head->readbuf_size - pos;
-
- if (size <= 0)
- return false;
- va_start(args, fmt);
- len = vsnprintf(head->read_buf + pos, size, fmt, args);
- va_end(args);
- if (pos + len >= head->readbuf_size)
- return false;
- head->read_avail += len;
- return true;
-}
-
-/**
- * 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.
- */
-static 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;
+ 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;
}
- up_read(&mm->mmap_sem);
- return cp;
+ return;
+ set_default:
+ *pref = &tomoyo_default_profile.preference;
+ return;
+ set_verbose:
+ tomoyo_set_bool(verbose, value, "verbose");
}
-/**
- * tomoyo_get_msg - Get warning message.
- *
- * @is_enforce: Is it enforcing mode?
- *
- * Returns "ERROR" or "WARNING".
- */
-const char *tomoyo_get_msg(const bool is_enforce)
+static int tomoyo_set_mode(char *name, const char *value,
+ const bool use_default,
+ struct tomoyo_profile *profile)
{
- if (is_enforce)
- return "ERROR";
- else
- return "WARNING";
-}
-
-/**
- * tomoyo_check_flags - Check mode for specified functionality.
- *
- * @domain: Pointer to "struct tomoyo_domain_info".
- * @index: The functionality to check mode.
- *
- * TOMOYO checks only process context.
- * This code disables TOMOYO's enforcement in case the function is called from
- * interrupt context.
- */
-unsigned int tomoyo_check_flags(const struct tomoyo_domain_info *domain,
- const u8 index)
-{
- const u8 profile = domain->profile;
-
- if (WARN_ON(in_interrupt()))
- return 0;
- return tomoyo_policy_loaded && index < TOMOYO_MAX_CONTROL_INDEX
-#if TOMOYO_MAX_PROFILES != 256
- && profile < TOMOYO_MAX_PROFILES
-#endif
- && tomoyo_profile_ptr[profile] ?
- tomoyo_profile_ptr[profile]->value[index] : 0;
-}
-
-/**
- * tomoyo_verbose_mode - Check whether TOMOYO is verbose mode.
- *
- * @domain: Pointer to "struct tomoyo_domain_info".
- *
- * Returns true if domain policy violation warning should be printed to
- * console.
- */
-bool tomoyo_verbose_mode(const struct tomoyo_domain_info *domain)
-{
- return tomoyo_check_flags(domain, TOMOYO_VERBOSE) != 0;
-}
-
-/**
- * tomoyo_domain_quota_is_ok - Check for domain's quota.
- *
- * @domain: Pointer to "struct tomoyo_domain_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_domain_info * const domain)
-{
- unsigned int count = 0;
- struct tomoyo_acl_info *ptr;
-
- if (!domain)
- return true;
- list_for_each_entry_rcu(ptr, &domain->acl_info_list, list) {
- switch (ptr->type) {
- struct tomoyo_path_acl *acl;
- u32 perm;
- u8 i;
- case TOMOYO_TYPE_PATH_ACL:
- acl = container_of(ptr, struct tomoyo_path_acl, head);
- perm = acl->perm | (((u32) acl->perm_high) << 16);
- 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++;
+ 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 (count < tomoyo_check_flags(domain, TOMOYO_MAX_ACCEPT_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;
-}
-
-/**
- * tomoyo_find_or_assign_new_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_find_or_assign_new_profile(const unsigned
- int profile)
-{
- struct tomoyo_profile *ptr = NULL;
- int i;
-
- if (profile >= TOMOYO_MAX_PROFILES)
- return NULL;
- if (mutex_lock_interruptible(&tomoyo_policy_lock))
- return NULL;
- ptr = tomoyo_profile_ptr[profile];
- if (ptr)
- goto ok;
- ptr = kmalloc(sizeof(*ptr), GFP_NOFS);
- if (!tomoyo_memory_ok(ptr)) {
- kfree(ptr);
- ptr = NULL;
- goto ok;
+ 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;
}
- for (i = 0; i < TOMOYO_MAX_CONTROL_INDEX; i++)
- ptr->value[i] = tomoyo_control_array[i].current_value;
- mb(); /* Avoid out-of-order execution. */
- tomoyo_profile_ptr[profile] = ptr;
- ok:
- mutex_unlock(&tomoyo_policy_lock);
- return ptr;
+ 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 to profile table.
+ * tomoyo_write_profile - Write profile table.
*
* @head: Pointer to "struct tomoyo_io_buffer".
*
@@ -980,153 +430,165 @@ static int tomoyo_write_profile(struct tomoyo_io_buffer *head)
{
char *data = head->write_buf;
unsigned int i;
- unsigned int value;
+ bool use_default = false;
char *cp;
struct tomoyo_profile *profile;
- unsigned long num;
-
- cp = strchr(data, '-');
- if (cp)
- *cp = '\0';
- if (strict_strtoul(data, 10, &num))
- return -EINVAL;
- if (cp)
+ 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_find_or_assign_new_profile(num);
- if (!profile)
- return -EINVAL;
+ profile = tomoyo_assign_profile(i);
+ if (!profile)
+ return -EINVAL;
+ }
cp = strchr(data, '=');
if (!cp)
return -EINVAL;
- *cp = '\0';
+ *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")) {
const struct tomoyo_path_info *old_comment = profile->comment;
- profile->comment = tomoyo_get_name(cp + 1);
+ profile->comment = tomoyo_get_name(cp);
tomoyo_put_name(old_comment);
return 0;
}
- for (i = 0; i < TOMOYO_MAX_CONTROL_INDEX; i++) {
- if (strcmp(data, tomoyo_control_array[i].keyword))
- continue;
- if (sscanf(cp + 1, "%u", &value) != 1) {
- int j;
- const char **modes;
- switch (i) {
- case TOMOYO_VERBOSE:
- modes = tomoyo_mode_2;
- break;
- default:
- modes = tomoyo_mode_4;
- break;
- }
- for (j = 0; j < 4; j++) {
- if (strcmp(cp + 1, modes[j]))
- continue;
- value = j;
- break;
- }
- if (j == 4)
- return -EINVAL;
- } else if (value > tomoyo_control_array[i].max_value) {
- value = tomoyo_control_array[i].max_value;
- }
- profile->value[i] = value;
- 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);
}
- return -EINVAL;
+ 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 from profile table.
+ * tomoyo_read_profile - Read profile table.
*
* @head: Pointer to "struct tomoyo_io_buffer".
- *
- * Returns 0.
*/
-static int tomoyo_read_profile(struct tomoyo_io_buffer *head)
+static void tomoyo_read_profile(struct tomoyo_io_buffer *head)
{
- static const int total = TOMOYO_MAX_CONTROL_INDEX + 1;
- int step;
-
- if (head->read_eof)
- return 0;
- for (step = head->read_step; step < TOMOYO_MAX_PROFILES * total;
- step++) {
- const u8 index = step / total;
- u8 type = step % total;
- const struct tomoyo_profile *profile
- = tomoyo_profile_ptr[index];
- head->read_step = step;
- if (!profile)
- continue;
- if (!type) { /* Print profile' comment tag. */
- if (!tomoyo_io_printf(head, "%u-COMMENT=%s\n",
- index, profile->comment ?
- profile->comment->name : ""))
+ 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;
- continue;
+ 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++;
}
- type--;
- if (type < TOMOYO_MAX_CONTROL_INDEX) {
- const unsigned int value = profile->value[type];
- const char **modes = NULL;
- const char *keyword
- = tomoyo_control_array[type].keyword;
- switch (tomoyo_control_array[type].max_value) {
- case 3:
- modes = tomoyo_mode_4;
- break;
- case 1:
- modes = tomoyo_mode_2;
- break;
- }
- if (modes) {
- if (!tomoyo_io_printf(head, "%u-%s=%s\n", index,
- keyword, modes[value]))
- break;
- } else {
- if (!tomoyo_io_printf(head, "%u-%s=%u\n", index,
- keyword, value))
- break;
- }
+ 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 (step == TOMOYO_MAX_PROFILES * total)
- head->read_eof = true;
- return 0;
+ if (tomoyo_flush(head))
+ goto next;
}
-/*
- * tomoyo_policy_manager_list is used for holding list of domainnames or
- * programs which are permitted to modify configuration via
- * /sys/kernel/security/tomoyo/ interface.
- *
- * An entry is added by
- *
- * # echo '<kernel> /sbin/mingetty /bin/login /bin/bash' > \
- * /sys/kernel/security/tomoyo/manager
- * (if you want to specify by a domainname)
- *
- * or
- *
- * # echo '/usr/lib/ccs/editpolicy' > /sys/kernel/security/tomoyo/manager
- * (if you want to specify by a program's location)
- *
- * and is deleted by
- *
- * # echo 'delete <kernel> /sbin/mingetty /bin/login /bin/bash' > \
- * /sys/kernel/security/tomoyo/manager
- *
- * or
- *
- * # echo 'delete /usr/lib/ccs/editpolicy' > \
- * /sys/kernel/security/tomoyo/manager
- *
- * and all entries are retrieved by
- *
- * # cat /sys/kernel/security/tomoyo/manager
- */
-LIST_HEAD(tomoyo_policy_manager_list);
+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.
@@ -1141,47 +603,29 @@ LIST_HEAD(tomoyo_policy_manager_list);
static int tomoyo_update_manager_entry(const char *manager,
const bool is_delete)
{
- struct tomoyo_policy_manager_entry *ptr;
- struct tomoyo_policy_manager_entry e = { };
- int error = is_delete ? -ENOENT : -ENOMEM;
+ struct tomoyo_manager e = { };
+ int error;
- if (tomoyo_is_domain_def(manager)) {
- if (!tomoyo_is_correct_domain(manager))
+ if (tomoyo_domain_def(manager)) {
+ if (!tomoyo_correct_domain(manager))
return -EINVAL;
e.is_domain = true;
} else {
- if (!tomoyo_is_correct_path(manager, 1, -1, -1))
+ if (!tomoyo_correct_path(manager))
return -EINVAL;
}
e.manager = tomoyo_get_name(manager);
if (!e.manager)
return -ENOMEM;
- if (mutex_lock_interruptible(&tomoyo_policy_lock))
- goto out;
- list_for_each_entry_rcu(ptr, &tomoyo_policy_manager_list, list) {
- if (ptr->manager != e.manager)
- continue;
- ptr->is_deleted = is_delete;
- error = 0;
- break;
- }
- if (!is_delete && error) {
- struct tomoyo_policy_manager_entry *entry =
- tomoyo_commit_ok(&e, sizeof(e));
- if (entry) {
- list_add_tail_rcu(&entry->list,
- &tomoyo_policy_manager_list);
- error = 0;
- }
- }
- mutex_unlock(&tomoyo_policy_lock);
- out:
+ 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_policy - Write manager policy.
+ * tomoyo_write_manager - Write manager policy.
*
* @head: Pointer to "struct tomoyo_io_buffer".
*
@@ -1189,7 +633,7 @@ static int tomoyo_update_manager_entry(const char *manager,
*
* Caller holds tomoyo_read_lock().
*/
-static int tomoyo_write_manager_policy(struct tomoyo_io_buffer *head)
+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);
@@ -1202,47 +646,41 @@ static int tomoyo_write_manager_policy(struct tomoyo_io_buffer *head)
}
/**
- * tomoyo_read_manager_policy - Read manager policy.
+ * tomoyo_read_manager - Read manager policy.
*
* @head: Pointer to "struct tomoyo_io_buffer".
*
- * Returns 0.
- *
* Caller holds tomoyo_read_lock().
*/
-static int tomoyo_read_manager_policy(struct tomoyo_io_buffer *head)
+static void tomoyo_read_manager(struct tomoyo_io_buffer *head)
{
- struct list_head *pos;
- bool done = true;
-
- if (head->read_eof)
- return 0;
- list_for_each_cookie(pos, head->read_var2,
- &tomoyo_policy_manager_list) {
- struct tomoyo_policy_manager_entry *ptr;
- ptr = list_entry(pos, struct tomoyo_policy_manager_entry,
- list);
- if (ptr->is_deleted)
+ 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;
- done = tomoyo_io_printf(head, "%s\n", ptr->manager->name);
- if (!done)
- break;
+ if (!tomoyo_flush(head))
+ return;
+ tomoyo_set_string(head, ptr->manager->name);
+ tomoyo_set_lf(head);
}
- head->read_eof = done;
- return 0;
+ head->r.eof = true;
}
/**
- * tomoyo_is_policy_manager - Check whether the current process is a policy manager.
+ * 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_is_policy_manager(void)
+static bool tomoyo_manager(void)
{
- struct tomoyo_policy_manager_entry *ptr;
+ struct tomoyo_manager *ptr;
const char *exe;
const struct task_struct *task = current;
const struct tomoyo_path_info *domainname = tomoyo_domain()->domainname;
@@ -1252,8 +690,9 @@ static bool tomoyo_is_policy_manager(void)
return true;
if (!tomoyo_manage_by_non_root && (task->cred->uid || task->cred->euid))
return false;
- list_for_each_entry_rcu(ptr, &tomoyo_policy_manager_list, list) {
- if (!ptr->is_deleted && ptr->is_domain
+ 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;
@@ -1264,8 +703,9 @@ static bool tomoyo_is_policy_manager(void)
exe = tomoyo_get_exe();
if (!exe)
return false;
- list_for_each_entry_rcu(ptr, &tomoyo_policy_manager_list, list) {
- if (!ptr->is_deleted && !ptr->is_domain
+ 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;
@@ -1285,7 +725,7 @@ static bool tomoyo_is_policy_manager(void)
}
/**
- * tomoyo_is_select_one - Parse select command.
+ * tomoyo_select_one - Parse select command.
*
* @head: Pointer to "struct tomoyo_io_buffer".
* @data: String to parse.
@@ -1294,23 +734,31 @@ static bool tomoyo_is_policy_manager(void)
*
* Caller holds tomoyo_read_lock().
*/
-static bool tomoyo_is_select_one(struct tomoyo_io_buffer *head,
- const char *data)
+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 (sscanf(data, "pid=%u", &pid) == 1) {
+ 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);
- p = find_task_by_vpid(pid);
+ 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_is_domain_def(data + 7))
+ if (tomoyo_domain_def(data + 7))
domain = tomoyo_find_domain(data + 7);
} else
return false;
@@ -1318,24 +766,15 @@ static bool tomoyo_is_select_one(struct tomoyo_io_buffer *head,
/* Accessing read_buf is safe because head->io_sem is held. */
if (!head->read_buf)
return true; /* Do nothing if open(O_WRONLY). */
- head->read_avail = 0;
+ 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);
- head->read_single_domain = true;
- head->read_eof = !domain;
- if (domain) {
- struct tomoyo_domain_info *d;
- head->read_var1 = NULL;
- list_for_each_entry_rcu(d, &tomoyo_domain_list, list) {
- if (d == domain)
- break;
- head->read_var1 = &d->list;
- }
- head->read_var2 = NULL;
- head->read_bit = 0;
- head->read_step = 0;
- if (domain->is_deleted)
- tomoyo_io_printf(head, "# This is a deleted domain.\n");
- }
+ if (domain && domain->is_deleted)
+ tomoyo_io_printf(head, "# This is a deleted domain.\n");
return true;
}
@@ -1373,7 +812,7 @@ static int tomoyo_delete_domain(char *domainname)
}
/**
- * tomoyo_write_domain_policy - Write domain policy.
+ * tomoyo_write_domain2 - Write domain policy.
*
* @head: Pointer to "struct tomoyo_io_buffer".
*
@@ -1381,7 +820,24 @@ static int tomoyo_delete_domain(char *domainname)
*
* Caller holds tomoyo_read_lock().
*/
-static int tomoyo_write_domain_policy(struct tomoyo_io_buffer *head)
+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;
@@ -1393,19 +849,19 @@ static int tomoyo_write_domain_policy(struct tomoyo_io_buffer *head)
is_delete = true;
else if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_SELECT))
is_select = true;
- if (is_select && tomoyo_is_select_one(head, data))
+ if (is_select && tomoyo_select_one(head, data))
return 0;
/* Don't allow updating policies by non manager programs. */
- if (!tomoyo_is_policy_manager())
+ if (!tomoyo_manager())
return -EPERM;
- if (tomoyo_is_domain_def(data)) {
+ 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_find_or_assign_new_domain(data, 0);
+ domain = tomoyo_assign_domain(data, 0);
head->write_var1 = domain;
return 0;
}
@@ -1422,179 +878,198 @@ static int tomoyo_write_domain_policy(struct tomoyo_io_buffer *head)
domain->ignore_global_allow_read = !is_delete;
return 0;
}
- return tomoyo_write_file_policy(data, domain, is_delete);
+ 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_print_path_acl - Print a single path ACL entry.
+ * tomoyo_fns - Find next set bit.
*
- * @head: Pointer to "struct tomoyo_io_buffer".
- * @ptr: Pointer to "struct tomoyo_path_acl".
+ * @perm: 8 bits value.
+ * @bit: First bit to find.
*
- * Returns true on success, false otherwise.
+ * Returns next on-bit on success, 8 otherwise.
*/
-static bool tomoyo_print_path_acl(struct tomoyo_io_buffer *head,
- struct tomoyo_path_acl *ptr)
+static u8 tomoyo_fns(const u8 perm, u8 bit)
{
- int pos;
- u8 bit;
- const u32 perm = ptr->perm | (((u32) ptr->perm_high) << 16);
-
- for (bit = head->read_bit; bit < TOMOYO_MAX_PATH_OPERATION; bit++) {
- if (!(perm & (1 << bit)))
- 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;
- pos = head->read_avail;
- if (!tomoyo_io_printf(head, "allow_%s ",
- tomoyo_path2keyword(bit)) ||
- !tomoyo_print_name_union(head, &ptr->name) ||
- !tomoyo_io_printf(head, "\n"))
- goto out;
- }
- head->read_bit = 0;
- return true;
- out:
- head->read_bit = bit;
- head->read_avail = pos;
- return false;
+ for ( ; bit < 8; bit++)
+ if (perm & (1 << bit))
+ break;
+ return bit;
}
/**
- * tomoyo_print_path2_acl - Print a double path ACL entry.
+ * tomoyo_print_entry - Print an ACL entry.
*
* @head: Pointer to "struct tomoyo_io_buffer".
- * @ptr: Pointer to "struct tomoyo_path2_acl".
+ * @acl: Pointer to an ACL entry.
*
* Returns true on success, false otherwise.
*/
-static bool tomoyo_print_path2_acl(struct tomoyo_io_buffer *head,
- struct tomoyo_path2_acl *ptr)
+static bool tomoyo_print_entry(struct tomoyo_io_buffer *head,
+ struct tomoyo_acl_info *acl)
{
- int pos;
- const u8 perm = ptr->perm;
+ const u8 acl_type = acl->type;
u8 bit;
- for (bit = head->read_bit; bit < TOMOYO_MAX_PATH2_OPERATION; bit++) {
- if (!(perm & (1 << bit)))
- continue;
- pos = head->read_avail;
- if (!tomoyo_io_printf(head, "allow_%s ",
- tomoyo_path22keyword(bit)) ||
- !tomoyo_print_name_union(head, &ptr->name1) ||
- !tomoyo_print_name_union(head, &ptr->name2) ||
- !tomoyo_io_printf(head, "\n"))
- goto out;
+ 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->read_bit = 0;
+ 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;
- out:
- head->read_bit = bit;
- head->read_avail = pos;
- return false;
}
/**
- * tomoyo_print_entry - Print an ACL entry.
+ * tomoyo_read_domain2 - Read domain policy.
*
- * @head: Pointer to "struct tomoyo_io_buffer".
- * @ptr: Pointer to an ACL entry.
+ * @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_print_entry(struct tomoyo_io_buffer *head,
- struct tomoyo_acl_info *ptr)
+static bool tomoyo_read_domain2(struct tomoyo_io_buffer *head,
+ struct tomoyo_domain_info *domain)
{
- const u8 acl_type = ptr->type;
-
- if (acl_type == TOMOYO_TYPE_PATH_ACL) {
- struct tomoyo_path_acl *acl
- = container_of(ptr, struct tomoyo_path_acl, head);
- return tomoyo_print_path_acl(head, acl);
- }
- if (acl_type == TOMOYO_TYPE_PATH2_ACL) {
- struct tomoyo_path2_acl *acl
- = container_of(ptr, struct tomoyo_path2_acl, head);
- return tomoyo_print_path2_acl(head, acl);
+ 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;
}
- BUG(); /* This must not happen. */
- return false;
+ head->r.acl = NULL;
+ return true;
}
/**
- * tomoyo_read_domain_policy - Read domain policy.
+ * tomoyo_read_domain - Read domain policy.
*
* @head: Pointer to "struct tomoyo_io_buffer".
*
- * Returns 0.
- *
* Caller holds tomoyo_read_lock().
*/
-static int tomoyo_read_domain_policy(struct tomoyo_io_buffer *head)
+static void tomoyo_read_domain(struct tomoyo_io_buffer *head)
{
- struct list_head *dpos;
- struct list_head *apos;
- bool done = true;
-
- if (head->read_eof)
- return 0;
- if (head->read_step == 0)
- head->read_step = 1;
- list_for_each_cookie(dpos, head->read_var1, &tomoyo_domain_list) {
- struct tomoyo_domain_info *domain;
- const char *quota_exceeded = "";
- const char *transition_failed = "";
- const char *ignore_global_allow_read = "";
- domain = list_entry(dpos, struct tomoyo_domain_info, list);
- if (head->read_step != 1)
- goto acl_loop;
- if (domain->is_deleted && !head->read_single_domain)
- continue;
- /* Print domainname and flags. */
- if (domain->quota_warned)
- quota_exceeded = "quota_exceeded\n";
- if (domain->transition_failed)
- transition_failed = "transition_failed\n";
- if (domain->ignore_global_allow_read)
- ignore_global_allow_read
- = TOMOYO_KEYWORD_IGNORE_GLOBAL_ALLOW_READ "\n";
- done = tomoyo_io_printf(head, "%s\n" TOMOYO_KEYWORD_USE_PROFILE
- "%u\n%s%s%s\n",
- domain->domainname->name,
- domain->profile, quota_exceeded,
- transition_failed,
- ignore_global_allow_read);
- if (!done)
- break;
- head->read_step = 2;
-acl_loop:
- if (head->read_step == 3)
- goto tail_mark;
- /* Print ACL entries in the domain. */
- list_for_each_cookie(apos, head->read_var2,
- &domain->acl_info_list) {
- struct tomoyo_acl_info *ptr
- = list_entry(apos, struct tomoyo_acl_info,
- list);
- done = tomoyo_print_entry(head, ptr);
- if (!done)
- break;
+ 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;
}
- if (!done)
- break;
- head->read_step = 3;
-tail_mark:
- done = tomoyo_io_printf(head, "\n");
- if (!done)
- break;
- head->read_step = 1;
- if (head->read_single_domain)
- break;
}
- head->read_eof = done;
- return 0;
+ done:
+ head->r.eof = true;
}
/**
@@ -1607,7 +1082,7 @@ tail_mark:
* This is equivalent to doing
*
* ( echo "select " $domainname; echo "use_profile " $profile ) |
- * /usr/lib/ccs/loadpolicy -d
+ * /usr/sbin/tomoyo-loadpolicy -d
*
* Caller holds tomoyo_read_lock().
*/
@@ -1646,25 +1121,22 @@ static int tomoyo_write_domain_profile(struct tomoyo_io_buffer *head)
*
* Caller holds tomoyo_read_lock().
*/
-static int tomoyo_read_domain_profile(struct tomoyo_io_buffer *head)
+static void tomoyo_read_domain_profile(struct tomoyo_io_buffer *head)
{
- struct list_head *pos;
- bool done = true;
-
- if (head->read_eof)
- return 0;
- list_for_each_cookie(pos, head->read_var1, &tomoyo_domain_list) {
- struct tomoyo_domain_info *domain;
- domain = list_entry(pos, struct tomoyo_domain_info, list);
+ 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;
- done = tomoyo_io_printf(head, "%u %s\n", domain->profile,
- domain->domainname->name);
- if (!done)
- break;
+ if (!tomoyo_flush(head))
+ return;
+ tomoyo_io_printf(head, "%u ", domain->profile);
+ tomoyo_set_string(head, domain->domainname->name);
+ tomoyo_set_lf(head);
}
- head->read_eof = done;
- return 0;
+ head->r.eof = true;
}
/**
@@ -1676,11 +1148,7 @@ static int tomoyo_read_domain_profile(struct tomoyo_io_buffer *head)
*/
static int tomoyo_write_pid(struct tomoyo_io_buffer *head)
{
- unsigned long pid;
- /* No error check. */
- strict_strtoul(head->write_buf, 10, &pid);
- head->read_step = (int) pid;
- head->read_eof = false;
+ head->r.eof = false;
return 0;
}
@@ -1694,29 +1162,57 @@ static int tomoyo_write_pid(struct tomoyo_io_buffer *head)
* The PID is specified by tomoyo_write_pid() so that the user can obtain
* using read()/write() interface rather than sysctl() interface.
*/
-static int tomoyo_read_pid(struct tomoyo_io_buffer *head)
+static void tomoyo_read_pid(struct tomoyo_io_buffer *head)
{
- if (head->read_avail == 0 && !head->read_eof) {
- const int pid = head->read_step;
- struct task_struct *p;
- struct tomoyo_domain_info *domain = NULL;
- rcu_read_lock();
- read_lock(&tasklist_lock);
- p = find_task_by_vpid(pid);
- if (p)
- domain = tomoyo_real_domain(p);
- read_unlock(&tasklist_lock);
- rcu_read_unlock();
- if (domain)
- tomoyo_io_printf(head, "%d %u %s", pid, domain->profile,
- domain->domainname->name);
- head->read_eof = true;
+ 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). */
}
- return 0;
+ 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_policy - Write exception policy.
+ * tomoyo_write_exception - Write exception policy.
*
* @head: Pointer to "struct tomoyo_io_buffer".
*
@@ -1724,186 +1220,527 @@ static int tomoyo_read_pid(struct tomoyo_io_buffer *head)
*
* Caller holds tomoyo_read_lock().
*/
-static int tomoyo_write_exception_policy(struct tomoyo_io_buffer *head)
+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);
-
- if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_KEEP_DOMAIN))
- return tomoyo_write_domain_keeper_policy(data, false,
- is_delete);
- if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_NO_KEEP_DOMAIN))
- return tomoyo_write_domain_keeper_policy(data, true, is_delete);
- if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_INITIALIZE_DOMAIN))
- return tomoyo_write_domain_initializer_policy(data, false,
- is_delete);
- if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_NO_INITIALIZE_DOMAIN))
- return tomoyo_write_domain_initializer_policy(data, true,
- is_delete);
- if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_ALIAS))
- return tomoyo_write_alias_policy(data, is_delete);
- if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_ALLOW_READ))
- return tomoyo_write_globally_readable_policy(data, is_delete);
- if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_FILE_PATTERN))
- return tomoyo_write_pattern_policy(data, is_delete);
- if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_DENY_REWRITE))
- return tomoyo_write_no_rewrite_policy(data, is_delete);
- if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_PATH_GROUP))
- return tomoyo_write_path_group_policy(data, is_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_exception_policy - Read exception policy.
+ * tomoyo_read_group - Read "struct tomoyo_path_group"/"struct tomoyo_number_group" list.
*
* @head: Pointer to "struct tomoyo_io_buffer".
+ * @idx: Index number.
*
- * Returns 0 on success, -EINVAL otherwise.
+ * Returns true on success, false otherwise.
*
* Caller holds tomoyo_read_lock().
*/
-static int tomoyo_read_exception_policy(struct tomoyo_io_buffer *head)
+static bool tomoyo_read_group(struct tomoyo_io_buffer *head, const int idx)
{
- if (!head->read_eof) {
- switch (head->read_step) {
- case 0:
- head->read_var2 = NULL;
- head->read_step = 1;
- case 1:
- if (!tomoyo_read_domain_keeper_policy(head))
- break;
- head->read_var2 = NULL;
- head->read_step = 2;
- case 2:
- if (!tomoyo_read_globally_readable_policy(head))
- break;
- head->read_var2 = NULL;
- head->read_step = 3;
- case 3:
- head->read_var2 = NULL;
- head->read_step = 4;
- case 4:
- if (!tomoyo_read_domain_initializer_policy(head))
- break;
- head->read_var2 = NULL;
- head->read_step = 5;
- case 5:
- if (!tomoyo_read_alias_policy(head))
- break;
- head->read_var2 = NULL;
- head->read_step = 6;
- case 6:
- head->read_var2 = NULL;
- head->read_step = 7;
- case 7:
- if (!tomoyo_read_file_pattern(head))
- break;
- head->read_var2 = NULL;
- head->read_step = 8;
- case 8:
- if (!tomoyo_read_no_rewrite_policy(head))
- break;
- head->read_var2 = NULL;
- head->read_step = 9;
- case 9:
- if (!tomoyo_read_path_group_policy(head))
- break;
- head->read_var1 = NULL;
- head->read_var2 = NULL;
- head->read_step = 10;
- case 10:
- head->read_eof = true;
+ 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:
- return -EINVAL;
+ continue;
}
+ tomoyo_set_lf(head);
}
- return 0;
+ head->r.acl = NULL;
+ return true;
}
-/* path to policy loader */
-static const char *tomoyo_loader = "/sbin/tomoyo-init";
+/**
+ * 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_policy_loader_exists - Check whether /sbin/tomoyo-init exists.
+ * tomoyo_print_header - Get header line of audit log.
+ *
+ * @r: Pointer to "struct tomoyo_request_info".
+ *
+ * Returns string representation.
*
- * Returns true if /sbin/tomoyo-init exists, false otherwise.
+ * This function uses kmalloc(), so caller must kfree() if this function
+ * didn't return NULL.
*/
-static bool tomoyo_policy_loader_exists(void)
+static char *tomoyo_print_header(struct tomoyo_request_info *r)
{
- /*
- * Don't activate MAC if the policy loader doesn't exist.
- * If the initrd includes /sbin/init but real-root-dev has not
- * mounted on / yet, activating MAC will block the system since
- * policies are not loaded yet.
- * Thus, let do_execve() call this function everytime.
- */
- struct path path;
+ 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;
+}
- 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;
+/**
+ * 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;
}
- path_put(&path);
- return true;
+ 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_load_policy - Run external policy loader to load policy.
+ * tomoyo_poll_query - poll() for /sys/kernel/security/tomoyo/query.
*
- * @filename: The program about to start.
+ * @file: Pointer to "struct file".
+ * @wait: Pointer to "poll_table".
*
- * 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 POLLIN | POLLRDNORM when ready to read, 0 otherwise.
*
- * Returns nothing.
+ * Waits for access requests which violated policy in enforcing mode.
*/
-void tomoyo_load_policy(const char *filename)
+static int tomoyo_poll_query(struct file *file, poll_table *wait)
{
- char *argv[2];
- char *envp[3];
+ 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;
+}
- if (tomoyo_policy_loaded)
+/**
+ * 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;
- /*
- * 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"))
+ 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;
- if (!tomoyo_policy_loader_exists())
+ }
+ 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);
+ }
+}
- 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);
-
- printk(KERN_INFO "TOMOYO: 2.2.0 2009/04/01\n");
- printk(KERN_INFO "Mandatory Access Control activated.\n");
- tomoyo_policy_loaded = true;
- { /* Check all profiles currently assigned to domains are defined. */
- struct tomoyo_domain_info *domain;
- list_for_each_entry_rcu(domain, &tomoyo_domain_list, list) {
- const u8 profile = domain->profile;
- if (tomoyo_profile_ptr[profile])
- continue;
- panic("Profile %u (used by '%s') not defined.\n",
- profile, domain->domainname->name);
- }
+/**
+ * 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;
}
/**
@@ -1913,13 +1750,12 @@ void tomoyo_load_policy(const char *filename)
*
* Returns version information.
*/
-static int tomoyo_read_version(struct tomoyo_io_buffer *head)
+static void tomoyo_read_version(struct tomoyo_io_buffer *head)
{
- if (!head->read_eof) {
- tomoyo_io_printf(head, "2.2.0");
- head->read_eof = true;
+ if (!head->r.eof) {
+ tomoyo_io_printf(head, "2.3.0");
+ head->r.eof = true;
}
- return 0;
}
/**
@@ -1929,18 +1765,17 @@ static int tomoyo_read_version(struct tomoyo_io_buffer *head)
*
* Returns the current process's domainname.
*/
-static int tomoyo_read_self_domain(struct tomoyo_io_buffer *head)
+static void tomoyo_read_self_domain(struct tomoyo_io_buffer *head)
{
- if (!head->read_eof) {
+ 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->read_eof = true;
+ head->r.eof = true;
}
- return 0;
}
/**
@@ -1953,23 +1788,24 @@ static int tomoyo_read_self_domain(struct tomoyo_io_buffer *head)
*
* Caller acquires tomoyo_read_lock().
*/
-static int tomoyo_open_control(const u8 type, struct file *file)
+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_policy;
- head->read = tomoyo_read_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_policy;
- head->read = tomoyo_read_exception_policy;
+ head->write = tomoyo_write_exception;
+ head->read = tomoyo_read_exception;
break;
case TOMOYO_SELFDOMAIN:
/* /sys/kernel/security/tomoyo/self_domain */
@@ -2001,10 +1837,15 @@ static int tomoyo_open_control(const u8 type, struct file *file)
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_policy;
- head->read = tomoyo_read_manager_policy;
+ head->write = tomoyo_write_manager;
+ head->read = tomoyo_read_manager;
break;
}
if (!(file->f_mode & FMODE_READ)) {
@@ -2013,7 +1854,9 @@ static int tomoyo_open_control(const u8 type, struct file *file)
* for reading.
*/
head->read = NULL;
- } else {
+ 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);
@@ -2037,7 +1880,8 @@ static int tomoyo_open_control(const u8 type, struct file *file)
return -ENOMEM;
}
}
- head->reader_idx = tomoyo_read_lock();
+ if (type != TOMOYO_QUERY)
+ head->reader_idx = tomoyo_read_lock();
file->private_data = head;
/*
* Call the handler now if the file is
@@ -2048,10 +1892,35 @@ static int tomoyo_open_control(const u8 type, struct file *file)
*/
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".
@@ -2062,36 +1931,23 @@ static int tomoyo_open_control(const u8 type, struct file *file)
*
* Caller holds tomoyo_read_lock().
*/
-static int tomoyo_read_control(struct file *file, char __user *buffer,
- const int buffer_len)
+int tomoyo_read_control(struct file *file, char __user *buffer,
+ const int buffer_len)
{
- int len = 0;
+ int len;
struct tomoyo_io_buffer *head = file->private_data;
- char *cp;
if (!head->read)
return -ENOSYS;
if (mutex_lock_interruptible(&head->io_sem))
return -EINTR;
- /* Call the policy handler. */
- len = head->read(head);
- if (len < 0)
- goto out;
- /* Write to buffer. */
- len = head->read_avail;
- if (len > buffer_len)
- len = buffer_len;
- if (!len)
- goto out;
- /* head->read_buf changes by some functions. */
- cp = head->read_buf;
- if (copy_to_user(buffer, cp, len)) {
- len = -EFAULT;
- goto out;
- }
- head->read_avail -= len;
- memmove(cp, cp + len, head->read_avail);
- out:
+ 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;
}
@@ -2107,8 +1963,8 @@ static int tomoyo_read_control(struct file *file, char __user *buffer,
*
* Caller holds tomoyo_read_lock().
*/
-static int tomoyo_write_control(struct file *file, const char __user *buffer,
- const int buffer_len)
+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;
@@ -2121,8 +1977,7 @@ static int tomoyo_write_control(struct file *file, const char __user *buffer,
return -EFAULT;
/* Don't allow updating policies by non manager programs. */
if (head->write != tomoyo_write_pid &&
- head->write != tomoyo_write_domain_policy &&
- !tomoyo_is_policy_manager())
+ head->write != tomoyo_write_domain && !tomoyo_manager())
return -EPERM;
if (mutex_lock_interruptible(&head->io_sem))
return -EINTR;
@@ -2159,12 +2014,19 @@ static int tomoyo_write_control(struct file *file, const char __user *buffer,
*
* Caller looses tomoyo_read_lock().
*/
-static int tomoyo_close_control(struct file *file)
+int tomoyo_close_control(struct file *file)
{
struct tomoyo_io_buffer *head = file->private_data;
const bool is_write = !!head->write_buf;
- tomoyo_read_unlock(head->reader_idx);
+ /*
+ * 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;
@@ -2179,129 +2041,34 @@ static int tomoyo_close_control(struct file *file)
}
/**
- * 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_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.
+ * tomoyo_check_profile - Check all profiles currently assigned to domains are defined.
*/
-static ssize_t tomoyo_read(struct file *file, char __user *buf, size_t count,
- loff_t *ppos)
+void tomoyo_check_profile(void)
{
- 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,
- .read = tomoyo_read,
- .write = tomoyo_write,
-};
-
-/**
- * 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("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;
+ 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");
}
-
-fs_initcall(tomoyo_initerface_init);
diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h
index 9f1ae5e..7c66bd8 100644
--- a/security/tomoyo/common.h
+++ b/security/tomoyo/common.h
@@ -20,6 +20,7 @@
#include <linux/mount.h>
#include <linux/list.h>
#include <linux/cred.h>
+#include <linux/poll.h>
struct linux_binprm;
/********** Constants definitions. **********/
@@ -32,20 +33,44 @@ struct linux_binprm;
#define TOMOYO_HASH_BITS 8
#define TOMOYO_MAX_HASH (1u<<TOMOYO_HASH_BITS)
-/*
- * This is the max length of a token.
- *
- * A token consists of only ASCII printable characters.
- * Non printable characters in a token is represented in \ooo style
- * octal string. Thus, \ itself is represented as \\.
- */
-#define TOMOYO_MAX_PATHNAME_LEN 4000
+#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_ALIAS "alias "
+#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 "
@@ -55,36 +80,51 @@ struct linux_binprm;
#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)
-/* Index numbers for Access Controls. */
-enum tomoyo_mac_index {
- TOMOYO_MAC_FOR_FILE, /* domain_policy.conf */
- TOMOYO_MAX_ACCEPT_ENTRY,
- TOMOYO_VERBOSE,
- TOMOYO_MAX_CONTROL_INDEX
+/* 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. */
/*
- * TYPE_READ_WRITE_ACL is special. TYPE_READ_WRITE_ACL is automatically set
- * if both TYPE_READ_ACL and TYPE_WRITE_ACL are set. Both TYPE_READ_ACL and
- * TYPE_WRITE_ACL are automatically set if TYPE_READ_WRITE_ACL is set.
- * TYPE_READ_WRITE_ACL is automatically cleared if either TYPE_READ_ACL or
- * TYPE_WRITE_ACL is cleared. Both TYPE_READ_ACL and TYPE_WRITE_ACL are
- * automatically cleared if TYPE_READ_WRITE_ACL is cleared.
+ * 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 {
@@ -92,27 +132,24 @@ enum tomoyo_path_acl_index {
TOMOYO_TYPE_EXECUTE,
TOMOYO_TYPE_READ,
TOMOYO_TYPE_WRITE,
- TOMOYO_TYPE_CREATE,
TOMOYO_TYPE_UNLINK,
- TOMOYO_TYPE_MKDIR,
TOMOYO_TYPE_RMDIR,
- TOMOYO_TYPE_MKFIFO,
- TOMOYO_TYPE_MKSOCK,
- TOMOYO_TYPE_MKBLOCK,
- TOMOYO_TYPE_MKCHAR,
TOMOYO_TYPE_TRUNCATE,
TOMOYO_TYPE_SYMLINK,
TOMOYO_TYPE_REWRITE,
- TOMOYO_TYPE_IOCTL,
- TOMOYO_TYPE_CHMOD,
- TOMOYO_TYPE_CHOWN,
- TOMOYO_TYPE_CHGRP,
TOMOYO_TYPE_CHROOT,
- TOMOYO_TYPE_MOUNT,
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,
@@ -120,6 +157,18 @@ enum tomoyo_path2_acl_index {
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,
@@ -129,20 +178,109 @@ enum tomoyo_securityfs_interface_index {
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_page_buffer is a structure which is used for holding a pathname
- * obtained from "struct dentry" and "struct vfsmount" pair.
- * As of now, it is 4096 bytes. If users complain that 4096 bytes is too small
- * (because TOMOYO escapes non ASCII printable characters using \ooo format),
- * we will make the buffer larger.
+ * 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_page_buffer {
- char buffer[4096];
+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;
};
/*
@@ -174,45 +312,31 @@ struct tomoyo_path_info {
};
/*
- * tomoyo_name_entry is a structure which is used for linking
+ * tomoyo_name is a structure which is used for linking
* "struct tomoyo_path_info" into tomoyo_name_list .
*/
-struct tomoyo_name_entry {
+struct tomoyo_name {
struct list_head list;
atomic_t users;
struct tomoyo_path_info entry;
};
-/*
- * tomoyo_path_info_with_data is a structure which is used for holding a
- * pathname obtained from "struct dentry" and "struct vfsmount" pair.
- *
- * "struct tomoyo_path_info_with_data" consists of "struct tomoyo_path_info"
- * and buffer for the pathname, while "struct tomoyo_page_buffer" consists of
- * buffer for the pathname only.
- *
- * "struct tomoyo_path_info_with_data" is intended to allow TOMOYO to release
- * both "struct tomoyo_path_info" and buffer for the pathname by single kfree()
- * so that we don't need to return two pointers to the caller. If the caller
- * puts "struct tomoyo_path_info" on stack memory, we will be able to remove
- * "struct tomoyo_path_info_with_data".
- */
-struct tomoyo_path_info_with_data {
- /* Keep "head" first, for this pointer is passed to kfree(). */
- struct tomoyo_path_info head;
- char barrier1[16]; /* Safeguard for overrun. */
- char body[TOMOYO_MAX_PATHNAME_LEN];
- char barrier2[16]; /* Safeguard for overrun. */
-};
-
struct tomoyo_name_union {
const struct tomoyo_path_info *filename;
- struct tomoyo_path_group *group;
+ struct tomoyo_group *group;
u8 is_group;
};
-/* Structure for "path_group" directive. */
-struct tomoyo_path_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;
@@ -220,28 +344,35 @@ struct tomoyo_path_group {
};
/* Structure for "path_group" directive. */
-struct tomoyo_path_group_member {
- struct list_head list;
- bool is_deleted;
+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) "type" which tells type of the entry (either
- * "struct tomoyo_path_acl" or "struct tomoyo_path2_acl").
+ * (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 "u8" + "u16" and
- * "struct tomoyo_path2_acl" to embed "u8"
- * without enlarging their structure size.
+ * "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;
- u8 type;
+ bool is_deleted;
+ u8 type; /* = one of values in "enum tomoyo_acl_entry_type_index". */
} __packed;
/*
@@ -299,20 +430,62 @@ struct tomoyo_domain_info {
* (3) "name" is the pathname.
*
* Directives held by this structure are "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_chmod", "allow_chown", "allow_chgrp", "allow_chroot", "allow_mount"
- * and "allow_unmount".
+ * "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 */
- u8 perm_high;
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.
@@ -333,53 +506,61 @@ struct tomoyo_path2_acl {
};
/*
- * tomoyo_io_buffer is a structure which is used for reading and modifying
- * configuration via /sys/kernel/security/tomoyo/ interface.
- * It has many fields. ->read_var1 , ->read_var2 , ->write_var1 are used as
- * cursors.
+ * tomoyo_mount_acl is a structure which is used for holding an
+ * entry for mount operation.
+ * It has following fields.
*
- * Since the content of /sys/kernel/security/tomoyo/domain_policy is a list of
- * "struct tomoyo_domain_info" entries and each "struct tomoyo_domain_info"
- * entry has a list of "struct tomoyo_acl_info", we need two cursors when
- * reading (one is for traversing tomoyo_domain_list and the other is for
- * traversing "struct tomoyo_acl_info"->acl_info_list ).
+ * (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.
*
- * If a line written to /sys/kernel/security/tomoyo/domain_policy starts with
- * "select ", TOMOYO seeks the cursor ->read_var1 and ->write_var1 to the
- * domain with the domainname specified by the rest of that line (NULL is set
- * if seek failed).
- * If a line written to /sys/kernel/security/tomoyo/domain_policy starts with
- * "delete ", TOMOYO deletes an entry or a domain specified by the rest of that
- * line (->write_var1 is set to NULL if a domain was deleted).
- * If a line written to /sys/kernel/security/tomoyo/domain_policy starts with
- * neither "select " nor "delete ", an entry or a domain specified by that line
- * is appended.
+ * 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 {
- int (*read) (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;
- /* The position currently reading from. */
- struct list_head *read_var1;
- /* Extra variables for reading. */
- struct list_head *read_var2;
+ 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;
- /* The step for reading. */
- int read_step;
/* Buffer for reading. */
char *read_buf;
- /* EOF flag for reading. */
- bool read_eof;
- /* Read domain ACL of specified PID? */
- bool read_single_domain;
- /* Extra variable for reading. */
- u8 read_bit;
- /* Bytes available for reading. */
- int read_avail;
/* Size of read buffer. */
int readbuf_size;
/* Buffer for writing. */
@@ -388,215 +569,200 @@ struct tomoyo_io_buffer {
int write_avail;
/* Size of write buffer. */
int writebuf_size;
+ /* Type of this interface. */
+ u8 type;
};
/*
- * tomoyo_globally_readable_file_entry is a structure which is used for holding
+ * tomoyo_readable_file is a structure which is used for holding
* "allow_read" entries.
* It has following fields.
*
- * (1) "list" which is linked to tomoyo_globally_readable_list .
+ * (1) "head" is "struct tomoyo_acl_head".
* (2) "filename" is a pathname which is allowed to open(O_RDONLY).
- * (3) "is_deleted" is a bool which is true if marked as deleted, false
- * otherwise.
*/
-struct tomoyo_globally_readable_file_entry {
- struct list_head list;
+struct tomoyo_readable_file {
+ struct tomoyo_acl_head head;
const struct tomoyo_path_info *filename;
- bool is_deleted;
};
/*
- * tomoyo_pattern_entry is a structure which is used for holding
- * "tomoyo_pattern_list" entries.
+ * tomoyo_no_pattern is a structure which is used for holding
+ * "file_pattern" entries.
* It has following fields.
*
- * (1) "list" which is linked to tomoyo_pattern_list .
+ * (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.
- * (3) "is_deleted" is a bool which is true if marked as deleted, false
- * otherwise.
*/
-struct tomoyo_pattern_entry {
- struct list_head list;
+struct tomoyo_no_pattern {
+ struct tomoyo_acl_head head;
const struct tomoyo_path_info *pattern;
- bool is_deleted;
};
/*
- * tomoyo_no_rewrite_entry is a structure which is used for holding
+ * tomoyo_no_rewrite is a structure which is used for holding
* "deny_rewrite" entries.
* It has following fields.
*
- * (1) "list" which is linked to tomoyo_no_rewrite_list .
+ * (1) "head" is "struct tomoyo_acl_head".
* (2) "pattern" is a pathname which is by default not permitted to modify
* already existing content.
- * (3) "is_deleted" is a bool which is true if marked as deleted, false
- * otherwise.
*/
-struct tomoyo_no_rewrite_entry {
- struct list_head list;
+struct tomoyo_no_rewrite {
+ struct tomoyo_acl_head head;
const struct tomoyo_path_info *pattern;
- bool is_deleted;
};
/*
- * tomoyo_domain_initializer_entry is a structure which is used for holding
- * "initialize_domain" and "no_initialize_domain" entries.
+ * 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) "list" which is linked to tomoyo_domain_initializer_list .
- * (2) "domainname" which is "a domainname" or "the last component of a
- * domainname". This field is NULL if "from" clause is not specified.
- * (3) "program" which is a program's pathname.
- * (4) "is_deleted" is a bool which is true if marked as deleted, false
- * otherwise.
- * (5) "is_not" is a bool which is true if "no_initialize_domain", false
- * otherwise.
- * (6) "is_last_name" is a bool which is true if "domainname" is "the last
+ * (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.
- */
-struct tomoyo_domain_initializer_entry {
- struct list_head list;
- const struct tomoyo_path_info *domainname; /* This may be NULL */
- const struct tomoyo_path_info *program;
- bool is_deleted;
- bool is_not; /* True if this entry is "no_initialize_domain". */
- /* True if the domainname is tomoyo_get_last_name(). */
- bool is_last_name;
-};
-
-/*
- * tomoyo_domain_keeper_entry is a structure which is used for holding
- * "keep_domain" and "no_keep_domain" entries.
- * It has following fields.
- *
- * (1) "list" which is linked to tomoyo_domain_keeper_list .
- * (2) "domainname" which is "a domainname" or "the last component of a
+ * (4) "domainname" which is "a domainname" or "the last component of a
* domainname".
- * (3) "program" which is a program's pathname.
- * This field is NULL if "from" clause is not specified.
- * (4) "is_deleted" is a bool which is true if marked as deleted, false
- * otherwise.
- * (5) "is_not" is a bool which is true if "no_initialize_domain", false
- * otherwise.
- * (6) "is_last_name" is a bool which is true if "domainname" is "the last
- * component of a domainname", false otherwise.
+ * (5) "program" which is a program's pathname.
*/
-struct tomoyo_domain_keeper_entry {
- struct list_head list;
- const struct tomoyo_path_info *domainname;
- const struct tomoyo_path_info *program; /* This may be NULL */
- bool is_deleted;
- bool is_not; /* True if this entry is "no_keep_domain". */
+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_alias_entry is a structure which is used for holding "alias" entries.
+ * tomoyo_aggregator is a structure which is used for holding
+ * "aggregator" entries.
* It has following fields.
*
- * (1) "list" which is linked to tomoyo_alias_list .
- * (2) "original_name" which is a dereferenced pathname.
- * (3) "aliased_name" which is a symlink's pathname.
- * (4) "is_deleted" is a bool which is true if marked as deleted, false
- * otherwise.
+ * (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_alias_entry {
- struct list_head list;
+struct tomoyo_aggregator {
+ struct tomoyo_acl_head head;
const struct tomoyo_path_info *original_name;
- const struct tomoyo_path_info *aliased_name;
- bool is_deleted;
+ const struct tomoyo_path_info *aggregated_name;
};
/*
- * tomoyo_policy_manager_entry is a structure which is used for holding list of
+ * 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) "list" which is linked to tomoyo_policy_manager_list .
- * (2) "manager" is a domainname or a program's pathname.
- * (3) "is_domain" is a bool which is true if "manager" is a domainname, false
- * otherwise.
- * (4) "is_deleted" is a bool which is true if marked as deleted, false
+ * (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_policy_manager_entry {
- struct list_head list;
+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;
- bool is_domain; /* True if manager is a domainname. */
- bool is_deleted; /* True if this entry is deleted. */
+};
+
+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 name matches the given name_union. */
-bool tomoyo_compare_name_union(const struct tomoyo_path_info *name,
- const struct tomoyo_name_union *ptr);
+/* 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_domain_info * const domain);
-/* Transactional sprintf() for policy dump. */
-bool tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt, ...)
+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_is_correct_domain(const unsigned char *domainname);
+bool tomoyo_correct_domain(const unsigned char *domainname);
/* Check whether the token is correct. */
-bool tomoyo_is_correct_path(const char *filename, const s8 start_type,
- const s8 pattern_type, const s8 end_type);
+bool tomoyo_correct_path(const char *filename);
+bool tomoyo_correct_word(const char *string);
/* Check whether the token can be a domainname. */
-bool tomoyo_is_domain_def(const unsigned char *buffer);
+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. */
-bool tomoyo_path_matches_group(const struct tomoyo_path_info *pathname,
- const struct tomoyo_path_group *group,
- const bool may_use_pattern);
+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);
-/* Read "alias" entry in exception policy. */
-bool tomoyo_read_alias_policy(struct tomoyo_io_buffer *head);
-/*
- * Read "initialize_domain" and "no_initialize_domain" entry
- * in exception policy.
- */
-bool tomoyo_read_domain_initializer_policy(struct tomoyo_io_buffer *head);
-/* Read "keep_domain" and "no_keep_domain" entry in exception policy. */
-bool tomoyo_read_domain_keeper_policy(struct tomoyo_io_buffer *head);
-/* Read "file_pattern" entry in exception policy. */
-bool tomoyo_read_file_pattern(struct tomoyo_io_buffer *head);
-/* Read "path_group" entry in exception policy. */
-bool tomoyo_read_path_group_policy(struct tomoyo_io_buffer *head);
-/* Read "allow_read" entry in exception policy. */
-bool tomoyo_read_globally_readable_policy(struct tomoyo_io_buffer *head);
-/* Read "deny_rewrite" entry in exception policy. */
-bool tomoyo_read_no_rewrite_policy(struct tomoyo_io_buffer *head);
+
+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);
-/* Convert double path operation to operation name. */
-const char *tomoyo_path22keyword(const u8 operation);
-/* Get the last component of the given domainname. */
-const char *tomoyo_get_last_name(const struct tomoyo_domain_info *domain);
-/* Get warning message. */
-const char *tomoyo_get_msg(const bool is_enforce);
-/* Convert single path operation to operation name. */
-const char *tomoyo_path2keyword(const u8 operation);
-/* Create "alias" entry in exception policy. */
-int tomoyo_write_alias_policy(char *data, const bool is_delete);
-/*
- * Create "initialize_domain" and "no_initialize_domain" entry
- * in exception policy.
- */
-int tomoyo_write_domain_initializer_policy(char *data, const bool is_not,
- const bool is_delete);
-/* Create "keep_domain" and "no_keep_domain" entry in exception policy. */
-int tomoyo_write_domain_keeper_policy(char *data, const bool is_not,
- const bool is_delete);
+/* 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",
@@ -604,25 +770,31 @@ int tomoyo_write_domain_keeper_policy(char *data, const bool is_not,
* "allow_truncate", "allow_symlink", "allow_rewrite", "allow_rename" and
* "allow_link" entry in domain policy.
*/
-int tomoyo_write_file_policy(char *data, struct tomoyo_domain_info *domain,
- const bool is_delete);
+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_policy(char *data, const bool is_delete);
+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_policy(char *data, const bool is_delete);
+int tomoyo_write_no_rewrite(char *data, const bool is_delete);
/* Create "file_pattern" entry in exception policy. */
-int tomoyo_write_pattern_policy(char *data, const bool is_delete);
-/* Create "path_group" entry in exception policy. */
-int tomoyo_write_path_group_policy(char *data, const bool is_delete);
+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_find_or_assign_new_domain(const char *
- domainname,
- const u8 profile);
-
-/* Allocate memory for "struct tomoyo_path_group". */
-struct tomoyo_path_group *tomoyo_get_path_group(const char *group_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,
@@ -632,25 +804,23 @@ void tomoyo_fill_path_info(struct tomoyo_path_info *ptr);
/* Run policy loader when /sbin/init starts. */
void tomoyo_load_policy(const char *filename);
-/* Convert binary string to ascii string. */
-int tomoyo_encode(char *buffer, int buflen, const char *str);
+void tomoyo_put_number_union(struct tomoyo_number_union *ptr);
-/* Returns realpath(3) of the given pathname but ignores chroot'ed root. */
-int tomoyo_realpath_from_path2(struct path *path, char *newname,
- int newname_len);
+/* Convert binary string to ascii string. */
+char *tomoyo_encode(const char *str);
/*
- * Returns realpath(3) of the given pathname but ignores chroot'ed root.
- * These functions use kzalloc(), so the caller must call kfree()
- * if these functions didn't return NULL.
+ * Returns realpath(3) of the given pathname except that
+ * ignores chroot'ed root and does not follow the final symlink.
*/
-char *tomoyo_realpath(const char *pathname);
+char *tomoyo_realpath_nofollow(const char *pathname);
/*
- * Same with tomoyo_realpath() except that it doesn't follow the final symlink.
+ * Returns realpath(3) of the given pathname except that
+ * ignores chroot'ed root and the pathname is already solved.
*/
-char *tomoyo_realpath_nofollow(const char *pathname);
-/* Same with tomoyo_realpath() except that 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);
@@ -663,23 +833,29 @@ void *tomoyo_commit_ok(void *data, const unsigned int size);
const struct tomoyo_path_info *tomoyo_get_name(const char *name);
/* Check for memory usage. */
-int tomoyo_read_memory_counter(struct tomoyo_io_buffer *head);
+void tomoyo_read_memory_counter(struct tomoyo_io_buffer *head);
/* Set memory quota. */
int tomoyo_write_memory_quota(struct tomoyo_io_buffer *head);
-/* Initialize realpath related code. */
-void __init tomoyo_realpath_init(void);
-int tomoyo_check_exec_perm(struct tomoyo_domain_info *domain,
+/* 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_check_rewrite_permission(struct file *filp);
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);
@@ -688,6 +864,25 @@ 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. */
@@ -696,14 +891,8 @@ 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_path_group_list;
-extern struct list_head tomoyo_domain_initializer_list;
-extern struct list_head tomoyo_domain_keeper_list;
-extern struct list_head tomoyo_alias_list;
-extern struct list_head tomoyo_globally_readable_list;
-extern struct list_head tomoyo_pattern_list;
-extern struct list_head tomoyo_no_rewrite_list;
-extern struct list_head tomoyo_policy_manager_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. */
@@ -715,6 +904,14 @@ 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)
@@ -735,25 +932,25 @@ static inline bool tomoyo_pathcmp(const struct tomoyo_path_info *a,
}
/**
- * tomoyo_is_valid - Check whether the character is a valid char.
+ * 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_is_valid(const unsigned char c)
+static inline bool tomoyo_valid(const unsigned char c)
{
return c > ' ' && c < 127;
}
/**
- * tomoyo_is_invalid - Check whether the character is an invalid char.
+ * 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_is_invalid(const unsigned char c)
+static inline bool tomoyo_invalid(const unsigned char c)
{
return c && (c <= ' ' || c >= 127);
}
@@ -761,13 +958,13 @@ static inline bool tomoyo_is_invalid(const unsigned char c)
static inline void tomoyo_put_name(const struct tomoyo_path_info *name)
{
if (name) {
- struct tomoyo_name_entry *ptr =
- container_of(name, struct tomoyo_name_entry, entry);
+ struct tomoyo_name *ptr =
+ container_of(name, typeof(*ptr), entry);
atomic_dec(&ptr->users);
}
}
-static inline void tomoyo_put_path_group(struct tomoyo_path_group *group)
+static inline void tomoyo_put_group(struct tomoyo_group *group)
{
if (group)
atomic_dec(&group->users);
@@ -784,75 +981,35 @@ static inline struct tomoyo_domain_info *tomoyo_real_domain(struct task_struct
return task_cred_xxx(task, security);
}
-static inline bool tomoyo_is_same_acl_head(const struct tomoyo_acl_info *p1,
+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_is_same_name_union
+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_is_same_path_acl(const struct tomoyo_path_acl *p1,
- const struct tomoyo_path_acl *p2)
-{
- return tomoyo_is_same_acl_head(&p1->head, &p2->head) &&
- tomoyo_is_same_name_union(&p1->name, &p2->name);
-}
-
-static inline bool tomoyo_is_same_path2_acl(const struct tomoyo_path2_acl *p1,
- const struct tomoyo_path2_acl *p2)
+static inline bool tomoyo_same_number_union
+(const struct tomoyo_number_union *p1, const struct tomoyo_number_union *p2)
{
- return tomoyo_is_same_acl_head(&p1->head, &p2->head) &&
- tomoyo_is_same_name_union(&p1->name1, &p2->name1) &&
- tomoyo_is_same_name_union(&p1->name2, &p2->name2);
-}
-
-static inline bool tomoyo_is_same_domain_initializer_entry
-(const struct tomoyo_domain_initializer_entry *p1,
- const struct tomoyo_domain_initializer_entry *p2)
-{
- return p1->is_not == p2->is_not && p1->is_last_name == p2->is_last_name
- && p1->domainname == p2->domainname
- && p1->program == p2->program;
-}
-
-static inline bool tomoyo_is_same_domain_keeper_entry
-(const struct tomoyo_domain_keeper_entry *p1,
- const struct tomoyo_domain_keeper_entry *p2)
-{
- return p1->is_not == p2->is_not && p1->is_last_name == p2->is_last_name
- && p1->domainname == p2->domainname
- && p1->program == p2->program;
-}
-
-static inline bool tomoyo_is_same_alias_entry
-(const struct tomoyo_alias_entry *p1, const struct tomoyo_alias_entry *p2)
-{
- return p1->original_name == p2->original_name &&
- p1->aliased_name == p2->aliased_name;
+ 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.
- * @cookie: the &struct list_head to use as a cookie.
* @head: the head for your list.
- *
- * Same with list_for_each_rcu() except that this primitive uses @cookie
- * so that we can continue iteration.
- * @cookie must be NULL when iteration starts, and @cookie will become
- * NULL when iteration finishes.
*/
-#define list_for_each_cookie(pos, cookie, head) \
- for (({ if (!cookie) \
- cookie = head; }), \
- pos = rcu_dereference((cookie)->next); \
- prefetch(pos->next), pos != (head) || ((cookie) = NULL); \
- (cookie) = pos, pos = rcu_dereference(pos->next))
+#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
index cd8ba44..3538840 100644
--- a/security/tomoyo/domain.c
+++ b/security/tomoyo/domain.c
@@ -1,12 +1,9 @@
/*
* security/tomoyo/domain.c
*
- * Implementation of the Domain-Based Mandatory Access Control.
- *
- * Copyright (C) 2005-2009 NTT DATA CORPORATION
- *
- * Version: 2.2.0 2009/04/01
+ * Domain transition functions for TOMOYO.
*
+ * Copyright (C) 2005-2010 NTT DATA CORPORATION
*/
#include "common.h"
@@ -18,366 +15,191 @@
/* The initial domain. */
struct tomoyo_domain_info tomoyo_kernel_domain;
-/*
- * tomoyo_domain_list is used for holding list of domains.
- * The ->acl_info_list of "struct tomoyo_domain_info" is used for holding
- * permissions (e.g. "allow_read /lib/libc-2.5.so") given to each domain.
- *
- * An entry is added by
- *
- * # ( echo "<kernel>"; echo "allow_execute /sbin/init" ) > \
- * /sys/kernel/security/tomoyo/domain_policy
- *
- * and is deleted by
- *
- * # ( echo "<kernel>"; echo "delete allow_execute /sbin/init" ) > \
- * /sys/kernel/security/tomoyo/domain_policy
- *
- * and all entries are retrieved by
- *
- * # cat /sys/kernel/security/tomoyo/domain_policy
- *
- * A domain is added by
- *
- * # echo "<kernel>" > /sys/kernel/security/tomoyo/domain_policy
- *
- * and is deleted by
- *
- * # echo "delete <kernel>" > /sys/kernel/security/tomoyo/domain_policy
- *
- * and all domains are retrieved by
- *
- * # grep '^<kernel>' /sys/kernel/security/tomoyo/domain_policy
- *
- * Normally, a domainname is monotonically getting longer because a domainname
- * which the process will belong to if an execve() operation succeeds is
- * defined as a concatenation of "current domainname" + "pathname passed to
- * execve()".
- * See tomoyo_domain_initializer_list and tomoyo_domain_keeper_list for
- * exceptions.
- */
-LIST_HEAD(tomoyo_domain_list);
-
/**
- * tomoyo_get_last_name - Get last component of a domainname.
- *
- * @domain: Pointer to "struct tomoyo_domain_info".
- *
- * Returns the last component of the domainname.
- */
-const char *tomoyo_get_last_name(const struct tomoyo_domain_info *domain)
-{
- const char *cp0 = domain->domainname->name;
- const char *cp1 = strrchr(cp0, ' ');
-
- if (cp1)
- return cp1 + 1;
- return cp0;
-}
-
-/*
- * tomoyo_domain_initializer_list is used for holding list of programs which
- * triggers reinitialization of domainname. Normally, a domainname is
- * monotonically getting longer. But sometimes, we restart daemon programs.
- * It would be convenient for us that "a daemon started upon system boot" and
- * "the daemon restarted from console" belong to the same domain. Thus, TOMOYO
- * provides a way to shorten domainnames.
+ * tomoyo_update_policy - Update an entry for exception policy.
*
- * An entry is added by
- *
- * # echo 'initialize_domain /usr/sbin/httpd' > \
- * /sys/kernel/security/tomoyo/exception_policy
- *
- * and is deleted by
- *
- * # echo 'delete initialize_domain /usr/sbin/httpd' > \
- * /sys/kernel/security/tomoyo/exception_policy
- *
- * and all entries are retrieved by
- *
- * # grep ^initialize_domain /sys/kernel/security/tomoyo/exception_policy
- *
- * In the example above, /usr/sbin/httpd will belong to
- * "<kernel> /usr/sbin/httpd" domain.
- *
- * You may specify a domainname using "from" keyword.
- * "initialize_domain /usr/sbin/httpd from <kernel> /etc/rc.d/init.d/httpd"
- * will cause "/usr/sbin/httpd" executed from "<kernel> /etc/rc.d/init.d/httpd"
- * domain to belong to "<kernel> /usr/sbin/httpd" domain.
- *
- * You may add "no_" prefix to "initialize_domain".
- * "initialize_domain /usr/sbin/httpd" and
- * "no_initialize_domain /usr/sbin/httpd from <kernel> /etc/rc.d/init.d/httpd"
- * will cause "/usr/sbin/httpd" to belong to "<kernel> /usr/sbin/httpd" domain
- * unless executed from "<kernel> /etc/rc.d/init.d/httpd" domain.
- */
-LIST_HEAD(tomoyo_domain_initializer_list);
-
-/**
- * tomoyo_update_domain_initializer_entry - Update "struct tomoyo_domain_initializer_entry" list.
- *
- * @domainname: The name of domain. May be NULL.
- * @program: The name of program.
- * @is_not: True if it is "no_initialize_domain" entry.
- * @is_delete: True if it is a delete request.
+ * @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().
*/
-static int tomoyo_update_domain_initializer_entry(const char *domainname,
- const char *program,
- const bool is_not,
- const bool is_delete)
+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
+ *))
{
- struct tomoyo_domain_initializer_entry *ptr;
- struct tomoyo_domain_initializer_entry e = { .is_not = is_not };
int error = is_delete ? -ENOENT : -ENOMEM;
+ struct tomoyo_acl_head *entry;
- if (!tomoyo_is_correct_path(program, 1, -1, -1))
- return -EINVAL; /* No patterns allowed. */
- if (domainname) {
- if (!tomoyo_is_domain_def(domainname) &&
- tomoyo_is_correct_path(domainname, 1, -1, -1))
- e.is_last_name = true;
- else if (!tomoyo_is_correct_domain(domainname))
- return -EINVAL;
- e.domainname = tomoyo_get_name(domainname);
- if (!e.domainname)
- goto out;
- }
- e.program = tomoyo_get_name(program);
- if (!e.program)
- goto out;
if (mutex_lock_interruptible(&tomoyo_policy_lock))
- goto out;
- list_for_each_entry_rcu(ptr, &tomoyo_domain_initializer_list, list) {
- if (!tomoyo_is_same_domain_initializer_entry(ptr, &e))
+ return -ENOMEM;
+ list_for_each_entry_rcu(entry, list, list) {
+ if (!check_duplicate(entry, new_entry))
continue;
- ptr->is_deleted = is_delete;
+ entry->is_deleted = is_delete;
error = 0;
break;
}
- if (!is_delete && error) {
- struct tomoyo_domain_initializer_entry *entry =
- tomoyo_commit_ok(&e, sizeof(e));
+ if (error && !is_delete) {
+ entry = tomoyo_commit_ok(new_entry, size);
if (entry) {
- list_add_tail_rcu(&entry->list,
- &tomoyo_domain_initializer_list);
+ list_add_tail_rcu(&entry->list, list);
error = 0;
}
}
mutex_unlock(&tomoyo_policy_lock);
- out:
- tomoyo_put_name(e.domainname);
- tomoyo_put_name(e.program);
return error;
}
/**
- * tomoyo_read_domain_initializer_policy - Read "struct tomoyo_domain_initializer_entry" list.
+ * tomoyo_update_domain - Update an entry for domain policy.
*
- * @head: Pointer to "struct tomoyo_io_buffer".
+ * @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 true on success, false otherwise.
+ * Returns 0 on success, negative value otherwise.
*
* Caller holds tomoyo_read_lock().
*/
-bool tomoyo_read_domain_initializer_policy(struct tomoyo_io_buffer *head)
+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))
{
- struct list_head *pos;
- bool done = true;
+ int error = is_delete ? -ENOENT : -ENOMEM;
+ struct tomoyo_acl_info *entry;
- list_for_each_cookie(pos, head->read_var2,
- &tomoyo_domain_initializer_list) {
- const char *no;
- const char *from = "";
- const char *domain = "";
- struct tomoyo_domain_initializer_entry *ptr;
- ptr = list_entry(pos, struct tomoyo_domain_initializer_entry,
- list);
- if (ptr->is_deleted)
+ 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;
- no = ptr->is_not ? "no_" : "";
- if (ptr->domainname) {
- from = " from ";
- domain = ptr->domainname->name;
+ 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;
}
- done = tomoyo_io_printf(head,
- "%s" TOMOYO_KEYWORD_INITIALIZE_DOMAIN
- "%s%s%s\n", no, ptr->program->name,
- from, domain);
- if (!done)
- break;
}
- return done;
+ mutex_unlock(&tomoyo_policy_lock);
+ return error;
}
-/**
- * tomoyo_write_domain_initializer_policy - Write "struct tomoyo_domain_initializer_entry" list.
- *
- * @data: String to parse.
- * @is_not: True if it is "no_initialize_domain" entry.
- * @is_delete: True if it is a delete request.
- *
- * Returns 0 on success, negative value otherwise.
- *
- * Caller holds tomoyo_read_lock().
- */
-int tomoyo_write_domain_initializer_policy(char *data, const bool is_not,
- const bool is_delete)
+void tomoyo_check_acl(struct tomoyo_request_info *r,
+ bool (*check_entry) (struct tomoyo_request_info *,
+ const struct tomoyo_acl_info *))
{
- char *cp = strstr(data, " from ");
+ const struct tomoyo_domain_info *domain = r->domain;
+ struct tomoyo_acl_info *ptr;
- if (cp) {
- *cp = '\0';
- return tomoyo_update_domain_initializer_entry(cp + 6, data,
- is_not,
- is_delete);
+ 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;
+ }
}
- return tomoyo_update_domain_initializer_entry(NULL, data, is_not,
- is_delete);
+ 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_is_domain_initializer - Check whether the given program causes domainname reinitialization.
- *
- * @domainname: The name of domain.
- * @program: The name of program.
- * @last_name: The last component of @domainname.
+ * tomoyo_last_word - Get last component of a domainname.
*
- * Returns true if executing @program reinitializes domain transition,
- * false otherwise.
+ * @domainname: Domainname to check.
*
- * Caller holds tomoyo_read_lock().
+ * Returns the last word of @domainname.
*/
-static bool tomoyo_is_domain_initializer(const struct tomoyo_path_info *
- domainname,
- const struct tomoyo_path_info *program,
- const struct tomoyo_path_info *
- last_name)
+static const char *tomoyo_last_word(const char *name)
{
- struct tomoyo_domain_initializer_entry *ptr;
- bool flag = false;
-
- list_for_each_entry_rcu(ptr, &tomoyo_domain_initializer_list, list) {
- if (ptr->is_deleted)
- continue;
- if (ptr->domainname) {
- if (!ptr->is_last_name) {
- if (ptr->domainname != domainname)
- continue;
- } else {
- if (tomoyo_pathcmp(ptr->domainname, last_name))
- continue;
- }
- }
- if (tomoyo_pathcmp(ptr->program, program))
- continue;
- if (ptr->is_not) {
- flag = false;
- break;
- }
- flag = true;
- }
- return flag;
+ const char *cp = strrchr(name, ' ');
+ if (cp)
+ return cp + 1;
+ return name;
}
-/*
- * tomoyo_domain_keeper_list is used for holding list of domainnames which
- * suppresses domain transition. Normally, a domainname is monotonically
- * getting longer. But sometimes, we want to suppress domain transition.
- * It would be convenient for us that programs executed from a login session
- * belong to the same domain. Thus, TOMOYO provides a way to suppress domain
- * transition.
- *
- * An entry is added by
- *
- * # echo 'keep_domain <kernel> /usr/sbin/sshd /bin/bash' > \
- * /sys/kernel/security/tomoyo/exception_policy
- *
- * and is deleted by
- *
- * # echo 'delete keep_domain <kernel> /usr/sbin/sshd /bin/bash' > \
- * /sys/kernel/security/tomoyo/exception_policy
- *
- * and all entries are retrieved by
- *
- * # grep ^keep_domain /sys/kernel/security/tomoyo/exception_policy
- *
- * In the example above, any process which belongs to
- * "<kernel> /usr/sbin/sshd /bin/bash" domain will remain in that domain,
- * unless explicitly specified by "initialize_domain" or "no_keep_domain".
- *
- * You may specify a program using "from" keyword.
- * "keep_domain /bin/pwd from <kernel> /usr/sbin/sshd /bin/bash"
- * will cause "/bin/pwd" executed from "<kernel> /usr/sbin/sshd /bin/bash"
- * domain to remain in "<kernel> /usr/sbin/sshd /bin/bash" domain.
- *
- * You may add "no_" prefix to "keep_domain".
- * "keep_domain <kernel> /usr/sbin/sshd /bin/bash" and
- * "no_keep_domain /usr/bin/passwd from <kernel> /usr/sbin/sshd /bin/bash" will
- * cause "/usr/bin/passwd" to belong to
- * "<kernel> /usr/sbin/sshd /bin/bash /usr/bin/passwd" domain, unless
- * explicitly specified by "initialize_domain".
- */
-LIST_HEAD(tomoyo_domain_keeper_list);
+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_domain_keeper_entry - Update "struct tomoyo_domain_keeper_entry" list.
+ * tomoyo_update_transition_control_entry - Update "struct tomoyo_transition_control" list.
*
- * @domainname: The name of domain.
- * @program: The name of program. May be NULL.
- * @is_not: True if it is "no_keep_domain" entry.
+ * @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.
- *
- * Caller holds tomoyo_read_lock().
*/
-static int tomoyo_update_domain_keeper_entry(const char *domainname,
- const char *program,
- const bool is_not,
- const bool is_delete)
+static int tomoyo_update_transition_control_entry(const char *domainname,
+ const char *program,
+ const u8 type,
+ const bool is_delete)
{
- struct tomoyo_domain_keeper_entry *ptr;
- struct tomoyo_domain_keeper_entry e = { .is_not = is_not };
+ struct tomoyo_transition_control e = { .type = type };
int error = is_delete ? -ENOENT : -ENOMEM;
-
- if (!tomoyo_is_domain_def(domainname) &&
- tomoyo_is_correct_path(domainname, 1, -1, -1))
- e.is_last_name = true;
- else if (!tomoyo_is_correct_domain(domainname))
- return -EINVAL;
if (program) {
- if (!tomoyo_is_correct_path(program, 1, -1, -1))
+ if (!tomoyo_correct_path(program))
return -EINVAL;
e.program = tomoyo_get_name(program);
if (!e.program)
goto out;
}
- e.domainname = tomoyo_get_name(domainname);
- if (!e.domainname)
- goto out;
- if (mutex_lock_interruptible(&tomoyo_policy_lock))
- goto out;
- list_for_each_entry_rcu(ptr, &tomoyo_domain_keeper_list, list) {
- if (!tomoyo_is_same_domain_keeper_entry(ptr, &e))
- continue;
- ptr->is_deleted = is_delete;
- error = 0;
- break;
- }
- if (!is_delete && error) {
- struct tomoyo_domain_keeper_entry *entry =
- tomoyo_commit_ok(&e, sizeof(e));
- if (entry) {
- list_add_tail_rcu(&entry->list,
- &tomoyo_domain_keeper_list);
- error = 0;
+ 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;
}
- mutex_unlock(&tomoyo_policy_lock);
+ 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);
@@ -385,219 +207,133 @@ static int tomoyo_update_domain_keeper_entry(const char *domainname,
}
/**
- * tomoyo_write_domain_keeper_policy - Write "struct tomoyo_domain_keeper_entry" list.
+ * tomoyo_write_transition_control - Write "struct tomoyo_transition_control" list.
*
* @data: String to parse.
- * @is_not: True if it is "no_keep_domain" entry.
* @is_delete: True if it is a delete request.
+ * @type: Type of this entry.
*
- * Caller holds tomoyo_read_lock().
- */
-int tomoyo_write_domain_keeper_policy(char *data, const bool is_not,
- const bool is_delete)
-{
- char *cp = strstr(data, " from ");
-
- if (cp) {
- *cp = '\0';
- return tomoyo_update_domain_keeper_entry(cp + 6, data, is_not,
- is_delete);
- }
- return tomoyo_update_domain_keeper_entry(data, NULL, is_not, is_delete);
-}
-
-/**
- * tomoyo_read_domain_keeper_policy - Read "struct tomoyo_domain_keeper_entry" list.
- *
- * @head: Pointer to "struct tomoyo_io_buffer".
- *
- * Returns true on success, false otherwise.
- *
- * Caller holds tomoyo_read_lock().
+ * Returns 0 on success, negative value otherwise.
*/
-bool tomoyo_read_domain_keeper_policy(struct tomoyo_io_buffer *head)
+int tomoyo_write_transition_control(char *data, const bool is_delete,
+ const u8 type)
{
- struct list_head *pos;
- bool done = true;
-
- list_for_each_cookie(pos, head->read_var2,
- &tomoyo_domain_keeper_list) {
- struct tomoyo_domain_keeper_entry *ptr;
- const char *no;
- const char *from = "";
- const char *program = "";
-
- ptr = list_entry(pos, struct tomoyo_domain_keeper_entry, list);
- if (ptr->is_deleted)
- continue;
- no = ptr->is_not ? "no_" : "";
- if (ptr->program) {
- from = " from ";
- program = ptr->program->name;
- }
- done = tomoyo_io_printf(head,
- "%s" TOMOYO_KEYWORD_KEEP_DOMAIN
- "%s%s%s\n", no, program, from,
- ptr->domainname->name);
- if (!done)
- break;
+ 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 done;
+ return tomoyo_update_transition_control_entry(domainname, data, type,
+ is_delete);
}
/**
- * tomoyo_is_domain_keeper - Check whether the given program causes domain transition suppression.
+ * tomoyo_transition_type - Get domain transition type.
*
* @domainname: The name of domain.
* @program: The name of program.
- * @last_name: The last component of @domainname.
*
- * Returns true if executing @program supresses domain transition,
- * false otherwise.
+ * 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 bool tomoyo_is_domain_keeper(const struct tomoyo_path_info *domainname,
- const struct tomoyo_path_info *program,
- const struct tomoyo_path_info *last_name)
+static u8 tomoyo_transition_type(const struct tomoyo_path_info *domainname,
+ const struct tomoyo_path_info *program)
{
- struct tomoyo_domain_keeper_entry *ptr;
- bool flag = false;
-
- list_for_each_entry_rcu(ptr, &tomoyo_domain_keeper_list, list) {
- if (ptr->is_deleted)
- continue;
- if (!ptr->is_last_name) {
- if (ptr->domainname != domainname)
+ 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;
- } else {
- if (tomoyo_pathcmp(ptr->domainname, last_name))
+ 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;
}
- if (ptr->program && tomoyo_pathcmp(ptr->program, program))
- continue;
- if (ptr->is_not) {
- flag = false;
- break;
- }
- flag = true;
}
- return flag;
+ done:
+ return type;
}
-/*
- * tomoyo_alias_list is used for holding list of symlink's pathnames which are
- * allowed to be passed to an execve() request. Normally, the domainname which
- * the current process will belong to after execve() succeeds is calculated
- * using dereferenced pathnames. But some programs behave differently depending
- * on the name passed to argv[0]. For busybox, calculating domainname using
- * dereferenced pathnames will cause all programs in the busybox to belong to
- * the same domain. Thus, TOMOYO provides a way to allow use of symlink's
- * pathname for checking execve()'s permission and calculating domainname which
- * the current process will belong to after execve() succeeds.
- *
- * An entry is added by
- *
- * # echo 'alias /bin/busybox /bin/cat' > \
- * /sys/kernel/security/tomoyo/exception_policy
- *
- * and is deleted by
- *
- * # echo 'delete alias /bin/busybox /bin/cat' > \
- * /sys/kernel/security/tomoyo/exception_policy
- *
- * and all entries are retrieved by
- *
- * # grep ^alias /sys/kernel/security/tomoyo/exception_policy
- *
- * In the example above, if /bin/cat is a symlink to /bin/busybox and execution
- * of /bin/cat is requested, permission is checked for /bin/cat rather than
- * /bin/busybox and domainname which the current process will belong to after
- * execve() succeeds is calculated using /bin/cat rather than /bin/busybox .
- */
-LIST_HEAD(tomoyo_alias_list);
+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_alias_entry - Update "struct tomoyo_alias_entry" list.
+ * tomoyo_update_aggregator_entry - Update "struct tomoyo_aggregator" list.
*
- * @original_name: The original program's real name.
- * @aliased_name: The symbolic program's symbolic link's name.
- * @is_delete: True if it is a delete request.
+ * @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_alias_entry(const char *original_name,
- const char *aliased_name,
- const bool is_delete)
+static int tomoyo_update_aggregator_entry(const char *original_name,
+ const char *aggregated_name,
+ const bool is_delete)
{
- struct tomoyo_alias_entry *ptr;
- struct tomoyo_alias_entry e = { };
+ struct tomoyo_aggregator e = { };
int error = is_delete ? -ENOENT : -ENOMEM;
- if (!tomoyo_is_correct_path(original_name, 1, -1, -1) ||
- !tomoyo_is_correct_path(aliased_name, 1, -1, -1))
- return -EINVAL; /* No patterns allowed. */
+ if (!tomoyo_correct_path(original_name) ||
+ !tomoyo_correct_path(aggregated_name))
+ return -EINVAL;
e.original_name = tomoyo_get_name(original_name);
- e.aliased_name = tomoyo_get_name(aliased_name);
- if (!e.original_name || !e.aliased_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;
- if (mutex_lock_interruptible(&tomoyo_policy_lock))
- goto out;
- list_for_each_entry_rcu(ptr, &tomoyo_alias_list, list) {
- if (!tomoyo_is_same_alias_entry(ptr, &e))
- continue;
- ptr->is_deleted = is_delete;
- error = 0;
- break;
- }
- if (!is_delete && error) {
- struct tomoyo_alias_entry *entry =
- tomoyo_commit_ok(&e, sizeof(e));
- if (entry) {
- list_add_tail_rcu(&entry->list, &tomoyo_alias_list);
- error = 0;
- }
- }
- mutex_unlock(&tomoyo_policy_lock);
+ 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.aliased_name);
+ tomoyo_put_name(e.aggregated_name);
return error;
}
/**
- * tomoyo_read_alias_policy - Read "struct tomoyo_alias_entry" list.
- *
- * @head: Pointer to "struct tomoyo_io_buffer".
- *
- * Returns true on success, false otherwise.
- *
- * Caller holds tomoyo_read_lock().
- */
-bool tomoyo_read_alias_policy(struct tomoyo_io_buffer *head)
-{
- struct list_head *pos;
- bool done = true;
-
- list_for_each_cookie(pos, head->read_var2, &tomoyo_alias_list) {
- struct tomoyo_alias_entry *ptr;
-
- ptr = list_entry(pos, struct tomoyo_alias_entry, list);
- if (ptr->is_deleted)
- continue;
- done = tomoyo_io_printf(head, TOMOYO_KEYWORD_ALIAS "%s %s\n",
- ptr->original_name->name,
- ptr->aliased_name->name);
- if (!done)
- break;
- }
- return done;
-}
-
-/**
- * tomoyo_write_alias_policy - Write "struct tomoyo_alias_entry" list.
+ * tomoyo_write_aggregator - Write "struct tomoyo_aggregator" list.
*
* @data: String to parse.
* @is_delete: True if it is a delete request.
@@ -606,18 +342,18 @@ bool tomoyo_read_alias_policy(struct tomoyo_io_buffer *head)
*
* Caller holds tomoyo_read_lock().
*/
-int tomoyo_write_alias_policy(char *data, const bool is_delete)
+int tomoyo_write_aggregator(char *data, const bool is_delete)
{
char *cp = strchr(data, ' ');
if (!cp)
return -EINVAL;
*cp++ = '\0';
- return tomoyo_update_alias_entry(data, cp, is_delete);
+ return tomoyo_update_aggregator_entry(data, cp, is_delete);
}
/**
- * tomoyo_find_or_assign_new_domain - Create a domain.
+ * tomoyo_assign_domain - Create a domain.
*
* @domainname: The name of domain.
* @profile: Profile number to assign if the domain was newly created.
@@ -626,16 +362,15 @@ int tomoyo_write_alias_policy(char *data, const bool is_delete)
*
* Caller holds tomoyo_read_lock().
*/
-struct tomoyo_domain_info *tomoyo_find_or_assign_new_domain(const char *
- domainname,
- const u8 profile)
+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_is_correct_domain(domainname))
+ if (!tomoyo_correct_domain(domainname))
return NULL;
saved_domainname = tomoyo_get_name(domainname);
if (!saved_domainname)
@@ -678,116 +413,118 @@ struct tomoyo_domain_info *tomoyo_find_or_assign_new_domain(const char *
*/
int tomoyo_find_next_domain(struct linux_binprm *bprm)
{
- /*
- * This function assumes that the size of buffer returned by
- * tomoyo_realpath() = TOMOYO_MAX_PATHNAME_LEN.
- */
- struct tomoyo_page_buffer *tmp = kzalloc(sizeof(*tmp), GFP_NOFS);
+ 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 *old_domain_name = old_domain->domainname->name;
const char *original_name = bprm->filename;
- char *new_domain_name = NULL;
- char *real_program_name = NULL;
- char *symlink_program_name = NULL;
- const u8 mode = tomoyo_check_flags(old_domain, TOMOYO_MAC_FOR_FILE);
- const bool is_enforce = (mode == 3);
+ u8 mode;
+ bool is_enforce;
int retval = -ENOMEM;
- struct tomoyo_path_info r; /* real name */
- struct tomoyo_path_info s; /* symlink name */
- struct tomoyo_path_info l; /* last name */
- static bool initialized;
+ 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;
- if (!initialized) {
- /*
- * Built-in initializers. This is needed because policies are
- * not loaded until starting /sbin/init.
- */
- tomoyo_update_domain_initializer_entry(NULL, "/sbin/hotplug",
- false, false);
- tomoyo_update_domain_initializer_entry(NULL, "/sbin/modprobe",
- false, false);
- initialized = true;
+ retry:
+ if (need_kfree) {
+ kfree(rn.name);
+ need_kfree = false;
}
-
- /* Get tomoyo_realpath of program. */
+ /* Get symlink's pathname of program. */
retval = -ENOENT;
- /* I hope tomoyo_realpath() won't fail with -ENOMEM. */
- real_program_name = tomoyo_realpath(original_name);
- if (!real_program_name)
- goto out;
- /* Get tomoyo_realpath of symbolic link. */
- symlink_program_name = tomoyo_realpath_nofollow(original_name);
- if (!symlink_program_name)
+ rn.name = tomoyo_realpath_nofollow(original_name);
+ if (!rn.name)
goto out;
-
- r.name = real_program_name;
- tomoyo_fill_path_info(&r);
- s.name = symlink_program_name;
- tomoyo_fill_path_info(&s);
- l.name = tomoyo_get_last_name(old_domain);
- tomoyo_fill_path_info(&l);
-
- /* Check 'alias' directive. */
- if (tomoyo_pathcmp(&r, &s)) {
- struct tomoyo_alias_entry *ptr;
- /* Is this program allowed to be called via symbolic links? */
- list_for_each_entry_rcu(ptr, &tomoyo_alias_list, list) {
- if (ptr->is_deleted ||
- tomoyo_pathcmp(&r, ptr->original_name) ||
- tomoyo_pathcmp(&s, ptr->aliased_name))
+ 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;
- memset(real_program_name, 0, TOMOYO_MAX_PATHNAME_LEN);
- strncpy(real_program_name, ptr->aliased_name->name,
- TOMOYO_MAX_PATHNAME_LEN - 1);
- tomoyo_fill_path_info(&r);
+ kfree(rn.name);
+ need_kfree = false;
+ /* This is OK because it is read only. */
+ rn = *ptr->aggregated_name;
break;
}
}
/* Check execute permission. */
- retval = tomoyo_check_exec_perm(old_domain, &r);
+ 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;
+ }
- new_domain_name = tmp->buffer;
- if (tomoyo_is_domain_initializer(old_domain->domainname, &r, &l)) {
+ /* 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(new_domain_name, TOMOYO_MAX_PATHNAME_LEN + 1,
- TOMOYO_ROOT_NAME " " "%s", real_program_name);
- } else 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 if (tomoyo_is_domain_keeper(old_domain->domainname, &r, &l)) {
+ snprintf(tmp, TOMOYO_EXEC_TMPSIZE - 1, TOMOYO_ROOT_NAME " "
+ "%s", rn.name);
+ break;
+ case TOMOYO_TRANSITION_CONTROL_KEEP:
/* Keep current domain. */
domain = old_domain;
- } else {
- /* Normal domain transition. */
- snprintf(new_domain_name, TOMOYO_MAX_PATHNAME_LEN + 1,
- "%s %s", old_domain_name, real_program_name);
+ 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(new_domain_name) >= TOMOYO_MAX_PATHNAME_LEN)
+ if (domain || strlen(tmp) >= TOMOYO_EXEC_TMPSIZE - 10)
goto done;
- domain = tomoyo_find_domain(new_domain_name);
+ domain = tomoyo_find_domain(tmp);
if (domain)
goto done;
- if (is_enforce)
- goto done;
- domain = tomoyo_find_or_assign_new_domain(new_domain_name,
- old_domain->profile);
+ 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",
- new_domain_name);
+ printk(KERN_WARNING "TOMOYO-ERROR: Domain '%s' not defined.\n", tmp);
if (is_enforce)
retval = -EPERM;
else
@@ -798,8 +535,8 @@ int tomoyo_find_next_domain(struct linux_binprm *bprm)
/* Update reference count on "struct tomoyo_domain_info". */
atomic_inc(&domain->users);
bprm->cred->security = domain;
- kfree(real_program_name);
- kfree(symlink_program_name);
+ if (need_kfree)
+ kfree(rn.name);
kfree(tmp);
return retval;
}
diff --git a/security/tomoyo/file.c b/security/tomoyo/file.c
index 1c6f823..9d32f18 100644
--- a/security/tomoyo/file.c
+++ b/security/tomoyo/file.c
@@ -1,48 +1,88 @@
/*
* security/tomoyo/file.c
*
- * Implementation of the Domain-Based Mandatory Access Control.
- *
- * Copyright (C) 2005-2009 NTT DATA CORPORATION
- *
- * Version: 2.2.0 2009/04/01
+ * Pathname restriction functions.
*
+ * Copyright (C) 2005-2010 NTT DATA CORPORATION
*/
#include "common.h"
#include <linux/slab.h>
-/* Keyword array for single path operations. */
-static const char *tomoyo_path_keyword[TOMOYO_MAX_PATH_OPERATION] = {
+/* 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_CREATE] = "create",
[TOMOYO_TYPE_UNLINK] = "unlink",
- [TOMOYO_TYPE_MKDIR] = "mkdir",
[TOMOYO_TYPE_RMDIR] = "rmdir",
- [TOMOYO_TYPE_MKFIFO] = "mkfifo",
- [TOMOYO_TYPE_MKSOCK] = "mksock",
- [TOMOYO_TYPE_MKBLOCK] = "mkblock",
- [TOMOYO_TYPE_MKCHAR] = "mkchar",
[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",
- [TOMOYO_TYPE_CHROOT] = "chroot",
- [TOMOYO_TYPE_MOUNT] = "mount",
- [TOMOYO_TYPE_UMOUNT] = "unmount",
};
-/* Keyword array for double path operations. */
-static const char *tomoyo_path2_keyword[TOMOYO_MAX_PATH2_OPERATION] = {
- [TOMOYO_TYPE_LINK] = "link",
- [TOMOYO_TYPE_RENAME] = "rename",
- [TOMOYO_TYPE_PIVOT_ROOT] = "pivot_root",
+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)
@@ -50,56 +90,45 @@ void tomoyo_put_name_union(struct tomoyo_name_union *ptr)
if (!ptr)
return;
if (ptr->is_group)
- tomoyo_put_path_group(ptr->group);
+ tomoyo_put_group(ptr->group);
else
tomoyo_put_name(ptr->filename);
}
-bool tomoyo_compare_name_union(const struct tomoyo_path_info *name,
- const struct tomoyo_name_union *ptr)
+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, 1);
- return tomoyo_path_matches_pattern(name, ptr->filename);
+ return tomoyo_path_matches_group(name, ptr->group);
+ if (tomoyo_path_matches_pattern(name, ptr->filename))
+ return ptr->filename;
+ return NULL;
}
-static bool tomoyo_compare_name_union_pattern(const struct tomoyo_path_info
- *name,
- const struct tomoyo_name_union
- *ptr, const bool may_use_pattern)
+void tomoyo_put_number_union(struct tomoyo_number_union *ptr)
{
- if (ptr->is_group)
- return tomoyo_path_matches_group(name, ptr->group,
- may_use_pattern);
- if (may_use_pattern || !ptr->filename->is_patterned)
- return tomoyo_path_matches_pattern(name, ptr->filename);
- return false;
+ if (ptr && ptr->is_group)
+ tomoyo_put_group(ptr->group);
}
-/**
- * tomoyo_path2keyword - Get the name of single path operation.
- *
- * @operation: Type of operation.
- *
- * Returns the name of single path operation.
- */
-const char *tomoyo_path2keyword(const u8 operation)
+bool tomoyo_compare_number_union(const unsigned long value,
+ const struct tomoyo_number_union *ptr)
{
- return (operation < TOMOYO_MAX_PATH_OPERATION)
- ? tomoyo_path_keyword[operation] : NULL;
+ if (ptr->is_group)
+ return tomoyo_number_matches_group(value, value, ptr->group);
+ return value >= ptr->values[0] && value <= ptr->values[1];
}
-/**
- * tomoyo_path22keyword - Get the name of double path operation.
- *
- * @operation: Type of operation.
- *
- * Returns the name of double path operation.
- */
-const char *tomoyo_path22keyword(const u8 operation)
+static void tomoyo_add_slash(struct tomoyo_path_info *buf)
{
- return (operation < TOMOYO_MAX_PATH2_OPERATION)
- ? tomoyo_path2_keyword[operation] : NULL;
+ if (buf->is_dir)
+ return;
+ /*
+ * This is OK because tomoyo_encode() reserves space for appending "/".
+ */
+ strcat((char *) buf->name, "/");
+ tomoyo_fill_path_info(buf);
}
/**
@@ -121,69 +150,134 @@ static bool tomoyo_strendswith(const char *name, const char *tail)
}
/**
- * tomoyo_get_path - Get realpath.
+ * tomoyo_get_realpath - Get realpath.
*
+ * @buf: Pointer to "struct tomoyo_path_info".
* @path: Pointer to "struct path".
*
- * Returns pointer to "struct tomoyo_path_info" on success, NULL otherwise.
+ * Returns true on success, false otherwise.
*/
-static struct tomoyo_path_info *tomoyo_get_path(struct path *path)
+static bool tomoyo_get_realpath(struct tomoyo_path_info *buf, struct path *path)
{
- int error;
- struct tomoyo_path_info_with_data *buf = kzalloc(sizeof(*buf),
- GFP_NOFS);
-
- if (!buf)
- return NULL;
- /* Reserve one byte for appending "/". */
- error = tomoyo_realpath_from_path2(path, buf->body,
- sizeof(buf->body) - 2);
- if (!error) {
- buf->head.name = buf->body;
- tomoyo_fill_path_info(&buf->head);
- return &buf->head;
+ buf->name = tomoyo_realpath_from_path(path);
+ if (buf->name) {
+ tomoyo_fill_path_info(buf);
+ return true;
}
- kfree(buf);
- return NULL;
+ return false;
}
-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);
-static int tomoyo_update_path_acl(const u8 type, const char *filename,
- struct tomoyo_domain_info *const domain,
- const bool is_delete);
-
-/*
- * tomoyo_globally_readable_list is used for holding list of pathnames which
- * are by default allowed to be open()ed for reading by any process.
+/**
+ * tomoyo_audit_path_log - Audit path request log.
+ *
+ * @r: Pointer to "struct tomoyo_request_info".
*
- * An entry is added by
+ * 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.
*
- * # echo 'allow_read /lib/libc-2.5.so' > \
- * /sys/kernel/security/tomoyo/exception_policy
+ * @r: Pointer to "struct tomoyo_request_info".
*
- * and is deleted by
+ * 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.
*
- * # echo 'delete allow_read /lib/libc-2.5.so' > \
- * /sys/kernel/security/tomoyo/exception_policy
+ * @r: Pointer to "struct tomoyo_request_info".
*
- * and all entries are retrieved by
+ * 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.
*
- * # grep ^allow_read /sys/kernel/security/tomoyo/exception_policy
+ * @r: Pointer to "struct tomoyo_request_info".
+ * @error: Error code.
*
- * In the example above, any process is allowed to
- * open("/lib/libc-2.5.so", O_RDONLY).
- * One exception is, if the domain which current process belongs to is marked
- * as "ignore_global_allow_read", current process can't do so unless explicitly
- * given "allow_read /lib/libc-2.5.so" to the domain which current process
- * belongs to.
+ * Returns 0 on success, negative value otherwise.
*/
-LIST_HEAD(tomoyo_globally_readable_list);
+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_globally_readable_file_entry" list.
+ * 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.
@@ -195,41 +289,24 @@ LIST_HEAD(tomoyo_globally_readable_list);
static int tomoyo_update_globally_readable_entry(const char *filename,
const bool is_delete)
{
- struct tomoyo_globally_readable_file_entry *ptr;
- struct tomoyo_globally_readable_file_entry e = { };
- int error = is_delete ? -ENOENT : -ENOMEM;
+ struct tomoyo_readable_file e = { };
+ int error;
- if (!tomoyo_is_correct_path(filename, 1, 0, -1))
+ if (!tomoyo_correct_word(filename))
return -EINVAL;
e.filename = tomoyo_get_name(filename);
if (!e.filename)
return -ENOMEM;
- if (mutex_lock_interruptible(&tomoyo_policy_lock))
- goto out;
- list_for_each_entry_rcu(ptr, &tomoyo_globally_readable_list, list) {
- if (ptr->filename != e.filename)
- continue;
- ptr->is_deleted = is_delete;
- error = 0;
- break;
- }
- if (!is_delete && error) {
- struct tomoyo_globally_readable_file_entry *entry =
- tomoyo_commit_ok(&e, sizeof(e));
- if (entry) {
- list_add_tail_rcu(&entry->list,
- &tomoyo_globally_readable_list);
- error = 0;
- }
- }
- mutex_unlock(&tomoyo_policy_lock);
- out:
+ 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_is_globally_readable_file - Check if the file is unconditionnaly permitted to be open()ed for reading.
+ * tomoyo_globally_readable_file - Check if the file is unconditionnaly permitted to be open()ed for reading.
*
* @filename: The filename to check.
*
@@ -237,14 +314,15 @@ static int tomoyo_update_globally_readable_entry(const char *filename,
*
* Caller holds tomoyo_read_lock().
*/
-static bool tomoyo_is_globally_readable_file(const struct tomoyo_path_info *
+static bool tomoyo_globally_readable_file(const struct tomoyo_path_info *
filename)
{
- struct tomoyo_globally_readable_file_entry *ptr;
+ struct tomoyo_readable_file *ptr;
bool found = false;
- list_for_each_entry_rcu(ptr, &tomoyo_globally_readable_list, list) {
- if (!ptr->is_deleted &&
+ 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;
@@ -254,7 +332,7 @@ static bool tomoyo_is_globally_readable_file(const struct tomoyo_path_info *
}
/**
- * tomoyo_write_globally_readable_policy - Write "struct tomoyo_globally_readable_file_entry" list.
+ * tomoyo_write_globally_readable - Write "struct tomoyo_readable_file" list.
*
* @data: String to parse.
* @is_delete: True if it is a delete request.
@@ -263,74 +341,20 @@ static bool tomoyo_is_globally_readable_file(const struct tomoyo_path_info *
*
* Caller holds tomoyo_read_lock().
*/
-int tomoyo_write_globally_readable_policy(char *data, const bool is_delete)
+int tomoyo_write_globally_readable(char *data, const bool is_delete)
{
return tomoyo_update_globally_readable_entry(data, is_delete);
}
-/**
- * tomoyo_read_globally_readable_policy - Read "struct tomoyo_globally_readable_file_entry" list.
- *
- * @head: Pointer to "struct tomoyo_io_buffer".
- *
- * Returns true on success, false otherwise.
- *
- * Caller holds tomoyo_read_lock().
- */
-bool tomoyo_read_globally_readable_policy(struct tomoyo_io_buffer *head)
+static bool tomoyo_same_pattern(const struct tomoyo_acl_head *a,
+ const struct tomoyo_acl_head *b)
{
- struct list_head *pos;
- bool done = true;
-
- list_for_each_cookie(pos, head->read_var2,
- &tomoyo_globally_readable_list) {
- struct tomoyo_globally_readable_file_entry *ptr;
- ptr = list_entry(pos,
- struct tomoyo_globally_readable_file_entry,
- list);
- if (ptr->is_deleted)
- continue;
- done = tomoyo_io_printf(head, TOMOYO_KEYWORD_ALLOW_READ "%s\n",
- ptr->filename->name);
- if (!done)
- break;
- }
- return done;
+ return container_of(a, struct tomoyo_no_pattern, head)->pattern ==
+ container_of(b, struct tomoyo_no_pattern, head)->pattern;
}
-/* tomoyo_pattern_list is used for holding list of pathnames which are used for
- * converting pathnames to pathname patterns during learning mode.
- *
- * An entry is added by
- *
- * # echo 'file_pattern /proc/\$/mounts' > \
- * /sys/kernel/security/tomoyo/exception_policy
- *
- * and is deleted by
- *
- * # echo 'delete file_pattern /proc/\$/mounts' > \
- * /sys/kernel/security/tomoyo/exception_policy
- *
- * and all entries are retrieved by
- *
- * # grep ^file_pattern /sys/kernel/security/tomoyo/exception_policy
- *
- * In the example above, if a process which belongs to a domain which is in
- * learning mode requested open("/proc/1/mounts", O_RDONLY),
- * "allow_read /proc/\$/mounts" is automatically added to the domain which that
- * process belongs to.
- *
- * It is not a desirable behavior that we have to use /proc/\$/ instead of
- * /proc/self/ when current process needs to access only current process's
- * information. As of now, LSM version of TOMOYO is using __d_path() for
- * calculating pathname. Non LSM version of TOMOYO is using its own function
- * which pretends as if /proc/self/ is not a symlink; so that we can forbid
- * current process from accessing other process's information.
- */
-LIST_HEAD(tomoyo_pattern_list);
-
/**
- * tomoyo_update_file_pattern_entry - Update "struct tomoyo_pattern_entry" list.
+ * tomoyo_update_file_pattern_entry - Update "struct tomoyo_no_pattern" list.
*
* @pattern: Pathname pattern.
* @is_delete: True if it is a delete request.
@@ -342,39 +366,23 @@ LIST_HEAD(tomoyo_pattern_list);
static int tomoyo_update_file_pattern_entry(const char *pattern,
const bool is_delete)
{
- struct tomoyo_pattern_entry *ptr;
- struct tomoyo_pattern_entry e = { .pattern = tomoyo_get_name(pattern) };
- int error = is_delete ? -ENOENT : -ENOMEM;
+ struct tomoyo_no_pattern e = { };
+ int error;
+ if (!tomoyo_correct_word(pattern))
+ return -EINVAL;
+ e.pattern = tomoyo_get_name(pattern);
if (!e.pattern)
- return error;
- if (!e.pattern->is_patterned)
- goto out;
- if (mutex_lock_interruptible(&tomoyo_policy_lock))
- goto out;
- list_for_each_entry_rcu(ptr, &tomoyo_pattern_list, list) {
- if (e.pattern != ptr->pattern)
- continue;
- ptr->is_deleted = is_delete;
- error = 0;
- break;
- }
- if (!is_delete && error) {
- struct tomoyo_pattern_entry *entry =
- tomoyo_commit_ok(&e, sizeof(e));
- if (entry) {
- list_add_tail_rcu(&entry->list, &tomoyo_pattern_list);
- error = 0;
- }
- }
- mutex_unlock(&tomoyo_policy_lock);
- out:
+ 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_get_file_pattern - Get patterned pathname.
+ * tomoyo_pattern - Get patterned pathname.
*
* @filename: The filename to find patterned pathname.
*
@@ -382,14 +390,14 @@ static int tomoyo_update_file_pattern_entry(const char *pattern,
*
* Caller holds tomoyo_read_lock().
*/
-static const struct tomoyo_path_info *
-tomoyo_get_file_pattern(const struct tomoyo_path_info *filename)
+const char *tomoyo_pattern(const struct tomoyo_path_info *filename)
{
- struct tomoyo_pattern_entry *ptr;
+ struct tomoyo_no_pattern *ptr;
const struct tomoyo_path_info *pattern = NULL;
- list_for_each_entry_rcu(ptr, &tomoyo_pattern_list, list) {
- if (ptr->is_deleted)
+ 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;
@@ -403,11 +411,11 @@ tomoyo_get_file_pattern(const struct tomoyo_path_info *filename)
}
if (pattern)
filename = pattern;
- return filename;
+ return filename->name;
}
/**
- * tomoyo_write_pattern_policy - Write "struct tomoyo_pattern_entry" list.
+ * tomoyo_write_pattern - Write "struct tomoyo_no_pattern" list.
*
* @data: String to parse.
* @is_delete: True if it is a delete request.
@@ -416,71 +424,21 @@ tomoyo_get_file_pattern(const struct tomoyo_path_info *filename)
*
* Caller holds tomoyo_read_lock().
*/
-int tomoyo_write_pattern_policy(char *data, const bool is_delete)
+int tomoyo_write_pattern(char *data, const bool is_delete)
{
return tomoyo_update_file_pattern_entry(data, is_delete);
}
-/**
- * tomoyo_read_file_pattern - Read "struct tomoyo_pattern_entry" list.
- *
- * @head: Pointer to "struct tomoyo_io_buffer".
- *
- * Returns true on success, false otherwise.
- *
- * Caller holds tomoyo_read_lock().
- */
-bool tomoyo_read_file_pattern(struct tomoyo_io_buffer *head)
+static bool tomoyo_same_no_rewrite(const struct tomoyo_acl_head *a,
+ const struct tomoyo_acl_head *b)
{
- struct list_head *pos;
- bool done = true;
-
- list_for_each_cookie(pos, head->read_var2, &tomoyo_pattern_list) {
- struct tomoyo_pattern_entry *ptr;
- ptr = list_entry(pos, struct tomoyo_pattern_entry, list);
- if (ptr->is_deleted)
- continue;
- done = tomoyo_io_printf(head, TOMOYO_KEYWORD_FILE_PATTERN
- "%s\n", ptr->pattern->name);
- if (!done)
- break;
- }
- return done;
+ return container_of(a, struct tomoyo_no_rewrite, head)->pattern
+ == container_of(b, struct tomoyo_no_rewrite, head)
+ ->pattern;
}
-/*
- * tomoyo_no_rewrite_list is used for holding list of pathnames which are by
- * default forbidden to modify already written content of a file.
- *
- * An entry is added by
- *
- * # echo 'deny_rewrite /var/log/messages' > \
- * /sys/kernel/security/tomoyo/exception_policy
- *
- * and is deleted by
- *
- * # echo 'delete deny_rewrite /var/log/messages' > \
- * /sys/kernel/security/tomoyo/exception_policy
- *
- * and all entries are retrieved by
- *
- * # grep ^deny_rewrite /sys/kernel/security/tomoyo/exception_policy
- *
- * In the example above, if a process requested to rewrite /var/log/messages ,
- * the process can't rewrite unless the domain which that process belongs to
- * has "allow_rewrite /var/log/messages" entry.
- *
- * It is not a desirable behavior that we have to add "\040(deleted)" suffix
- * when we want to allow rewriting already unlink()ed file. As of now,
- * LSM version of TOMOYO is using __d_path() for calculating pathname.
- * Non LSM version of TOMOYO is using its own function which doesn't append
- * " (deleted)" suffix if the file is already unlink()ed; so that we don't
- * need to worry whether the file is already unlink()ed or not.
- */
-LIST_HEAD(tomoyo_no_rewrite_list);
-
/**
- * tomoyo_update_no_rewrite_entry - Update "struct tomoyo_no_rewrite_entry" list.
+ * 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.
@@ -492,41 +450,23 @@ LIST_HEAD(tomoyo_no_rewrite_list);
static int tomoyo_update_no_rewrite_entry(const char *pattern,
const bool is_delete)
{
- struct tomoyo_no_rewrite_entry *ptr;
- struct tomoyo_no_rewrite_entry e = { };
- int error = is_delete ? -ENOENT : -ENOMEM;
+ struct tomoyo_no_rewrite e = { };
+ int error;
- if (!tomoyo_is_correct_path(pattern, 0, 0, 0))
+ if (!tomoyo_correct_word(pattern))
return -EINVAL;
e.pattern = tomoyo_get_name(pattern);
if (!e.pattern)
- return error;
- if (mutex_lock_interruptible(&tomoyo_policy_lock))
- goto out;
- list_for_each_entry_rcu(ptr, &tomoyo_no_rewrite_list, list) {
- if (ptr->pattern != e.pattern)
- continue;
- ptr->is_deleted = is_delete;
- error = 0;
- break;
- }
- if (!is_delete && error) {
- struct tomoyo_no_rewrite_entry *entry =
- tomoyo_commit_ok(&e, sizeof(e));
- if (entry) {
- list_add_tail_rcu(&entry->list,
- &tomoyo_no_rewrite_list);
- error = 0;
- }
- }
- mutex_unlock(&tomoyo_policy_lock);
- out:
+ 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_is_no_rewrite_file - Check if the given pathname is not permitted to be rewrited.
+ * tomoyo_no_rewrite_file - Check if the given pathname is not permitted to be rewrited.
*
* @filename: Filename to check.
*
@@ -535,13 +475,14 @@ static int tomoyo_update_no_rewrite_entry(const char *pattern,
*
* Caller holds tomoyo_read_lock().
*/
-static bool tomoyo_is_no_rewrite_file(const struct tomoyo_path_info *filename)
+static bool tomoyo_no_rewrite_file(const struct tomoyo_path_info *filename)
{
- struct tomoyo_no_rewrite_entry *ptr;
+ struct tomoyo_no_rewrite *ptr;
bool found = false;
- list_for_each_entry_rcu(ptr, &tomoyo_no_rewrite_list, list) {
- if (ptr->is_deleted)
+ 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;
@@ -552,7 +493,7 @@ static bool tomoyo_is_no_rewrite_file(const struct tomoyo_path_info *filename)
}
/**
- * tomoyo_write_no_rewrite_policy - Write "struct tomoyo_no_rewrite_entry" list.
+ * tomoyo_write_no_rewrite - Write "struct tomoyo_no_rewrite" list.
*
* @data: String to parse.
* @is_delete: True if it is a delete request.
@@ -561,214 +502,103 @@ static bool tomoyo_is_no_rewrite_file(const struct tomoyo_path_info *filename)
*
* Caller holds tomoyo_read_lock().
*/
-int tomoyo_write_no_rewrite_policy(char *data, const bool is_delete)
+int tomoyo_write_no_rewrite(char *data, const bool is_delete)
{
return tomoyo_update_no_rewrite_entry(data, is_delete);
}
-/**
- * tomoyo_read_no_rewrite_policy - Read "struct tomoyo_no_rewrite_entry" list.
- *
- * @head: Pointer to "struct tomoyo_io_buffer".
- *
- * Returns true on success, false otherwise.
- *
- * Caller holds tomoyo_read_lock().
- */
-bool tomoyo_read_no_rewrite_policy(struct tomoyo_io_buffer *head)
+static bool tomoyo_check_path_acl(struct tomoyo_request_info *r,
+ const struct tomoyo_acl_info *ptr)
{
- struct list_head *pos;
- bool done = true;
-
- list_for_each_cookie(pos, head->read_var2, &tomoyo_no_rewrite_list) {
- struct tomoyo_no_rewrite_entry *ptr;
- ptr = list_entry(pos, struct tomoyo_no_rewrite_entry, list);
- if (ptr->is_deleted)
- continue;
- done = tomoyo_io_printf(head, TOMOYO_KEYWORD_DENY_REWRITE
- "%s\n", ptr->pattern->name);
- if (!done)
- break;
+ 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 done;
+ return false;
}
-/**
- * tomoyo_update_file_acl - Update file's read/write/execute ACL.
- *
- * @filename: Filename.
- * @perm: Permission (between 1 to 7).
- * @domain: Pointer to "struct tomoyo_domain_info".
- * @is_delete: True if it is a delete request.
- *
- * Returns 0 on success, negative value otherwise.
- *
- * This is legacy support interface for older policy syntax.
- * Current policy syntax uses "allow_read/write" instead of "6",
- * "allow_read" instead of "4", "allow_write" instead of "2",
- * "allow_execute" instead of "1".
- *
- * Caller holds tomoyo_read_lock().
- */
-static int tomoyo_update_file_acl(const char *filename, u8 perm,
- struct tomoyo_domain_info * const domain,
- const bool is_delete)
+static bool tomoyo_check_path_number_acl(struct tomoyo_request_info *r,
+ const struct tomoyo_acl_info *ptr)
{
- if (perm > 7 || !perm) {
- printk(KERN_DEBUG "%s: Invalid permission '%d %s'\n",
- __func__, perm, filename);
- return -EINVAL;
- }
- if (filename[0] != '@' && tomoyo_strendswith(filename, "/"))
- /*
- * Only 'allow_mkdir' and 'allow_rmdir' are valid for
- * directory permissions.
- */
- return 0;
- if (perm & 4)
- tomoyo_update_path_acl(TOMOYO_TYPE_READ, filename, domain,
- is_delete);
- if (perm & 2)
- tomoyo_update_path_acl(TOMOYO_TYPE_WRITE, filename, domain,
- is_delete);
- if (perm & 1)
- tomoyo_update_path_acl(TOMOYO_TYPE_EXECUTE, filename, domain,
- is_delete);
- return 0;
+ 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);
}
-/**
- * tomoyo_path_acl2 - Check permission for single path operation.
- *
- * @domain: Pointer to "struct tomoyo_domain_info".
- * @filename: Filename to check.
- * @perm: Permission.
- * @may_use_pattern: True if patterned ACL is permitted.
- *
- * Returns 0 on success, -EPERM otherwise.
- *
- * Caller holds tomoyo_read_lock().
- */
-static int tomoyo_path_acl2(const struct tomoyo_domain_info *domain,
- const struct tomoyo_path_info *filename,
- const u32 perm, const bool may_use_pattern)
+static bool tomoyo_check_path2_acl(struct tomoyo_request_info *r,
+ const struct tomoyo_acl_info *ptr)
{
- struct tomoyo_acl_info *ptr;
- int error = -EPERM;
-
- list_for_each_entry_rcu(ptr, &domain->acl_info_list, list) {
- struct tomoyo_path_acl *acl;
- if (ptr->type != TOMOYO_TYPE_PATH_ACL)
- continue;
- acl = container_of(ptr, struct tomoyo_path_acl, head);
- if (perm <= 0xFFFF) {
- if (!(acl->perm & perm))
- continue;
- } else {
- if (!(acl->perm_high & (perm >> 16)))
- continue;
- }
- if (!tomoyo_compare_name_union_pattern(filename, &acl->name,
- may_use_pattern))
- continue;
- error = 0;
- break;
- }
- return error;
+ 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);
}
-/**
- * tomoyo_check_file_acl - Check permission for opening files.
- *
- * @domain: Pointer to "struct tomoyo_domain_info".
- * @filename: Filename to check.
- * @operation: Mode ("read" or "write" or "read/write" or "execute").
- *
- * Returns 0 on success, -EPERM otherwise.
- *
- * Caller holds tomoyo_read_lock().
- */
-static int tomoyo_check_file_acl(const struct tomoyo_domain_info *domain,
- const struct tomoyo_path_info *filename,
- const u8 operation)
+static bool tomoyo_check_mkdev_acl(struct tomoyo_request_info *r,
+ const struct tomoyo_acl_info *ptr)
{
- u32 perm = 0;
-
- if (!tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE))
- return 0;
- if (operation == 6)
- perm = 1 << TOMOYO_TYPE_READ_WRITE;
- else if (operation == 4)
- perm = 1 << TOMOYO_TYPE_READ;
- else if (operation == 2)
- perm = 1 << TOMOYO_TYPE_WRITE;
- else if (operation == 1)
- perm = 1 << TOMOYO_TYPE_EXECUTE;
- else
- BUG();
- return tomoyo_path_acl2(domain, filename, perm, operation != 1);
+ 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);
}
-/**
- * tomoyo_check_file_perm2 - Check permission for opening files.
- *
- * @domain: Pointer to "struct tomoyo_domain_info".
- * @filename: Filename to check.
- * @perm: Mode ("read" or "write" or "read/write" or "execute").
- * @operation: Operation name passed used for verbose mode.
- * @mode: Access control mode.
- *
- * Returns 0 on success, negative value otherwise.
- *
- * Caller holds tomoyo_read_lock().
- */
-static int tomoyo_check_file_perm2(struct tomoyo_domain_info * const domain,
- const struct tomoyo_path_info *filename,
- const u8 perm, const char *operation,
- const u8 mode)
+static bool tomoyo_same_path_acl(const struct tomoyo_acl_info *a,
+ const struct tomoyo_acl_info *b)
{
- const bool is_enforce = (mode == 3);
- const char *msg = "<unknown>";
- int error = 0;
+ 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);
+}
- if (!filename)
- return 0;
- error = tomoyo_check_file_acl(domain, filename, perm);
- if (error && perm == 4 && !domain->ignore_global_allow_read
- && tomoyo_is_globally_readable_file(filename))
- error = 0;
- if (perm == 6)
- msg = tomoyo_path2keyword(TOMOYO_TYPE_READ_WRITE);
- else if (perm == 4)
- msg = tomoyo_path2keyword(TOMOYO_TYPE_READ);
- else if (perm == 2)
- msg = tomoyo_path2keyword(TOMOYO_TYPE_WRITE);
- else if (perm == 1)
- msg = tomoyo_path2keyword(TOMOYO_TYPE_EXECUTE);
- else
- BUG();
- if (!error)
- return 0;
- if (tomoyo_verbose_mode(domain))
- printk(KERN_WARNING "TOMOYO-%s: Access '%s(%s) %s' denied "
- "for %s\n", tomoyo_get_msg(is_enforce), msg, operation,
- filename->name, tomoyo_get_last_name(domain));
- if (is_enforce)
- return error;
- if (mode == 1 && tomoyo_domain_quota_is_ok(domain)) {
- /* Don't use patterns for execute permission. */
- const struct tomoyo_path_info *patterned_file = (perm != 1) ?
- tomoyo_get_file_pattern(filename) : filename;
- tomoyo_update_file_acl(patterned_file->name, perm,
- domain, false);
+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;
}
- return 0;
+ *a_perm = perm;
+ return !perm;
}
/**
- * tomoyo_write_file_policy - Update file related list.
+ * tomoyo_update_path_acl - Update "struct tomoyo_path_acl" list.
*
- * @data: String to parse.
+ * @type: Type of operation.
+ * @filename: Filename.
* @domain: Pointer to "struct tomoyo_domain_info".
* @is_delete: True if it is a delete request.
*
@@ -776,48 +606,65 @@ static int tomoyo_check_file_perm2(struct tomoyo_domain_info * const domain,
*
* Caller holds tomoyo_read_lock().
*/
-int tomoyo_write_file_policy(char *data, struct tomoyo_domain_info *domain,
- const bool is_delete)
+static int tomoyo_update_path_acl(const u8 type, const char *filename,
+ struct tomoyo_domain_info * const domain,
+ const bool is_delete)
{
- char *filename = strchr(data, ' ');
- char *filename2;
- unsigned int perm;
- u8 type;
-
- if (!filename)
+ 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;
- *filename++ = '\0';
- if (sscanf(data, "%u", &perm) == 1)
- return tomoyo_update_file_acl(filename, (u8) perm, domain,
- is_delete);
- if (strncmp(data, "allow_", 6))
- goto out;
- data += 6;
- for (type = 0; type < TOMOYO_MAX_PATH_OPERATION; type++) {
- if (strcmp(data, tomoyo_path_keyword[type]))
- continue;
- return tomoyo_update_path_acl(type, filename, domain,
- is_delete);
- }
- filename2 = strchr(filename, ' ');
- if (!filename2)
- goto out;
- *filename2++ = '\0';
- for (type = 0; type < TOMOYO_MAX_PATH2_OPERATION; type++) {
- if (strcmp(data, tomoyo_path2_keyword[type]))
- continue;
- return tomoyo_update_path2_acl(type, filename, filename2,
- domain, is_delete);
- }
- out:
- 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_path_acl - Update "struct tomoyo_path_acl" list.
+ * 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.
*
@@ -825,71 +672,58 @@ int tomoyo_write_file_policy(char *data, struct tomoyo_domain_info *domain,
*
* 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)
+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)
{
- static const u32 tomoyo_rw_mask =
- (1 << TOMOYO_TYPE_READ) | (1 << TOMOYO_TYPE_WRITE);
- const u32 perm = 1 << type;
- struct tomoyo_acl_info *ptr;
- struct tomoyo_path_acl e = {
- .head.type = TOMOYO_TYPE_PATH_ACL,
- .perm_high = perm >> 16,
- .perm = perm
+ struct tomoyo_mkdev_acl e = {
+ .head.type = TOMOYO_TYPE_MKDEV_ACL,
+ .perm = 1 << type
};
int error = is_delete ? -ENOENT : -ENOMEM;
-
- if (type == TOMOYO_TYPE_READ_WRITE)
- e.perm |= tomoyo_rw_mask;
- if (!domain)
- return -EINVAL;
- if (!tomoyo_parse_name_union(filename, &e.name))
- return -EINVAL;
- if (mutex_lock_interruptible(&tomoyo_policy_lock))
+ 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;
- list_for_each_entry_rcu(ptr, &domain->acl_info_list, list) {
- struct tomoyo_path_acl *acl =
- container_of(ptr, struct tomoyo_path_acl, head);
- if (!tomoyo_is_same_path_acl(acl, &e))
- continue;
- if (is_delete) {
- if (perm <= 0xFFFF)
- acl->perm &= ~perm;
- else
- acl->perm_high &= ~(perm >> 16);
- if ((acl->perm & tomoyo_rw_mask) != tomoyo_rw_mask)
- acl->perm &= ~(1 << TOMOYO_TYPE_READ_WRITE);
- else if (!(acl->perm & (1 << TOMOYO_TYPE_READ_WRITE)))
- acl->perm &= ~tomoyo_rw_mask;
- } else {
- if (perm <= 0xFFFF)
- acl->perm |= perm;
- else
- acl->perm_high |= (perm >> 16);
- if ((acl->perm & tomoyo_rw_mask) == tomoyo_rw_mask)
- acl->perm |= 1 << TOMOYO_TYPE_READ_WRITE;
- else if (acl->perm & (1 << TOMOYO_TYPE_READ_WRITE))
- acl->perm |= tomoyo_rw_mask;
- }
- error = 0;
- break;
- }
- if (!is_delete && error) {
- struct tomoyo_path_acl *entry =
- tomoyo_commit_ok(&e, sizeof(e));
- if (entry) {
- list_add_tail_rcu(&entry->head.list,
- &domain->acl_info_list);
- error = 0;
- }
- }
- mutex_unlock(&tomoyo_policy_lock);
+ 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.
*
@@ -905,46 +739,20 @@ static int tomoyo_update_path_acl(const u8 type, const char *filename,
*/
static int tomoyo_update_path2_acl(const u8 type, const char *filename1,
const char *filename2,
- struct tomoyo_domain_info *const domain,
+ struct tomoyo_domain_info * const domain,
const bool is_delete)
{
- const u8 perm = 1 << type;
struct tomoyo_path2_acl e = {
.head.type = TOMOYO_TYPE_PATH2_ACL,
- .perm = perm
+ .perm = 1 << type
};
- struct tomoyo_acl_info *ptr;
int error = is_delete ? -ENOENT : -ENOMEM;
-
- if (!domain)
- return -EINVAL;
if (!tomoyo_parse_name_union(filename1, &e.name1) ||
!tomoyo_parse_name_union(filename2, &e.name2))
goto out;
- if (mutex_lock_interruptible(&tomoyo_policy_lock))
- goto out;
- list_for_each_entry_rcu(ptr, &domain->acl_info_list, list) {
- struct tomoyo_path2_acl *acl =
- container_of(ptr, struct tomoyo_path2_acl, head);
- if (!tomoyo_is_same_path2_acl(acl, &e))
- continue;
- if (is_delete)
- acl->perm &= ~perm;
- else
- acl->perm |= perm;
- error = 0;
- break;
- }
- if (!is_delete && error) {
- struct tomoyo_path2_acl *entry =
- tomoyo_commit_ok(&e, sizeof(e));
- if (entry) {
- list_add_tail_rcu(&entry->head.list,
- &domain->acl_info_list);
- error = 0;
- }
- }
- mutex_unlock(&tomoyo_policy_lock);
+ 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);
@@ -952,134 +760,158 @@ static int tomoyo_update_path2_acl(const u8 type, const char *filename1,
}
/**
- * tomoyo_path_acl - Check permission for single path operation.
- *
- * @domain: Pointer to "struct tomoyo_domain_info".
- * @type: Type of operation.
- * @filename: Filename to check.
- *
- * Returns 0 on success, negative value otherwise.
- *
- * Caller holds tomoyo_read_lock().
- */
-static int tomoyo_path_acl(struct tomoyo_domain_info *domain, const u8 type,
- const struct tomoyo_path_info *filename)
-{
- if (!tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE))
- return 0;
- return tomoyo_path_acl2(domain, filename, 1 << type, 1);
-}
-
-/**
- * tomoyo_path2_acl - Check permission for double path operation.
+ * tomoyo_path_permission - Check permission for single path operation.
*
- * @domain: Pointer to "struct tomoyo_domain_info".
- * @type: Type of operation.
- * @filename1: First filename to check.
- * @filename2: Second filename to check.
- *
- * Returns 0 on success, -EPERM otherwise.
- *
- * Caller holds tomoyo_read_lock().
- */
-static int tomoyo_path2_acl(const struct tomoyo_domain_info *domain,
- const u8 type,
- const struct tomoyo_path_info *filename1,
- const struct tomoyo_path_info *filename2)
-{
- struct tomoyo_acl_info *ptr;
- const u8 perm = 1 << type;
- int error = -EPERM;
-
- if (!tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE))
- return 0;
- list_for_each_entry_rcu(ptr, &domain->acl_info_list, list) {
- struct tomoyo_path2_acl *acl;
- if (ptr->type != TOMOYO_TYPE_PATH2_ACL)
- continue;
- acl = container_of(ptr, struct tomoyo_path2_acl, head);
- if (!(acl->perm & perm))
- continue;
- if (!tomoyo_compare_name_union(filename1, &acl->name1))
- continue;
- if (!tomoyo_compare_name_union(filename2, &acl->name2))
- continue;
- error = 0;
- break;
- }
- return error;
-}
-
-/**
- * tomoyo_path_permission2 - Check permission for single path operation.
- *
- * @domain: Pointer to "struct tomoyo_domain_info".
+ * @r: Pointer to "struct tomoyo_request_info".
* @operation: Type of operation.
* @filename: Filename to check.
- * @mode: Access control mode.
*
* Returns 0 on success, negative value otherwise.
*
* Caller holds tomoyo_read_lock().
*/
-static int tomoyo_path_permission2(struct tomoyo_domain_info *const domain,
- u8 operation,
- const struct tomoyo_path_info *filename,
- const u8 mode)
+int tomoyo_path_permission(struct tomoyo_request_info *r, u8 operation,
+ const struct tomoyo_path_info *filename)
{
- const char *msg;
int error;
- const bool is_enforce = (mode == 3);
- if (!mode)
- return 0;
next:
- error = tomoyo_path_acl(domain, operation, filename);
- msg = tomoyo_path2keyword(operation);
- if (!error)
- goto ok;
- if (tomoyo_verbose_mode(domain))
- printk(KERN_WARNING "TOMOYO-%s: Access '%s %s' denied for %s\n",
- tomoyo_get_msg(is_enforce), msg, filename->name,
- tomoyo_get_last_name(domain));
- if (mode == 1 && tomoyo_domain_quota_is_ok(domain)) {
- const char *name = tomoyo_get_file_pattern(filename)->name;
- tomoyo_update_path_acl(operation, name, domain, false);
- }
- if (!is_enforce)
- error = 0;
- ok:
+ 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_is_no_rewrite_file(filename)) {
+ 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_check_exec_perm - Check permission for "execute".
+ * 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.
*
- * @domain: Pointer to "struct tomoyo_domain_info".
- * @filename: Check permission for "execute".
+ * 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".
*
- * Returns 0 on success, negativevalue otherwise.
+ * @type: Type of operation.
+ * @path: Pointer to "struct path".
+ * @number: Number.
*
- * Caller holds tomoyo_read_lock().
+ * Returns 0 on success, negative value otherwise.
*/
-int tomoyo_check_exec_perm(struct tomoyo_domain_info *domain,
- const struct tomoyo_path_info *filename)
+int tomoyo_path_number_perm(const u8 type, struct path *path,
+ unsigned long number)
{
- const u8 mode = tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE);
+ struct tomoyo_request_info r;
+ int error = -ENOMEM;
+ struct tomoyo_path_info buf;
+ int idx;
- if (!mode)
+ if (tomoyo_init_request_info(&r, NULL, tomoyo_pn2mac[type])
+ == TOMOYO_CONFIG_DISABLED || !path->mnt || !path->dentry)
return 0;
- return tomoyo_check_file_perm2(domain, filename, 1, "do_execve", mode);
+ 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;
}
/**
@@ -1096,24 +928,17 @@ int tomoyo_check_open_permission(struct tomoyo_domain_info *domain,
{
const u8 acc_mode = ACC_MODE(flag);
int error = -ENOMEM;
- struct tomoyo_path_info *buf;
- const u8 mode = tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE);
- const bool is_enforce = (mode == 3);
+ struct tomoyo_path_info buf;
+ struct tomoyo_request_info r;
int idx;
- if (!mode || !path->mnt)
- return 0;
- if (acc_mode == 0)
- return 0;
- if (path->dentry->d_inode && S_ISDIR(path->dentry->d_inode->i_mode))
- /*
- * I don't check directories here because mkdir() and rmdir()
- * don't call me.
- */
+ 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();
- buf = tomoyo_get_path(path);
- if (!buf)
+ if (!tomoyo_get_realpath(&buf, path))
goto out;
error = 0;
/*
@@ -1121,28 +946,43 @@ int tomoyo_check_open_permission(struct tomoyo_domain_info *domain,
* 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_TRUNC) || !(flag & O_APPEND)) &&
- (tomoyo_is_no_rewrite_file(buf))) {
- error = tomoyo_path_permission2(domain, TOMOYO_TYPE_REWRITE,
- buf, mode);
+ 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);
}
- if (!error)
- error = tomoyo_check_file_perm2(domain, buf, acc_mode, "open",
- mode);
- if (!error && (flag & O_TRUNC))
- error = tomoyo_path_permission2(domain, TOMOYO_TYPE_TRUNCATE,
- buf, mode);
out:
- kfree(buf);
+ kfree(buf.name);
tomoyo_read_unlock(idx);
- if (!is_enforce)
+ if (r.mode != TOMOYO_CONFIG_ENFORCING)
error = 0;
return error;
}
/**
- * tomoyo_path_perm - Check permission for "create", "unlink", "mkdir", "rmdir", "mkfifo", "mksock", "mkblock", "mkchar", "truncate", "symlink", "ioctl", "chmod", "chown", "chgrp", "chroot", "mount" and "unmount".
+ * tomoyo_path_perm - Check permission for "unlink", "rmdir", "truncate", "symlink", "rewrite", "chroot" and "unmount".
*
* @operation: Type of operation.
* @path: Pointer to "struct path".
@@ -1152,71 +992,79 @@ int tomoyo_check_open_permission(struct tomoyo_domain_info *domain,
int tomoyo_path_perm(const u8 operation, struct path *path)
{
int error = -ENOMEM;
- struct tomoyo_path_info *buf;
- struct tomoyo_domain_info *domain = tomoyo_domain();
- const u8 mode = tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE);
- const bool is_enforce = (mode == 3);
+ struct tomoyo_path_info buf;
+ struct tomoyo_request_info r;
int idx;
- if (!mode || !path->mnt)
+ 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();
- buf = tomoyo_get_path(path);
- if (!buf)
+ if (!tomoyo_get_realpath(&buf, path))
goto out;
switch (operation) {
- case TOMOYO_TYPE_MKDIR:
+ case TOMOYO_TYPE_REWRITE:
+ if (!tomoyo_no_rewrite_file(&buf)) {
+ error = 0;
+ goto out;
+ }
+ break;
case TOMOYO_TYPE_RMDIR:
case TOMOYO_TYPE_CHROOT:
- if (!buf->is_dir) {
- /*
- * tomoyo_get_path() reserves space for appending "/."
- */
- strcat((char *) buf->name, "/");
- tomoyo_fill_path_info(buf);
- }
+ case TOMOYO_TYPE_UMOUNT:
+ tomoyo_add_slash(&buf);
+ break;
}
- error = tomoyo_path_permission2(domain, operation, buf, mode);
+ error = tomoyo_path_permission(&r, operation, &buf);
out:
- kfree(buf);
+ kfree(buf.name);
tomoyo_read_unlock(idx);
- if (!is_enforce)
+ if (r.mode != TOMOYO_CONFIG_ENFORCING)
error = 0;
return error;
}
/**
- * tomoyo_check_rewrite_permission - Check permission for "rewrite".
+ * tomoyo_mkdev_perm - Check permission for "mkblock" and "mkchar".
*
- * @filp: Pointer to "struct file".
+ * @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_check_rewrite_permission(struct file *filp)
+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_domain_info *domain = tomoyo_domain();
- const u8 mode = tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE);
- const bool is_enforce = (mode == 3);
- struct tomoyo_path_info *buf;
+ struct tomoyo_path_info buf;
int idx;
- if (!mode || !filp->f_path.mnt)
+ if (!path->mnt ||
+ tomoyo_init_request_info(&r, NULL, tomoyo_pnnn2mac[operation])
+ == TOMOYO_CONFIG_DISABLED)
return 0;
-
idx = tomoyo_read_lock();
- buf = tomoyo_get_path(&filp->f_path);
- if (!buf)
- goto out;
- if (!tomoyo_is_no_rewrite_file(buf)) {
- error = 0;
- goto out;
+ 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);
}
- error = tomoyo_path_permission2(domain, TOMOYO_TYPE_REWRITE, buf, mode);
- out:
- kfree(buf);
tomoyo_read_unlock(idx);
- if (!is_enforce)
+ if (r.mode != TOMOYO_CONFIG_ENFORCING)
error = 0;
return error;
}
@@ -1234,56 +1082,99 @@ int tomoyo_path2_perm(const u8 operation, struct path *path1,
struct path *path2)
{
int error = -ENOMEM;
- struct tomoyo_path_info *buf1, *buf2;
- struct tomoyo_domain_info *domain = tomoyo_domain();
- const u8 mode = tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE);
- const bool is_enforce = (mode == 3);
- const char *msg;
+ struct tomoyo_path_info buf1;
+ struct tomoyo_path_info buf2;
+ struct tomoyo_request_info r;
int idx;
- if (!mode || !path1->mnt || !path2->mnt)
+ 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();
- buf1 = tomoyo_get_path(path1);
- buf2 = tomoyo_get_path(path2);
- if (!buf1 || !buf2)
- goto out;
- {
- struct dentry *dentry = path1->dentry;
- if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)) {
- /*
- * tomoyo_get_path() reserves space for appending "/."
- */
- if (!buf1->is_dir) {
- strcat((char *) buf1->name, "/");
- tomoyo_fill_path_info(buf1);
- }
- if (!buf2->is_dir) {
- strcat((char *) buf2->name, "/");
- tomoyo_fill_path_info(buf2);
- }
- }
- }
- error = tomoyo_path2_acl(domain, operation, buf1, buf2);
- msg = tomoyo_path22keyword(operation);
- if (!error)
+ if (!tomoyo_get_realpath(&buf1, path1) ||
+ !tomoyo_get_realpath(&buf2, path2))
goto out;
- if (tomoyo_verbose_mode(domain))
- printk(KERN_WARNING "TOMOYO-%s: Access '%s %s %s' "
- "denied for %s\n", tomoyo_get_msg(is_enforce),
- msg, buf1->name, buf2->name,
- tomoyo_get_last_name(domain));
- if (mode == 1 && tomoyo_domain_quota_is_ok(domain)) {
- const char *name1 = tomoyo_get_file_pattern(buf1)->name;
- const char *name2 = tomoyo_get_file_pattern(buf2)->name;
- tomoyo_update_path2_acl(operation, name1, name2, domain,
- false);
- }
+ 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);
- kfree(buf2);
+ kfree(buf1.name);
+ kfree(buf2.name);
tomoyo_read_unlock(idx);
- if (!is_enforce)
+ 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
index b9cc71b..a877e4c 100644
--- a/security/tomoyo/gc.c
+++ b/security/tomoyo/gc.c
@@ -11,83 +11,75 @@
#include <linux/kthread.h>
#include <linux/slab.h>
-enum tomoyo_gc_id {
- TOMOYO_ID_PATH_GROUP,
- TOMOYO_ID_PATH_GROUP_MEMBER,
- TOMOYO_ID_DOMAIN_INITIALIZER,
- TOMOYO_ID_DOMAIN_KEEPER,
- TOMOYO_ID_ALIAS,
- TOMOYO_ID_GLOBALLY_READABLE,
- TOMOYO_ID_PATTERN,
- TOMOYO_ID_NO_REWRITE,
- TOMOYO_ID_MANAGER,
- TOMOYO_ID_NAME,
- TOMOYO_ID_ACL,
- TOMOYO_ID_DOMAIN
-};
-
-struct tomoyo_gc_entry {
+struct tomoyo_gc {
struct list_head list;
int type;
- void *element;
+ struct list_head *element;
};
static LIST_HEAD(tomoyo_gc_queue);
static DEFINE_MUTEX(tomoyo_gc_mutex);
/* Caller holds tomoyo_policy_lock mutex. */
-static bool tomoyo_add_to_gc(const int type, void *element)
+static bool tomoyo_add_to_gc(const int type, struct list_head *element)
{
- struct tomoyo_gc_entry *entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
+ struct tomoyo_gc *entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
if (!entry)
return false;
entry->type = type;
entry->element = element;
list_add(&entry->list, &tomoyo_gc_queue);
+ list_del_rcu(element);
return true;
}
-static void tomoyo_del_allow_read
-(struct tomoyo_globally_readable_file_entry *ptr)
+static void tomoyo_del_allow_read(struct list_head *element)
{
+ struct tomoyo_readable_file *ptr =
+ container_of(element, typeof(*ptr), head.list);
tomoyo_put_name(ptr->filename);
}
-static void tomoyo_del_file_pattern(struct tomoyo_pattern_entry *ptr)
+static void tomoyo_del_file_pattern(struct list_head *element)
{
+ struct tomoyo_no_pattern *ptr =
+ container_of(element, typeof(*ptr), head.list);
tomoyo_put_name(ptr->pattern);
}
-static void tomoyo_del_no_rewrite(struct tomoyo_no_rewrite_entry *ptr)
+static void tomoyo_del_no_rewrite(struct list_head *element)
{
+ struct tomoyo_no_rewrite *ptr =
+ container_of(element, typeof(*ptr), head.list);
tomoyo_put_name(ptr->pattern);
}
-static void tomoyo_del_domain_initializer
-(struct tomoyo_domain_initializer_entry *ptr)
+static void tomoyo_del_transition_control(struct list_head *element)
{
+ struct tomoyo_transition_control *ptr =
+ container_of(element, typeof(*ptr), head.list);
tomoyo_put_name(ptr->domainname);
tomoyo_put_name(ptr->program);
}
-static void tomoyo_del_domain_keeper(struct tomoyo_domain_keeper_entry *ptr)
-{
- tomoyo_put_name(ptr->domainname);
- tomoyo_put_name(ptr->program);
-}
-
-static void tomoyo_del_alias(struct tomoyo_alias_entry *ptr)
+static void tomoyo_del_aggregator(struct list_head *element)
{
+ struct tomoyo_aggregator *ptr =
+ container_of(element, typeof(*ptr), head.list);
tomoyo_put_name(ptr->original_name);
- tomoyo_put_name(ptr->aliased_name);
+ tomoyo_put_name(ptr->aggregated_name);
}
-static void tomoyo_del_manager(struct tomoyo_policy_manager_entry *ptr)
+static void tomoyo_del_manager(struct list_head *element)
{
+ struct tomoyo_manager *ptr =
+ container_of(element, typeof(*ptr), head.list);
tomoyo_put_name(ptr->manager);
}
-static void tomoyo_del_acl(struct tomoyo_acl_info *acl)
+static void tomoyo_del_acl(struct list_head *element)
{
+ struct tomoyo_acl_info *acl =
+ container_of(element, typeof(*acl), list);
switch (acl->type) {
case TOMOYO_TYPE_PATH_ACL:
{
@@ -104,14 +96,41 @@ static void tomoyo_del_acl(struct tomoyo_acl_info *acl)
tomoyo_put_name_union(&entry->name2);
}
break;
- default:
- printk(KERN_WARNING "Unknown type\n");
+ case TOMOYO_TYPE_PATH_NUMBER_ACL:
+ {
+ struct tomoyo_path_number_acl *entry
+ = container_of(acl, typeof(*entry), head);
+ tomoyo_put_name_union(&entry->name);
+ tomoyo_put_number_union(&entry->number);
+ }
+ break;
+ case TOMOYO_TYPE_MKDEV_ACL:
+ {
+ struct tomoyo_mkdev_acl *entry
+ = container_of(acl, typeof(*entry), head);
+ tomoyo_put_name_union(&entry->name);
+ tomoyo_put_number_union(&entry->mode);
+ tomoyo_put_number_union(&entry->major);
+ tomoyo_put_number_union(&entry->minor);
+ }
+ break;
+ case TOMOYO_TYPE_MOUNT_ACL:
+ {
+ struct tomoyo_mount_acl *entry
+ = container_of(acl, typeof(*entry), head);
+ tomoyo_put_name_union(&entry->dev_name);
+ tomoyo_put_name_union(&entry->dir_name);
+ tomoyo_put_name_union(&entry->fs_type);
+ tomoyo_put_number_union(&entry->flags);
+ }
break;
}
}
-static bool tomoyo_del_domain(struct tomoyo_domain_info *domain)
+static bool tomoyo_del_domain(struct list_head *element)
{
+ struct tomoyo_domain_info *domain =
+ container_of(element, typeof(*domain), list);
struct tomoyo_acl_info *acl;
struct tomoyo_acl_info *tmp;
/*
@@ -139,7 +158,7 @@ static bool tomoyo_del_domain(struct tomoyo_domain_info *domain)
if (atomic_read(&domain->users))
return false;
list_for_each_entry_safe(acl, tmp, &domain->acl_info_list, list) {
- tomoyo_del_acl(acl);
+ tomoyo_del_acl(&acl->list);
tomoyo_memory_free(acl);
}
tomoyo_put_name(domain->domainname);
@@ -147,135 +166,70 @@ static bool tomoyo_del_domain(struct tomoyo_domain_info *domain)
}
-static void tomoyo_del_name(const struct tomoyo_name_entry *ptr)
+static void tomoyo_del_name(struct list_head *element)
{
+ const struct tomoyo_name *ptr =
+ container_of(element, typeof(*ptr), list);
}
-static void tomoyo_del_path_group_member(struct tomoyo_path_group_member
- *member)
+static void tomoyo_del_path_group(struct list_head *element)
{
+ struct tomoyo_path_group *member =
+ container_of(element, typeof(*member), head.list);
tomoyo_put_name(member->member_name);
}
-static void tomoyo_del_path_group(struct tomoyo_path_group *group)
+static void tomoyo_del_group(struct list_head *element)
{
+ struct tomoyo_group *group =
+ container_of(element, typeof(*group), list);
tomoyo_put_name(group->group_name);
}
+static void tomoyo_del_number_group(struct list_head *element)
+{
+ struct tomoyo_number_group *member =
+ container_of(element, typeof(*member), head.list);
+}
+
+static bool tomoyo_collect_member(struct list_head *member_list, int id)
+{
+ struct tomoyo_acl_head *member;
+ list_for_each_entry(member, member_list, list) {
+ if (!member->is_deleted)
+ continue;
+ if (!tomoyo_add_to_gc(id, &member->list))
+ return false;
+ }
+ return true;
+}
+
+static bool tomoyo_collect_acl(struct tomoyo_domain_info *domain)
+{
+ struct tomoyo_acl_info *acl;
+ list_for_each_entry(acl, &domain->acl_info_list, list) {
+ if (!acl->is_deleted)
+ continue;
+ if (!tomoyo_add_to_gc(TOMOYO_ID_ACL, &acl->list))
+ return false;
+ }
+ return true;
+}
+
static void tomoyo_collect_entry(void)
{
+ int i;
if (mutex_lock_interruptible(&tomoyo_policy_lock))
return;
- {
- struct tomoyo_globally_readable_file_entry *ptr;
- list_for_each_entry_rcu(ptr, &tomoyo_globally_readable_list,
- list) {
- if (!ptr->is_deleted)
- continue;
- if (tomoyo_add_to_gc(TOMOYO_ID_GLOBALLY_READABLE, ptr))
- list_del_rcu(&ptr->list);
- else
- break;
- }
- }
- {
- struct tomoyo_pattern_entry *ptr;
- list_for_each_entry_rcu(ptr, &tomoyo_pattern_list, list) {
- if (!ptr->is_deleted)
- continue;
- if (tomoyo_add_to_gc(TOMOYO_ID_PATTERN, ptr))
- list_del_rcu(&ptr->list);
- else
- break;
- }
- }
- {
- struct tomoyo_no_rewrite_entry *ptr;
- list_for_each_entry_rcu(ptr, &tomoyo_no_rewrite_list, list) {
- if (!ptr->is_deleted)
- continue;
- if (tomoyo_add_to_gc(TOMOYO_ID_NO_REWRITE, ptr))
- list_del_rcu(&ptr->list);
- else
- break;
- }
- }
- {
- struct tomoyo_domain_initializer_entry *ptr;
- list_for_each_entry_rcu(ptr, &tomoyo_domain_initializer_list,
- list) {
- if (!ptr->is_deleted)
- continue;
- if (tomoyo_add_to_gc(TOMOYO_ID_DOMAIN_INITIALIZER, ptr))
- list_del_rcu(&ptr->list);
- else
- break;
- }
- }
- {
- struct tomoyo_domain_keeper_entry *ptr;
- list_for_each_entry_rcu(ptr, &tomoyo_domain_keeper_list, list) {
- if (!ptr->is_deleted)
- continue;
- if (tomoyo_add_to_gc(TOMOYO_ID_DOMAIN_KEEPER, ptr))
- list_del_rcu(&ptr->list);
- else
- break;
- }
- }
- {
- struct tomoyo_alias_entry *ptr;
- list_for_each_entry_rcu(ptr, &tomoyo_alias_list, list) {
- if (!ptr->is_deleted)
- continue;
- if (tomoyo_add_to_gc(TOMOYO_ID_ALIAS, ptr))
- list_del_rcu(&ptr->list);
- else
- break;
- }
- }
- {
- struct tomoyo_policy_manager_entry *ptr;
- list_for_each_entry_rcu(ptr, &tomoyo_policy_manager_list,
- list) {
- if (!ptr->is_deleted)
- continue;
- if (tomoyo_add_to_gc(TOMOYO_ID_MANAGER, ptr))
- list_del_rcu(&ptr->list);
- else
- break;
- }
+ for (i = 0; i < TOMOYO_MAX_POLICY; i++) {
+ if (!tomoyo_collect_member(&tomoyo_policy_list[i], i))
+ goto unlock;
}
{
struct tomoyo_domain_info *domain;
list_for_each_entry_rcu(domain, &tomoyo_domain_list, list) {
- struct tomoyo_acl_info *acl;
- list_for_each_entry_rcu(acl, &domain->acl_info_list,
- list) {
- switch (acl->type) {
- case TOMOYO_TYPE_PATH_ACL:
- if (container_of(acl,
- struct tomoyo_path_acl,
- head)->perm ||
- container_of(acl,
- struct tomoyo_path_acl,
- head)->perm_high)
- continue;
- break;
- case TOMOYO_TYPE_PATH2_ACL:
- if (container_of(acl,
- struct tomoyo_path2_acl,
- head)->perm)
- continue;
- break;
- default:
- continue;
- }
- if (tomoyo_add_to_gc(TOMOYO_ID_ACL, acl))
- list_del_rcu(&acl->list);
- else
- break;
- }
+ if (!tomoyo_collect_acl(domain))
+ goto unlock;
if (!domain->is_deleted || atomic_read(&domain->users))
continue;
/*
@@ -283,104 +237,92 @@ static void tomoyo_collect_entry(void)
* refer this domain after successful execve().
* We recheck domain->users after SRCU synchronization.
*/
- if (tomoyo_add_to_gc(TOMOYO_ID_DOMAIN, domain))
- list_del_rcu(&domain->list);
- else
- break;
+ if (!tomoyo_add_to_gc(TOMOYO_ID_DOMAIN, &domain->list))
+ goto unlock;
}
}
- {
- int i;
- for (i = 0; i < TOMOYO_MAX_HASH; i++) {
- struct tomoyo_name_entry *ptr;
- list_for_each_entry_rcu(ptr, &tomoyo_name_list[i],
- list) {
- if (atomic_read(&ptr->users))
- continue;
- if (tomoyo_add_to_gc(TOMOYO_ID_NAME, ptr))
- list_del_rcu(&ptr->list);
- else {
- i = TOMOYO_MAX_HASH;
- break;
- }
- }
+ for (i = 0; i < TOMOYO_MAX_HASH; i++) {
+ struct tomoyo_name *ptr;
+ list_for_each_entry_rcu(ptr, &tomoyo_name_list[i], list) {
+ if (atomic_read(&ptr->users))
+ continue;
+ if (!tomoyo_add_to_gc(TOMOYO_ID_NAME, &ptr->list))
+ goto unlock;
}
}
- {
- struct tomoyo_path_group *group;
- list_for_each_entry_rcu(group, &tomoyo_path_group_list, list) {
- struct tomoyo_path_group_member *member;
- list_for_each_entry_rcu(member, &group->member_list,
- list) {
- if (!member->is_deleted)
- continue;
- if (tomoyo_add_to_gc(TOMOYO_ID_PATH_GROUP_MEMBER,
- member))
- list_del_rcu(&member->list);
- else
- break;
- }
+ for (i = 0; i < TOMOYO_MAX_GROUP; i++) {
+ struct list_head *list = &tomoyo_group_list[i];
+ int id;
+ struct tomoyo_group *group;
+ switch (i) {
+ case 0:
+ id = TOMOYO_ID_PATH_GROUP;
+ break;
+ default:
+ id = TOMOYO_ID_NUMBER_GROUP;
+ break;
+ }
+ list_for_each_entry(group, list, list) {
+ if (!tomoyo_collect_member(&group->member_list, id))
+ goto unlock;
if (!list_empty(&group->member_list) ||
atomic_read(&group->users))
continue;
- if (tomoyo_add_to_gc(TOMOYO_ID_PATH_GROUP, group))
- list_del_rcu(&group->list);
- else
- break;
+ if (!tomoyo_add_to_gc(TOMOYO_ID_GROUP, &group->list))
+ goto unlock;
}
}
+ unlock:
mutex_unlock(&tomoyo_policy_lock);
}
static void tomoyo_kfree_entry(void)
{
- struct tomoyo_gc_entry *p;
- struct tomoyo_gc_entry *tmp;
+ struct tomoyo_gc *p;
+ struct tomoyo_gc *tmp;
list_for_each_entry_safe(p, tmp, &tomoyo_gc_queue, list) {
+ struct list_head *element = p->element;
switch (p->type) {
- case TOMOYO_ID_DOMAIN_INITIALIZER:
- tomoyo_del_domain_initializer(p->element);
- break;
- case TOMOYO_ID_DOMAIN_KEEPER:
- tomoyo_del_domain_keeper(p->element);
+ case TOMOYO_ID_TRANSITION_CONTROL:
+ tomoyo_del_transition_control(element);
break;
- case TOMOYO_ID_ALIAS:
- tomoyo_del_alias(p->element);
+ case TOMOYO_ID_AGGREGATOR:
+ tomoyo_del_aggregator(element);
break;
case TOMOYO_ID_GLOBALLY_READABLE:
- tomoyo_del_allow_read(p->element);
+ tomoyo_del_allow_read(element);
break;
case TOMOYO_ID_PATTERN:
- tomoyo_del_file_pattern(p->element);
+ tomoyo_del_file_pattern(element);
break;
case TOMOYO_ID_NO_REWRITE:
- tomoyo_del_no_rewrite(p->element);
+ tomoyo_del_no_rewrite(element);
break;
case TOMOYO_ID_MANAGER:
- tomoyo_del_manager(p->element);
+ tomoyo_del_manager(element);
break;
case TOMOYO_ID_NAME:
- tomoyo_del_name(p->element);
+ tomoyo_del_name(element);
break;
case TOMOYO_ID_ACL:
- tomoyo_del_acl(p->element);
+ tomoyo_del_acl(element);
break;
case TOMOYO_ID_DOMAIN:
- if (!tomoyo_del_domain(p->element))
+ if (!tomoyo_del_domain(element))
continue;
break;
- case TOMOYO_ID_PATH_GROUP_MEMBER:
- tomoyo_del_path_group_member(p->element);
- break;
case TOMOYO_ID_PATH_GROUP:
- tomoyo_del_path_group(p->element);
+ tomoyo_del_path_group(element);
break;
- default:
- printk(KERN_WARNING "Unknown type\n");
+ case TOMOYO_ID_GROUP:
+ tomoyo_del_group(element);
+ break;
+ case TOMOYO_ID_NUMBER_GROUP:
+ tomoyo_del_number_group(element);
break;
}
- tomoyo_memory_free(p->element);
+ tomoyo_memory_free(element);
list_del(&p->list);
kfree(p);
}
diff --git a/security/tomoyo/group.c b/security/tomoyo/group.c
new file mode 100644
index 0000000..e94352c
--- /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 0000000..bbada7c
--- /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 everytime.
+ */
+ struct path path;
+
+ if (kern_path(tomoyo_loader, LOOKUP_FOLLOW, &path)) {
+ printk(KERN_INFO "Not activating Mandatory Access Control now "
+ "since %s doesn't exist.\n", tomoyo_loader);
+ return false;
+ }
+ path_put(&path);
+ return true;
+}
+
+/**
+ * tomoyo_load_policy - Run external policy loader to load policy.
+ *
+ * @filename: The program about to start.
+ *
+ * This function checks whether @filename is /sbin/init , and if so
+ * invoke /sbin/tomoyo-init and wait for the termination of /sbin/tomoyo-init
+ * and then continues invocation of /sbin/init.
+ * /sbin/tomoyo-init reads policy files in /etc/tomoyo/ directory and
+ * writes to /sys/kernel/security/tomoyo/ interfaces.
+ *
+ * Returns nothing.
+ */
+void tomoyo_load_policy(const char *filename)
+{
+ char *argv[2];
+ char *envp[3];
+
+ if (tomoyo_policy_loaded)
+ return;
+ /*
+ * Check filename is /sbin/init or /sbin/tomoyo-start.
+ * /sbin/tomoyo-start is a dummy filename in case where /sbin/init can't
+ * be passed.
+ * You can create /sbin/tomoyo-start by
+ * "ln -s /bin/true /sbin/tomoyo-start".
+ */
+ if (strcmp(filename, "/sbin/init") &&
+ strcmp(filename, "/sbin/tomoyo-start"))
+ return;
+ if (!tomoyo_policy_loader_exists())
+ return;
+
+ printk(KERN_INFO "Calling %s to load policy. Please wait.\n",
+ tomoyo_loader);
+ argv[0] = (char *) tomoyo_loader;
+ argv[1] = NULL;
+ envp[0] = "HOME=/";
+ envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
+ envp[2] = NULL;
+ call_usermodehelper(argv[0], argv, envp, 1);
+ tomoyo_check_profile();
+}
diff --git a/security/tomoyo/memory.c b/security/tomoyo/memory.c
new file mode 100644
index 0000000..2976126
--- /dev/null
+++ b/security/tomoyo/memory.c
@@ -0,0 +1,282 @@
+/*
+ * security/tomoyo/memory.c
+ *
+ * Memory management functions for TOMOYO.
+ *
+ * Copyright (C) 2005-2010 NTT DATA CORPORATION
+ */
+
+#include <linux/hash.h>
+#include <linux/slab.h>
+#include "common.h"
+
+/**
+ * tomoyo_warn_oom - Print out of memory warning message.
+ *
+ * @function: Function's name.
+ */
+void tomoyo_warn_oom(const char *function)
+{
+ /* Reduce error messages. */
+ static pid_t tomoyo_last_pid;
+ const pid_t pid = current->pid;
+ if (tomoyo_last_pid != pid) {
+ printk(KERN_WARNING "ERROR: Out of memory at %s.\n",
+ function);
+ tomoyo_last_pid = pid;
+ }
+ if (!tomoyo_policy_loaded)
+ panic("MAC Initialization failed.\n");
+}
+
+/* Memory allocated for policy. */
+static atomic_t tomoyo_policy_memory_size;
+/* Quota for holding policy. */
+static unsigned int tomoyo_quota_for_policy;
+
+/**
+ * tomoyo_memory_ok - Check memory quota.
+ *
+ * @ptr: Pointer to allocated memory.
+ *
+ * Returns true on success, false otherwise.
+ *
+ * Returns true if @ptr is not NULL and quota not exceeded, false otherwise.
+ */
+bool tomoyo_memory_ok(void *ptr)
+{
+ size_t s = ptr ? ksize(ptr) : 0;
+ atomic_add(s, &tomoyo_policy_memory_size);
+ if (ptr && (!tomoyo_quota_for_policy ||
+ atomic_read(&tomoyo_policy_memory_size)
+ <= tomoyo_quota_for_policy)) {
+ memset(ptr, 0, s);
+ return true;
+ }
+ atomic_sub(s, &tomoyo_policy_memory_size);
+ tomoyo_warn_oom(__func__);
+ return false;
+}
+
+/**
+ * tomoyo_commit_ok - Check memory quota.
+ *
+ * @data: Data to copy from.
+ * @size: Size in byte.
+ *
+ * Returns pointer to allocated memory on success, NULL otherwise.
+ * @data is zero-cleared on success.
+ */
+void *tomoyo_commit_ok(void *data, const unsigned int size)
+{
+ void *ptr = kzalloc(size, GFP_NOFS);
+ if (tomoyo_memory_ok(ptr)) {
+ memmove(ptr, data, size);
+ memset(data, 0, size);
+ return ptr;
+ }
+ return NULL;
+}
+
+/**
+ * tomoyo_memory_free - Free memory for elements.
+ *
+ * @ptr: Pointer to allocated memory.
+ */
+void tomoyo_memory_free(void *ptr)
+{
+ atomic_sub(ksize(ptr), &tomoyo_policy_memory_size);
+ kfree(ptr);
+}
+
+/**
+ * tomoyo_get_group - Allocate memory for "struct tomoyo_path_group"/"struct tomoyo_number_group".
+ *
+ * @group_name: The name of address group.
+ * @idx: Index number.
+ *
+ * Returns pointer to "struct tomoyo_group" on success, NULL otherwise.
+ */
+struct tomoyo_group *tomoyo_get_group(const char *group_name, const u8 idx)
+{
+ struct tomoyo_group e = { };
+ struct tomoyo_group *group = NULL;
+ bool found = false;
+ if (!tomoyo_correct_word(group_name) || idx >= TOMOYO_MAX_GROUP)
+ return NULL;
+ e.group_name = tomoyo_get_name(group_name);
+ if (!e.group_name)
+ return NULL;
+ if (mutex_lock_interruptible(&tomoyo_policy_lock))
+ goto out;
+ list_for_each_entry(group, &tomoyo_group_list[idx], list) {
+ if (e.group_name != group->group_name)
+ continue;
+ atomic_inc(&group->users);
+ found = true;
+ break;
+ }
+ if (!found) {
+ struct tomoyo_group *entry = tomoyo_commit_ok(&e, sizeof(e));
+ if (entry) {
+ INIT_LIST_HEAD(&entry->member_list);
+ atomic_set(&entry->users, 1);
+ list_add_tail_rcu(&entry->list,
+ &tomoyo_group_list[idx]);
+ group = entry;
+ found = true;
+ }
+ }
+ mutex_unlock(&tomoyo_policy_lock);
+ out:
+ tomoyo_put_name(e.group_name);
+ return found ? group : NULL;
+}
+
+/*
+ * tomoyo_name_list is used for holding string data used by TOMOYO.
+ * Since same string data is likely used for multiple times (e.g.
+ * "/lib/libc-2.5.so"), TOMOYO shares string data in the form of
+ * "const struct tomoyo_path_info *".
+ */
+struct list_head tomoyo_name_list[TOMOYO_MAX_HASH];
+
+/**
+ * tomoyo_get_name - Allocate permanent memory for string data.
+ *
+ * @name: The string to store into the permernent memory.
+ *
+ * Returns pointer to "struct tomoyo_path_info" on success, NULL otherwise.
+ */
+const struct tomoyo_path_info *tomoyo_get_name(const char *name)
+{
+ struct tomoyo_name *ptr;
+ unsigned int hash;
+ int len;
+ int allocated_len;
+ struct list_head *head;
+
+ if (!name)
+ return NULL;
+ len = strlen(name) + 1;
+ hash = full_name_hash((const unsigned char *) name, len - 1);
+ head = &tomoyo_name_list[hash_long(hash, TOMOYO_HASH_BITS)];
+ if (mutex_lock_interruptible(&tomoyo_policy_lock))
+ return NULL;
+ list_for_each_entry(ptr, head, list) {
+ if (hash != ptr->entry.hash || strcmp(name, ptr->entry.name))
+ continue;
+ atomic_inc(&ptr->users);
+ goto out;
+ }
+ ptr = kzalloc(sizeof(*ptr) + len, GFP_NOFS);
+ allocated_len = ptr ? ksize(ptr) : 0;
+ if (!ptr || (tomoyo_quota_for_policy &&
+ atomic_read(&tomoyo_policy_memory_size) + allocated_len
+ > tomoyo_quota_for_policy)) {
+ kfree(ptr);
+ ptr = NULL;
+ tomoyo_warn_oom(__func__);
+ goto out;
+ }
+ atomic_add(allocated_len, &tomoyo_policy_memory_size);
+ ptr->entry.name = ((char *) ptr) + sizeof(*ptr);
+ memmove((char *) ptr->entry.name, name, len);
+ atomic_set(&ptr->users, 1);
+ tomoyo_fill_path_info(&ptr->entry);
+ list_add_tail(&ptr->list, head);
+ out:
+ mutex_unlock(&tomoyo_policy_lock);
+ return ptr ? &ptr->entry : NULL;
+}
+
+/**
+ * tomoyo_mm_init - Initialize mm related code.
+ */
+void __init tomoyo_mm_init(void)
+{
+ int idx;
+
+ for (idx = 0; idx < TOMOYO_MAX_POLICY; idx++)
+ INIT_LIST_HEAD(&tomoyo_policy_list[idx]);
+ for (idx = 0; idx < TOMOYO_MAX_GROUP; idx++)
+ INIT_LIST_HEAD(&tomoyo_group_list[idx]);
+ for (idx = 0; idx < TOMOYO_MAX_HASH; idx++)
+ INIT_LIST_HEAD(&tomoyo_name_list[idx]);
+ INIT_LIST_HEAD(&tomoyo_kernel_domain.acl_info_list);
+ tomoyo_kernel_domain.domainname = tomoyo_get_name(TOMOYO_ROOT_NAME);
+ list_add_tail_rcu(&tomoyo_kernel_domain.list, &tomoyo_domain_list);
+ idx = tomoyo_read_lock();
+ if (tomoyo_find_domain(TOMOYO_ROOT_NAME) != &tomoyo_kernel_domain)
+ panic("Can't register tomoyo_kernel_domain");
+ {
+ /* Load built-in policy. */
+ tomoyo_write_transition_control("/sbin/hotplug", false,
+ TOMOYO_TRANSITION_CONTROL_INITIALIZE);
+ tomoyo_write_transition_control("/sbin/modprobe", false,
+ TOMOYO_TRANSITION_CONTROL_INITIALIZE);
+ }
+ tomoyo_read_unlock(idx);
+}
+
+
+/* Memory allocated for query lists. */
+unsigned int tomoyo_query_memory_size;
+/* Quota for holding query lists. */
+unsigned int tomoyo_quota_for_query;
+
+/**
+ * tomoyo_read_memory_counter - Check for memory usage in bytes.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns memory usage.
+ */
+void tomoyo_read_memory_counter(struct tomoyo_io_buffer *head)
+{
+ if (!head->r.eof) {
+ const unsigned int policy
+ = atomic_read(&tomoyo_policy_memory_size);
+ const unsigned int query = tomoyo_query_memory_size;
+ char buffer[64];
+
+ memset(buffer, 0, sizeof(buffer));
+ if (tomoyo_quota_for_policy)
+ snprintf(buffer, sizeof(buffer) - 1,
+ " (Quota: %10u)",
+ tomoyo_quota_for_policy);
+ else
+ buffer[0] = '\0';
+ tomoyo_io_printf(head, "Policy: %10u%s\n", policy,
+ buffer);
+ if (tomoyo_quota_for_query)
+ snprintf(buffer, sizeof(buffer) - 1,
+ " (Quota: %10u)",
+ tomoyo_quota_for_query);
+ else
+ buffer[0] = '\0';
+ tomoyo_io_printf(head, "Query lists: %10u%s\n", query,
+ buffer);
+ tomoyo_io_printf(head, "Total: %10u\n", policy + query);
+ head->r.eof = true;
+ }
+}
+
+/**
+ * tomoyo_write_memory_quota - Set memory quota.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns 0.
+ */
+int tomoyo_write_memory_quota(struct tomoyo_io_buffer *head)
+{
+ char *data = head->write_buf;
+ unsigned int size;
+
+ if (sscanf(data, "Policy: %u", &size) == 1)
+ tomoyo_quota_for_policy = size;
+ else if (sscanf(data, "Query lists: %u", &size) == 1)
+ tomoyo_quota_for_query = size;
+ return 0;
+}
diff --git a/security/tomoyo/mount.c b/security/tomoyo/mount.c
new file mode 100644
index 0000000..82bf8c2
--- /dev/null
+++ b/security/tomoyo/mount.c
@@ -0,0 +1,284 @@
+/*
+ * security/tomoyo/mount.c
+ *
+ * Copyright (C) 2005-2010 NTT DATA CORPORATION
+ */
+
+#include <linux/slab.h>
+#include "common.h"
+
+/* Keywords for mount restrictions. */
+
+/* Allow to call 'mount --bind /source_dir /dest_dir' */
+#define TOMOYO_MOUNT_BIND_KEYWORD "--bind"
+/* Allow to call 'mount --move /old_dir /new_dir ' */
+#define TOMOYO_MOUNT_MOVE_KEYWORD "--move"
+/* Allow to call 'mount -o remount /dir ' */
+#define TOMOYO_MOUNT_REMOUNT_KEYWORD "--remount"
+/* Allow to call 'mount --make-unbindable /dir' */
+#define TOMOYO_MOUNT_MAKE_UNBINDABLE_KEYWORD "--make-unbindable"
+/* Allow to call 'mount --make-private /dir' */
+#define TOMOYO_MOUNT_MAKE_PRIVATE_KEYWORD "--make-private"
+/* Allow to call 'mount --make-slave /dir' */
+#define TOMOYO_MOUNT_MAKE_SLAVE_KEYWORD "--make-slave"
+/* Allow to call 'mount --make-shared /dir' */
+#define TOMOYO_MOUNT_MAKE_SHARED_KEYWORD "--make-shared"
+
+/**
+ * tomoyo_audit_mount_log - Audit mount log.
+ *
+ * @r: Pointer to "struct tomoyo_request_info".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_audit_mount_log(struct tomoyo_request_info *r)
+{
+ const char *dev = r->param.mount.dev->name;
+ const char *dir = r->param.mount.dir->name;
+ const char *type = r->param.mount.type->name;
+ const unsigned long flags = r->param.mount.flags;
+ if (r->granted)
+ return 0;
+ if (!strcmp(type, TOMOYO_MOUNT_REMOUNT_KEYWORD))
+ tomoyo_warn_log(r, "mount -o remount %s 0x%lX", dir, flags);
+ else if (!strcmp(type, TOMOYO_MOUNT_BIND_KEYWORD)
+ || !strcmp(type, TOMOYO_MOUNT_MOVE_KEYWORD))
+ tomoyo_warn_log(r, "mount %s %s %s 0x%lX", type, dev, dir,
+ flags);
+ else if (!strcmp(type, TOMOYO_MOUNT_MAKE_UNBINDABLE_KEYWORD) ||
+ !strcmp(type, TOMOYO_MOUNT_MAKE_PRIVATE_KEYWORD) ||
+ !strcmp(type, TOMOYO_MOUNT_MAKE_SLAVE_KEYWORD) ||
+ !strcmp(type, TOMOYO_MOUNT_MAKE_SHARED_KEYWORD))
+ tomoyo_warn_log(r, "mount %s %s 0x%lX", type, dir, flags);
+ else
+ tomoyo_warn_log(r, "mount -t %s %s %s 0x%lX", type, dev, dir,
+ flags);
+ return tomoyo_supervisor(r,
+ TOMOYO_KEYWORD_ALLOW_MOUNT "%s %s %s 0x%lX\n",
+ tomoyo_pattern(r->param.mount.dev),
+ tomoyo_pattern(r->param.mount.dir), type,
+ flags);
+}
+
+static bool tomoyo_check_mount_acl(struct tomoyo_request_info *r,
+ const struct tomoyo_acl_info *ptr)
+{
+ const struct tomoyo_mount_acl *acl =
+ container_of(ptr, typeof(*acl), head);
+ return tomoyo_compare_number_union(r->param.mount.flags, &acl->flags) &&
+ tomoyo_compare_name_union(r->param.mount.type, &acl->fs_type) &&
+ tomoyo_compare_name_union(r->param.mount.dir, &acl->dir_name) &&
+ (!r->param.mount.need_dev ||
+ tomoyo_compare_name_union(r->param.mount.dev, &acl->dev_name));
+}
+
+/**
+ * tomoyo_mount_acl - Check permission for mount() operation.
+ *
+ * @r: Pointer to "struct tomoyo_request_info".
+ * @dev_name: Name of device file.
+ * @dir: Pointer to "struct path".
+ * @type: Name of filesystem type.
+ * @flags: Mount options.
+ *
+ * Returns 0 on success, negative value otherwise.
+ *
+ * Caller holds tomoyo_read_lock().
+ */
+static int tomoyo_mount_acl(struct tomoyo_request_info *r, char *dev_name,
+ struct path *dir, char *type, unsigned long flags)
+{
+ struct path path;
+ struct file_system_type *fstype = NULL;
+ const char *requested_type = NULL;
+ const char *requested_dir_name = NULL;
+ const char *requested_dev_name = NULL;
+ struct tomoyo_path_info rtype;
+ struct tomoyo_path_info rdev;
+ struct tomoyo_path_info rdir;
+ int need_dev = 0;
+ int error = -ENOMEM;
+
+ /* Get fstype. */
+ requested_type = tomoyo_encode(type);
+ if (!requested_type)
+ goto out;
+ rtype.name = requested_type;
+ tomoyo_fill_path_info(&rtype);
+
+ /* Get mount point. */
+ requested_dir_name = tomoyo_realpath_from_path(dir);
+ if (!requested_dir_name) {
+ error = -ENOMEM;
+ goto out;
+ }
+ rdir.name = requested_dir_name;
+ tomoyo_fill_path_info(&rdir);
+
+ /* Compare fs name. */
+ if (!strcmp(type, TOMOYO_MOUNT_REMOUNT_KEYWORD)) {
+ /* dev_name is ignored. */
+ } else if (!strcmp(type, TOMOYO_MOUNT_MAKE_UNBINDABLE_KEYWORD) ||
+ !strcmp(type, TOMOYO_MOUNT_MAKE_PRIVATE_KEYWORD) ||
+ !strcmp(type, TOMOYO_MOUNT_MAKE_SLAVE_KEYWORD) ||
+ !strcmp(type, TOMOYO_MOUNT_MAKE_SHARED_KEYWORD)) {
+ /* dev_name is ignored. */
+ } else if (!strcmp(type, TOMOYO_MOUNT_BIND_KEYWORD) ||
+ !strcmp(type, TOMOYO_MOUNT_MOVE_KEYWORD)) {
+ need_dev = -1; /* dev_name is a directory */
+ } else {
+ fstype = get_fs_type(type);
+ if (!fstype) {
+ error = -ENODEV;
+ goto out;
+ }
+ if (fstype->fs_flags & FS_REQUIRES_DEV)
+ /* dev_name is a block device file. */
+ need_dev = 1;
+ }
+ if (need_dev) {
+ /* Get mount point or device file. */
+ if (kern_path(dev_name, LOOKUP_FOLLOW, &path)) {
+ error = -ENOENT;
+ goto out;
+ }
+ requested_dev_name = tomoyo_realpath_from_path(&path);
+ if (!requested_dev_name) {
+ error = -ENOENT;
+ goto out;
+ }
+ } else {
+ /* Map dev_name to "<NULL>" if no dev_name given. */
+ if (!dev_name)
+ dev_name = "<NULL>";
+ requested_dev_name = tomoyo_encode(dev_name);
+ if (!requested_dev_name) {
+ error = -ENOMEM;
+ goto out;
+ }
+ }
+ rdev.name = requested_dev_name;
+ tomoyo_fill_path_info(&rdev);
+ r->param_type = TOMOYO_TYPE_MOUNT_ACL;
+ r->param.mount.need_dev = need_dev;
+ r->param.mount.dev = &rdev;
+ r->param.mount.dir = &rdir;
+ r->param.mount.type = &rtype;
+ r->param.mount.flags = flags;
+ do {
+ tomoyo_check_acl(r, tomoyo_check_mount_acl);
+ error = tomoyo_audit_mount_log(r);
+ } while (error == TOMOYO_RETRY_REQUEST);
+ out:
+ kfree(requested_dev_name);
+ kfree(requested_dir_name);
+ if (fstype)
+ put_filesystem(fstype);
+ kfree(requested_type);
+ return error;
+}
+
+/**
+ * tomoyo_mount_permission - Check permission for mount() operation.
+ *
+ * @dev_name: Name of device file.
+ * @path: Pointer to "struct path".
+ * @type: Name of filesystem type. May be NULL.
+ * @flags: Mount options.
+ * @data_page: Optional data. May be NULL.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tomoyo_mount_permission(char *dev_name, struct path *path, char *type,
+ unsigned long flags, void *data_page)
+{
+ struct tomoyo_request_info r;
+ int error;
+ int idx;
+
+ if (tomoyo_init_request_info(&r, NULL, TOMOYO_MAC_FILE_MOUNT)
+ == TOMOYO_CONFIG_DISABLED)
+ return 0;
+ if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
+ flags &= ~MS_MGC_MSK;
+ if (flags & MS_REMOUNT) {
+ type = TOMOYO_MOUNT_REMOUNT_KEYWORD;
+ flags &= ~MS_REMOUNT;
+ }
+ if (flags & MS_MOVE) {
+ type = TOMOYO_MOUNT_MOVE_KEYWORD;
+ flags &= ~MS_MOVE;
+ }
+ if (flags & MS_BIND) {
+ type = TOMOYO_MOUNT_BIND_KEYWORD;
+ flags &= ~MS_BIND;
+ }
+ if (flags & MS_UNBINDABLE) {
+ type = TOMOYO_MOUNT_MAKE_UNBINDABLE_KEYWORD;
+ flags &= ~MS_UNBINDABLE;
+ }
+ if (flags & MS_PRIVATE) {
+ type = TOMOYO_MOUNT_MAKE_PRIVATE_KEYWORD;
+ flags &= ~MS_PRIVATE;
+ }
+ if (flags & MS_SLAVE) {
+ type = TOMOYO_MOUNT_MAKE_SLAVE_KEYWORD;
+ flags &= ~MS_SLAVE;
+ }
+ if (flags & MS_SHARED) {
+ type = TOMOYO_MOUNT_MAKE_SHARED_KEYWORD;
+ flags &= ~MS_SHARED;
+ }
+ if (!type)
+ type = "<NULL>";
+ idx = tomoyo_read_lock();
+ error = tomoyo_mount_acl(&r, dev_name, path, type, flags);
+ tomoyo_read_unlock(idx);
+ return error;
+}
+
+static bool tomoyo_same_mount_acl(const struct tomoyo_acl_info *a,
+ const struct tomoyo_acl_info *b)
+{
+ const struct tomoyo_mount_acl *p1 = container_of(a, typeof(*p1), head);
+ const struct tomoyo_mount_acl *p2 = container_of(b, typeof(*p2), head);
+ return tomoyo_same_acl_head(&p1->head, &p2->head) &&
+ tomoyo_same_name_union(&p1->dev_name, &p2->dev_name) &&
+ tomoyo_same_name_union(&p1->dir_name, &p2->dir_name) &&
+ tomoyo_same_name_union(&p1->fs_type, &p2->fs_type) &&
+ tomoyo_same_number_union(&p1->flags, &p2->flags);
+}
+
+/**
+ * tomoyo_write_mount - Write "struct tomoyo_mount_acl" list.
+ *
+ * @data: String to parse.
+ * @domain: Pointer to "struct tomoyo_domain_info".
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ *
+ * Caller holds tomoyo_read_lock().
+ */
+int tomoyo_write_mount(char *data, struct tomoyo_domain_info *domain,
+ const bool is_delete)
+{
+ struct tomoyo_mount_acl e = { .head.type = TOMOYO_TYPE_MOUNT_ACL };
+ int error = is_delete ? -ENOENT : -ENOMEM;
+ char *w[4];
+ if (!tomoyo_tokenize(data, w, sizeof(w)) || !w[3][0])
+ return -EINVAL;
+ if (!tomoyo_parse_name_union(w[0], &e.dev_name) ||
+ !tomoyo_parse_name_union(w[1], &e.dir_name) ||
+ !tomoyo_parse_name_union(w[2], &e.fs_type) ||
+ !tomoyo_parse_number_union(w[3], &e.flags))
+ goto out;
+ error = tomoyo_update_domain(&e.head, sizeof(e), is_delete, domain,
+ tomoyo_same_mount_acl, NULL);
+ out:
+ tomoyo_put_name_union(&e.dev_name);
+ tomoyo_put_name_union(&e.dir_name);
+ tomoyo_put_name_union(&e.fs_type);
+ tomoyo_put_number_union(&e.flags);
+ return error;
+}
diff --git a/security/tomoyo/path_group.c b/security/tomoyo/path_group.c
deleted file mode 100644
index c988041..0000000
--- a/security/tomoyo/path_group.c
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * security/tomoyo/path_group.c
- *
- * Copyright (C) 2005-2009 NTT DATA CORPORATION
- */
-
-#include <linux/slab.h>
-#include "common.h"
-/* The list for "struct ccs_path_group". */
-LIST_HEAD(tomoyo_path_group_list);
-
-/**
- * tomoyo_get_path_group - Allocate memory for "struct tomoyo_path_group".
- *
- * @group_name: The name of pathname group.
- *
- * Returns pointer to "struct tomoyo_path_group" on success, NULL otherwise.
- */
-struct tomoyo_path_group *tomoyo_get_path_group(const char *group_name)
-{
- struct tomoyo_path_group *entry = NULL;
- struct tomoyo_path_group *group = NULL;
- const struct tomoyo_path_info *saved_group_name;
- int error = -ENOMEM;
- if (!tomoyo_is_correct_path(group_name, 0, 0, 0) ||
- !group_name[0])
- return NULL;
- saved_group_name = tomoyo_get_name(group_name);
- if (!saved_group_name)
- return NULL;
- entry = kzalloc(sizeof(*entry), GFP_NOFS);
- if (mutex_lock_interruptible(&tomoyo_policy_lock))
- goto out;
- list_for_each_entry_rcu(group, &tomoyo_path_group_list, list) {
- if (saved_group_name != group->group_name)
- continue;
- atomic_inc(&group->users);
- error = 0;
- break;
- }
- if (error && tomoyo_memory_ok(entry)) {
- INIT_LIST_HEAD(&entry->member_list);
- entry->group_name = saved_group_name;
- saved_group_name = NULL;
- atomic_set(&entry->users, 1);
- list_add_tail_rcu(&entry->list, &tomoyo_path_group_list);
- group = entry;
- entry = NULL;
- error = 0;
- }
- mutex_unlock(&tomoyo_policy_lock);
- out:
- tomoyo_put_name(saved_group_name);
- kfree(entry);
- return !error ? group : NULL;
-}
-
-/**
- * tomoyo_write_path_group_policy - Write "struct tomoyo_path_group" list.
- *
- * @data: String to parse.
- * @is_delete: True if it is a delete request.
- *
- * Returns 0 on success, nagative value otherwise.
- */
-int tomoyo_write_path_group_policy(char *data, const bool is_delete)
-{
- struct tomoyo_path_group *group;
- struct tomoyo_path_group_member *member;
- struct tomoyo_path_group_member e = { };
- int error = is_delete ? -ENOENT : -ENOMEM;
- char *w[2];
- if (!tomoyo_tokenize(data, w, sizeof(w)) || !w[1][0])
- return -EINVAL;
- group = tomoyo_get_path_group(w[0]);
- if (!group)
- return -ENOMEM;
- e.member_name = tomoyo_get_name(w[1]);
- if (!e.member_name)
- goto out;
- if (mutex_lock_interruptible(&tomoyo_policy_lock))
- goto out;
- list_for_each_entry_rcu(member, &group->member_list, list) {
- if (member->member_name != e.member_name)
- continue;
- member->is_deleted = is_delete;
- error = 0;
- break;
- }
- if (!is_delete && error) {
- struct tomoyo_path_group_member *entry =
- tomoyo_commit_ok(&e, sizeof(e));
- if (entry) {
- list_add_tail_rcu(&entry->list, &group->member_list);
- error = 0;
- }
- }
- mutex_unlock(&tomoyo_policy_lock);
- out:
- tomoyo_put_name(e.member_name);
- tomoyo_put_path_group(group);
- return error;
-}
-
-/**
- * tomoyo_read_path_group_policy - Read "struct tomoyo_path_group" list.
- *
- * @head: Pointer to "struct tomoyo_io_buffer".
- *
- * Returns true on success, false otherwise.
- *
- * Caller holds tomoyo_read_lock().
- */
-bool tomoyo_read_path_group_policy(struct tomoyo_io_buffer *head)
-{
- struct list_head *gpos;
- struct list_head *mpos;
- list_for_each_cookie(gpos, head->read_var1, &tomoyo_path_group_list) {
- struct tomoyo_path_group *group;
- group = list_entry(gpos, struct tomoyo_path_group, list);
- list_for_each_cookie(mpos, head->read_var2,
- &group->member_list) {
- struct tomoyo_path_group_member *member;
- member = list_entry(mpos,
- struct tomoyo_path_group_member,
- list);
- if (member->is_deleted)
- continue;
- if (!tomoyo_io_printf(head, TOMOYO_KEYWORD_PATH_GROUP
- "%s %s\n",
- group->group_name->name,
- member->member_name->name))
- return false;
- }
- }
- return true;
-}
-
-/**
- * tomoyo_path_matches_group - Check whether the given pathname matches members of the given pathname group.
- *
- * @pathname: The name of pathname.
- * @group: Pointer to "struct tomoyo_path_group".
- * @may_use_pattern: True if wild card is permitted.
- *
- * Returns true if @pathname matches pathnames in @group, false otherwise.
- *
- * Caller holds tomoyo_read_lock().
- */
-bool tomoyo_path_matches_group(const struct tomoyo_path_info *pathname,
- const struct tomoyo_path_group *group,
- const bool may_use_pattern)
-{
- struct tomoyo_path_group_member *member;
- bool matched = false;
- list_for_each_entry_rcu(member, &group->member_list, list) {
- if (member->is_deleted)
- continue;
- if (!member->member_name->is_patterned) {
- if (tomoyo_pathcmp(pathname, member->member_name))
- continue;
- } else if (may_use_pattern) {
- if (!tomoyo_path_matches_pattern(pathname,
- member->member_name))
- continue;
- } else
- continue;
- matched = true;
- break;
- }
- return matched;
-}
diff --git a/security/tomoyo/realpath.c b/security/tomoyo/realpath.c
index d1b96f0..1d0bf8f 100644
--- a/security/tomoyo/realpath.c
+++ b/security/tomoyo/realpath.c
@@ -1,174 +1,162 @@
/*
* security/tomoyo/realpath.c
*
- * Get the canonicalized absolute pathnames. The basis for TOMOYO.
- *
- * Copyright (C) 2005-2009 NTT DATA CORPORATION
- *
- * Version: 2.2.0 2009/04/01
+ * Pathname calculation functions for TOMOYO.
*
+ * Copyright (C) 2005-2010 NTT DATA CORPORATION
*/
#include <linux/types.h>
#include <linux/mount.h>
#include <linux/mnt_namespace.h>
#include <linux/fs_struct.h>
-#include <linux/hash.h>
#include <linux/magic.h>
#include <linux/slab.h>
+#include <net/sock.h>
#include "common.h"
/**
* tomoyo_encode: Convert binary string to ascii string.
*
- * @buffer: Buffer for ASCII string.
- * @buflen: Size of @buffer.
- * @str: Binary string.
+ * @str: String in binary format.
+ *
+ * Returns pointer to @str in ascii format on success, NULL otherwise.
*
- * Returns 0 on success, -ENOMEM otherwise.
+ * This function uses kzalloc(), so caller must kfree() if this function
+ * didn't return NULL.
*/
-int tomoyo_encode(char *buffer, int buflen, const char *str)
+char *tomoyo_encode(const char *str)
{
- while (1) {
- const unsigned char c = *(unsigned char *) str++;
+ int len = 0;
+ const char *p = str;
+ char *cp;
+ char *cp0;
- if (tomoyo_is_valid(c)) {
- if (--buflen <= 0)
- break;
- *buffer++ = (char) c;
- if (c != '\\')
- continue;
- if (--buflen <= 0)
- break;
- *buffer++ = (char) c;
- continue;
- }
- if (!c) {
- if (--buflen <= 0)
- break;
- *buffer = '\0';
- return 0;
+ 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';
}
- buflen -= 4;
- if (buflen <= 0)
- break;
- *buffer++ = '\\';
- *buffer++ = (c >> 6) + '0';
- *buffer++ = ((c >> 3) & 7) + '0';
- *buffer++ = (c & 7) + '0';
}
- return -ENOMEM;
+ return cp0;
}
/**
- * tomoyo_realpath_from_path2 - Returns realpath(3) of the given dentry but ignores chroot'ed root.
+ * tomoyo_realpath_from_path - Returns realpath(3) of the given pathname but ignores chroot'ed root.
*
- * @path: Pointer to "struct path".
- * @newname: Pointer to buffer to return value in.
- * @newname_len: Size of @newname.
+ * @path: Pointer to "struct path".
*
- * Returns 0 on success, negative value otherwise.
+ * 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.
*/
-int tomoyo_realpath_from_path2(struct path *path, char *newname,
- int newname_len)
+char *tomoyo_realpath_from_path(struct path *path)
{
- int error = -ENOMEM;
+ char *buf = NULL;
+ char *name = NULL;
+ unsigned int buf_len = PAGE_SIZE / 2;
struct dentry *dentry = path->dentry;
- char *sp;
-
- if (!dentry || !path->mnt || !newname || newname_len <= 2048)
- return -EINVAL;
- if (dentry->d_op && dentry->d_op->d_dname) {
+ bool is_dir;
+ if (!dentry)
+ return NULL;
+ is_dir = dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode);
+ while (1) {
+ struct path ns_root = { .mnt = NULL, .dentry = NULL };
+ char *pos;
+ buf_len <<= 1;
+ kfree(buf);
+ buf = kmalloc(buf_len, GFP_NOFS);
+ if (!buf)
+ break;
+ /* Get better name for socket. */
+ if (dentry->d_sb && dentry->d_sb->s_magic == SOCKFS_MAGIC) {
+ struct inode *inode = dentry->d_inode;
+ struct socket *sock = inode ? SOCKET_I(inode) : NULL;
+ struct sock *sk = sock ? sock->sk : NULL;
+ if (sk) {
+ snprintf(buf, buf_len - 1, "socket:[family=%u:"
+ "type=%u:protocol=%u]", sk->sk_family,
+ sk->sk_type, sk->sk_protocol);
+ } else {
+ snprintf(buf, buf_len - 1, "socket:[unknown]");
+ }
+ name = tomoyo_encode(buf);
+ break;
+ }
/* For "socket:[\$]" and "pipe:[\$]". */
- static const int offset = 1536;
- sp = dentry->d_op->d_dname(dentry, newname + offset,
- newname_len - offset);
- } else {
- struct path ns_root = {.mnt = NULL, .dentry = NULL};
-
- spin_lock(&dcache_lock);
+ 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;
/* go to whatever namespace root we are under */
- sp = __d_path(path, &ns_root, newname, newname_len);
- spin_unlock(&dcache_lock);
+ pos = __d_path(path, &ns_root, buf, buf_len);
/* Prepend "/proc" prefix if using internal proc vfs mount. */
- if (!IS_ERR(sp) && (path->mnt->mnt_flags & MNT_INTERNAL) &&
+ if (!IS_ERR(pos) && (path->mnt->mnt_flags & MNT_INTERNAL) &&
(path->mnt->mnt_sb->s_magic == PROC_SUPER_MAGIC)) {
- sp -= 5;
- if (sp >= newname)
- memcpy(sp, "/proc", 5);
+ pos -= 5;
+ if (pos >= buf)
+ memcpy(pos, "/proc", 5);
else
- sp = ERR_PTR(-ENOMEM);
- }
- }
- if (IS_ERR(sp))
- error = PTR_ERR(sp);
- else
- error = tomoyo_encode(newname, sp - newname, sp);
- /* Append trailing '/' if dentry is a directory. */
- if (!error && dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)
- && *newname) {
- sp = newname + strlen(newname);
- if (*(sp - 1) != '/') {
- if (sp < newname + newname_len - 4) {
- *sp++ = '/';
- *sp = '\0';
- } else {
- error = -ENOMEM;
- }
+ pos = ERR_PTR(-ENOMEM);
}
+ if (IS_ERR(pos))
+ continue;
+ name = tomoyo_encode(pos);
+ break;
}
- if (error)
- printk(KERN_WARNING "tomoyo_realpath: Pathname too long.\n");
- return error;
-}
-
-/**
- * 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.
- *
- * These functions use kzalloc(), so the caller must call kfree()
- * if these functions didn't return NULL.
- */
-char *tomoyo_realpath_from_path(struct path *path)
-{
- char *buf = kzalloc(sizeof(struct tomoyo_page_buffer), GFP_NOFS);
-
- BUILD_BUG_ON(sizeof(struct tomoyo_page_buffer)
- <= TOMOYO_MAX_PATHNAME_LEN - 1);
- if (!buf)
- return NULL;
- if (tomoyo_realpath_from_path2(path, buf,
- TOMOYO_MAX_PATHNAME_LEN - 1) == 0)
- return buf;
kfree(buf);
- return NULL;
-}
-
-/**
- * tomoyo_realpath - Get realpath of a pathname.
- *
- * @pathname: The pathname to solve.
- *
- * Returns the realpath of @pathname on success, NULL otherwise.
- */
-char *tomoyo_realpath(const char *pathname)
-{
- struct path path;
-
- if (pathname && kern_path(pathname, LOOKUP_FOLLOW, &path) == 0) {
- char *buf = tomoyo_realpath_from_path(&path);
- path_put(&path);
- return buf;
+ 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 NULL;
+ return name;
}
/**
@@ -189,191 +177,3 @@ char *tomoyo_realpath_nofollow(const char *pathname)
}
return NULL;
}
-
-/* Memory allocated for non-string data. */
-static atomic_t tomoyo_policy_memory_size;
-/* Quota for holding policy. */
-static unsigned int tomoyo_quota_for_policy;
-
-/**
- * tomoyo_memory_ok - Check memory quota.
- *
- * @ptr: Pointer to allocated memory.
- *
- * Returns true on success, false otherwise.
- *
- * Caller holds tomoyo_policy_lock.
- * Memory pointed by @ptr will be zeroed on success.
- */
-bool tomoyo_memory_ok(void *ptr)
-{
- int allocated_len = ptr ? ksize(ptr) : 0;
- atomic_add(allocated_len, &tomoyo_policy_memory_size);
- if (ptr && (!tomoyo_quota_for_policy ||
- atomic_read(&tomoyo_policy_memory_size)
- <= tomoyo_quota_for_policy)) {
- memset(ptr, 0, allocated_len);
- return true;
- }
- printk(KERN_WARNING "ERROR: Out of memory "
- "for tomoyo_alloc_element().\n");
- if (!tomoyo_policy_loaded)
- panic("MAC Initialization failed.\n");
- return false;
-}
-
-/**
- * tomoyo_commit_ok - Check memory quota.
- *
- * @data: Data to copy from.
- * @size: Size in byte.
- *
- * Returns pointer to allocated memory on success, NULL otherwise.
- */
-void *tomoyo_commit_ok(void *data, const unsigned int size)
-{
- void *ptr = kzalloc(size, GFP_NOFS);
- if (tomoyo_memory_ok(ptr)) {
- memmove(ptr, data, size);
- memset(data, 0, size);
- return ptr;
- }
- return NULL;
-}
-
-/**
- * tomoyo_memory_free - Free memory for elements.
- *
- * @ptr: Pointer to allocated memory.
- */
-void tomoyo_memory_free(void *ptr)
-{
- atomic_sub(ksize(ptr), &tomoyo_policy_memory_size);
- kfree(ptr);
-}
-
-/*
- * tomoyo_name_list is used for holding string data used by TOMOYO.
- * Since same string data is likely used for multiple times (e.g.
- * "/lib/libc-2.5.so"), TOMOYO shares string data in the form of
- * "const struct tomoyo_path_info *".
- */
-struct list_head tomoyo_name_list[TOMOYO_MAX_HASH];
-
-/**
- * tomoyo_get_name - Allocate permanent memory for string data.
- *
- * @name: The string to store into the permernent memory.
- *
- * Returns pointer to "struct tomoyo_path_info" on success, NULL otherwise.
- */
-const struct tomoyo_path_info *tomoyo_get_name(const char *name)
-{
- struct tomoyo_name_entry *ptr;
- unsigned int hash;
- int len;
- int allocated_len;
- struct list_head *head;
-
- if (!name)
- return NULL;
- len = strlen(name) + 1;
- hash = full_name_hash((const unsigned char *) name, len - 1);
- head = &tomoyo_name_list[hash_long(hash, TOMOYO_HASH_BITS)];
- if (mutex_lock_interruptible(&tomoyo_policy_lock))
- return NULL;
- list_for_each_entry(ptr, head, list) {
- if (hash != ptr->entry.hash || strcmp(name, ptr->entry.name))
- continue;
- atomic_inc(&ptr->users);
- goto out;
- }
- ptr = kzalloc(sizeof(*ptr) + len, GFP_NOFS);
- allocated_len = ptr ? ksize(ptr) : 0;
- if (!ptr || (tomoyo_quota_for_policy &&
- atomic_read(&tomoyo_policy_memory_size) + allocated_len
- > tomoyo_quota_for_policy)) {
- kfree(ptr);
- printk(KERN_WARNING "ERROR: Out of memory "
- "for tomoyo_get_name().\n");
- if (!tomoyo_policy_loaded)
- panic("MAC Initialization failed.\n");
- ptr = NULL;
- goto out;
- }
- atomic_add(allocated_len, &tomoyo_policy_memory_size);
- ptr->entry.name = ((char *) ptr) + sizeof(*ptr);
- memmove((char *) ptr->entry.name, name, len);
- atomic_set(&ptr->users, 1);
- tomoyo_fill_path_info(&ptr->entry);
- list_add_tail(&ptr->list, head);
- out:
- mutex_unlock(&tomoyo_policy_lock);
- return ptr ? &ptr->entry : NULL;
-}
-
-/**
- * tomoyo_realpath_init - Initialize realpath related code.
- */
-void __init tomoyo_realpath_init(void)
-{
- int i;
-
- BUILD_BUG_ON(TOMOYO_MAX_PATHNAME_LEN > PATH_MAX);
- for (i = 0; i < TOMOYO_MAX_HASH; i++)
- INIT_LIST_HEAD(&tomoyo_name_list[i]);
- INIT_LIST_HEAD(&tomoyo_kernel_domain.acl_info_list);
- tomoyo_kernel_domain.domainname = tomoyo_get_name(TOMOYO_ROOT_NAME);
- /*
- * tomoyo_read_lock() is not needed because this function is
- * called before the first "delete" request.
- */
- list_add_tail_rcu(&tomoyo_kernel_domain.list, &tomoyo_domain_list);
- if (tomoyo_find_domain(TOMOYO_ROOT_NAME) != &tomoyo_kernel_domain)
- panic("Can't register tomoyo_kernel_domain");
-}
-
-/**
- * tomoyo_read_memory_counter - Check for memory usage in bytes.
- *
- * @head: Pointer to "struct tomoyo_io_buffer".
- *
- * Returns memory usage.
- */
-int tomoyo_read_memory_counter(struct tomoyo_io_buffer *head)
-{
- if (!head->read_eof) {
- const unsigned int policy
- = atomic_read(&tomoyo_policy_memory_size);
- char buffer[64];
-
- memset(buffer, 0, sizeof(buffer));
- if (tomoyo_quota_for_policy)
- snprintf(buffer, sizeof(buffer) - 1,
- " (Quota: %10u)",
- tomoyo_quota_for_policy);
- else
- buffer[0] = '\0';
- tomoyo_io_printf(head, "Policy: %10u%s\n", policy, buffer);
- tomoyo_io_printf(head, "Total: %10u\n", policy);
- head->read_eof = true;
- }
- return 0;
-}
-
-/**
- * tomoyo_write_memory_quota - Set memory quota.
- *
- * @head: Pointer to "struct tomoyo_io_buffer".
- *
- * Returns 0.
- */
-int tomoyo_write_memory_quota(struct tomoyo_io_buffer *head)
-{
- char *data = head->write_buf;
- unsigned int size;
-
- if (sscanf(data, "Policy: %u", &size) == 1)
- tomoyo_quota_for_policy = size;
- return 0;
-}
diff --git a/security/tomoyo/securityfs_if.c b/security/tomoyo/securityfs_if.c
new file mode 100644
index 0000000..e43d555
--- /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
index dedd97d..95d3f95 100644
--- a/security/tomoyo/tomoyo.c
+++ b/security/tomoyo/tomoyo.c
@@ -3,10 +3,7 @@
*
* LSM hooks for TOMOYO Linux.
*
- * Copyright (C) 2005-2009 NTT DATA CORPORATION
- *
- * Version: 2.2.0 2009/04/01
- *
+ * Copyright (C) 2005-2010 NTT DATA CORPORATION
*/
#include <linux/security.h>
@@ -96,8 +93,7 @@ static int tomoyo_bprm_check_security(struct linux_binprm *bprm)
return tomoyo_check_open_permission(domain, &bprm->file->f_path, O_RDONLY);
}
-static int tomoyo_path_truncate(struct path *path, loff_t length,
- unsigned int time_attrs)
+static int tomoyo_path_truncate(struct path *path)
{
return tomoyo_path_perm(TOMOYO_TYPE_TRUNCATE, path);
}
@@ -112,7 +108,8 @@ static int tomoyo_path_mkdir(struct path *parent, struct dentry *dentry,
int mode)
{
struct path path = { parent->mnt, dentry };
- return tomoyo_path_perm(TOMOYO_TYPE_MKDIR, &path);
+ return tomoyo_path_number_perm(TOMOYO_TYPE_MKDIR, &path,
+ mode & S_IALLUGO);
}
static int tomoyo_path_rmdir(struct path *parent, struct dentry *dentry)
@@ -133,6 +130,7 @@ static int tomoyo_path_mknod(struct path *parent, struct dentry *dentry,
{
struct path path = { parent->mnt, dentry };
int type = TOMOYO_TYPE_CREATE;
+ const unsigned int perm = mode & S_IALLUGO;
switch (mode & S_IFMT) {
case S_IFCHR:
@@ -141,6 +139,12 @@ static int tomoyo_path_mknod(struct path *parent, struct dentry *dentry,
case S_IFBLK:
type = TOMOYO_TYPE_MKBLOCK;
break;
+ default:
+ goto no_dev;
+ }
+ return tomoyo_mkdev_perm(type, &path, perm, dev);
+ no_dev:
+ switch (mode & S_IFMT) {
case S_IFIFO:
type = TOMOYO_TYPE_MKFIFO;
break;
@@ -148,7 +152,7 @@ static int tomoyo_path_mknod(struct path *parent, struct dentry *dentry,
type = TOMOYO_TYPE_MKSOCK;
break;
}
- return tomoyo_path_perm(type, &path);
+ return tomoyo_path_number_perm(type, &path, perm);
}
static int tomoyo_path_link(struct dentry *old_dentry, struct path *new_dir,
@@ -173,7 +177,7 @@ static int tomoyo_file_fcntl(struct file *file, unsigned int cmd,
unsigned long arg)
{
if (cmd == F_SETFL && ((arg ^ file->f_flags) & O_APPEND))
- return tomoyo_check_rewrite_permission(file);
+ return tomoyo_path_perm(TOMOYO_TYPE_REWRITE, &file->f_path);
return 0;
}
@@ -189,23 +193,24 @@ static int tomoyo_dentry_open(struct file *f, const struct cred *cred)
static int tomoyo_file_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
- return tomoyo_path_perm(TOMOYO_TYPE_IOCTL, &file->f_path);
+ return tomoyo_path_number_perm(TOMOYO_TYPE_IOCTL, &file->f_path, cmd);
}
static int tomoyo_path_chmod(struct dentry *dentry, struct vfsmount *mnt,
mode_t mode)
{
struct path path = { mnt, dentry };
- return tomoyo_path_perm(TOMOYO_TYPE_CHMOD, &path);
+ return tomoyo_path_number_perm(TOMOYO_TYPE_CHMOD, &path,
+ mode & S_IALLUGO);
}
static int tomoyo_path_chown(struct path *path, uid_t uid, gid_t gid)
{
int error = 0;
if (uid != (uid_t) -1)
- error = tomoyo_path_perm(TOMOYO_TYPE_CHOWN, path);
+ error = tomoyo_path_number_perm(TOMOYO_TYPE_CHOWN, path, uid);
if (!error && gid != (gid_t) -1)
- error = tomoyo_path_perm(TOMOYO_TYPE_CHGRP, path);
+ error = tomoyo_path_number_perm(TOMOYO_TYPE_CHGRP, path, gid);
return error;
}
@@ -217,7 +222,7 @@ static int tomoyo_path_chroot(struct path *path)
static int tomoyo_sb_mount(char *dev_name, struct path *path,
char *type, unsigned long flags, void *data)
{
- return tomoyo_path_perm(TOMOYO_TYPE_MOUNT, path);
+ return tomoyo_mount_permission(dev_name, path, type, flags, data);
}
static int tomoyo_sb_umount(struct vfsmount *mnt, int flags)
@@ -277,7 +282,7 @@ static int __init tomoyo_init(void)
panic("Failure registering TOMOYO Linux");
printk(KERN_INFO "TOMOYO Linux initialized\n");
cred->security = &tomoyo_kernel_domain;
- tomoyo_realpath_init();
+ tomoyo_mm_init();
return 0;
}
diff --git a/security/tomoyo/util.c b/security/tomoyo/util.c
new file mode 100644
index 0000000..9bfc1ee
--- /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 - 1))
+ goto out;
+ domainname = cp + 1;
+ }
+ return tomoyo_correct_path(domainname);
+ out:
+ return false;
+}
+
+/**
+ * tomoyo_domain_def - Check whether the given token can be a domainname.
+ *
+ * @buffer: The token to check.
+ *
+ * Returns true if @buffer possibly be a domainname, false otherwise.
+ */
+bool tomoyo_domain_def(const unsigned char *buffer)
+{
+ return !strncmp(buffer, TOMOYO_ROOT_NAME, TOMOYO_ROOT_NAME_LEN);
+}
+
+/**
+ * tomoyo_find_domain - Find a domain by the given name.
+ *
+ * @domainname: The domainname to find.
+ *
+ * Returns pointer to "struct tomoyo_domain_info" if found, NULL otherwise.
+ *
+ * Caller holds tomoyo_read_lock().
+ */
+struct tomoyo_domain_info *tomoyo_find_domain(const char *domainname)
+{
+ struct tomoyo_domain_info *domain;
+ struct tomoyo_path_info name;
+
+ name.name = domainname;
+ tomoyo_fill_path_info(&name);
+ list_for_each_entry_rcu(domain, &tomoyo_domain_list, list) {
+ if (!domain->is_deleted &&
+ !tomoyo_pathcmp(&name, domain->domainname))
+ return domain;
+ }
+ return NULL;
+}
+
+/**
+ * tomoyo_const_part_length - Evaluate the initial length without a pattern in a token.
+ *
+ * @filename: The string to evaluate.
+ *
+ * Returns the initial length without a pattern in @filename.
+ */
+static int tomoyo_const_part_length(const char *filename)
+{
+ char c;
+ int len = 0;
+
+ if (!filename)
+ return 0;
+ while ((c = *filename++) != '\0') {
+ if (c != '\\') {
+ len++;
+ continue;
+ }
+ c = *filename++;
+ switch (c) {
+ case '\\': /* "\\" */
+ len += 2;
+ continue;
+ case '0': /* "\ooo" */
+ case '1':
+ case '2':
+ case '3':
+ c = *filename++;
+ if (c < '0' || c > '7')
+ break;
+ c = *filename++;
+ if (c < '0' || c > '7')
+ break;
+ len += 4;
+ continue;
+ }
+ break;
+ }
+ return len;
+}
+
+/**
+ * tomoyo_fill_path_info - Fill in "struct tomoyo_path_info" members.
+ *
+ * @ptr: Pointer to "struct tomoyo_path_info" to fill in.
+ *
+ * The caller sets "struct tomoyo_path_info"->name.
+ */
+void tomoyo_fill_path_info(struct tomoyo_path_info *ptr)
+{
+ const char *name = ptr->name;
+ const int len = strlen(name);
+
+ ptr->const_len = tomoyo_const_part_length(name);
+ ptr->is_dir = len && (name[len - 1] == '/');
+ ptr->is_patterned = (ptr->const_len < len);
+ ptr->hash = full_name_hash(name, len);
+}
+
+/**
+ * tomoyo_file_matches_pattern2 - Pattern matching without '/' character and "\-" pattern.
+ *
+ * @filename: The start of string to check.
+ * @filename_end: The end of string to check.
+ * @pattern: The start of pattern to compare.
+ * @pattern_end: The end of pattern to compare.
+ *
+ * Returns true if @filename matches @pattern, false otherwise.
+ */
+static bool tomoyo_file_matches_pattern2(const char *filename,
+ const char *filename_end,
+ const char *pattern,
+ const char *pattern_end)
+{
+ while (filename < filename_end && pattern < pattern_end) {
+ char c;
+ if (*pattern != '\\') {
+ if (*filename++ != *pattern++)
+ return false;
+ continue;
+ }
+ c = *filename;
+ pattern++;
+ switch (*pattern) {
+ int i;
+ int j;
+ case '?':
+ if (c == '/') {
+ return false;
+ } else if (c == '\\') {
+ if (filename[1] == '\\')
+ filename++;
+ else if (tomoyo_byte_range(filename + 1))
+ filename += 3;
+ else
+ return false;
+ }
+ break;
+ case '\\':
+ if (c != '\\')
+ return false;
+ if (*++filename != '\\')
+ return false;
+ break;
+ case '+':
+ if (!isdigit(c))
+ return false;
+ break;
+ case 'x':
+ if (!isxdigit(c))
+ return false;
+ break;
+ case 'a':
+ if (!tomoyo_alphabet_char(c))
+ return false;
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ if (c == '\\' && tomoyo_byte_range(filename + 1)
+ && strncmp(filename + 1, pattern, 3) == 0) {
+ filename += 3;
+ pattern += 2;
+ break;
+ }
+ return false; /* Not matched. */
+ case '*':
+ case '@':
+ for (i = 0; i <= filename_end - filename; i++) {
+ if (tomoyo_file_matches_pattern2(
+ filename + i, filename_end,
+ pattern + 1, pattern_end))
+ return true;
+ c = filename[i];
+ if (c == '.' && *pattern == '@')
+ break;
+ if (c != '\\')
+ continue;
+ if (filename[i + 1] == '\\')
+ i++;
+ else if (tomoyo_byte_range(filename + i + 1))
+ i += 3;
+ else
+ break; /* Bad pattern. */
+ }
+ return false; /* Not matched. */
+ default:
+ j = 0;
+ c = *pattern;
+ if (c == '$') {
+ while (isdigit(filename[j]))
+ j++;
+ } else if (c == 'X') {
+ while (isxdigit(filename[j]))
+ j++;
+ } else if (c == 'A') {
+ while (tomoyo_alphabet_char(filename[j]))
+ j++;
+ }
+ for (i = 1; i <= j; i++) {
+ if (tomoyo_file_matches_pattern2(
+ filename + i, filename_end,
+ pattern + 1, pattern_end))
+ return true;
+ }
+ return false; /* Not matched or bad pattern. */
+ }
+ filename++;
+ pattern++;
+ }
+ while (*pattern == '\\' &&
+ (*(pattern + 1) == '*' || *(pattern + 1) == '@'))
+ pattern += 2;
+ return filename == filename_end && pattern == pattern_end;
+}
+
+/**
+ * tomoyo_file_matches_pattern - Pattern matching without '/' character.
+ *
+ * @filename: The start of string to check.
+ * @filename_end: The end of string to check.
+ * @pattern: The start of pattern to compare.
+ * @pattern_end: The end of pattern to compare.
+ *
+ * Returns true if @filename matches @pattern, false otherwise.
+ */
+static bool tomoyo_file_matches_pattern(const char *filename,
+ const char *filename_end,
+ const char *pattern,
+ const char *pattern_end)
+{
+ const char *pattern_start = pattern;
+ bool first = true;
+ bool result;
+
+ while (pattern < pattern_end - 1) {
+ /* Split at "\-" pattern. */
+ if (*pattern++ != '\\' || *pattern++ != '-')
+ continue;
+ result = tomoyo_file_matches_pattern2(filename,
+ filename_end,
+ pattern_start,
+ pattern - 2);
+ if (first)
+ result = !result;
+ if (result)
+ return false;
+ first = false;
+ pattern_start = pattern;
+ }
+ result = tomoyo_file_matches_pattern2(filename, filename_end,
+ pattern_start, pattern_end);
+ return first ? result : !result;
+}
+
+/**
+ * tomoyo_path_matches_pattern2 - Do pathname pattern matching.
+ *
+ * @f: The start of string to check.
+ * @p: The start of pattern to compare.
+ *
+ * Returns true if @f matches @p, false otherwise.
+ */
+static bool tomoyo_path_matches_pattern2(const char *f, const char *p)
+{
+ const char *f_delimiter;
+ const char *p_delimiter;
+
+ while (*f && *p) {
+ f_delimiter = strchr(f, '/');
+ if (!f_delimiter)
+ f_delimiter = f + strlen(f);
+ p_delimiter = strchr(p, '/');
+ if (!p_delimiter)
+ p_delimiter = p + strlen(p);
+ if (*p == '\\' && *(p + 1) == '{')
+ goto recursive;
+ if (!tomoyo_file_matches_pattern(f, f_delimiter, p,
+ p_delimiter))
+ return false;
+ f = f_delimiter;
+ if (*f)
+ f++;
+ p = p_delimiter;
+ if (*p)
+ p++;
+ }
+ /* Ignore trailing "\*" and "\@" in @pattern. */
+ while (*p == '\\' &&
+ (*(p + 1) == '*' || *(p + 1) == '@'))
+ p += 2;
+ return !*f && !*p;
+ recursive:
+ /*
+ * The "\{" pattern is permitted only after '/' character.
+ * This guarantees that below "*(p - 1)" is safe.
+ * Also, the "\}" pattern is permitted only before '/' character
+ * so that "\{" + "\}" pair will not break the "\-" operator.
+ */
+ if (*(p - 1) != '/' || p_delimiter <= p + 3 || *p_delimiter != '/' ||
+ *(p_delimiter - 1) != '}' || *(p_delimiter - 2) != '\\')
+ return false; /* Bad pattern. */
+ do {
+ /* Compare current component with pattern. */
+ if (!tomoyo_file_matches_pattern(f, f_delimiter, p + 2,
+ p_delimiter - 2))
+ break;
+ /* Proceed to next component. */
+ f = f_delimiter;
+ if (!*f)
+ break;
+ f++;
+ /* Continue comparison. */
+ if (tomoyo_path_matches_pattern2(f, p_delimiter + 1))
+ return true;
+ f_delimiter = strchr(f, '/');
+ } while (f_delimiter);
+ return false; /* Not matched. */
+}
+
+/**
+ * tomoyo_path_matches_pattern - Check whether the given filename matches the given pattern.
+ *
+ * @filename: The filename to check.
+ * @pattern: The pattern to compare.
+ *
+ * Returns true if matches, false otherwise.
+ *
+ * The following patterns are available.
+ * \\ \ itself.
+ * \ooo Octal representation of a byte.
+ * \* Zero or more repetitions of characters other than '/'.
+ * \@ Zero or more repetitions of characters other than '/' or '.'.
+ * \? 1 byte character other than '/'.
+ * \$ One or more repetitions of decimal digits.
+ * \+ 1 decimal digit.
+ * \X One or more repetitions of hexadecimal digits.
+ * \x 1 hexadecimal digit.
+ * \A One or more repetitions of alphabet characters.
+ * \a 1 alphabet character.
+ *
+ * \- Subtraction operator.
+ *
+ * /\{dir\}/ '/' + 'One or more repetitions of dir/' (e.g. /dir/ /dir/dir/
+ * /dir/dir/dir/ ).
+ */
+bool tomoyo_path_matches_pattern(const struct tomoyo_path_info *filename,
+ const struct tomoyo_path_info *pattern)
+{
+ const char *f = filename->name;
+ const char *p = pattern->name;
+ const int len = pattern->const_len;
+
+ /* If @pattern doesn't contain pattern, I can use strcmp(). */
+ if (!pattern->is_patterned)
+ return !tomoyo_pathcmp(filename, pattern);
+ /* Don't compare directory and non-directory. */
+ if (filename->is_dir != pattern->is_dir)
+ return false;
+ /* Compare the initial length without patterns. */
+ if (strncmp(f, p, len))
+ return false;
+ f += len;
+ p += len;
+ return tomoyo_path_matches_pattern2(f, p);
+}
+
+/**
+ * tomoyo_get_exe - Get tomoyo_realpath() of current process.
+ *
+ * Returns the tomoyo_realpath() of current process on success, NULL otherwise.
+ *
+ * This function uses kzalloc(), so the caller must call kfree()
+ * if this function didn't return NULL.
+ */
+const char *tomoyo_get_exe(void)
+{
+ struct mm_struct *mm = current->mm;
+ struct vm_area_struct *vma;
+ const char *cp = NULL;
+
+ if (!mm)
+ return NULL;
+ down_read(&mm->mmap_sem);
+ for (vma = mm->mmap; vma; vma = vma->vm_next) {
+ if ((vma->vm_flags & VM_EXECUTABLE) && vma->vm_file) {
+ cp = tomoyo_realpath_from_path(&vma->vm_file->f_path);
+ break;
+ }
+ }
+ up_read(&mm->mmap_sem);
+ return cp;
+}
+
+/**
+ * tomoyo_get_mode - Get MAC mode.
+ *
+ * @profile: Profile number.
+ * @index: Index number of functionality.
+ *
+ * Returns mode.
+ */
+int tomoyo_get_mode(const u8 profile, const u8 index)
+{
+ u8 mode;
+ const u8 category = TOMOYO_MAC_CATEGORY_FILE;
+ if (!tomoyo_policy_loaded)
+ return TOMOYO_CONFIG_DISABLED;
+ mode = tomoyo_profile(profile)->config[index];
+ if (mode == TOMOYO_CONFIG_USE_DEFAULT)
+ mode = tomoyo_profile(profile)->config[category];
+ if (mode == TOMOYO_CONFIG_USE_DEFAULT)
+ mode = tomoyo_profile(profile)->default_config;
+ return mode & 3;
+}
+
+/**
+ * tomoyo_init_request_info - Initialize "struct tomoyo_request_info" members.
+ *
+ * @r: Pointer to "struct tomoyo_request_info" to initialize.
+ * @domain: Pointer to "struct tomoyo_domain_info". NULL for tomoyo_domain().
+ * @index: Index number of functionality.
+ *
+ * Returns mode.
+ */
+int tomoyo_init_request_info(struct tomoyo_request_info *r,
+ struct tomoyo_domain_info *domain, const u8 index)
+{
+ u8 profile;
+ memset(r, 0, sizeof(*r));
+ if (!domain)
+ domain = tomoyo_domain();
+ r->domain = domain;
+ profile = domain->profile;
+ r->profile = profile;
+ r->type = index;
+ r->mode = tomoyo_get_mode(profile, index);
+ return r->mode;
+}
+
+/**
+ * tomoyo_last_word - Get last component of a line.
+ *
+ * @line: A line.
+ *
+ * Returns the last word of a line.
+ */
+const char *tomoyo_last_word(const char *name)
+{
+ const char *cp = strrchr(name, ' ');
+ if (cp)
+ return cp + 1;
+ return name;
+}
+
+/**
+ * tomoyo_warn_log - Print warning or error message on console.
+ *
+ * @r: Pointer to "struct tomoyo_request_info".
+ * @fmt: The printf()'s format string, followed by parameters.
+ */
+void tomoyo_warn_log(struct tomoyo_request_info *r, const char *fmt, ...)
+{
+ va_list args;
+ char *buffer;
+ const struct tomoyo_domain_info * const domain = r->domain;
+ const struct tomoyo_profile *profile = tomoyo_profile(domain->profile);
+ switch (r->mode) {
+ case TOMOYO_CONFIG_ENFORCING:
+ if (!profile->enforcing->enforcing_verbose)
+ return;
+ break;
+ case TOMOYO_CONFIG_PERMISSIVE:
+ if (!profile->permissive->permissive_verbose)
+ return;
+ break;
+ case TOMOYO_CONFIG_LEARNING:
+ if (!profile->learning->learning_verbose)
+ return;
+ break;
+ }
+ buffer = kmalloc(4096, GFP_NOFS);
+ if (!buffer)
+ return;
+ va_start(args, fmt);
+ vsnprintf(buffer, 4095, fmt, args);
+ va_end(args);
+ buffer[4095] = '\0';
+ printk(KERN_WARNING "%s: Access %s denied for %s\n",
+ r->mode == TOMOYO_CONFIG_ENFORCING ? "ERROR" : "WARNING", buffer,
+ tomoyo_last_word(domain->domainname->name));
+ kfree(buffer);
+}
+
+/**
+ * tomoyo_domain_quota_is_ok - Check for domain's quota.
+ *
+ * @r: Pointer to "struct tomoyo_request_info".
+ *
+ * Returns true if the domain is not exceeded quota, false otherwise.
+ *
+ * Caller holds tomoyo_read_lock().
+ */
+bool tomoyo_domain_quota_is_ok(struct tomoyo_request_info *r)
+{
+ unsigned int count = 0;
+ struct tomoyo_domain_info *domain = r->domain;
+ struct tomoyo_acl_info *ptr;
+
+ if (r->mode != TOMOYO_CONFIG_LEARNING)
+ return false;
+ if (!domain)
+ return true;
+ list_for_each_entry_rcu(ptr, &domain->acl_info_list, list) {
+ if (ptr->is_deleted)
+ continue;
+ switch (ptr->type) {
+ u16 perm;
+ u8 i;
+ case TOMOYO_TYPE_PATH_ACL:
+ perm = container_of(ptr, struct tomoyo_path_acl, head)
+ ->perm;
+ for (i = 0; i < TOMOYO_MAX_PATH_OPERATION; i++)
+ if (perm & (1 << i))
+ count++;
+ if (perm & (1 << TOMOYO_TYPE_READ_WRITE))
+ count -= 2;
+ break;
+ case TOMOYO_TYPE_PATH2_ACL:
+ perm = container_of(ptr, struct tomoyo_path2_acl, head)
+ ->perm;
+ for (i = 0; i < TOMOYO_MAX_PATH2_OPERATION; i++)
+ if (perm & (1 << i))
+ count++;
+ break;
+ case TOMOYO_TYPE_PATH_NUMBER_ACL:
+ perm = container_of(ptr, struct tomoyo_path_number_acl,
+ head)->perm;
+ for (i = 0; i < TOMOYO_MAX_PATH_NUMBER_OPERATION; i++)
+ if (perm & (1 << i))
+ count++;
+ break;
+ case TOMOYO_TYPE_MKDEV_ACL:
+ perm = container_of(ptr, struct tomoyo_mkdev_acl,
+ head)->perm;
+ for (i = 0; i < TOMOYO_MAX_MKDEV_OPERATION; i++)
+ if (perm & (1 << i))
+ count++;
+ break;
+ default:
+ count++;
+ }
+ }
+ if (count < tomoyo_profile(domain->profile)->learning->
+ learning_max_entry)
+ return true;
+ if (!domain->quota_warned) {
+ domain->quota_warned = true;
+ printk(KERN_WARNING "TOMOYO-WARNING: "
+ "Domain '%s' has so many ACLs to hold. "
+ "Stopped learning mode.\n", domain->domainname->name);
+ }
+ return false;
+}
OpenPOWER on IntegriCloud