LKML Archive on lore.kernel.org
help / color / mirror / Atom feed
* [TOMOYO #12 (2.6.28-rc2-mm1) 00/11] TOMOYO Linux
@ 2008-11-04  6:08 Kentaro Takeda
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 01/11] Introduce security_path_clear() hook Kentaro Takeda
                   ` (10 more replies)
  0 siblings, 11 replies; 32+ messages in thread
From: Kentaro Takeda @ 2008-11-04  6:08 UTC (permalink / raw)
  To: Andrew Morton; +Cc: Toshiharu Harada, linux-security-module, linux-kernel

TOMOYO Linux is a pathname-based MAC extension (LSM module) for the 
Linux kernel.

This patchset is for 2.6.28-rc2-mm1.

Serge Hallyn wrote:
> Well I think the patchset is at a stage where it needs a test-spin in
> -mm (or something).
All right. ;-)
Andrew, please put this patchset to -mm.

How to try:
1. Compile kernel with CONFIG_SECURITY_TOMOYO=y.
2. 'make' and 'make install' userspace tools (ccs-tools) available at 
   http://osdn.dl.sourceforge.jp/tomoyo/30298/ .
3. Run /usr/lib/ccs/tomoyo_init_policy.sh .
4. Run following commands to set learning-mode as default.
   (This step is optional but recommended on your first try.)
   # echo '<kernel>' > /etc/tomoyo/domain_policy.conf
   # echo 'use_profile 1' >> /etc/tomoyo/domain_policy.conf
4. Reboot.
   (If you compiled kernel with CONFIG_SECURITY_{SELINUX,SMACK}=y,
    add 'security=tomoyo' to kernel cmdline.)

Run ccs-editpolicy to browse and edit policy. LiveCD-based tutorials 
are available at
http://tomoyo.sourceforge.jp/en/1.6.x/1st-step/ubuntu8.04-live/
http://tomoyo.sourceforge.jp/en/1.6.x/1st-step/centos5-live/ .
Though these tutorials use non-LSM version of TOMOYO, they are useful 
for you to know what TOMOYO is.

Regards,
--


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [TOMOYO #12 (2.6.28-rc2-mm1) 01/11] Introduce security_path_clear() hook.
  2008-11-04  6:08 [TOMOYO #12 (2.6.28-rc2-mm1) 00/11] TOMOYO Linux Kentaro Takeda
@ 2008-11-04  6:08 ` Kentaro Takeda
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 02/11] Add in_execve flag into task_struct Kentaro Takeda
                   ` (9 subsequent siblings)
  10 siblings, 0 replies; 32+ messages in thread
From: Kentaro Takeda @ 2008-11-04  6:08 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Toshiharu Harada, linux-security-module, linux-kernel,
	Kentaro Takeda, Tetsuo Handa, Al Viro, Christoph Hellwig,
	Crispin Cowan, Stephen Smalley, Casey Schaufler, James Morris

To perform DAC performed in vfs_foo() before MAC, we let security_path_foo()
save a result into our own hash table and return 0, and let security_inode_foo()
return the saved result. Since security_inode_foo() is not always called after
security_path_foo(), we need security_path_clear() to clear the hash table.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Christoph Hellwig <hch@lst.de>
Cc: Crispin Cowan <crispin@crispincowan.com>
Cc: Stephen Smalley <sds@tycho.nsa.gov>
Cc: Casey Schaufler <casey@schaufler-ca.com>
Cc: James Morris <jmorris@namei.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
---

 fs/namei.c               |    9 +++++++++
 fs/open.c                |    2 ++
 include/linux/security.h |   12 ++++++++++++
 net/unix/af_unix.c       |    1 +
 security/capability.c    |    5 +++++
 security/security.c      |    6 ++++++
 6 files changed, 35 insertions(+)

--- linux-2.6.28-rc2-mm1.orig/fs/namei.c
+++ linux-2.6.28-rc2-mm1/fs/namei.c
@@ -1566,6 +1566,7 @@ int may_open(struct nameidata *nd, int a
 			error = do_truncate(dentry, 0,
 					    ATTR_MTIME|ATTR_CTIME|ATTR_OPEN,
 					    NULL);
+			security_path_clear();
 		}
 		put_write_access(inode);
 		if (error)
@@ -1594,6 +1595,7 @@ static int __open_namei_create(struct na
 	if (error)
 		goto out_unlock;
 	error = vfs_create(dir->d_inode, path->dentry, mode, nd);
+	security_path_clear();
 out_unlock:
 	mutex_unlock(&dir->d_inode->i_mutex);
 	dput(nd->path.dentry);
@@ -2022,6 +2024,7 @@ asmlinkage long sys_mknodat(int dfd, con
 			error = vfs_mknod(nd.path.dentry->d_inode,dentry,mode,0);
 			break;
 	}
+	security_path_clear();
 out_drop_write:
 	mnt_drop_write(nd.path.mnt);
 out_dput:
@@ -2086,6 +2089,7 @@ asmlinkage long sys_mkdirat(int dfd, con
 	if (error)
 		goto out_drop_write;
 	error = vfs_mkdir(nd.path.dentry->d_inode, dentry, mode);
+	security_path_clear();
 out_drop_write:
 	mnt_drop_write(nd.path.mnt);
 out_dput:
@@ -2200,6 +2204,7 @@ static long do_rmdir(int dfd, const char
 	if (error)
 		goto exit4;
 	error = vfs_rmdir(nd.path.dentry->d_inode, dentry);
+	security_path_clear();
 exit4:
 	mnt_drop_write(nd.path.mnt);
 exit3:
@@ -2289,6 +2294,7 @@ static long do_unlinkat(int dfd, const c
 		if (error)
 			goto exit3;
 		error = vfs_unlink(nd.path.dentry->d_inode, dentry);
+		security_path_clear();
 exit3:
 		mnt_drop_write(nd.path.mnt);
 	exit2:
@@ -2374,6 +2380,7 @@ asmlinkage long sys_symlinkat(const char
 	if (error)
 		goto out_drop_write;
 	error = vfs_symlink(nd.path.dentry->d_inode, dentry, from);
+	security_path_clear();
 out_drop_write:
 	mnt_drop_write(nd.path.mnt);
 out_dput:
@@ -2475,6 +2482,7 @@ asmlinkage long sys_linkat(int olddfd, c
 	if (error)
 		goto out_drop_write;
 	error = vfs_link(old_path.dentry, nd.path.dentry->d_inode, new_dentry);
+	security_path_clear();
 out_drop_write:
 	mnt_drop_write(nd.path.mnt);
 out_dput:
@@ -2715,6 +2723,7 @@ asmlinkage long sys_renameat(int olddfd,
 		goto exit6;
 	error = vfs_rename(old_dir->d_inode, old_dentry,
 				   new_dir->d_inode, new_dentry);
+	security_path_clear();
 exit6:
 	mnt_drop_write(oldnd.path.mnt);
 exit5:
--- linux-2.6.28-rc2-mm1.orig/fs/open.c
+++ linux-2.6.28-rc2-mm1/fs/open.c
@@ -277,6 +277,7 @@ static long do_sys_truncate(const char _
 	if (!error) {
 		DQUOT_INIT(inode);
 		error = do_truncate(path.dentry, length, 0, NULL);
+		security_path_clear();
 	}
 
 put_write_and_out:
@@ -335,6 +336,7 @@ static long do_sys_ftruncate(unsigned in
 					       ATTR_MTIME|ATTR_CTIME, file);
 	if (!error)
 		error = do_truncate(dentry, length, ATTR_MTIME|ATTR_CTIME, file);
+	security_path_clear();
 out_putf:
 	fput(file);
 out:
--- linux-2.6.28-rc2-mm1.orig/include/linux/security.h
+++ linux-2.6.28-rc2-mm1/include/linux/security.h
@@ -523,6 +523,12 @@ static inline void security_free_mnt_opt
  *	@inode contains a pointer to the inode.
  *	@secid contains a pointer to the location where result will be saved.
  *	In case of failure, @secid will be set to zero.
+ * @path_clear:
+ *	Clear error code stored by security_path_*() in case
+ *	security_inode_*() was not called when DAC returned an error.
+ *	This hook allows LSM modules which use security_path_*() defer
+ *	returning LSM's error code till security_inode_*() is called so that
+ *	DAC's error (if any) is returned to the caller instead of LSM's error.
  *
  * Security hooks for file operations
  *
@@ -1398,6 +1404,7 @@ struct security_operations {
 			  struct dentry *new_dentry);
 	int (*path_rename) (struct path *old_dir, struct dentry *old_dentry,
 			    struct path *new_dir, struct dentry *new_dentry);
+	void (*path_clear) (void);
 #endif
 
 	int (*inode_alloc_security) (struct inode *inode);
@@ -2778,6 +2785,7 @@ int security_path_link(struct dentry *ol
 		       struct dentry *new_dentry);
 int security_path_rename(struct path *old_dir, struct dentry *old_dentry,
 			 struct path *new_dir, struct dentry *new_dentry);
+void security_path_clear(void);
 #else	/* CONFIG_SECURITY_PATH */
 static inline int security_path_unlink(struct path *dir, struct dentry *dentry)
 {
@@ -2828,6 +2836,10 @@ static inline int security_path_rename(s
 {
 	return 0;
 }
+
+static inline void security_path_clear(void)
+{
+}
 #endif	/* CONFIG_SECURITY_PATH */
 
 #ifdef CONFIG_KEYS
--- linux-2.6.28-rc2-mm1.orig/net/unix/af_unix.c
+++ linux-2.6.28-rc2-mm1/net/unix/af_unix.c
@@ -832,6 +832,7 @@ static int unix_bind(struct socket *sock
 		if (err)
 			goto out_mknod_drop_write;
 		err = vfs_mknod(nd.path.dentry->d_inode, dentry, mode, 0);
+		security_path_clear();
 out_mknod_drop_write:
 		mnt_drop_write(nd.path.mnt);
 		if (err)
--- linux-2.6.28-rc2-mm1.orig/security/capability.c
+++ linux-2.6.28-rc2-mm1/security/capability.c
@@ -308,6 +308,10 @@ static int cap_path_truncate(struct path
 {
 	return 0;
 }
+
+static void cap_path_clear(void)
+{
+}
 #endif
 
 static int cap_file_permission(struct file *file, int mask)
@@ -939,6 +943,7 @@ void security_fixup_ops(struct security_
 	set_to_cap_if_null(ops, path_link);
 	set_to_cap_if_null(ops, path_rename);
 	set_to_cap_if_null(ops, path_truncate);
+	set_to_cap_if_null(ops, path_clear);
 #endif
 	set_to_cap_if_null(ops, file_permission);
 	set_to_cap_if_null(ops, file_alloc_security);
--- linux-2.6.28-rc2-mm1.orig/security/security.c
+++ linux-2.6.28-rc2-mm1/security/security.c
@@ -414,6 +414,12 @@ int security_path_truncate(struct path *
 		return 0;
 	return security_ops->path_truncate(path, length, time_attrs, filp);
 }
+
+void security_path_clear(void)
+{
+	return security_ops->path_clear();
+}
+EXPORT_SYMBOL(security_path_clear);
 #endif
 
 int security_inode_create(struct inode *dir, struct dentry *dentry, int mode)

--


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [TOMOYO #12 (2.6.28-rc2-mm1) 02/11] Add in_execve flag into task_struct.
  2008-11-04  6:08 [TOMOYO #12 (2.6.28-rc2-mm1) 00/11] TOMOYO Linux Kentaro Takeda
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 01/11] Introduce security_path_clear() hook Kentaro Takeda
@ 2008-11-04  6:08 ` Kentaro Takeda
  2008-11-05 23:12   ` Andrew Morton
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 03/11] Singly linked list implementation Kentaro Takeda
                   ` (8 subsequent siblings)
  10 siblings, 1 reply; 32+ messages in thread
From: Kentaro Takeda @ 2008-11-04  6:08 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Toshiharu Harada, linux-security-module, linux-kernel,
	Tetsuo Handa, David Howells

This patch allows LSM modules to determine whether current process is in an
execve operation or not so that they can behave differently while an execve
operation is in progress.

This allows TOMOYO to dispense with a readability check on a file to be
executed under the process's current credentials, and to do it instead under
the proposed credentials.

This is required with the new COW credentials because TOMOYO is no longer
allowed to mark the state temporarily in the security struct attached to the
task_struct.

Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: David Howells <dhowells@redhat.com>
---
 fs/compat.c           |    3 +++
 fs/exec.c             |    3 +++
 include/linux/sched.h |    2 ++
 3 files changed, 8 insertions(+)

--- linux-2.6.28-rc2-mm1.orig/fs/compat.c
+++ linux-2.6.28-rc2-mm1/fs/compat.c
@@ -1396,6 +1396,7 @@ int compat_do_execve(char * filename,
 	retval = mutex_lock_interruptible(&current->cred_exec_mutex);
 	if (retval < 0)
 		goto out_free;
+	current->in_execve = 1;
 
 	retval = -ENOMEM;
 	bprm->cred = prepare_exec_creds();
@@ -1448,6 +1449,7 @@ int compat_do_execve(char * filename,
 		goto out;
 
 	/* execve succeeded */
+	current->in_execve = 0;
 	mutex_unlock(&current->cred_exec_mutex);
 	acct_update_integrals(current);
 	free_bprm(bprm);
@@ -1464,6 +1466,7 @@ out_file:
 	}
 
 out_unlock:
+	current->in_execve = 0;
 	mutex_unlock(&current->cred_exec_mutex);
 
 out_free:
--- linux-2.6.28-rc2-mm1.orig/fs/exec.c
+++ linux-2.6.28-rc2-mm1/fs/exec.c
@@ -1301,6 +1301,7 @@ int do_execve(char * filename,
 	retval = mutex_lock_interruptible(&current->cred_exec_mutex);
 	if (retval < 0)
 		goto out_free;
+	current->in_execve = 1;
 
 	retval = -ENOMEM;
 	bprm->cred = prepare_exec_creds();
@@ -1354,6 +1355,7 @@ int do_execve(char * filename,
 		goto out;
 
 	/* execve succeeded */
+	current->in_execve = 0;
 	mutex_unlock(&current->cred_exec_mutex);
 	acct_update_integrals(current);
 	free_bprm(bprm);
@@ -1372,6 +1374,7 @@ out_file:
 	}
 
 out_unlock:
+	current->in_execve = 0;
 	mutex_unlock(&current->cred_exec_mutex);
 
 out_free:
--- linux-2.6.28-rc2-mm1.orig/include/linux/sched.h
+++ linux-2.6.28-rc2-mm1/include/linux/sched.h
@@ -1095,6 +1095,8 @@ struct task_struct {
 	/* ??? */
 	unsigned int personality;
 	unsigned did_exec:1;
+	unsigned in_execve:1;	/* Tell the LSMs that the process is doing an
+				 * execve */
 	pid_t pid;
 	pid_t tgid;
 

--


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [TOMOYO #12 (2.6.28-rc2-mm1) 03/11] Singly linked list implementation.
  2008-11-04  6:08 [TOMOYO #12 (2.6.28-rc2-mm1) 00/11] TOMOYO Linux Kentaro Takeda
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 01/11] Introduce security_path_clear() hook Kentaro Takeda
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 02/11] Add in_execve flag into task_struct Kentaro Takeda
@ 2008-11-04  6:08 ` Kentaro Takeda
  2008-11-05 23:12   ` Andrew Morton
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 04/11] Introduce d_realpath() Kentaro Takeda
                   ` (7 subsequent siblings)
  10 siblings, 1 reply; 32+ messages in thread
From: Kentaro Takeda @ 2008-11-04  6:08 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Toshiharu Harada, linux-security-module, linux-kernel, Tetsuo Handa

Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Reviewed-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
---
 include/linux/list1.h |   81 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 81 insertions(+)

--- /dev/null
+++ linux-2.6.28-rc2-mm1/include/linux/list1.h
@@ -0,0 +1,81 @@
+#ifndef _LINUX_LIST1_H
+#define _LINUX_LIST1_H
+
+#include <linux/list.h>
+#include <linux/rcupdate.h>
+
+/*
+ * Singly linked list implementation.
+ *
+ * This list supports only two operations.
+ * (1) Append an entry to the tail of the list.
+ * (2) Read all entries starting from the head of the list.
+ *
+ * This list is designed for holding "write once, read many" entries.
+ * This list requires no locks for read operation.
+ * This list doesn't support "remove an entry from the list" operation.
+ */
+
+/* To reduce memory usage, this list doesn't use "->prev" pointer. */
+struct list1_head {
+	struct list1_head *next;
+};
+
+#define LIST1_HEAD_INIT(name) { &(name) }
+#define LIST1_HEAD(name) struct list1_head name = LIST1_HEAD_INIT(name)
+
+static inline void INIT_LIST1_HEAD(struct list1_head *list)
+{
+	list->next = list;
+}
+
+/* Reuse list_entry because it doesn't use "->prev" pointer. */
+#define list1_entry list_entry
+
+/* Reuse list_for_each_rcu because it doesn't use "->prev" pointer. */
+#define list1_for_each list_for_each_rcu
+/* Reuse list_for_each_entry_rcu because it doesn't use "->prev" pointer. */
+#define list1_for_each_entry list_for_each_entry_rcu
+
+/**
+ * list1_for_each_cookie - iterate over a list with cookie.
+ * @pos:        the &struct list1_head to use as a loop cursor.
+ * @cookie:     the &struct list1_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.
+ *
+ * Since list elements are never removed, we don't need to get a lock
+ * or a reference count.
+ */
+#define list1_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))
+
+/**
+ * list1_add_tail - add a new entry to list1 list.
+ * @new: new entry to be added.
+ * @head: list head to add it before.
+ *
+ * Same with list_add_tail_rcu() without "->prev" pointer.
+ *
+ * Caller must hold a lock for protecting @head.
+ */
+static inline void list1_add_tail(struct list1_head *new,
+				  struct list1_head *head)
+{
+	struct list1_head *prev = head;
+
+	new->next = head;
+	while (prev->next != head)
+		prev = prev->next;
+	rcu_assign_pointer(prev->next, new);
+}
+
+#endif

--


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [TOMOYO #12 (2.6.28-rc2-mm1) 04/11] Introduce d_realpath().
  2008-11-04  6:08 [TOMOYO #12 (2.6.28-rc2-mm1) 00/11] TOMOYO Linux Kentaro Takeda
                   ` (2 preceding siblings ...)
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 03/11] Singly linked list implementation Kentaro Takeda
@ 2008-11-04  6:08 ` Kentaro Takeda
  2008-11-05 23:12   ` Andrew Morton
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 05/11] Memory and pathname management functions Kentaro Takeda
                   ` (6 subsequent siblings)
  10 siblings, 1 reply; 32+ messages in thread
From: Kentaro Takeda @ 2008-11-04  6:08 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Toshiharu Harada, linux-security-module, linux-kernel,
	Kentaro Takeda, Tetsuo Handa

To remove factors that make pathname based access control difficult
(e.g. symbolic links, "..", "//", chroot() etc.), a variant of d_path()
which traverses up to the root of the namespace is needed.

Three differences compared to d_path().
(1) Ignores current process's root directory.
(2) Trailing '/' is added if the pathname refers to a directory.
(3) /proc/PID/ is represented as /proc/self/ if PID equals current->tgid.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/dcache.c            |   84 +++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/dcache.h |    1 
 2 files changed, 85 insertions(+)

--- linux-2.6.28-rc2-mm1.orig/fs/dcache.c
+++ linux-2.6.28-rc2-mm1/fs/dcache.c
@@ -32,6 +32,7 @@
 #include <linux/seqlock.h>
 #include <linux/swap.h>
 #include <linux/bootmem.h>
+#include <linux/magic.h>
 #include "internal.h"
 
 
@@ -1980,6 +1981,89 @@ Elong:
 }
 
 /**
+ * d_realpath - Get the realpath of a dentry.
+ *
+ * @path: Pointer to "struct path".
+ * @buffer: Pointer to buffer to return value in.
+ * @buflen: Sizeof @buffer.
+ *
+ * Returns pointer to the realpath on success, an error code othersize.
+ *
+ * If @dentry is a directory, trailing '/' is appended.
+ * /proc/PID/ is replaced by /proc/self/ if PID == task_tgid_nr_ns(current).
+ */
+char *d_realpath(struct path *path, char *buffer, int buflen)
+{
+	struct dentry *dentry = path->dentry;
+	struct vfsmount *vfsmnt = path->mnt;
+	char *end = buffer + buflen;
+
+	spin_lock(&dcache_lock);
+	spin_lock(&vfsmount_lock);
+	if (buflen < 1 || prepend(&end, &buflen, "", 1))
+		goto Elong;
+	/*
+	 * Exception: Add trailing '/' for directory.
+	 */
+	if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode) &&
+	    prepend(&end, &buflen, "/", 1))
+		goto Elong;
+	for (;;) {
+		struct dentry *parent;
+		const char *name;
+		int name_len;
+		unsigned long pid;
+
+		if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
+			/* Global root? */
+			if (vfsmnt->mnt_parent == vfsmnt)
+				break;
+			dentry = vfsmnt->mnt_mountpoint;
+			vfsmnt = vfsmnt->mnt_parent;
+			continue;
+		}
+		parent = dentry->d_parent;
+		prefetch(parent);
+		/*
+		 * Exception: Use /proc/self/ rather than /proc/\$/
+		 * for current process.
+		 */
+		name = dentry->d_name.name;
+		name_len = dentry->d_name.len;
+		if (IS_ROOT(parent) &&
+		    parent->d_sb->s_magic == PROC_SUPER_MAGIC &&
+		    !strict_strtoul(name, 10, &pid)) {
+			const pid_t tgid
+				= task_tgid_nr_ns(current,
+						  dentry->d_sb->s_fs_info);
+			if (tgid && (pid_t) pid == tgid) {
+				name = "self";
+				name_len = 4;
+			}
+		}
+		if (prepend(&end, &buflen, name, name_len))
+			goto Elong;
+		if (prepend(&end, &buflen, "/", 1))
+			goto Elong;
+		dentry = parent;
+	}
+	if (*end == '/') {
+		/* hit the slash */
+		buflen++;
+		end++;
+	}
+	if (prepend_name(&end, &buflen, &dentry->d_name))
+		goto Elong;
+ out:
+	spin_unlock(&vfsmount_lock);
+	spin_unlock(&dcache_lock);
+	return end;
+ Elong:
+	end = ERR_PTR(-ENAMETOOLONG);
+	goto out;
+}
+
+/**
  * d_path - return the path of a dentry
  * @path: path to report
  * @buf: buffer to return value in
--- linux-2.6.28-rc2-mm1.orig/include/linux/dcache.h
+++ linux-2.6.28-rc2-mm1/include/linux/dcache.h
@@ -305,6 +305,7 @@ extern char *dynamic_dname(struct dentry
 extern char *__d_path(const struct path *path, struct path *root, char *, int);
 extern char *d_path(const struct path *, char *, int);
 extern char *dentry_path(struct dentry *, char *, int);
+extern char *d_realpath(struct path *, char *, int);
 
 /* Allocation counts.. */
 

--


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [TOMOYO #12 (2.6.28-rc2-mm1) 05/11] Memory and pathname management functions.
  2008-11-04  6:08 [TOMOYO #12 (2.6.28-rc2-mm1) 00/11] TOMOYO Linux Kentaro Takeda
                   ` (3 preceding siblings ...)
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 04/11] Introduce d_realpath() Kentaro Takeda
@ 2008-11-04  6:08 ` Kentaro Takeda
  2008-11-05 23:12   ` Andrew Morton
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYO Linux Kentaro Takeda
                   ` (5 subsequent siblings)
  10 siblings, 1 reply; 32+ messages in thread
From: Kentaro Takeda @ 2008-11-04  6:08 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Toshiharu Harada, linux-security-module, linux-kernel,
	Kentaro Takeda, Tetsuo Handa

TOMOYO Linux performs pathname based access control.
To remove factors that make pathname based access control difficult
(e.g. symbolic links, "..", "//" etc.), TOMOYO Linux derives realpath
of requested pathname from "struct dentry" and "struct vfsmount".

The maximum length of string data is limited to 4000 including trailing '\0'.
Since TOMOYO Linux uses '\ooo' style representation for non ASCII printable
characters, may be TOMOYO Linux should be able to support 16336 (which means
(NAME_MAX * (PATH_MAX / (NAME_MAX + 1)) * 4 + (PATH_MAX / (NAME_MAX + 1)))
including trailing '\0'), but I think 4000 is enough for practical use.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 security/tomoyo/realpath.c |  540 +++++++++++++++++++++++++++++++++++++++++++++
 security/tomoyo/realpath.h |   60 +++++
 2 files changed, 600 insertions(+)

--- /dev/null
+++ linux-2.6.28-rc2-mm1/security/tomoyo/realpath.c
@@ -0,0 +1,540 @@
+/*
+ * security/tomoyo/realpath.c
+ *
+ * Get the canonicalized absolute pathnames. The basis for TOMOYO.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 2.2.0-pre   2008/10/10
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/mount.h>
+#include <linux/magic.h>
+#include <linux/sysctl.h>
+#include "common.h"
+#include "realpath.h"
+
+/**
+ * tmy_realpath_from_path2 - Returns realpath(3) of the given dentry but ignores chroot'ed root.
+ *
+ * @path:        Pointer to "struct path".
+ * @newname:     Pointer to buffer to return value in.
+ * @newname_len: Size of @newname.
+ *
+ * Returns 0 on success, negative value otherwise.
+ *
+ * If dentry is a directory, trailing '/' is appended.
+ * Characters out of 0x20 < c < 0x7F range are converted to
+ * \ooo style octal string.
+ * Character \ is converted to \\ string.
+ */
+int tmy_realpath_from_path2(struct path *path, char *newname, int newname_len)
+{
+	int error = -ENOMEM;
+	struct dentry *dentry = path->dentry;
+	char *sp;
+
+	if (!dentry || !path->mnt || !newname || newname_len <= 2048)
+		return -EINVAL;
+	if (dentry->d_op && dentry->d_op->d_dname) {
+		/* For "socket:[\$]" and "pipe:[\$]". */
+		static const int offset = 1536;
+		sp = dentry->d_op->d_dname(dentry, newname + offset,
+					   newname_len - offset);
+	} else {
+		path_get(path);
+		sp = d_realpath(path, newname, newname_len);
+		path_put(path);
+	}
+	if (IS_ERR(sp)) {
+		error = PTR_ERR(sp);
+	} else {
+		char *dp = newname;
+		newname += newname_len - 5;
+		while (dp <= newname) {
+			const unsigned char c = *(unsigned char *) sp++;
+			*dp++ = c;
+			if (c == '\\') {
+				*dp++ = '\\';
+			} else if (c > ' ' && c < 127) {
+				continue;
+			} else if (!c) {
+				error = 0;
+				break;
+			} else {
+				*dp++ = '\\';
+				*dp++ = (c >> 6) + '0';
+				*dp++ = ((c >> 3) & 7) + '0';
+				*dp++ = (c & 7) + '0';
+			}
+		}
+	}
+	if (error)
+		printk(KERN_WARNING "tmy_realpath: Pathname too long.\n");
+	return error;
+}
+
+/**
+ * tmy_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 tmy_alloc(), so the caller must call tmy_free()
+ * if these functions didn't return NULL.
+ */
+char *tmy_realpath_from_path(struct path *path)
+{
+	char *buf = tmy_alloc(sizeof(struct tmy_page_buffer));
+
+	if (buf && tmy_realpath_from_path2(path, buf,
+					     TMY_MAX_PATHNAME_LEN - 1) == 0)
+		return buf;
+	tmy_free(buf);
+	return NULL;
+}
+
+/**
+ * tmy_realpath - Get realpath of a pathname.
+ *
+ * @pathname: The pathname to solve.
+ *
+ * Returns the realpath of @pathname on success, NULL otherwise.
+ */
+char *tmy_realpath(const char *pathname)
+{
+	struct nameidata nd;
+
+	if (pathname && path_lookup(pathname, LOOKUP_FOLLOW, &nd) == 0) {
+		char *buf = tmy_realpath_from_path(&nd.path);
+		path_put(&nd.path);
+		return buf;
+	}
+	return NULL;
+}
+
+/**
+ * tmy_realpath_nofollow - Get realpath of a pathname.
+ *
+ * @pathname: The pathname to solve.
+ *
+ * Returns the realpath of @pathname on success, NULL otherwise.
+ */
+char *tmy_realpath_nofollow(const char *pathname)
+{
+	struct nameidata nd;
+
+	if (pathname && path_lookup(pathname, 0, &nd) == 0) {
+		char *buf = tmy_realpath_from_path(&nd.path);
+		path_put(&nd.path);
+		return buf;
+	}
+	return NULL;
+}
+
+/**
+ * round_up - Round up an integer so that the returned pointers are appropriately aligned.
+ *
+ * @size: Size in bytes.
+ *
+ * Returns rounded value of @size.
+ *
+ * FIXME: Are there more requirements that is needed for assigning value
+ * atomically?
+ */
+static inline unsigned int round_up(const unsigned int size)
+{
+	if (sizeof(void *) >= sizeof(long))
+		return ((size + sizeof(void *) - 1)
+			/ sizeof(void *)) * sizeof(void *);
+	else
+		return ((size + sizeof(long) - 1)
+			/ sizeof(long)) * sizeof(long);
+}
+
+/* Memory allocated for non-string data. */
+static unsigned int allocated_memory_for_elements;
+/* Quota for holding non-string data. */
+static unsigned int quota_for_elements;
+
+/**
+ * tmy_alloc_element - Allocate permanent memory for structures.
+ *
+ * @size: Size in bytes.
+ *
+ * Returns pointer to allocated memory on success, NULL otherwise.
+ *
+ * The RAM is chunked, so NEVER try to kfree() the returned pointer.
+ */
+void *tmy_alloc_element(const unsigned int size)
+{
+	static char *buf;
+	static DEFINE_MUTEX(lock);
+	static unsigned int buf_used_len = PAGE_SIZE;
+	char *ptr = NULL;
+	const unsigned int word_aligned_size = round_up(size);
+
+	if (word_aligned_size > PAGE_SIZE)
+		return NULL;
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&lock);
+	if (buf_used_len + word_aligned_size > PAGE_SIZE) {
+		if (!quota_for_elements || allocated_memory_for_elements
+		    + PAGE_SIZE <= quota_for_elements)
+			ptr = kzalloc(PAGE_SIZE, GFP_KERNEL);
+		if (!ptr) {
+			printk(KERN_WARNING "ERROR: Out of memory "
+			       "for tmy_alloc_element().\n");
+			if (!sbin_init_started)
+				panic("MAC Initialization failed.\n");
+		} else {
+			buf = ptr;
+			allocated_memory_for_elements += PAGE_SIZE;
+			buf_used_len = word_aligned_size;
+			ptr = buf;
+		}
+	} else if (word_aligned_size) {
+		int i;
+		ptr = buf + buf_used_len;
+		buf_used_len += word_aligned_size;
+		for (i = 0; i < word_aligned_size; i++) {
+			if (!ptr[i])
+				continue;
+			printk(KERN_ERR "WARNING: Reserved memory was tainted! "
+			       "The system might go wrong.\n");
+			ptr[i] = '\0';
+		}
+	}
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	return ptr;
+}
+
+/* Memory allocated for string data. */
+static unsigned int allocated_memory_for_savename;
+/* Quota for holding string data. */
+static unsigned int quota_for_savename;
+
+/*
+ * TOMOYO uses this hash only when appending a string into the string
+ * table. Frequency of appending strings is very low. So we don't need
+ * large (e.g. 64k) hash size. 256 will be sufficient.
+ */
+#define MAX_HASH 256
+
+/* Structure for string data. */
+struct name_entry {
+	struct list1_head list;
+	struct path_info entry;
+};
+
+/* Structure for available memory region. */
+struct free_memory_block_list {
+	struct list_head list;
+	char *ptr;             /* Pointer to a free area. */
+	int len;               /* Length of the area.     */
+};
+
+/*
+ * The list for "struct name_entry".
+ *
+ * This list is updated only inside tmy_save_name(), thus
+ * no global mutex exists.
+ */
+static struct list1_head name_list[MAX_HASH];
+
+/**
+ * tmy_save_name - Allocate permanent memory for string data.
+ *
+ * @name: The string to store into the permernent memory.
+ *
+ * Returns pointer to "struct path_info" on success, NULL otherwise.
+ *
+ * The RAM is shared, so NEVER try to modify or kfree() the returned name.
+ */
+const struct path_info *tmy_save_name(const char *name)
+{
+	static LIST_HEAD(fmb_list);
+	static DEFINE_MUTEX(lock);
+	struct name_entry *ptr;
+	unsigned int hash;
+	struct free_memory_block_list *fmb;
+	int len;
+	char *cp;
+
+	if (!name)
+		return NULL;
+	len = strlen(name) + 1;
+	if (len > TMY_MAX_PATHNAME_LEN) {
+		printk(KERN_WARNING "ERROR: Name too long "
+		       "for tmy_save_name().\n");
+		return NULL;
+	}
+	hash = full_name_hash((const unsigned char *) name, len - 1);
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &name_list[hash % MAX_HASH], list) {
+		if (hash == ptr->entry.hash && !strcmp(name, ptr->entry.name))
+			goto out;
+	}
+	list_for_each_entry(fmb, &fmb_list, list) {
+		if (len <= fmb->len)
+			goto ready;
+	}
+	if (!quota_for_savename || allocated_memory_for_savename + PAGE_SIZE
+	    <= quota_for_savename)
+		cp = kzalloc(PAGE_SIZE, GFP_KERNEL);
+	else
+		cp = NULL;
+	fmb = kzalloc(sizeof(*fmb), GFP_KERNEL);
+	if (!cp || !fmb) {
+		kfree(cp);
+		kfree(fmb);
+		printk(KERN_WARNING "ERROR: Out of memory "
+		       "for tmy_save_name().\n");
+		if (!sbin_init_started)
+			panic("MAC Initialization failed.\n");
+		ptr = NULL;
+		goto out;
+	}
+	allocated_memory_for_savename += PAGE_SIZE;
+	list_add(&fmb->list, &fmb_list);
+	fmb->ptr = cp;
+	fmb->len = PAGE_SIZE;
+ready:
+	ptr = tmy_alloc_element(sizeof(*ptr));
+	if (!ptr)
+		goto out;
+	ptr->entry.name = fmb->ptr;
+	memmove(fmb->ptr, name, len);
+	tmy_fill_path_info(&ptr->entry);
+	fmb->ptr += len;
+	fmb->len -= len;
+	list1_add_tail(&ptr->list, &name_list[hash % MAX_HASH]);
+	if (fmb->len == 0) {
+		list_del(&fmb->list);
+		kfree(fmb);
+	}
+out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	return ptr ? &ptr->entry : NULL;
+}
+
+/**
+ * tmy_realpath_init - Initialize realpath related code.
+ *
+ * Returns 0.
+ */
+static int __init tmy_realpath_init(void)
+{
+	int i;
+
+	if (TMY_MAX_PATHNAME_LEN > PAGE_SIZE)
+		panic("Bad size.");
+	for (i = 0; i < MAX_HASH; i++)
+		INIT_LIST1_HEAD(&name_list[i]);
+	INIT_LIST1_HEAD(&KERNEL_DOMAIN.acl_info_list);
+	KERNEL_DOMAIN.domainname = tmy_save_name(ROOT_NAME);
+	list1_add_tail(&KERNEL_DOMAIN.list, &domain_list);
+	if (tmy_find_domain(ROOT_NAME) != &KERNEL_DOMAIN)
+		panic("Can't register KERNEL_DOMAIN");
+	return 0;
+}
+
+security_initcall(tmy_realpath_init);
+
+/* Memory allocated for temporal purpose. */
+static atomic_t dynamic_memory_size;
+
+/**
+ * tmy_alloc - Allocate memory for temporal purpose.
+ *
+ * @size: Size in bytes.
+ *
+ * Returns pointer to allocated memory on success, NULL otherwise.
+ */
+void *tmy_alloc(const size_t size)
+{
+	void *p = kzalloc(size, GFP_KERNEL);
+	if (p)
+		atomic_add(ksize(p), &dynamic_memory_size);
+	return p;
+}
+
+/**
+ * tmy_free - Release memory allocated by tmy_alloc().
+ *
+ * @p: Pointer returned by tmy_alloc(). May be NULL.
+ *
+ * Returns nothing.
+ */
+void tmy_free(const void *p)
+{
+	if (p)
+		atomic_sub(ksize(p), &dynamic_memory_size);
+	kfree(p);
+}
+
+static int tmy_print_ascii(const char *sp, const char *cp,
+			   int *buflen0, char **end0)
+{
+	int error = -ENOMEM;
+	int buflen = *buflen0;
+	char *end = *end0;
+
+	while (sp <= cp) {
+		unsigned char c;
+
+		c = *(unsigned char *) cp;
+		if (c == '\\') {
+			buflen -= 2;
+			if (buflen < 0)
+				goto out;
+			*--end = '\\';
+			*--end = '\\';
+		} else if (c > ' ' && c < 127) {
+			if (--buflen < 0)
+				goto out;
+			*--end = (char) c;
+		} else {
+			buflen -= 4;
+			if (buflen < 0)
+				goto out;
+			*--end = (c & 7) + '0';
+			*--end = ((c >> 3) & 7) + '0';
+			*--end = (c >> 6) + '0';
+			*--end = '\\';
+		}
+		cp--;
+	}
+
+	*buflen0 = buflen;
+	*end0 = end;
+	error = 0;
+out:
+	return error;
+}
+
+
+/* tmy_realpath_from_path2() for "struct ctl_table". */
+static int tmy_sysctl_path(struct ctl_table *table, char *buffer, int buflen)
+{
+	int error = -ENOMEM;
+	char *end = buffer + buflen;
+
+	if (buflen < 256)
+		goto out;
+
+	*--end = '\0';
+	buflen--;
+
+	buflen -= 9; /* for "/proc/sys" prefix */
+
+	while (table) {
+		char buf[32];
+		const char *sp = table->procname;
+		const char *cp;
+
+		if (!sp) {
+			memset(buf, 0, sizeof(buf));
+			snprintf(buf, sizeof(buf) - 1, "=%d=", table->ctl_name);
+			sp = buf;
+		}
+		cp = strchr(sp, '\0') - 1;
+
+		if (tmy_print_ascii(sp, cp, &buflen, &end))
+			goto out;
+
+		if (--buflen < 0)
+			goto out;
+
+		*--end = '/';
+		table = table->parent;
+	}
+
+	/* Move the pathname to the top of the buffer. */
+	memmove(buffer, "/proc/sys", 9);
+	memmove(buffer + 9, end, strlen(end) + 1);
+	error = 0;
+out:
+	return error;
+}
+
+/**
+ * sysctlpath_from_table - return the realpath of a ctl_table.
+ * @table: pointer to "struct ctl_table".
+ *
+ * Returns realpath(3) of the @table on success.
+ * Returns NULL on failure.
+ *
+ * This function uses tmy_alloc(), so the caller must call tmy_free()
+ * if this function didn't return NULL.
+ */
+char *sysctlpath_from_table(struct ctl_table *table)
+{
+	char *buf = tmy_alloc(TMY_MAX_PATHNAME_LEN);
+
+	if (buf && tmy_sysctl_path(table, buf, TMY_MAX_PATHNAME_LEN - 1) == 0)
+		return buf;
+	tmy_free(buf);
+	return NULL;
+}
+
+/**
+ * tmy_read_memory_counter - Check for memory usage.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns memory usage.
+ */
+int tmy_read_memory_counter(struct tmy_io_buffer *head)
+{
+	if (!head->read_eof) {
+		const unsigned int shared = allocated_memory_for_savename;
+		const unsigned int private = allocated_memory_for_elements;
+		const unsigned int dynamic = atomic_read(&dynamic_memory_size);
+		char buffer[64];
+
+		memset(buffer, 0, sizeof(buffer));
+		if (quota_for_savename)
+			snprintf(buffer, sizeof(buffer) - 1,
+				 "   (Quota: %10u)", quota_for_savename);
+		else
+			buffer[0] = '\0';
+		tmy_io_printf(head, "Shared:  %10u%s\n", shared, buffer);
+		if (quota_for_elements)
+			snprintf(buffer, sizeof(buffer) - 1,
+				 "   (Quota: %10u)", quota_for_elements);
+		else
+			buffer[0] = '\0';
+		tmy_io_printf(head, "Private: %10u%s\n", private, buffer);
+		tmy_io_printf(head, "Dynamic: %10u\n", dynamic);
+		tmy_io_printf(head, "Total:   %10u\n",
+			      shared + private + dynamic);
+		head->read_eof = true;
+	}
+	return 0;
+}
+
+/**
+ * tmy_write_memory_quota - Set memory quota.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns 0.
+ */
+int tmy_write_memory_quota(struct tmy_io_buffer *head)
+{
+	char *data = head->write_buf;
+	unsigned int size;
+
+	if (sscanf(data, "Shared: %u", &size) == 1)
+		quota_for_savename = size;
+	else if (sscanf(data, "Private: %u", &size) == 1)
+		quota_for_elements = size;
+	return 0;
+}
--- /dev/null
+++ linux-2.6.28-rc2-mm1/security/tomoyo/realpath.h
@@ -0,0 +1,60 @@
+/*
+ * security/tomoyo/realpath.h
+ *
+ * Get the canonicalized absolute pathnames. The basis for TOMOYO.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 2.2.0-pre   2008/10/10
+ *
+ */
+
+#ifndef _SECURITY_TOMOYO_REALPATH_H
+#define _SECURITY_TOMOYO_REALPATH_H
+
+struct path;
+struct condition_list;
+struct path_info;
+struct tmy_io_buffer;
+
+/* Returns realpath(3) of the given pathname but ignores chroot'ed root. */
+int tmy_realpath_from_path2(struct path *path, char *newname, int newname_len);
+
+/*
+ * Returns realpath(3) of the given pathname but ignores chroot'ed root.
+ * These functions use tmy_alloc(), so the caller must call tmy_free()
+ * if these functions didn't return NULL.
+ */
+char *tmy_realpath(const char *pathname);
+/* Same with tmy_realpath() except that it doesn't follow the final symlink. */
+char *tmy_realpath_nofollow(const char *pathname);
+/* Same with tmy_realpath() except that the pathname is already solved. */
+char *tmy_realpath_from_path(struct path *path);
+/* Same with tmy_realpath() except that it uses struct ctl_table. */
+char *sysctlpath_from_table(struct ctl_table *table);
+
+/*
+ * Allocate memory for ACL entry.
+ * The RAM is chunked, so NEVER try to kfree() the returned pointer.
+ */
+void *tmy_alloc_element(const unsigned int size);
+
+/*
+ * Keep the given name on the RAM.
+ * The RAM is shared, so NEVER try to modify or kfree() the returned name.
+ */
+const struct path_info *tmy_save_name(const char *name);
+
+/* Allocate memory for temporary use (e.g. permission checks). */
+void *tmy_alloc(const size_t size);
+
+/* Free memory allocated by tmy_alloc(). */
+void tmy_free(const void *p);
+
+/* Check for memory usage. */
+int tmy_read_memory_counter(struct tmy_io_buffer *head);
+
+/* Set memory quota. */
+int tmy_write_memory_quota(struct tmy_io_buffer *head);
+
+#endif /* !defined(_SECURITY_TOMOYO_REALPATH_H) */

--


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYO Linux.
  2008-11-04  6:08 [TOMOYO #12 (2.6.28-rc2-mm1) 00/11] TOMOYO Linux Kentaro Takeda
                   ` (4 preceding siblings ...)
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 05/11] Memory and pathname management functions Kentaro Takeda
@ 2008-11-04  6:08 ` Kentaro Takeda
  2008-11-05 23:12   ` Andrew Morton
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 07/11] File operation restriction part Kentaro Takeda
                   ` (4 subsequent siblings)
  10 siblings, 1 reply; 32+ messages in thread
From: Kentaro Takeda @ 2008-11-04  6:08 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Toshiharu Harada, linux-security-module, linux-kernel,
	Kentaro Takeda, Tetsuo Handa

This file contains common functions (e.g. policy I/O, pattern matching).

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 security/tomoyo/common.c | 2209 +++++++++++++++++++++++++++++++++++++++++++++++
 security/tomoyo/common.h |  320 ++++++
 2 files changed, 2529 insertions(+)

--- /dev/null
+++ linux-2.6.28-rc2-mm1/security/tomoyo/common.c
@@ -0,0 +1,2209 @@
+/*
+ * security/tomoyo/common.c
+ *
+ * Common functions for TOMOYO.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 2.2.0-pre   2008/10/10
+ *
+ */
+
+#include <linux/uaccess.h>
+#include <linux/security.h>
+#include <linux/hardirq.h>
+#include "realpath.h"
+#include "common.h"
+#include "tomoyo.h"
+
+/* Set default specified by the kernel config. */
+#define MAX_ACCEPT_ENTRY 2048
+
+/* Has /sbin/init started? */
+bool sbin_init_started;
+
+/* String table for functionality that takes 4 modes. */
+static const char *mode_4[4] = {
+	"disabled", "learning", "permissive", "enforcing"
+};
+/* String table for functionality that takes 2 modes. */
+static const char *mode_2[4] = {
+	"disabled", "enabled", "enabled", "enabled"
+};
+
+/* Table for profile. */
+static struct {
+	const char *keyword;
+	unsigned int current_value;
+	const unsigned int max_value;
+} tmy_control_array[TMY_MAX_CONTROL_INDEX] = {
+	[TMY_TOMOYO_MAC_FOR_FILE]        = { "MAC_FOR_FILE",        0, 3 },
+	[TMY_TOMOYO_MAX_ACCEPT_ENTRY]
+	= { "MAX_ACCEPT_ENTRY",    MAX_ACCEPT_ENTRY, INT_MAX },
+	[TMY_TOMOYO_VERBOSE]             = { "TOMOYO_VERBOSE",      1, 1 },
+};
+
+/* Profile table. Memory is allocated as needed. */
+static struct profile {
+	unsigned int value[TMY_MAX_CONTROL_INDEX];
+	const struct path_info *comment;
+} *profile_ptr[MAX_PROFILES];
+
+/* Permit policy management by non-root user? */
+static bool manage_by_non_root;
+
+/* Utility functions. */
+
+/* Open operation for /sys/kernel/security/tomoyo/ interface. */
+static int tmy_open_control(const u8 type, struct file *file);
+/* Close /sys/kernel/security/tomoyo/ interface. */
+static int tmy_close_control(struct file *file);
+/* Read operation for /sys/kernel/security/tomoyo/ interface. */
+static int tmy_read_control(struct file *file, char __user *buffer,
+			    const int buffer_len);
+/* Write operation for /sys/kernel/security/tomoyo/ interface. */
+static int tmy_write_control(struct file *file, const char __user *buffer,
+			     const int buffer_len);
+
+/**
+ * is_byte_range - Check whether the string isa \ooo style octal value.
+ *
+ * @str: Pointer to the string.
+ *
+ * Returns true if @str is a \ooo style octal value, false otherwise.
+ */
+static bool is_byte_range(const char *str)
+{
+	return *str >= '0' && *str++ <= '3' &&
+		*str >= '0' && *str++ <= '7' &&
+		*str >= '0' && *str <= '7';
+}
+
+/**
+ * is_decimal - Check whether the character is a decimal character.
+ *
+ * @c: The character to check.
+ *
+ * Returns true if @c is a decimal character, false otherwise.
+ */
+static bool is_decimal(const char c)
+{
+	return c >= '0' && c <= '9';
+}
+
+/**
+ * is_hexadecimal - Check whether the character is a hexadecimal character.
+ *
+ * @c: The character to check.
+ *
+ * Returns true if @c is a hexadecimal character, false otherwise.
+ */
+static bool is_hexadecimal(const char c)
+{
+	return (c >= '0' && c <= '9') ||
+		(c >= 'A' && c <= 'F') ||
+		(c >= 'a' && c <= 'f');
+}
+
+/**
+ * is_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 bool is_alphabet_char(const char c)
+{
+	return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
+}
+
+/**
+ * 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 u8 make_byte(const u8 c1, const u8 c2, const u8 c3)
+{
+	return ((c1 - '0') << 6) + ((c2 - '0') << 3) + (c3 - '0');
+}
+
+/**
+ * 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 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;
+}
+
+/**
+ * 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 normalize_line(unsigned char *buffer)
+{
+	unsigned char *sp = buffer;
+	unsigned char *dp = buffer;
+	bool first = true;
+
+	while (*sp && (*sp <= ' ' || *sp >= 127))
+		sp++;
+	while (*sp) {
+		if (!first)
+			*dp++ = ' ';
+		first = false;
+		while (*sp > ' ' && *sp < 127)
+			*dp++ = *sp++;
+		while (*sp && (*sp <= ' ' || *sp >= 127))
+			sp++;
+	}
+	*dp = '\0';
+}
+
+/**
+ * tmy_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
+ * @function:     The name of function calling me.
+ *
+ * Check whether the given filename follows the naming rules.
+ * Returns true if @filename follows the naming rules, false otherwise.
+ */
+bool tmy_is_correct_path(const char *filename, const s8 start_type,
+			 const s8 pattern_type, const s8 end_type,
+			 const char *function)
+{
+	bool contains_pattern = false;
+	unsigned char c;
+	unsigned char d;
+	unsigned char e;
+	const char *original_filename = filename;
+
+	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 = *(strchr(filename, '\0') - 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 ((c = *filename++) != '\0') {
+		if (c == '\\') {
+			switch ((c = *filename++)) {
+			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 '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 = make_byte(c, d, e);
+				if (c && (c <= ' ' || c >= 127))
+					continue; /* pattern is not \000 */
+			}
+			goto out;
+		} else if (c <= ' ' || c >= 127) {
+			goto out;
+		}
+	}
+	if (pattern_type == 1) { /* Must contain pattern */
+		if (!contains_pattern)
+			goto out;
+	}
+	return true;
+out:
+	printk(KERN_DEBUG "%s: Invalid pathname '%s'\n", function,
+	       original_filename);
+	return false;
+}
+
+/**
+ * tmy_is_correct_domain - Check whether the given domainname follows the naming rules.
+ * @domainname:   The domainname to check.
+ * @function:     The name of function calling me.
+ *
+ * Returns true if @domainname follows the naming rules, false otherwise.
+ */
+bool tmy_is_correct_domain(const unsigned char *domainname,
+			   const char *function)
+{
+	unsigned char c;
+	unsigned char d;
+	unsigned char e;
+	const char *org_domainname = domainname;
+
+	if (!domainname || strncmp(domainname, ROOT_NAME, ROOT_NAME_LEN))
+		goto out;
+	domainname += 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 = make_byte(c, d, e);
+					if (c && (c <= ' ' || c >= 127))
+						/* pattern is not \000 */
+						continue;
+				}
+				goto out;
+			} else if (c < ' ' || c >= 127) {
+				goto out;
+			}
+		}
+	} while (*domainname);
+	return true;
+out:
+	printk(KERN_DEBUG "%s: Invalid domainname '%s'\n", function,
+	       org_domainname);
+	return false;
+}
+
+/**
+ * tmy_is_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 tmy_is_domain_def(const unsigned char *buffer)
+{
+	return !strncmp(buffer, ROOT_NAME, ROOT_NAME_LEN);
+}
+
+/**
+ * tmy_find_domain - Find a domain by the given name.
+ *
+ * @domainname: The domainname to find.
+ *
+ * Returns pointer to "struct domain_info" if found, NULL otherwise.
+ */
+struct domain_info *tmy_find_domain(const char *domainname)
+{
+	struct domain_info *domain;
+	struct path_info name;
+
+	name.name = domainname;
+	tmy_fill_path_info(&name);
+	list1_for_each_entry(domain, &domain_list, list) {
+		if (!domain->is_deleted &&
+		    !tmy_pathcmp(&name, domain->domainname))
+			return domain;
+	}
+	return NULL;
+}
+
+/**
+ * path_depth - Evaluate the number of '/' in a string.
+ *
+ * @pathname: The string to evaluate.
+ *
+ * Returns path depth of the string.
+ *
+ * I score 2 for each of the '/' in the @pathname
+ * and score 1 if the @pathname ends with '/'.
+ */
+static int path_depth(const char *pathname)
+{
+	int i = 0;
+
+	if (pathname) {
+		char *ep = strchr(pathname, '\0');
+		if (pathname < ep--) {
+			if (*ep != '/')
+				i++;
+			while (pathname <= ep)
+				if (*ep-- == '/')
+					i += 2;
+		}
+	}
+	return i;
+}
+
+/**
+ * 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 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;
+}
+
+/**
+ * tmy_fill_path_info - Fill in "struct path_info" members.
+ *
+ * @ptr: Pointer to "struct path_info" to fill in.
+ *
+ * The caller sets "struct path_info"->name.
+ */
+void tmy_fill_path_info(struct path_info *ptr)
+{
+	const char *name = ptr->name;
+	const int len = strlen(name);
+
+	ptr->total_len = len;
+	ptr->const_len = 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);
+	ptr->depth = path_depth(name);
+}
+
+/**
+ * file_matches_to_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 file_matches_to_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 (is_byte_range(filename + 1))
+					filename += 3;
+				else
+					return false;
+			}
+			break;
+		case '\\':
+			if (c != '\\')
+				return false;
+			if (*++filename != '\\')
+				return false;
+			break;
+		case '+':
+			if (!is_decimal(c))
+				return false;
+			break;
+		case 'x':
+			if (!is_hexadecimal(c))
+				return false;
+			break;
+		case 'a':
+			if (!is_alphabet_char(c))
+				return false;
+			break;
+		case '0':
+		case '1':
+		case '2':
+		case '3':
+			if (c == '\\' && 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 (file_matches_to_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 (is_byte_range(filename + i + 1))
+					i += 3;
+				else
+					break; /* Bad pattern. */
+			}
+			return false; /* Not matched. */
+		default:
+			j = 0;
+			c = *pattern;
+			if (c == '$') {
+				while (is_decimal(filename[j]))
+					j++;
+			} else if (c == 'X') {
+				while (is_hexadecimal(filename[j]))
+					j++;
+			} else if (c == 'A') {
+				while (is_alphabet_char(filename[j]))
+					j++;
+			}
+			for (i = 1; i <= j; i++) {
+				if (file_matches_to_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;
+}
+
+/**
+ * file_matches_to_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 file_matches_to_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 = file_matches_to_pattern2(filename, filename_end,
+						  pattern_start, pattern - 2);
+		if (first)
+			result = !result;
+		if (result)
+			return false;
+		first = false;
+		pattern_start = pattern;
+	}
+	result = file_matches_to_pattern2(filename, filename_end,
+					  pattern_start, pattern_end);
+	return first ? result : !result;
+}
+
+/**
+ * tmy_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.
+ *   \*     More than or equals to 0 character other than '/'.
+ *   \@     More than or equals to 0 character other than '/' or '.'.
+ *   \?     1 byte character other than '/'.
+ *   \$     More than or equals to 1 decimal digit.
+ *   \+     1 decimal digit.
+ *   \X     More than or equals to 1 hexadecimal digit.
+ *   \x     1 hexadecimal digit.
+ *   \A     More than or equals to 1 alphabet character.
+ *   \a     1 alphabet character.
+ *   \-     Subtraction operator.
+ */
+bool tmy_path_matches_pattern(const struct path_info *filename,
+			      const struct path_info *pattern)
+{
+	/*
+	  if (!filename || !pattern)
+	  return false;
+	*/
+	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 !tmy_pathcmp(filename, pattern);
+	/* Dont compare if the number of '/' differs. */
+	if (filename->depth != pattern->depth)
+		return false;
+	/* Compare the initial length without patterns. */
+	if (strncmp(f, p, len))
+		return false;
+	f += len;
+	p += len;
+	/* Main loop. Compare each directory component. */
+	while (*f && *p) {
+		const char *f_delimiter = strchr(f, '/');
+		const char *p_delimiter = strchr(p, '/');
+		if (!f_delimiter)
+			f_delimiter = strchr(f, '\0');
+		if (!p_delimiter)
+			p_delimiter = strchr(p, '\0');
+		if (!file_matches_to_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;
+}
+
+/**
+ * tmy_io_printf - Transactional printf() to "struct tmy_io_buffer" structure.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ * @fmt:  The printf()'s format string, followed by parameters.
+ *
+ * Returns true on success, false otherwise.
+ *
+ * The snprintf() will truncate, but tmy_io_printf() won't.
+ */
+bool tmy_io_printf(struct tmy_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;
+}
+
+/**
+ * tmy_get_exe - Get tmy_realpath() of current process.
+ *
+ * Returns the tmy_realpath() of current process on success, NULL otherwise.
+ *
+ * This function uses tmy_alloc(), so the caller must call tmy_free()
+ * if this function didn't return NULL.
+ */
+static const char *tmy_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 = tmy_realpath_from_path(&vma->vm_file->f_path);
+			break;
+		}
+	}
+	up_read(&mm->mmap_sem);
+	return cp;
+}
+
+/**
+ * tmy_get_msg - Get warning message.
+ *
+ * @is_enforce: Is it enforcing mode?
+ *
+ * Returns "ERROR" or "WARNING".
+ */
+const char *tmy_get_msg(const bool is_enforce)
+{
+	if (is_enforce)
+		return "ERROR";
+	else
+		return "WARNING";
+}
+
+/**
+ * tmy_check_flags - Check mode for specified functionality.
+ *
+ * @domain: Pointer to "struct domain_info".
+ * @index:  The functionality to check mode.
+ *
+ * Returns the mode of specified functionality.
+ */
+unsigned int tmy_check_flags(const struct domain_info *domain, const u8 index)
+{
+	const u8 profile = domain->profile;
+
+	if (unlikely(in_interrupt())) {
+		static u8 count = 20;
+		if (count) {
+			count--;
+			printk(KERN_ERR "BUG: sleeping function called "
+			       "from invalid context.\n");
+			dump_stack();
+		}
+		return 0;
+	}
+	return sbin_init_started && index < TMY_MAX_CONTROL_INDEX
+#if MAX_PROFILES != 256
+		&& profile < MAX_PROFILES
+#endif
+		&& profile_ptr[profile] ?
+		profile_ptr[profile]->value[index] : 0;
+}
+
+/**
+ * tmy_verbose_mode - Check whether TOMOYO is verbose mode.
+ *
+ * @domain: Pointer to "struct domain_info".
+ *
+ * Returns true if domain policy violation warning should be printed to
+ * console.
+ */
+bool tmy_verbose_mode(const struct domain_info *domain)
+{
+	return tmy_check_flags(domain, TMY_TOMOYO_VERBOSE) != 0;
+}
+
+/**
+ * tmy_check_domain_quota - Check for domain's quota.
+ *
+ * @domain: Pointer to "struct domain_info".
+ *
+ * Returns true if the domain is not exceeded quota, false otherwise.
+ */
+bool tmy_check_domain_quota(struct domain_info * const domain)
+{
+	unsigned int count = 0;
+	struct acl_info *ptr;
+
+	if (!domain)
+		return true;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (ptr->type & ACL_DELETED)
+			continue;
+		switch (tmy_acl_type2(ptr)) {
+			struct single_path_acl_record *acl1;
+			struct double_path_acl_record *acl2;
+			u16 perm;
+		case TYPE_SINGLE_PATH_ACL:
+			acl1 = container_of(ptr, struct single_path_acl_record,
+					    head);
+			perm = acl1->perm;
+			if (perm & (1 << TMY_TYPE_EXECUTE_ACL))
+				count++;
+			if (perm &
+			    ((1 << TMY_TYPE_READ_ACL) |
+			     (1 << TMY_TYPE_WRITE_ACL)))
+				count++;
+			if (perm & (1 << TMY_TYPE_CREATE_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_UNLINK_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_MKDIR_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_RMDIR_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_MKFIFO_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_MKSOCK_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_MKBLOCK_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_MKCHAR_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_TRUNCATE_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_SYMLINK_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_REWRITE_ACL))
+				count++;
+			break;
+		case TYPE_DOUBLE_PATH_ACL:
+			acl2 = container_of(ptr, struct double_path_acl_record,
+					    head);
+			perm = acl2->perm;
+			if (perm & (1 << TMY_TYPE_LINK_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_RENAME_ACL))
+				count++;
+			break;
+		}
+	}
+	if (count < tmy_check_flags(domain, TMY_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;
+}
+
+/**
+ * tmy_find_or_assign_new_profile - Create a new profile.
+ *
+ * @profile: Profile number to create.
+ *
+ * Returns pointer to "struct profile" on success, NULL otherwise.
+ */
+static struct profile *tmy_find_or_assign_new_profile(const unsigned int
+						      profile)
+{
+	static DEFINE_MUTEX(lock);
+	struct profile *ptr = NULL;
+
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&lock);
+	if (profile < MAX_PROFILES) {
+		ptr = profile_ptr[profile];
+		if (ptr)
+			goto ok;
+		ptr = tmy_alloc_element(sizeof(*ptr));
+		if (ptr) {
+			int i;
+			for (i = 0; i < TMY_MAX_CONTROL_INDEX; i++)
+				ptr->value[i]
+					= tmy_control_array[i].current_value;
+			mb(); /* Avoid out-of-order execution. */
+			profile_ptr[profile] = ptr;
+		}
+	}
+ok:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	return ptr;
+}
+
+/**
+ * write_profile - Write profile table.
+ *
+ * @head: Pointer to "struct tmy_io_buffer"
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int write_profile(struct tmy_io_buffer *head)
+{
+	char *data = head->write_buf;
+	unsigned int i;
+	unsigned int value;
+	char *cp;
+	struct profile *profile;
+	unsigned long num;
+
+	cp = strchr(data, '-');
+	if (cp)
+		*cp = '\0';
+	if (strict_strtoul(data, 10, &num))
+		return -EINVAL;
+	if (cp)
+		data = cp + 1;
+	profile = tmy_find_or_assign_new_profile(num);
+	if (!profile)
+		return -EINVAL;
+	cp = strchr(data, '=');
+	if (!cp)
+		return -EINVAL;
+	*cp = '\0';
+	tmy_update_counter(TMY_UPDATES_COUNTER_PROFILE);
+	if (!strcmp(data, "COMMENT")) {
+		profile->comment = tmy_save_name(cp + 1);
+		return 0;
+	}
+	for (i = 0; i < TMY_MAX_CONTROL_INDEX; i++) {
+		if (strcmp(data, tmy_control_array[i].keyword))
+			continue;
+		if (sscanf(cp + 1, "%u", &value) != 1) {
+			int j;
+			const char **modes;
+			switch (i) {
+			case TMY_TOMOYO_VERBOSE:
+				modes = mode_2;
+				break;
+			default:
+				modes = 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 > tmy_control_array[i].max_value) {
+			value = tmy_control_array[i].max_value;
+		}
+		profile->value[i] = value;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+/**
+ * read_profile - Read profile table.
+ *
+ * @head: Pointer to "struct tmy_io_buffer"
+ *
+ * Returns 0.
+ */
+static int read_profile(struct tmy_io_buffer *head)
+{
+	static const int total = TMY_MAX_CONTROL_INDEX + 1;
+	int step;
+
+	if (head->read_eof)
+		return 0;
+	for (step = head->read_step; step < MAX_PROFILES * total; step++) {
+		const u8 index = step / total;
+		u8 type = step % total;
+		const struct profile *profile = profile_ptr[index];
+		head->read_step = step;
+		if (!profile)
+			continue;
+		if (!type) { /* Print profile' comment tag. */
+			if (!tmy_io_printf(head, "%u-COMMENT=%s\n",
+					   index, profile->comment ?
+					   profile->comment->name : ""))
+				break;
+			continue;
+		}
+		type--;
+		if (type < TMY_MAX_CONTROL_INDEX) {
+			const unsigned int value = profile->value[type];
+			const char **modes = NULL;
+			const char *keyword = tmy_control_array[type].keyword;
+			switch (tmy_control_array[type].max_value) {
+			case 3:
+				modes = mode_4;
+				break;
+			case 1:
+				modes = mode_2;
+				break;
+			}
+			if (modes) {
+				if (!tmy_io_printf(head, "%u-%s=%s\n", index,
+						   keyword, modes[value]))
+					break;
+			} else {
+				if (!tmy_io_printf(head, "%u-%s=%u\n", index,
+						   keyword, value))
+					break;
+			}
+		}
+	}
+	if (step == MAX_PROFILES * total)
+		head->read_eof = true;
+	return 0;
+}
+
+/* Structure for policy manager. */
+struct policy_manager_entry {
+	struct list1_head list;
+	/* A path to program or a domainname. */
+	const struct path_info *manager;
+	bool is_domain;  /* True if manager is a domainname. */
+	bool is_deleted; /* True if this entry is deleted. */
+};
+
+/*
+ * The list for "struct policy_manager_entry".
+ *
+ * This list is updated only inside update_manager_entry(), thus
+ * no global mutex exists.
+ */
+static LIST1_HEAD(policy_manager_list);
+
+/**
+ * update_manager_entry - Add a manager entry.
+ *
+ * @manager:   The path to manager or the domainnamme.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_manager_entry(const char *manager, const bool is_delete)
+{
+	struct policy_manager_entry *new_entry;
+	struct policy_manager_entry *ptr;
+	static DEFINE_MUTEX(lock);
+	const struct path_info *saved_manager;
+	int error = -ENOMEM;
+	bool is_domain = false;
+
+	if (tmy_is_domain_def(manager)) {
+		if (!tmy_is_correct_domain(manager, __func__))
+			return -EINVAL;
+		is_domain = true;
+	} else {
+		if (!tmy_is_correct_path(manager, 1, -1, -1, __func__))
+			return -EINVAL;
+	}
+	saved_manager = tmy_save_name(manager);
+	if (!saved_manager)
+		return -ENOMEM;
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &policy_manager_list, list) {
+		if (ptr->manager != saved_manager)
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = tmy_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->manager = saved_manager;
+	new_entry->is_domain = is_domain;
+	list1_add_tail(&new_entry->list, &policy_manager_list);
+	error = 0;
+out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	if (!error)
+		tmy_update_counter(TMY_UPDATES_COUNTER_MANAGER);
+	return error;
+}
+
+/**
+ * write_manager_policy - Write manager policy.
+ *
+ * @head: Pointer to "struct tmy_io_buffer"
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int write_manager_policy(struct tmy_io_buffer *head)
+{
+	char *data = head->write_buf;
+	bool is_delete = str_starts(&data, KEYWORD_DELETE);
+
+	if (!strcmp(data, "manage_by_non_root")) {
+		manage_by_non_root = !is_delete;
+		return 0;
+	}
+	return update_manager_entry(data, is_delete);
+}
+
+/**
+ * read_manager_policy - Read manager policy.
+ *
+ * @head: Pointer to "struct tmy_io_buffer"
+ *
+ * Returns 0.
+ */
+static int read_manager_policy(struct tmy_io_buffer *head)
+{
+	struct list1_head *pos;
+
+	if (head->read_eof)
+		return 0;
+	list1_for_each_cookie(pos, head->read_var2, &policy_manager_list) {
+		struct policy_manager_entry *ptr;
+		ptr = list1_entry(pos, struct policy_manager_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (!tmy_io_printf(head, "%s\n", ptr->manager->name))
+			return 0;
+	}
+	head->read_eof = true;
+	return 0;
+}
+
+/**
+ * is_policy_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.
+ */
+static bool is_policy_manager(void)
+{
+	struct policy_manager_entry *ptr;
+	const char *exe;
+	const struct task_struct *task = current;
+	const struct path_info *domainname = tmy_domain()->domainname;
+	bool found = false;
+
+	if (!sbin_init_started)
+		return true;
+	if (!manage_by_non_root && (task->cred->uid || task->cred->euid))
+		return false;
+	list1_for_each_entry(ptr, &policy_manager_list, list) {
+		if (!ptr->is_deleted && ptr->is_domain
+		    && !tmy_pathcmp(domainname, ptr->manager))
+			return true;
+	}
+	exe = tmy_get_exe();
+	if (!exe)
+		return false;
+	list1_for_each_entry(ptr, &policy_manager_list, list) {
+		if (!ptr->is_deleted && !ptr->is_domain
+		    && !strcmp(exe, ptr->manager->name)) {
+			found = true;
+			break;
+		}
+	}
+	if (!found) { /* Reduce error messages. */
+		static pid_t last_pid;
+		const pid_t pid = current->pid;
+		if (last_pid != pid) {
+			printk(KERN_WARNING "%s ( %s ) is not permitted to "
+			       "update policies.\n", domainname->name, exe);
+			last_pid = pid;
+		}
+	}
+	tmy_free(exe);
+	return found;
+}
+
+/**
+ * is_select_one - Parse select command.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ * @data: String to parse.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool is_select_one(struct tmy_io_buffer *head, const char *data)
+{
+	unsigned int pid;
+	struct domain_info *domain = NULL;
+
+	if (sscanf(data, "pid=%u", &pid) == 1) {
+		struct task_struct *p;
+		/***** CRITICAL SECTION START *****/
+		read_lock(&tasklist_lock);
+		p = find_task_by_vpid(pid);
+		if (p)
+			domain = tmy_real_domain(p);
+		read_unlock(&tasklist_lock);
+		/***** CRITICAL SECTION END *****/
+	} else if (!strncmp(data, "domain=", 7)) {
+		if (tmy_is_domain_def(data + 7))
+			domain = tmy_find_domain(data + 7);
+	} else
+		return false;
+	head->read_avail = 0;
+	tmy_io_printf(head, "# select %s\n", data);
+	head->read_single_domain = true;
+	head->read_eof = !domain;
+	if (domain) {
+		struct domain_info *d;
+		head->read_var1 = NULL;
+		list1_for_each_entry(d, &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)
+			tmy_io_printf(head, "# This is a deleted domain.\n");
+	}
+	head->write_var1 = domain;
+	return true;
+}
+
+/**
+ * write_domain_policy - Write domain policy.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int write_domain_policy(struct tmy_io_buffer *head)
+{
+	char *data = head->write_buf;
+	struct domain_info *domain = head->write_var1;
+	bool is_delete = false;
+	bool is_select = false;
+	bool is_undelete = false;
+	unsigned int profile;
+
+	if (str_starts(&data, KEYWORD_DELETE))
+		is_delete = true;
+	else if (str_starts(&data, KEYWORD_SELECT))
+		is_select = true;
+	else if (str_starts(&data, KEYWORD_UNDELETE))
+		is_undelete = true;
+	if (is_select && is_select_one(head, data))
+		return 0;
+	/* Don't allow updating policies by non manager programs. */
+	if (!is_policy_manager())
+		return -EPERM;
+	if (tmy_is_domain_def(data)) {
+		domain = NULL;
+		if (is_delete)
+			tmy_delete_domain(data);
+		else if (is_select)
+			domain = tmy_find_domain(data);
+		else if (is_undelete)
+			domain = tmy_undelete_domain(data);
+		else
+			domain = tmy_find_or_assign_new_domain(data, 0);
+		head->write_var1 = domain;
+		tmy_update_counter(TMY_UPDATES_COUNTER_DOMAIN_POLICY);
+		return 0;
+	}
+	if (!domain)
+		return -EINVAL;
+
+	if (sscanf(data, KEYWORD_USE_PROFILE "%u", &profile) == 1
+	    && profile < MAX_PROFILES) {
+		if (profile_ptr[profile] || !sbin_init_started)
+			domain->profile = (u8) profile;
+		return 0;
+	}
+	if (!strcmp(data, KEYWORD_IGNORE_GLOBAL_ALLOW_READ)) {
+		tmy_set_domain_flag(domain, is_delete,
+				    DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ);
+		return 0;
+	}
+	return tmy_write_file_policy(data, domain, is_delete);
+}
+
+/**
+ * print_single_path_acl - Print a single path ACL entry.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ * @ptr:  Pointer to "struct single_path_acl_record".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool print_single_path_acl(struct tmy_io_buffer *head,
+				  struct single_path_acl_record *ptr)
+{
+	int pos;
+	u8 bit;
+	const char *atmark = "";
+	const char *filename;
+	const u16 perm = ptr->perm;
+
+	filename = ptr->filename->name;
+	for (bit = head->read_bit; bit < MAX_SINGLE_PATH_OPERATION; bit++) {
+		const char *msg;
+		if (!(perm & (1 << bit)))
+			continue;
+		/* Print "read/write" instead of "read" and "write". */
+		if ((bit == TMY_TYPE_READ_ACL || bit == TMY_TYPE_WRITE_ACL)
+		    && (perm & (1 << TMY_TYPE_READ_WRITE_ACL)))
+			continue;
+		msg = tmy_sp2keyword(bit);
+		pos = head->read_avail;
+		if (!tmy_io_printf(head, "allow_%s %s%s\n", msg,
+				   atmark, filename))
+			goto out;
+	}
+	head->read_bit = 0;
+	return true;
+out:
+	head->read_bit = bit;
+	head->read_avail = pos;
+	return false;
+}
+
+/**
+ * print_double_path_acl - Print a double path ACL entry.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ * @ptr:  Pointer to "struct double_path_acl_record".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool print_double_path_acl(struct tmy_io_buffer *head,
+				  struct double_path_acl_record *ptr)
+{
+	int pos;
+	const char *atmark1 = "";
+	const char *atmark2 = "";
+	const char *filename1;
+	const char *filename2;
+	const u8 perm = ptr->perm;
+	u8 bit;
+
+	filename1 = ptr->filename1->name;
+	filename2 = ptr->filename2->name;
+	for (bit = head->read_bit; bit < MAX_DOUBLE_PATH_OPERATION; bit++) {
+		const char *msg;
+		if (!(perm & (1 << bit)))
+			continue;
+		msg = tmy_dp2keyword(bit);
+		pos = head->read_avail;
+		if (!tmy_io_printf(head, "allow_%s %s%s %s%s\n", msg,
+				   atmark1, filename1, atmark2, filename2))
+			goto out;
+	}
+	head->read_bit = 0;
+	return true;
+out:
+	head->read_bit = bit;
+	head->read_avail = pos;
+	return false;
+}
+
+/**
+ * print_entry - Print an ACL entry.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ * @ptr:  Pointer to an ACL entry.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool print_entry(struct tmy_io_buffer *head, struct acl_info *ptr)
+{
+	const u8 acl_type = tmy_acl_type2(ptr);
+
+	if (acl_type & ACL_DELETED)
+		return true;
+	if (acl_type == TYPE_SINGLE_PATH_ACL) {
+		struct single_path_acl_record *acl
+			= container_of(ptr, struct single_path_acl_record,
+				       head);
+		return print_single_path_acl(head, acl);
+	}
+	if (acl_type == TYPE_DOUBLE_PATH_ACL) {
+		struct double_path_acl_record *acl
+			= container_of(ptr, struct double_path_acl_record,
+				       head);
+		return print_double_path_acl(head, acl);
+	}
+	BUG(); /* This must not happen. */
+	return false;
+}
+
+/**
+ * read_domain_policy - Read domain policy.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns 0.
+ */
+static int read_domain_policy(struct tmy_io_buffer *head)
+{
+	struct list1_head *dpos;
+	struct list1_head *apos;
+
+	if (head->read_eof)
+		return 0;
+	if (head->read_step == 0)
+		head->read_step = 1;
+	list1_for_each_cookie(dpos, head->read_var1, &domain_list) {
+		struct domain_info *domain;
+		const char *quota_exceeded = "";
+		const char *transition_failed = "";
+		const char *ignore_global_allow_read = "";
+		domain = list1_entry(dpos, struct 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->flags & DOMAIN_FLAGS_TRANSITION_FAILED)
+			transition_failed = "transition_failed\n";
+		if (domain->flags & DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ)
+			ignore_global_allow_read
+				= KEYWORD_IGNORE_GLOBAL_ALLOW_READ "\n";
+		if (!tmy_io_printf(head, "%s\n" KEYWORD_USE_PROFILE "%u\n"
+				   "%s%s%s\n", domain->domainname->name,
+				   domain->profile, quota_exceeded,
+				   transition_failed, ignore_global_allow_read))
+			return 0;
+		head->read_step = 2;
+acl_loop:
+		if (head->read_step == 3)
+			goto tail_mark;
+		/* Print ACL entries in the domain. */
+		list1_for_each_cookie(apos, head->read_var2,
+				      &domain->acl_info_list) {
+			struct acl_info *ptr
+				= list1_entry(apos, struct acl_info, list);
+			if (!print_entry(head, ptr))
+				return 0;
+		}
+		head->read_step = 3;
+tail_mark:
+		if (!tmy_io_printf(head, "\n"))
+			return 0;
+		head->read_step = 1;
+		if (head->read_single_domain)
+			break;
+	}
+	head->read_eof = true;
+	return 0;
+}
+
+/**
+ * write_domain_profile - Assign profile for specified domain.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns 0 on success, -EINVAL otherwise.
+ *
+ * This is equivalent to doing
+ *
+ *     ( echo "select " $domainname; echo "use_profile " $profile ) |
+ *     /usr/lib/ccs/loadpolicy -d
+ */
+static int write_domain_profile(struct tmy_io_buffer *head)
+{
+	char *data = head->write_buf;
+	char *cp = strchr(data, ' ');
+	struct domain_info *domain;
+	unsigned long profile;
+
+	if (!cp)
+		return -EINVAL;
+	*cp = '\0';
+	domain = tmy_find_domain(cp + 1);
+	strict_strtoul(data, 10, &profile);
+	if (domain && profile < MAX_PROFILES
+	    && (profile_ptr[profile] || !sbin_init_started))
+		domain->profile = (u8) profile;
+	tmy_update_counter(TMY_UPDATES_COUNTER_DOMAIN_POLICY);
+	return 0;
+}
+
+/**
+ * read_domain_profile - Read only domainname and profile.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns list of profile number and domainname pairs.
+ *
+ * This is equivalent to doing
+ *
+ *     grep -A 1 '^<kernel>' /sys/kernel/security/tomoyo/domain_policy |
+ *     awk ' { if ( domainname == "" ) { if ( $1 == "<kernel>" )
+ *     domainname = $0; } else if ( $1 == "use_profile" ) {
+ *     print $2 " " domainname; domainname = ""; } } ; '
+ */
+static int read_domain_profile(struct tmy_io_buffer *head)
+{
+	struct list1_head *pos;
+
+	if (head->read_eof)
+		return 0;
+	list1_for_each_cookie(pos, head->read_var1, &domain_list) {
+		struct domain_info *domain;
+		domain = list1_entry(pos, struct domain_info, list);
+		if (domain->is_deleted)
+			continue;
+		if (!tmy_io_printf(head, "%u %s\n", domain->profile,
+				   domain->domainname->name))
+			return 0;
+	}
+	head->read_eof = true;
+	return 0;
+}
+
+/**
+ * write_pid: Specify PID to obtain domainname.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns 0.
+ */
+static int write_pid(struct tmy_io_buffer *head)
+{
+	unsigned long pid;
+	strict_strtoul(head->write_buf, 10, &pid);
+	head->read_step = (int) pid;
+	head->read_eof = false;
+	return 0;
+}
+
+/**
+ * read_pid - Get domainname of the specified PID.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns the domainname which the specified PID is in on success,
+ * empty string otherwise.
+ * The PID is specified by write_pid() so that the user can obtain
+ * using read()/write() interface rather than sysctl() interface.
+ */
+static int read_pid(struct tmy_io_buffer *head)
+{
+	if (head->read_avail == 0 && !head->read_eof) {
+		const int pid = head->read_step;
+		struct task_struct *p;
+		struct domain_info *domain = NULL;
+		/***** CRITICAL SECTION START *****/
+		read_lock(&tasklist_lock);
+		p = find_task_by_vpid(pid);
+		if (p)
+			domain = tmy_real_domain(p);
+		read_unlock(&tasklist_lock);
+		/***** CRITICAL SECTION END *****/
+		if (domain)
+			tmy_io_printf(head, "%d %u %s", pid, domain->profile,
+				      domain->domainname->name);
+		head->read_eof = true;
+	}
+	return 0;
+}
+
+/**
+ * write_exception_policy - Write exception policy.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int write_exception_policy(struct tmy_io_buffer *head)
+{
+	char *data = head->write_buf;
+	bool is_delete = str_starts(&data, KEYWORD_DELETE);
+
+	if (str_starts(&data, KEYWORD_KEEP_DOMAIN))
+		return tmy_write_domain_keeper_policy(data, false, is_delete);
+	if (str_starts(&data, KEYWORD_NO_KEEP_DOMAIN))
+		return tmy_write_domain_keeper_policy(data, true, is_delete);
+	if (str_starts(&data, KEYWORD_INITIALIZE_DOMAIN))
+		return tmy_write_domain_initializer_policy(data, false,
+							   is_delete);
+	if (str_starts(&data, KEYWORD_NO_INITIALIZE_DOMAIN))
+		return tmy_write_domain_initializer_policy(data, true,
+							   is_delete);
+	if (str_starts(&data, KEYWORD_ALIAS))
+		return tmy_write_alias_policy(data, is_delete);
+	if (str_starts(&data, KEYWORD_ALLOW_READ))
+		return tmy_write_globally_readable_policy(data, is_delete);
+	if (str_starts(&data, KEYWORD_FILE_PATTERN))
+		return tmy_write_pattern_policy(data, is_delete);
+	if (str_starts(&data, KEYWORD_DENY_REWRITE))
+		return tmy_write_no_rewrite_policy(data, is_delete);
+	return -EINVAL;
+}
+
+/**
+ * read_exception_policy - Read exception policy.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns 0 on success, -EINVAL otherwise.
+ */
+static int read_exception_policy(struct tmy_io_buffer *head)
+{
+	if (!head->read_eof) {
+		switch (head->read_step) {
+		case 0:
+			head->read_var2 = NULL;
+			head->read_step = 1;
+		case 1:
+			if (!tmy_read_domain_keeper_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 2;
+		case 2:
+			if (!tmy_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 (!tmy_read_domain_initializer_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 5;
+		case 5:
+			if (!tmy_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 (!tmy_read_file_pattern(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 8;
+		case 8:
+			if (!tmy_read_no_rewrite_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 9;
+		case 9:
+			head->read_eof = true;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+/* path to policy loader */
+static const char *tmy_loader = "/sbin/tomoyo-init";
+
+/**
+ * policy_loader_exists - Check whether /sbin/tomoyo-init exists.
+ *
+ * Returns true if /sbin/tomoyo-init exists, false otherwise.
+ */
+static bool 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 nameidata nd;
+
+	if (path_lookup(tmy_loader, LOOKUP_FOLLOW, &nd)) {
+		printk(KERN_INFO "Not activating Mandatory Access Control now "
+		       "since %s doesn't exist.\n", tmy_loader);
+		return false;
+	}
+	path_put(&nd.path);
+	return true;
+}
+
+/**
+ * tmy_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 tmy_load_policy(const char *filename)
+{
+	char *argv[2];
+	char *envp[3];
+
+	if (sbin_init_started)
+		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 (!policy_loader_exists())
+		return;
+
+	printk(KERN_INFO "Calling %s to load policy. Please wait.\n",
+	       tmy_loader);
+	argv[0] = (char *) tmy_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-pre   2008/10/10\n");
+	printk(KERN_INFO "Mandatory Access Control activated.\n");
+	sbin_init_started = true;
+	{ /* Check all profiles currently assigned to domains are defined. */
+		struct domain_info *domain;
+		list1_for_each_entry(domain, &domain_list, list) {
+			const u8 profile = domain->profile;
+			if (profile_ptr[profile])
+				continue;
+			panic("Profile %u (used by '%s') not defined.\n",
+			      profile, domain->domainname->name);
+		}
+	}
+}
+
+/* Policy updates counter. */
+static atomic_t updates_counter[MAX_TMY_UPDATES_COUNTER];
+
+/**
+ * tmy_update_counter - Increment policy change counter.
+ *
+ * @index: Type of policy.
+ *
+ * Returns nothing.
+ */
+void tmy_update_counter(const unsigned char index)
+{
+	if (index < MAX_TMY_UPDATES_COUNTER)
+		atomic_inc(&updates_counter[index]);
+}
+
+/**
+ * read_updates_counter - Check for policy change counter.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns how many times policy has changed since the previous check.
+ */
+static int read_updates_counter(struct tmy_io_buffer *head)
+{
+	if (head->read_eof)
+		return 0;
+	tmy_io_printf(head,
+		      "/sys/kernel/security/tomoyo/domain_policy:    %10u\n"
+		      "/sys/kernel/security/tomoyo/exception_policy: %10u\n"
+		      "/sys/kernel/security/tomoyo/profile:          %10u\n"
+		      "/sys/kernel/security/tomoyo/manager:          %10u\n",
+		      atomic_xchg(&updates_counter
+				  [TMY_UPDATES_COUNTER_DOMAIN_POLICY], 0),
+		      atomic_xchg(&updates_counter
+				  [TMY_UPDATES_COUNTER_EXCEPTION_POLICY], 0),
+		      atomic_xchg(&updates_counter
+				  [TMY_UPDATES_COUNTER_PROFILE], 0),
+		      atomic_xchg(&updates_counter
+				  [TMY_UPDATES_COUNTER_MANAGER], 0));
+	head->read_eof = true;
+	return 0;
+}
+
+/**
+ * read_version: Get version.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns version information.
+ */
+static int read_version(struct tmy_io_buffer *head)
+{
+	if (!head->read_eof) {
+		tmy_io_printf(head, "2.2.0-pre");
+		head->read_eof = true;
+	}
+	return 0;
+}
+
+/**
+ * read_self_domain - Get the current process's domainname.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns the current process's domainname.
+ */
+static int read_self_domain(struct tmy_io_buffer *head)
+{
+	if (!head->read_eof) {
+		/*
+		 * tmy_domain()->domainname != NULL
+		 * because every process belongs to a domain and
+		 * the domain's name cannot be NULL.
+		 */
+		tmy_io_printf(head, "%s", tmy_domain()->domainname->name);
+		head->read_eof = true;
+	}
+	return 0;
+}
+
+/**
+ * tmy_open_control - open() for /sys/kernel/security/tomoyo/ interface.
+ *
+ * @type: Type of interface.
+ * @file: Pointer to "struct file".
+ *
+ * Associates policy handler and returns 0 on success, -ENOMEM otherwise.
+ */
+static int tmy_open_control(const u8 type, struct file *file)
+{
+	struct tmy_io_buffer *head = tmy_alloc(sizeof(*head));
+
+	if (!head)
+		return -ENOMEM;
+	mutex_init(&head->io_sem);
+	switch (type) {
+	case TMY_DOMAINPOLICY:
+		/* /sys/kernel/security/tomoyo/domain_policy */
+		head->write = write_domain_policy;
+		head->read = read_domain_policy;
+		break;
+	case TMY_EXCEPTIONPOLICY:
+		/* /sys/kernel/security/tomoyo/exception_policy */
+		head->write = write_exception_policy;
+		head->read = read_exception_policy;
+		break;
+	case TMY_SELFDOMAIN:
+		/* /sys/kernel/security/tomoyo/self_domain */
+		head->read = read_self_domain;
+		break;
+	case TMY_DOMAIN_STATUS:
+		/* /sys/kernel/security/tomoyo/.domain_status */
+		head->write = write_domain_profile;
+		head->read = read_domain_profile;
+		break;
+	case TMY_PROCESS_STATUS:
+		/* /sys/kernel/security/tomoyo/.process_status */
+		head->write = write_pid;
+		head->read = read_pid;
+		break;
+	case TMY_VERSION:
+		/* /sys/kernel/security/tomoyo/version */
+		head->read = read_version;
+		head->readbuf_size = 128;
+		break;
+	case TMY_MEMINFO:
+		/* /sys/kernel/security/tomoyo/meminfo */
+		head->write = tmy_write_memory_quota;
+		head->read = tmy_read_memory_counter;
+		head->readbuf_size = 512;
+		break;
+	case TMY_PROFILE:
+		/* /sys/kernel/security/tomoyo/profile */
+		head->write = write_profile;
+		head->read = read_profile;
+		break;
+	case TMY_MANAGER:
+		/* /sys/kernel/security/tomoyo/manager */
+		head->write = write_manager_policy;
+		head->read = read_manager_policy;
+		break;
+	case TMY_UPDATESCOUNTER:
+		/* /sys/kernel/security/tomoyo/.updates_counter */
+		head->read = read_updates_counter;
+		break;
+	}
+	if (!(file->f_mode & FMODE_READ)) {
+		/*
+		 * No need to allocate read_buf since it is not opened
+		 * for reading.
+		 */
+		head->read = NULL;
+	} else {
+		if (!head->readbuf_size)
+			head->readbuf_size = 4096 * 2;
+		head->read_buf = tmy_alloc(head->readbuf_size);
+		if (!head->read_buf) {
+			tmy_free(head);
+			return -ENOMEM;
+		}
+	}
+	if (!(file->f_mode & FMODE_WRITE)) {
+		/*
+		 * No need to allocate write_buf since it is not opened
+		 * for writing.
+		 */
+		head->write = NULL;
+	} else if (head->write) {
+		head->writebuf_size = 4096 * 2;
+		head->write_buf = tmy_alloc(head->writebuf_size);
+		if (!head->write_buf) {
+			tmy_free(head->read_buf);
+			tmy_free(head);
+			return -ENOMEM;
+		}
+	}
+	file->private_data = head;
+	/*
+	 * Call the handler now if the file is
+	 * /sys/kernel/security/tomoyo/self_domain
+	 * so that the user can use
+	 * cat < /sys/kernel/security/tomoyo/self_domain"
+	 * to know the current process's domainname.
+	 */
+	if (type == TMY_SELFDOMAIN)
+		tmy_read_control(file, NULL, 0);
+	return 0;
+}
+
+/**
+ * tmy_read_control - read() for /sys/kernel/security/tomoyo/ interface.
+ *
+ * @file:       Pointer to "struct file".
+ * @buffer:     Poiner to buffer to write to.
+ * @buffer_len: Size of @buffer.
+ *
+ * Returns bytes read on success, negative value otherwise.
+ */
+static int tmy_read_control(struct file *file, char __user *buffer,
+			    const int buffer_len)
+{
+	int len = 0;
+	struct tmy_io_buffer *head = file->private_data;
+	char *cp;
+
+	if (!head->read)
+		return -ENOSYS;
+	if (!access_ok(VERIFY_WRITE, buffer, buffer_len))
+		return -EFAULT;
+	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:
+	mutex_unlock(&head->io_sem);
+	return len;
+}
+
+/**
+ * tmy_write_control - write() for /sys/kernel/security/tomoyo/ interface.
+ *
+ * @file:       Pointer to "struct file".
+ * @buffer:     Pointer to buffer to read from.
+ * @buffer_len: Size of @buffer.
+ *
+ * Returns @buffer_len on success, negative value otherwise.
+ */
+static int tmy_write_control(struct file *file, const char __user *buffer,
+			     const int buffer_len)
+{
+	struct tmy_io_buffer *head = file->private_data;
+	int error = buffer_len;
+	int avail_len = buffer_len;
+	char *cp0 = head->write_buf;
+
+	if (!head->write)
+		return -ENOSYS;
+	if (!access_ok(VERIFY_READ, buffer, buffer_len))
+		return -EFAULT;
+	/* Don't allow updating policies by non manager programs. */
+	if (head->write != write_pid && head->write != write_domain_policy &&
+	    !is_policy_manager())
+		return -EPERM;
+	if (mutex_lock_interruptible(&head->io_sem))
+		return -EINTR;
+	/* Read a line and dispatch it to the policy handler. */
+	while (avail_len > 0) {
+		char c;
+		if (head->write_avail >= head->writebuf_size - 1) {
+			error = -ENOMEM;
+			break;
+		} else if (get_user(c, buffer)) {
+			error = -EFAULT;
+			break;
+		}
+		buffer++;
+		avail_len--;
+		cp0[head->write_avail++] = c;
+		if (c != '\n')
+			continue;
+		cp0[head->write_avail - 1] = '\0';
+		head->write_avail = 0;
+		normalize_line(cp0);
+		head->write(head);
+	}
+	mutex_unlock(&head->io_sem);
+	return error;
+}
+
+/**
+ * tmy_close_control - close() for /sys/kernel/security/tomoyo/ interface.
+ *
+ * @file: Pointer to "struct file".
+ *
+ * Releases memory and returns 0.
+ */
+static int tmy_close_control(struct file *file)
+{
+	struct tmy_io_buffer *head = file->private_data;
+
+	/* Release memory used for policy I/O. */
+	tmy_free(head->read_buf);
+	head->read_buf = NULL;
+	tmy_free(head->write_buf);
+	head->write_buf = NULL;
+	tmy_free(head);
+	head = NULL;
+	file->private_data = NULL;
+	return 0;
+}
+
+/**
+ * tmy_alloc_acl_element - Allocate permanent memory for ACL entry.
+ *
+ * @acl_type:  Type of ACL entry.
+ *
+ * Returns pointer to the ACL entry on success, NULL otherwise.
+ */
+void *tmy_alloc_acl_element(const u8 acl_type)
+{
+	int len;
+	struct acl_info *ptr;
+
+	switch (acl_type) {
+	case TYPE_SINGLE_PATH_ACL:
+		len = sizeof(struct single_path_acl_record);
+		break;
+	case TYPE_DOUBLE_PATH_ACL:
+		len = sizeof(struct double_path_acl_record);
+		break;
+	default:
+		return NULL;
+	}
+	ptr = tmy_alloc_element(len);
+	if (!ptr)
+		return NULL;
+	ptr->type = acl_type;
+	return ptr;
+}
+
+/**
+ * tmy_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 tmy_open(struct inode *inode, struct file *file)
+{
+	return tmy_open_control(((u8 *) file->f_path.dentry->d_inode->i_private)
+				- ((u8 *) NULL), file);
+}
+
+/**
+ * tmy_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 tmy_release(struct inode *inode, struct file *file)
+{
+	return tmy_close_control(file);
+}
+
+/**
+ * tmy_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 tmy_read(struct file *file, char __user *buf, size_t count,
+			loff_t *ppos)
+{
+	return tmy_read_control(file, buf, count);
+}
+
+/**
+ * tmy_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 tmy_write(struct file *file, const char __user *buf,
+			 size_t count, loff_t *ppos)
+{
+	return tmy_write_control(file, buf, count);
+}
+
+/* Operations for /sys/kernel/security/tomoyo/ interface. */
+static struct file_operations tmy_operations = {
+	.open    = tmy_open,
+	.release = tmy_release,
+	.read    = tmy_read,
+	.write   = tmy_write,
+};
+
+/**
+ * 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 create_entry(const char *name, const mode_t mode,
+				struct dentry *parent, const u8 key)
+{
+	securityfs_create_file(name, mode, parent, ((u8 *) NULL) + key,
+			       &tmy_operations);
+}
+
+/**
+ * tmy_initerface_init - Initialize /sys/kernel/security/tomoyo/ interface.
+ *
+ * Returns 0.
+ */
+static int __init tmy_initerface_init(void)
+{
+	struct dentry *tmy_dir;
+
+	tmy_dir = securityfs_create_dir("tomoyo", NULL);
+	create_entry("domain_policy",    0600, tmy_dir, TMY_DOMAINPOLICY);
+	create_entry("exception_policy", 0600, tmy_dir, TMY_EXCEPTIONPOLICY);
+	create_entry("self_domain",      0400, tmy_dir, TMY_SELFDOMAIN);
+	create_entry(".domain_status",   0600, tmy_dir, TMY_DOMAIN_STATUS);
+	create_entry(".process_status",  0600, tmy_dir, TMY_PROCESS_STATUS);
+	create_entry("meminfo",          0600, tmy_dir, TMY_MEMINFO);
+	create_entry("profile",          0600, tmy_dir, TMY_PROFILE);
+	create_entry("manager",          0600, tmy_dir, TMY_MANAGER);
+	create_entry(".updates_counter", 0400, tmy_dir, TMY_UPDATESCOUNTER);
+	create_entry("version",          0400, tmy_dir, TMY_VERSION);
+	return 0;
+}
+
+fs_initcall(tmy_initerface_init);
--- /dev/null
+++ linux-2.6.28-rc2-mm1/security/tomoyo/common.h
@@ -0,0 +1,320 @@
+/*
+ * security/tomoyo/common.h
+ *
+ * Common functions for TOMOYO.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 2.2.0-pre   2008/10/10
+ *
+ */
+
+#ifndef _SECURITY_TOMOYO_COMMON_H
+#define _SECURITY_TOMOYO_COMMON_H
+
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/file.h>
+#include <linux/kmod.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/namei.h>
+#include <linux/mount.h>
+#include <linux/list1.h>
+
+struct dentry;
+struct vfsmount;
+
+#define false 0
+#define true 1
+
+/* Temporary buffer for holding pathnames. */
+struct tmy_page_buffer {
+	char buffer[4096];
+};
+
+/* Structure for attribute checks in addition to pathname checks. */
+struct obj_info {
+	struct tmy_page_buffer *tmp;
+};
+
+/* Structure for holding a token. */
+struct path_info {
+	const char *name;
+	u32 hash;          /* = full_name_hash(name, strlen(name)) */
+	u16 total_len;     /* = strlen(name)                       */
+	u16 const_len;     /* = const_part_length(name)            */
+	bool is_dir;       /* = strendswith(name, "/")             */
+	bool is_patterned; /* = path_contains_pattern(name)        */
+	u16 depth;         /* = path_depth(name)                   */
+};
+
+/*
+ * 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 TMY_MAX_PATHNAME_LEN 4000
+
+/* Structure for holding requested pathname. */
+struct path_info_with_data {
+	/* Keep "head" first, for this pointer is passed to tmy_free(). */
+	struct path_info head;
+	char bariier1[16]; /* Safeguard for overrun. */
+	char body[TMY_MAX_PATHNAME_LEN];
+	char barrier2[16]; /* Safeguard for overrun. */
+};
+
+/* Common header for holding ACL entries. */
+struct acl_info {
+	struct list1_head list;
+	/*
+	 * Type of this ACL entry.
+	 *
+	 * MSB is is_deleted flag.
+	 */
+	u8 type;
+} __attribute__((__packed__));
+
+/* This ACL entry is deleted.           */
+#define ACL_DELETED        0x80
+
+/* Structure for domain information. */
+struct domain_info {
+	struct list1_head list;
+	struct list1_head acl_info_list;
+	/* Name of this domain. Never NULL.          */
+	const struct path_info *domainname;
+	u8 profile;        /* Profile number to use. */
+	u8 is_deleted;     /* Delete flag.
+			      0 = active.
+			      1 = deleted but undeletable.
+			      255 = deleted and no longer undeletable. */
+	bool quota_warned; /* Quota warnning flag.   */
+	/* DOMAIN_FLAGS_*. Use tmy_set_domain_flag() to modify. */
+	u8 flags;
+};
+
+/* Profile number is an integer between 0 and 255. */
+#define MAX_PROFILES 256
+
+/* Ignore "allow_read" directive in exception policy. */
+#define DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ 1
+/*
+ * This domain was unable to create a new domain at tmy_find_next_domain()
+ * because the name of the domain to be created was too long or
+ * it could not allocate memory.
+ * More than one process continued execve() without domain transition.
+ */
+#define DOMAIN_FLAGS_TRANSITION_FAILED        2
+
+/*
+ * Structure for "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" and "allow_rewrite" directive.
+ */
+struct single_path_acl_record {
+	struct acl_info head; /* type = TYPE_SINGLE_PATH_ACL */
+	u16 perm;
+	/* Pointer to single pathname. */
+	const struct path_info *filename;
+};
+
+/* Structure for "allow_rename" and "allow_link" directive. */
+struct double_path_acl_record {
+	struct acl_info head; /* type = TYPE_DOUBLE_PATH_ACL */
+	u8 perm;
+	/* Pointer to single pathname. */
+	const struct path_info *filename1;
+	/* Pointer to single pathname. */
+	const struct path_info *filename2;
+};
+
+/* Keywords for ACLs. */
+#define KEYWORD_ALIAS                     "alias "
+#define KEYWORD_ALLOW_READ                "allow_read "
+#define KEYWORD_DELETE                    "delete "
+#define KEYWORD_DENY_REWRITE              "deny_rewrite "
+#define KEYWORD_FILE_PATTERN              "file_pattern "
+#define KEYWORD_INITIALIZE_DOMAIN         "initialize_domain "
+#define KEYWORD_KEEP_DOMAIN               "keep_domain "
+#define KEYWORD_NO_INITIALIZE_DOMAIN      "no_initialize_domain "
+#define KEYWORD_NO_KEEP_DOMAIN            "no_keep_domain "
+#define KEYWORD_SELECT                    "select "
+#define KEYWORD_UNDELETE                  "undelete "
+#define KEYWORD_USE_PROFILE               "use_profile "
+#define KEYWORD_IGNORE_GLOBAL_ALLOW_READ  "ignore_global_allow_read"
+/* A domain definition starts with <kernel>. */
+#define ROOT_NAME                         "<kernel>"
+#define ROOT_NAME_LEN                     (sizeof(ROOT_NAME) - 1)
+
+/* Index numbers for Access Controls. */
+#define TMY_TOMOYO_MAC_FOR_FILE                  0  /* domain_policy.conf */
+#define TMY_TOMOYO_MAX_ACCEPT_ENTRY              1
+#define TMY_TOMOYO_VERBOSE                       2
+#define TMY_MAX_CONTROL_INDEX                    3
+
+/* Index numbers for updates counter. */
+#define TMY_UPDATES_COUNTER_DOMAIN_POLICY    0
+#define TMY_UPDATES_COUNTER_EXCEPTION_POLICY 1
+#define TMY_UPDATES_COUNTER_PROFILE          2
+#define TMY_UPDATES_COUNTER_MANAGER          3
+#define MAX_TMY_UPDATES_COUNTER              4
+
+/* Structure for reading/writing policy via securityfs interfaces. */
+struct tmy_io_buffer {
+	int (*read) (struct tmy_io_buffer *);
+	int (*write) (struct tmy_io_buffer *);
+	/* Exclusive lock for this structure.   */
+	struct mutex io_sem;
+	/* The position currently reading from. */
+	struct list1_head *read_var1;
+	/* Extra variables for reading.         */
+	struct list1_head *read_var2;
+	/* The position currently writing to.   */
+	struct 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.                  */
+	char *write_buf;
+	/* Bytes available for writing.         */
+	int write_avail;
+	/* Size of write buffer.                */
+	int writebuf_size;
+};
+
+/* Check whether the domain has too many ACL entries to hold. */
+bool tmy_check_domain_quota(struct domain_info * const domain);
+/* Transactional sprintf() for policy dump. */
+bool tmy_io_printf(struct tmy_io_buffer *head, const char *fmt, ...)
+	__attribute__ ((format(printf, 2, 3)));
+/* Check whether the domainname is correct. */
+bool tmy_is_correct_domain(const unsigned char *domainname,
+			   const char *function);
+/* Check whether the token is correct. */
+bool tmy_is_correct_path(const char *filename, const s8 start_type,
+			 const s8 pattern_type, const s8 end_type,
+			 const char *function);
+/* Check whether the token can be a domainname. */
+bool tmy_is_domain_def(const unsigned char *buffer);
+/* Check whether the given filename matches the given pattern. */
+bool tmy_path_matches_pattern(const struct path_info *filename,
+			      const struct path_info *pattern);
+/* Read "alias" entry in exception policy. */
+bool tmy_read_alias_policy(struct tmy_io_buffer *head);
+/*
+ * Read "initialize_domain" and "no_initialize_domain" entry
+ * in exception policy.
+ */
+bool tmy_read_domain_initializer_policy(struct tmy_io_buffer *head);
+/* Read "keep_domain" and "no_keep_domain" entry in exception policy. */
+bool tmy_read_domain_keeper_policy(struct tmy_io_buffer *head);
+/* Read "file_pattern" entry in exception policy. */
+bool tmy_read_file_pattern(struct tmy_io_buffer *head);
+/* Read "allow_read" entry in exception policy. */
+bool tmy_read_globally_readable_policy(struct tmy_io_buffer *head);
+/* Read "deny_rewrite" entry in exception policy. */
+bool tmy_read_no_rewrite_policy(struct tmy_io_buffer *head);
+/* Write domain policy violation warning message to console? */
+bool tmy_verbose_mode(const struct domain_info *domain);
+/* Convert double path operation to operation name. */
+const char *tmy_dp2keyword(const u8 operation);
+/* Get the last component of the given domainname. */
+const char *tmy_get_last_name(const struct domain_info *domain);
+/* Get warning message. */
+const char *tmy_get_msg(const bool is_enforce);
+/* Convert single path operation to operation name. */
+const char *tmy_sp2keyword(const u8 operation);
+/* Delete a domain. */
+int tmy_delete_domain(char *data);
+/* Create "alias" entry in exception policy. */
+int tmy_write_alias_policy(char *data, const bool is_delete);
+/*
+ * Create "initialize_domain" and "no_initialize_domain" entry
+ * in exception policy.
+ */
+int tmy_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 tmy_write_domain_keeper_policy(char *data, const bool is_not,
+				   const bool is_delete);
+/*
+ * Create "allow_read/write", "allow_execute", "allow_read", "allow_write",
+ * "allow_create", "allow_unlink", "allow_mkdir", "allow_rmdir",
+ * "allow_mkfifo", "allow_mksock", "allow_mkblock", "allow_mkchar",
+ * "allow_truncate", "allow_symlink", "allow_rewrite", "allow_rename" and
+ * "allow_link" entry in domain policy.
+ */
+int tmy_write_file_policy(char *data, struct domain_info *domain,
+			  const bool is_delete);
+/* Create "allow_read" entry in exception policy. */
+int tmy_write_globally_readable_policy(char *data, const bool is_delete);
+/* Create "deny_rewrite" entry in exception policy. */
+int tmy_write_no_rewrite_policy(char *data, const bool is_delete);
+/* Create "file_pattern" entry in exception policy. */
+int tmy_write_pattern_policy(char *data, const bool is_delete);
+/* Find a domain by the given name. */
+struct domain_info *tmy_find_domain(const char *domainname);
+/* Find or create a domain by the given name. */
+struct domain_info *tmy_find_or_assign_new_domain(const char *domainname,
+						  const u8 profile);
+/* Undelete a domain. */
+struct domain_info *tmy_undelete_domain(const char *domainname);
+/* Check mode for specified functionality. */
+unsigned int tmy_check_flags(const struct domain_info *domain, const u8 index);
+/* Allocate memory for structures. */
+void *tmy_alloc_acl_element(const u8 acl_type);
+/* Fill in "struct path_info" members. */
+void tmy_fill_path_info(struct path_info *ptr);
+/* Run policy loader when /sbin/init starts. */
+void tmy_load_policy(const char *filename);
+/* Change "struct domain_info"->flags. */
+void tmy_set_domain_flag(struct domain_info *domain, const bool is_delete,
+			 const u8 flags);
+/* Update the policy change counter. */
+void tmy_update_counter(const unsigned char index);
+
+/* strcmp() for "struct path_info" structure. */
+static inline bool tmy_pathcmp(const struct path_info *a,
+			       const struct path_info *b)
+{
+	return a->hash != b->hash || strcmp(a->name, b->name);
+}
+
+/* Get type of an ACL entry. */
+static inline u8 tmy_acl_type1(struct acl_info *ptr)
+{
+	return ptr->type & ~ACL_DELETED;
+}
+
+/* Get type of an ACL entry. */
+static inline u8 tmy_acl_type2(struct acl_info *ptr)
+{
+	return ptr->type;
+}
+
+/* The list for "struct domain_info". */
+extern struct list1_head domain_list;
+
+/* Has /sbin/init started? */
+extern bool sbin_init_started;
+
+/* The kernel's domain. */
+extern struct domain_info KERNEL_DOMAIN;
+
+#endif /* !defined(_SECURITY_TOMOYO_COMMON_H) */

--


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [TOMOYO #12 (2.6.28-rc2-mm1) 07/11] File operation restriction part.
  2008-11-04  6:08 [TOMOYO #12 (2.6.28-rc2-mm1) 00/11] TOMOYO Linux Kentaro Takeda
                   ` (5 preceding siblings ...)
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYO Linux Kentaro Takeda
@ 2008-11-04  6:08 ` Kentaro Takeda
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 08/11] Domain transition handler Kentaro Takeda
                   ` (3 subsequent siblings)
  10 siblings, 0 replies; 32+ messages in thread
From: Kentaro Takeda @ 2008-11-04  6:08 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Toshiharu Harada, linux-security-module, linux-kernel,
	Kentaro Takeda, Tetsuo Handa

This file controls file related operations.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 security/tomoyo/file.c | 1232 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1232 insertions(+)

--- /dev/null
+++ linux-2.6.28-rc2-mm1/security/tomoyo/file.c
@@ -0,0 +1,1232 @@
+/*
+ * security/tomoyo/file.c
+ *
+ * Implementation of the Domain-Based Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 2.2.0-pre   2008/10/10
+ *
+ */
+
+#include "common.h"
+#include "tomoyo.h"
+#include "realpath.h"
+#define ACC_MODE(x) ("\000\004\002\006"[(x)&O_ACCMODE])
+
+/* Structure for "allow_read" keyword. */
+struct globally_readable_file_entry {
+	struct list1_head list;
+	const struct path_info *filename;
+	bool is_deleted;
+};
+
+/* Structure for "file_pattern" keyword. */
+struct pattern_entry {
+	struct list1_head list;
+	const struct path_info *pattern;
+	bool is_deleted;
+};
+
+/* Structure for "deny_rewrite" keyword. */
+struct no_rewrite_entry {
+	struct list1_head list;
+	const struct path_info *pattern;
+	bool is_deleted;
+};
+
+/* Keyword array for single path operations. */
+static const char *sp_keyword[MAX_SINGLE_PATH_OPERATION] = {
+	[TMY_TYPE_READ_WRITE_ACL] = "read/write",
+	[TMY_TYPE_EXECUTE_ACL]    = "execute",
+	[TMY_TYPE_READ_ACL]       = "read",
+	[TMY_TYPE_WRITE_ACL]      = "write",
+	[TMY_TYPE_CREATE_ACL]     = "create",
+	[TMY_TYPE_UNLINK_ACL]     = "unlink",
+	[TMY_TYPE_MKDIR_ACL]      = "mkdir",
+	[TMY_TYPE_RMDIR_ACL]      = "rmdir",
+	[TMY_TYPE_MKFIFO_ACL]     = "mkfifo",
+	[TMY_TYPE_MKSOCK_ACL]     = "mksock",
+	[TMY_TYPE_MKBLOCK_ACL]    = "mkblock",
+	[TMY_TYPE_MKCHAR_ACL]     = "mkchar",
+	[TMY_TYPE_TRUNCATE_ACL]   = "truncate",
+	[TMY_TYPE_SYMLINK_ACL]    = "symlink",
+	[TMY_TYPE_REWRITE_ACL]    = "rewrite",
+};
+
+/* Keyword array for double path operations. */
+static const char *dp_keyword[MAX_DOUBLE_PATH_OPERATION] = {
+	[TMY_TYPE_LINK_ACL]    = "link",
+	[TMY_TYPE_RENAME_ACL]  = "rename",
+};
+
+/**
+ * tmy_sp2keyword - Get the name of single path operation.
+ *
+ * @operation: Type of operation.
+ *
+ * Returns the name of single path operation.
+ */
+const char *tmy_sp2keyword(const u8 operation)
+{
+	return (operation < MAX_SINGLE_PATH_OPERATION)
+		? sp_keyword[operation] : NULL;
+}
+
+/**
+ * tmy_dp2keyword - Get the name of double path operation.
+ *
+ * @operation: Type of operation.
+ *
+ * Returns the name of double path operation.
+ */
+const char *tmy_dp2keyword(const u8 operation)
+{
+	return (operation < MAX_DOUBLE_PATH_OPERATION)
+		? dp_keyword[operation] : NULL;
+}
+
+/**
+ * strendswith - Check whether the token ends with the given token.
+ *
+ * @name: The token to check.
+ * @tail: The token to find.
+ *
+ * Returns true if @name ends with @tail, false otherwise.
+ */
+static bool strendswith(const char *name, const char *tail)
+{
+	int len;
+
+	if (!name || !tail)
+		return false;
+	len = strlen(name) - strlen(tail);
+	return len >= 0 && !strcmp(name + len, tail);
+}
+
+/**
+ * tmy_get_path - Get realpath.
+ *
+ * @path: Pointer to "struct path".
+ *
+ * Returns pointer to "struct path_info" on success, NULL otherwise.
+ */
+static struct path_info *tmy_get_path(struct path *path)
+{
+	int error;
+	struct path_info_with_data *buf = tmy_alloc(sizeof(*buf));
+
+	if (!buf)
+		return NULL;
+	/* Preserve one byte for appending "/". */
+	error = tmy_realpath_from_path2(path, buf->body,
+					sizeof(buf->body) - 2);
+	if (!error) {
+		buf->head.name = buf->body;
+		tmy_fill_path_info(&buf->head);
+		return &buf->head;
+	}
+	tmy_free(buf);
+	return NULL;
+}
+
+static int update_double_path_acl(const u8 type, const char *filename1,
+				  const char *filename2,
+				  struct domain_info * const domain,
+				  const bool is_delete);
+static int update_single_path_acl(const u8 type, const char *filename,
+				  struct domain_info * const domain,
+				  const bool is_delete);
+
+/**
+ * tmy_add_domain_acl - Add the given ACL to the given domain.
+ *
+ * @domain: Pointer to "struct domain_info". May be NULL.
+ * @acl:    Pointer to "struct acl_info".
+ *
+ * Returns 0.
+ */
+static int tmy_add_domain_acl(struct domain_info *domain, struct acl_info *acl)
+{
+	if (domain) {
+		/*
+		 * We need to serialize because this function is called by
+		 * update_single_path_acl() and update_double_path_acl().
+		 */
+		static DEFINE_SPINLOCK(lock);
+		/***** CRITICAL SECTION START *****/
+		spin_lock(&lock);
+		list1_add_tail(&acl->list, &domain->acl_info_list);
+		spin_unlock(&lock);
+		/***** CRITICAL SECTION END *****/
+	} else {
+		acl->type &= ~ACL_DELETED;
+	}
+	tmy_update_counter(TMY_UPDATES_COUNTER_DOMAIN_POLICY);
+	return 0;
+}
+
+/**
+ * tmy_del_domain_acl - Delete the given ACL from the domain.
+ *
+ * @acl: Pointer to "struct acl_info". May be NULL.
+ *
+ * Returns 0.
+ */
+static int tmy_del_domain_acl(struct acl_info *acl)
+{
+	if (acl)
+		acl->type |= ACL_DELETED;
+	tmy_update_counter(TMY_UPDATES_COUNTER_DOMAIN_POLICY);
+	return 0;
+}
+
+/*
+ * The list for "struct globally_readable_file_entry".
+ *
+ * This list is updated only inside update_globally_readable_entry(), thus
+ * no global mutex exists.
+ */
+static LIST1_HEAD(globally_readable_list);
+
+/**
+ * update_globally_readable_entry - Update "struct globally_readable_file_entry" list.
+ *
+ * @filename:  Filename unconditionally permitted to open() for reading.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_globally_readable_entry(const char *filename,
+					  const bool is_delete)
+{
+	struct globally_readable_file_entry *new_entry;
+	struct globally_readable_file_entry *ptr;
+	static DEFINE_MUTEX(lock);
+	const struct path_info *saved_filename;
+	int error = -ENOMEM;
+
+	if (!tmy_is_correct_path(filename, 1, 0, -1, __func__))
+		return -EINVAL;
+	saved_filename = tmy_save_name(filename);
+	if (!saved_filename)
+		return -ENOMEM;
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &globally_readable_list, list) {
+		if (ptr->filename != saved_filename)
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = tmy_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->filename = saved_filename;
+	list1_add_tail(&new_entry->list, &globally_readable_list);
+	error = 0;
+out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	tmy_update_counter(TMY_UPDATES_COUNTER_EXCEPTION_POLICY);
+	return error;
+}
+
+/**
+ * is_globally_readable_file - Check if the file is unconditionnaly permitted to be open()ed for reading.
+ *
+ * @filename: The filename to check.
+ *
+ * Returns true if any domain can open @filename for reading, false otherwise.
+ */
+static bool is_globally_readable_file(const struct path_info *filename)
+{
+	struct globally_readable_file_entry *ptr;
+
+	list1_for_each_entry(ptr, &globally_readable_list, list) {
+		if (!ptr->is_deleted &&
+		    tmy_path_matches_pattern(filename, ptr->filename))
+			return true;
+	}
+	return false;
+}
+
+/**
+ * tmy_write_globally_readable_policy - Write "struct globally_readable_file_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tmy_write_globally_readable_policy(char *data, const bool is_delete)
+{
+	return update_globally_readable_entry(data, is_delete);
+}
+
+/**
+ * tmy_read_globally_readable_policy - Read "struct globally_readable_file_entry" list.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool tmy_read_globally_readable_policy(struct tmy_io_buffer *head)
+{
+	struct list1_head *pos;
+
+	list1_for_each_cookie(pos, head->read_var2, &globally_readable_list) {
+		struct globally_readable_file_entry *ptr;
+		ptr = list1_entry(pos, struct globally_readable_file_entry,
+				  list);
+		if (ptr->is_deleted)
+			continue;
+		if (!tmy_io_printf(head, KEYWORD_ALLOW_READ "%s\n",
+				   ptr->filename->name))
+			goto out;
+	}
+	return true;
+out:
+	return false;
+}
+
+/*
+ * The list for "struct pattern_entry".
+ *
+ * This list is updated only inside update_file_pattern_entry(), thus
+ * no global mutex exists.
+ */
+static LIST1_HEAD(pattern_list);
+
+/**
+ * update_file_pattern_entry - Update "struct pattern_entry" list.
+ *
+ * @pattern:   Pathname pattern.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_file_pattern_entry(const char *pattern, const bool is_delete)
+{
+	struct pattern_entry *new_entry;
+	struct pattern_entry *ptr;
+	static DEFINE_MUTEX(lock);
+	const struct path_info *saved_pattern;
+	int error = -ENOMEM;
+
+	if (!tmy_is_correct_path(pattern, 0, 1, 0, __func__))
+		return -EINVAL;
+	saved_pattern = tmy_save_name(pattern);
+	if (!saved_pattern)
+		return -ENOMEM;
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &pattern_list, list) {
+		if (saved_pattern != ptr->pattern)
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = tmy_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->pattern = saved_pattern;
+	list1_add_tail(&new_entry->list, &pattern_list);
+	error = 0;
+out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	tmy_update_counter(TMY_UPDATES_COUNTER_EXCEPTION_POLICY);
+	return error;
+}
+
+/**
+ * get_file_pattern - Get patterned pathname.
+ *
+ * @filename: The filename to find patterned pathname.
+ *
+ * Returns pointer to pathname pattern if matched, @filename otherwise.
+ */
+static const struct path_info *
+get_file_pattern(const struct path_info *filename)
+{
+	struct pattern_entry *ptr;
+	const struct path_info *pattern = NULL;
+
+	list1_for_each_entry(ptr, &pattern_list, list) {
+		if (ptr->is_deleted)
+			continue;
+		if (!tmy_path_matches_pattern(filename, ptr->pattern))
+			continue;
+		pattern = ptr->pattern;
+		if (strendswith(pattern->name, "/\\*")) {
+			/* Do nothing. Try to find the better match. */
+		} else {
+			/* This would be the better match. Use this. */
+			break;
+		}
+	}
+	if (pattern)
+		filename = pattern;
+	return filename;
+}
+
+/**
+ * tmy_write_pattern_policy - Write "struct pattern_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tmy_write_pattern_policy(char *data, const bool is_delete)
+{
+	return update_file_pattern_entry(data, is_delete);
+}
+
+/**
+ * tmy_read_file_pattern - Read "struct pattern_entry" list.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool tmy_read_file_pattern(struct tmy_io_buffer *head)
+{
+	struct list1_head *pos;
+
+	list1_for_each_cookie(pos, head->read_var2, &pattern_list) {
+		struct pattern_entry *ptr;
+		ptr = list1_entry(pos, struct pattern_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (!tmy_io_printf(head, KEYWORD_FILE_PATTERN "%s\n",
+				   ptr->pattern->name))
+			goto out;
+	}
+	return true;
+out:
+	return false;
+}
+
+/*
+ * The list for "struct no_rewrite_entry".
+ *
+ * This list is updated only inside update_no_rewrite_entry(), thus
+ * no global mutex exists.
+ */
+static LIST1_HEAD(no_rewrite_list);
+
+/**
+ * update_no_rewrite_entry - Update "struct no_rewrite_entry" list.
+ *
+ * @pattern:   Pathname pattern that are not rewritable by default.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_no_rewrite_entry(const char *pattern, const bool is_delete)
+{
+	struct no_rewrite_entry *new_entry, *ptr;
+	static DEFINE_MUTEX(lock);
+	const struct path_info *saved_pattern;
+	int error = -ENOMEM;
+
+	if (!tmy_is_correct_path(pattern, 0, 0, 0, __func__))
+		return -EINVAL;
+	saved_pattern = tmy_save_name(pattern);
+	if (!saved_pattern)
+		return -ENOMEM;
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &no_rewrite_list, list) {
+		if (ptr->pattern != saved_pattern)
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = tmy_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->pattern = saved_pattern;
+	list1_add_tail(&new_entry->list, &no_rewrite_list);
+	error = 0;
+out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	tmy_update_counter(TMY_UPDATES_COUNTER_EXCEPTION_POLICY);
+	return error;
+}
+
+/**
+ * is_no_rewrite_file - Check if the given pathname is not permitted to be rewrited.
+ *
+ * @filename: Filename to check.
+ *
+ * Returns true if @filename is specified by "deny_rewrite" directive,
+ * false otherwise.
+ */
+static bool is_no_rewrite_file(const struct path_info *filename)
+{
+	struct no_rewrite_entry *ptr;
+
+	list1_for_each_entry(ptr, &no_rewrite_list, list) {
+		if (ptr->is_deleted)
+			continue;
+		if (!tmy_path_matches_pattern(filename, ptr->pattern))
+			continue;
+		return true;
+	}
+	return false;
+}
+
+/**
+ * tmy_write_no_rewrite_policy - Write "struct no_rewrite_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tmy_write_no_rewrite_policy(char *data, const bool is_delete)
+{
+	return update_no_rewrite_entry(data, is_delete);
+}
+
+/**
+ * tmy_read_no_rewrite_policy - Read "struct no_rewrite_entry" list.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool tmy_read_no_rewrite_policy(struct tmy_io_buffer *head)
+{
+	struct list1_head *pos;
+
+	list1_for_each_cookie(pos, head->read_var2, &no_rewrite_list) {
+		struct no_rewrite_entry *ptr;
+		ptr = list1_entry(pos, struct no_rewrite_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (!tmy_io_printf(head, KEYWORD_DENY_REWRITE "%s\n",
+				   ptr->pattern->name))
+			goto out;
+	}
+	return true;
+out:
+	return false;
+}
+
+/**
+ * update_file_acl - Update file's read/write/execute ACL.
+ *
+ * @filename:  Filename.
+ * @perm:      Permission (between 1 to 7).
+ * @domain:    Pointer to "struct 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".
+ */
+static int update_file_acl(const char *filename, u8 perm,
+			   struct domain_info * const domain,
+			   const bool is_delete)
+{
+	if (perm > 7 || !perm) {
+		printk(KERN_DEBUG "%s: Invalid permission '%d %s'\n",
+		       __func__, perm, filename);
+		return -EINVAL;
+	}
+	if (filename[0] != '@' && strendswith(filename, "/"))
+		/*
+		 * Only 'allow_mkdir' and 'allow_rmdir' are valid for
+		 * directory permissions.
+		 */
+		return 0;
+	if (perm & 4)
+		update_single_path_acl(TMY_TYPE_READ_ACL, filename, domain,
+				       is_delete);
+	if (perm & 2)
+		update_single_path_acl(TMY_TYPE_WRITE_ACL, filename, domain,
+				       is_delete);
+	if (perm & 1)
+		update_single_path_acl(TMY_TYPE_EXECUTE_ACL, filename, domain,
+				       is_delete);
+	return 0;
+}
+
+/**
+ * check_single_path_acl2 - Check permission for single path operation.
+ *
+ * @domain:          Pointer to "struct domain_info".
+ * @filename:        Filename to check.
+ * @perm:            Permission.
+ * @may_use_pattern: True if patterned ACL is permitted.
+ *
+ * Returns 0 on success, -EPERM otherwise.
+ */
+static int check_single_path_acl2(const struct domain_info *domain,
+				  const struct path_info *filename,
+				  const u16 perm, const bool may_use_pattern)
+{
+	struct acl_info *ptr;
+
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		struct single_path_acl_record *acl;
+		if (tmy_acl_type2(ptr) != TYPE_SINGLE_PATH_ACL)
+			continue;
+		acl = container_of(ptr, struct single_path_acl_record, head);
+		if (!(acl->perm & perm))
+			continue;
+		if (may_use_pattern || !acl->filename->is_patterned) {
+			if (!tmy_path_matches_pattern(filename,
+						      acl->filename))
+				continue;
+		} else {
+			continue;
+		}
+		return 0;
+	}
+	return -EPERM;
+}
+
+/**
+ * check_file_acl - Check permission for opening files.
+ *
+ * @domain:    Pointer to "struct domain_info".
+ * @filename:  Filename to check.
+ * @operation: Mode ("read" or "write" or "read/write" or "execute").
+ *
+ * Returns 0 on success, -EPERM otherwise.
+ */
+static int check_file_acl(const struct domain_info *domain,
+			  const struct path_info *filename, const u8 operation)
+{
+	u16 perm = 0;
+
+	if (!tmy_check_flags(domain, TMY_TOMOYO_MAC_FOR_FILE))
+		return 0;
+	if (operation == 6)
+		perm = 1 << TMY_TYPE_READ_WRITE_ACL;
+	else if (operation == 4)
+		perm = 1 << TMY_TYPE_READ_ACL;
+	else if (operation == 2)
+		perm = 1 << TMY_TYPE_WRITE_ACL;
+	else if (operation == 1)
+		perm = 1 << TMY_TYPE_EXECUTE_ACL;
+	else
+		BUG();
+	return check_single_path_acl2(domain, filename, perm, operation != 1);
+}
+
+/**
+ * check_file_perm2 - Check permission for opening files.
+ *
+ * @domain:    Pointer to "struct 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.
+ */
+static int check_file_perm2(struct domain_info * const domain,
+			    const struct path_info *filename, const u8 perm,
+			    const char *operation, const u8 mode)
+{
+	const bool is_enforce = (mode == 3);
+	const char *msg = "<unknown>";
+	int error = 0;
+
+	if (!filename)
+		return 0;
+	error = check_file_acl(domain, filename, perm);
+	if (error && perm == 4 &&
+	    (domain->flags & DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ) == 0 &&
+	    is_globally_readable_file(filename))
+		error = 0;
+	if (perm == 6)
+		msg = tmy_sp2keyword(TMY_TYPE_READ_WRITE_ACL);
+	else if (perm == 4)
+		msg = tmy_sp2keyword(TMY_TYPE_READ_ACL);
+	else if (perm == 2)
+		msg = tmy_sp2keyword(TMY_TYPE_WRITE_ACL);
+	else if (perm == 1)
+		msg = tmy_sp2keyword(TMY_TYPE_EXECUTE_ACL);
+	else
+		BUG();
+	if (!error)
+		return 0;
+	if (tmy_verbose_mode(domain))
+		printk(KERN_WARNING "TOMOYO-%s: Access '%s(%s) %s' denied "
+		       "for %s\n", tmy_get_msg(is_enforce), msg, operation,
+		       filename->name, tmy_get_last_name(domain));
+	if (is_enforce)
+		return error;
+	if (mode == 1 && tmy_check_domain_quota(domain)) {
+		/* Don't use patterns for execute permission. */
+		const struct path_info *patterned_file = (perm != 1) ?
+			get_file_pattern(filename) : filename;
+		update_file_acl(patterned_file->name, perm,
+				domain, false);
+	}
+	return 0;
+}
+
+/**
+ * tmy_write_file_policy - Update file related list.
+ *
+ * @data:      String to parse.
+ * @domain:    Pointer to "struct domain_info".
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tmy_write_file_policy(char *data, struct domain_info *domain,
+			  const bool is_delete)
+{
+	char *filename = strchr(data, ' ');
+	char *filename2;
+	unsigned int perm;
+	u8 type;
+
+	if (!filename)
+		return -EINVAL;
+	*filename++ = '\0';
+	if (sscanf(data, "%u", &perm) == 1)
+		return update_file_acl(filename, (u8) perm, domain, is_delete);
+	if (strncmp(data, "allow_", 6))
+		goto out;
+	data += 6;
+	for (type = 0; type < MAX_SINGLE_PATH_OPERATION; type++) {
+		if (strcmp(data, sp_keyword[type]))
+			continue;
+		return update_single_path_acl(type, filename,
+					      domain, is_delete);
+	}
+	filename2 = strchr(filename, ' ');
+	if (!filename2)
+		goto out;
+	*filename2++ = '\0';
+	for (type = 0; type < MAX_DOUBLE_PATH_OPERATION; type++) {
+		if (strcmp(data, dp_keyword[type]))
+			continue;
+		return update_double_path_acl(type, filename, filename2, domain,
+					      is_delete);
+	}
+out:
+	return -EINVAL;
+}
+
+/**
+ * update_single_path_acl - Update "struct single_path_acl_record" list.
+ *
+ * @type:      Type of operation.
+ * @filename:  Filename.
+ * @domain:    Pointer to "struct domain_info".
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_single_path_acl(const u8 type, const char *filename,
+				  struct domain_info * const domain,
+				  const bool is_delete)
+{
+	static DEFINE_MUTEX(lock);
+	static const u16 rw_mask =
+		(1 << TMY_TYPE_READ_ACL) | (1 << TMY_TYPE_WRITE_ACL);
+	const struct path_info *saved_filename;
+	struct acl_info *ptr;
+	struct single_path_acl_record *acl;
+	int error = -ENOMEM;
+	const u16 perm = 1 << type;
+
+	if (!domain)
+		return -EINVAL;
+	if (!tmy_is_correct_path(filename, 0, 0, 0, __func__))
+		return -EINVAL;
+	saved_filename = tmy_save_name(filename);
+	if (!saved_filename)
+		return -ENOMEM;
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&lock);
+	if (is_delete)
+		goto delete;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (tmy_acl_type1(ptr) != TYPE_SINGLE_PATH_ACL)
+			continue;
+		acl = container_of(ptr, struct single_path_acl_record, head);
+		if (acl->filename != saved_filename)
+			continue;
+		/* Special case. Clear all bits if marked as deleted. */
+		if (ptr->type & ACL_DELETED)
+			acl->perm = 0;
+		acl->perm |= perm;
+		if ((acl->perm & rw_mask) == rw_mask)
+			acl->perm |= 1 << TMY_TYPE_READ_WRITE_ACL;
+		else if (acl->perm & (1 << TMY_TYPE_READ_WRITE_ACL))
+			acl->perm |= rw_mask;
+		error = tmy_add_domain_acl(NULL, ptr);
+		goto out;
+	}
+	/* Not found. Append it to the tail. */
+	acl = tmy_alloc_acl_element(TYPE_SINGLE_PATH_ACL);
+	if (!acl)
+		goto out;
+	acl->perm = perm;
+	acl->filename = saved_filename;
+	error = tmy_add_domain_acl(domain, &acl->head);
+	goto out;
+delete:
+	error = -ENOENT;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (tmy_acl_type2(ptr) != TYPE_SINGLE_PATH_ACL)
+			continue;
+		acl = container_of(ptr, struct single_path_acl_record, head);
+		if (acl->filename != saved_filename)
+			continue;
+		acl->perm &= ~perm;
+		if ((acl->perm & rw_mask) != rw_mask)
+			acl->perm &= ~(1 << TMY_TYPE_READ_WRITE_ACL);
+		else if (!(acl->perm & (1 << TMY_TYPE_READ_WRITE_ACL)))
+			acl->perm &= ~rw_mask;
+		error = tmy_del_domain_acl(acl->perm ? NULL : ptr);
+		break;
+	}
+out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	return error;
+}
+
+/**
+ * update_double_path_acl - Update "struct double_path_acl_record" list.
+ *
+ * @type:      Type of operation.
+ * @filename1: First filename.
+ * @filename2: Second filename.
+ * @domain:    Pointer to "struct domain_info".
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_double_path_acl(const u8 type, const char *filename1,
+				  const char *filename2,
+				  struct domain_info * const domain,
+				  const bool is_delete)
+{
+	static DEFINE_MUTEX(lock);
+	const struct path_info *saved_filename1;
+	const struct path_info *saved_filename2;
+	struct acl_info *ptr;
+	struct double_path_acl_record *acl;
+	int error = -ENOMEM;
+	const u8 perm = 1 << type;
+
+	if (!domain)
+		return -EINVAL;
+	if (!tmy_is_correct_path(filename1, 0, 0, 0, __func__) ||
+	    !tmy_is_correct_path(filename2, 0, 0, 0, __func__))
+		return -EINVAL;
+	saved_filename1 = tmy_save_name(filename1);
+	saved_filename2 = tmy_save_name(filename2);
+	if (!saved_filename1 || !saved_filename2)
+		return -ENOMEM;
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&lock);
+	if (is_delete)
+		goto delete;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (tmy_acl_type1(ptr) != TYPE_DOUBLE_PATH_ACL)
+			continue;
+		acl = container_of(ptr, struct double_path_acl_record, head);
+		if (acl->filename1 != saved_filename1 ||
+		    acl->filename2 != saved_filename2)
+			continue;
+		/* Special case. Clear all bits if marked as deleted. */
+		if (ptr->type & ACL_DELETED)
+			acl->perm = 0;
+		acl->perm |= perm;
+		error = tmy_add_domain_acl(NULL, ptr);
+		goto out;
+	}
+	/* Not found. Append it to the tail. */
+	acl = tmy_alloc_acl_element(TYPE_DOUBLE_PATH_ACL);
+	if (!acl)
+		goto out;
+	acl->perm = perm;
+	acl->filename1 = saved_filename1;
+	acl->filename2 = saved_filename2;
+	error = tmy_add_domain_acl(domain, &acl->head);
+	goto out;
+delete:
+	error = -ENOENT;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (tmy_acl_type2(ptr) != TYPE_DOUBLE_PATH_ACL)
+			continue;
+		acl = container_of(ptr, struct double_path_acl_record, head);
+		if (acl->filename1 != saved_filename1 ||
+		    acl->filename2 != saved_filename2)
+			continue;
+		acl->perm &= ~perm;
+		error = tmy_del_domain_acl(acl->perm ? NULL : ptr);
+		break;
+	}
+out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	return error;
+}
+
+/**
+ * check_single_path_acl - Check permission for single path operation.
+ *
+ * @domain:   Pointer to "struct domain_info".
+ * @type:     Type of operation.
+ * @filename: Filename to check.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int check_single_path_acl(struct domain_info *domain, const u8 type,
+				 const struct path_info *filename)
+{
+	if (!tmy_check_flags(domain, TMY_TOMOYO_MAC_FOR_FILE))
+		return 0;
+	return check_single_path_acl2(domain, filename, 1 << type, 1);
+}
+
+/**
+ * check_double_path_acl - Check permission for double path operation.
+ *
+ * @domain:    Pointer to "struct domain_info".
+ * @type:      Type of operation.
+ * @filename1: First filename to check.
+ * @filename2: Second filename to check.
+ *
+ * Returns 0 on success, -EPERM otherwise.
+ */
+static int check_double_path_acl(const struct domain_info *domain,
+				 const u8 type,
+				 const struct path_info *filename1,
+				 const struct path_info *filename2)
+{
+	struct acl_info *ptr;
+	const u8 perm = 1 << type;
+
+	if (!tmy_check_flags(domain, TMY_TOMOYO_MAC_FOR_FILE))
+		return 0;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		struct double_path_acl_record *acl;
+		if (tmy_acl_type2(ptr) != TYPE_DOUBLE_PATH_ACL)
+			continue;
+		acl = container_of(ptr, struct double_path_acl_record, head);
+		if (!(acl->perm & perm))
+			continue;
+		if (!tmy_path_matches_pattern(filename1, acl->filename1))
+			continue;
+		if (!tmy_path_matches_pattern(filename2, acl->filename2))
+			continue;
+		return 0;
+	}
+	return -EPERM;
+}
+
+/**
+ * check_single_path_permission2 - Check permission for single path operation.
+ *
+ * @domain:    Pointer to "struct domain_info".
+ * @operation: Type of operation.
+ * @filename:  Filename to check.
+ * @mode:      Access control mode.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int check_single_path_permission2(struct domain_info * const domain,
+					 u8 operation,
+					 const struct path_info *filename,
+					 const u8 mode)
+{
+	const char *msg;
+	int error;
+	const bool is_enforce = (mode == 3);
+
+	if (!mode)
+		return 0;
+next:
+	error = check_single_path_acl(domain, operation, filename);
+	msg = tmy_sp2keyword(operation);
+	if (!error)
+		goto ok;
+	if (tmy_verbose_mode(domain))
+		printk(KERN_WARNING "TOMOYO-%s: Access '%s %s' denied for %s\n",
+		       tmy_get_msg(is_enforce), msg, filename->name,
+		       tmy_get_last_name(domain));
+	if (mode == 1 && tmy_check_domain_quota(domain))
+		update_single_path_acl(operation,
+				       get_file_pattern(filename)->name,
+				       domain, false);
+	if (!is_enforce)
+		error = 0;
+ok:
+	/*
+	 * 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 == TMY_TYPE_TRUNCATE_ACL &&
+	    is_no_rewrite_file(filename)) {
+		operation = TMY_TYPE_REWRITE_ACL;
+		goto next;
+	}
+	return error;
+}
+
+/**
+ * tmy_check_file_perm - Check permission for sysctl()'s "read" and "write".
+ *
+ * @domain:    Pointer to "struct domain_info".
+ * @filename:  Filename to check.
+ * @perm:      Mode ("read" or "write" or "read/write").
+ * Returns 0 on success, negative value otherwise.
+ */
+int tmy_check_file_perm(struct domain_info *domain, const char *filename,
+			const u8 perm)
+{
+	struct path_info name;
+	const u8 mode = tmy_check_flags(domain, TMY_TOMOYO_MAC_FOR_FILE);
+
+	if (!mode)
+		return 0;
+	name.name = filename;
+	tmy_fill_path_info(&name);
+	return check_file_perm2(domain, &name, perm, "sysctl", mode);
+}
+
+/**
+ * tmy_check_exec_perm - Check permission for "execute".
+ *
+ * @domain:   Pointer to "struct domain_info".
+ * @filename: Check permission for "execute".
+ * @tmp:      Buffer for temporal use.
+ *
+ * Returns 0 on success, negativevalue otherwise.
+ */
+int tmy_check_exec_perm(struct domain_info *domain,
+			const struct path_info *filename,
+			struct tmy_page_buffer *tmp)
+{
+	const u8 mode = tmy_check_flags(domain, TMY_TOMOYO_MAC_FOR_FILE);
+
+	if (!mode)
+		return 0;
+	return check_file_perm2(domain, filename, 1, "do_execve", mode);
+}
+
+/**
+ * tmy_check_open_permission - Check permission for "read" and "write".
+ *
+ * @domain: Pointer to "struct domain_info".
+ * @path:   Pointer to "struct path".
+ * @flag:   Flags for open().
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tmy_check_open_permission(struct domain_info *domain,
+			      struct path *path, const int flag)
+{
+	const u8 acc_mode = ACC_MODE(flag);
+	int error = -ENOMEM;
+	struct path_info *buf;
+	const u8 mode = tmy_check_flags(domain, TMY_TOMOYO_MAC_FOR_FILE);
+	const bool is_enforce = (mode == 3);
+
+	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.
+		 */
+		return 0;
+	buf = tmy_get_path(path);
+	if (!buf)
+		goto out;
+	error = 0;
+	/*
+	 * If the filename is specified by "deny_rewrite" keyword,
+	 * we need to check "allow_rewrite" permission when the filename is not
+	 * opened for append mode or the filename is truncated at open time.
+	 */
+	if ((acc_mode & MAY_WRITE) &&
+	    ((flag & O_TRUNC) || !(flag & O_APPEND)) &&
+	    (is_no_rewrite_file(buf))) {
+		error = check_single_path_permission2(domain,
+						      TMY_TYPE_REWRITE_ACL,
+						      buf, mode);
+	}
+	if (!error)
+		error = check_file_perm2(domain, buf, acc_mode, "open", mode);
+	if (!error && (flag & O_TRUNC))
+		error = check_single_path_permission2(domain,
+						      TMY_TYPE_TRUNCATE_ACL,
+						      buf, mode);
+out:
+	tmy_free(buf);
+	if (!is_enforce)
+		error = 0;
+	return error;
+}
+
+/**
+ * tmy_check_1path_perm - Check permission for "create", "unlink", "mkdir", "rmdir", "mkfifo", "mksock", "mkblock", "mkchar", "truncate" and "symlink".
+ *
+ * @domain:    Pointer to "struct domain_info".
+ * @operation: Type of operation.
+ * @path:      Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tmy_check_1path_perm(struct domain_info *domain, const u8 operation,
+			 struct path *path)
+{
+	int error = -ENOMEM;
+	struct path_info *buf;
+	const u8 mode = tmy_check_flags(domain, TMY_TOMOYO_MAC_FOR_FILE);
+	const bool is_enforce = (mode == 3);
+
+	if (!mode || !path->mnt)
+		return 0;
+	buf = tmy_get_path(path);
+	if (!buf)
+		goto out;
+	switch (operation) {
+	case TMY_TYPE_MKDIR_ACL:
+	case TMY_TYPE_RMDIR_ACL:
+		if (!buf->is_dir) {
+			/* tmy_get_path() preserves space for appending "/." */
+			strcat((char *) buf->name, "/");
+			tmy_fill_path_info(buf);
+		}
+	}
+	error = check_single_path_permission2(domain, operation, buf, mode);
+out:
+	tmy_free(buf);
+	if (!is_enforce)
+		error = 0;
+	return error;
+}
+
+/**
+ * tmy_check_rewrite_permission - Check permission for "rewrite".
+ *
+ * @domain: Pointer to "struct domain_info".
+ * @filp: Pointer to "struct file".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tmy_check_rewrite_permission(struct domain_info *domain, struct file *filp)
+{
+	int error = -ENOMEM;
+	const u8 mode = tmy_check_flags(domain, TMY_TOMOYO_MAC_FOR_FILE);
+	const bool is_enforce = (mode == 3);
+	struct path_info *buf;
+
+	if (!mode || !filp->f_path.mnt)
+		return 0;
+	buf = tmy_get_path(&filp->f_path);
+	if (!buf)
+		goto out;
+	if (!is_no_rewrite_file(buf)) {
+		error = 0;
+		goto out;
+	}
+	error = check_single_path_permission2(domain, TMY_TYPE_REWRITE_ACL,
+					      buf, mode);
+out:
+	tmy_free(buf);
+	if (!is_enforce)
+		error = 0;
+	return error;
+}
+
+/**
+ * tmy_check_2path_perm - Check permission for "rename" and "link".
+ *
+ * @domain:    Pointer to "struct domain_info".
+ * @operation: Type of operation.
+ * @path1:      Pointer to "struct path".
+ * @path2:      Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tmy_check_2path_perm(struct domain_info * const domain, const u8 operation,
+			 struct path *path1, struct path *path2)
+{
+	int error = -ENOMEM;
+	struct path_info *buf1, *buf2;
+	const u8 mode = tmy_check_flags(domain, TMY_TOMOYO_MAC_FOR_FILE);
+	const bool is_enforce = (mode == 3);
+	const char *msg;
+
+	if (!mode || !path1->mnt || !path2->mnt)
+		return 0;
+	buf1 = tmy_get_path(path1);
+	buf2 = tmy_get_path(path2);
+	if (!buf1 || !buf2)
+		goto out;
+	{
+		struct dentry *dentry = path1->dentry;
+		if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)) {
+			/* tmy_get_path() preserves space for appending "/." */
+			if (!buf1->is_dir) {
+				strcat((char *) buf1->name, "/");
+				tmy_fill_path_info(buf1);
+			}
+			if (!buf2->is_dir) {
+				strcat((char *) buf2->name, "/");
+				tmy_fill_path_info(buf2);
+			}
+		}
+	}
+	error = check_double_path_acl(domain, operation, buf1, buf2);
+	msg = tmy_dp2keyword(operation);
+	if (!error)
+		goto out;
+	if (tmy_verbose_mode(domain))
+		printk(KERN_WARNING "TOMOYO-%s: Access '%s %s %s' "
+		       "denied for %s\n", tmy_get_msg(is_enforce),
+		       msg, buf1->name, buf2->name, tmy_get_last_name(domain));
+	if (mode == 1 && tmy_check_domain_quota(domain))
+		update_double_path_acl(operation,
+				       get_file_pattern(buf1)->name,
+				       get_file_pattern(buf2)->name,
+				       domain, false);
+out:
+	tmy_free(buf1);
+	tmy_free(buf2);
+	if (!is_enforce)
+		error = 0;
+	return error;
+}

--


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [TOMOYO #12 (2.6.28-rc2-mm1) 08/11] Domain transition handler.
  2008-11-04  6:08 [TOMOYO #12 (2.6.28-rc2-mm1) 00/11] TOMOYO Linux Kentaro Takeda
                   ` (6 preceding siblings ...)
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 07/11] File operation restriction part Kentaro Takeda
@ 2008-11-04  6:08 ` Kentaro Takeda
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 09/11] LSM adapter functions Kentaro Takeda
                   ` (2 subsequent siblings)
  10 siblings, 0 replies; 32+ messages in thread
From: Kentaro Takeda @ 2008-11-04  6:08 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Toshiharu Harada, linux-security-module, linux-kernel,
	Kentaro Takeda, Tetsuo Handa

This file controls domain creation/deletion/transition.

Every process belongs to a domain in TOMOYO Linux.
Domain transition occurs when execve(2) is called
and the domain is expressed as 'process invocation history',
such as '<kernel> /sbin/init /etc/init.d/rc'.
Domain information is stored in task_struct->cred->security field.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 security/tomoyo/domain.c |  865 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 865 insertions(+)

--- /dev/null
+++ linux-2.6.28-rc2-mm1/security/tomoyo/domain.c
@@ -0,0 +1,865 @@
+/*
+ * security/tomoyo/domain.c
+ *
+ * Implementation of the Domain-Based Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 2.2.0-pre   2008/10/10
+ *
+ */
+
+#include "common.h"
+#include "tomoyo.h"
+#include "realpath.h"
+#include <linux/binfmts.h>
+
+/* Variables definitions.*/
+
+/* The initial domain. */
+struct domain_info KERNEL_DOMAIN;
+
+/*
+ * The list for "struct domain_info".
+ *
+ * The domain_list_lock mutex protects the domain_list list.
+ */
+LIST1_HEAD(domain_list);
+static DEFINE_MUTEX(domain_list_lock);
+
+/* Structure for "initialize_domain" and "no_initialize_domain" keyword. */
+struct domain_initializer_entry {
+	struct list1_head list;
+	const struct path_info *domainname;    /* This may be NULL */
+	const struct path_info *program;
+	bool is_deleted;
+	bool is_not;       /* True if this entry is "no_initialize_domain".  */
+	bool is_last_name; /* True if the domainname is tmy_get_last_name(). */
+};
+
+/* Structure for "keep_domain" and "no_keep_domain" keyword. */
+struct domain_keeper_entry {
+	struct list1_head list;
+	const struct path_info *domainname;
+	const struct path_info *program;       /* This may be NULL */
+	bool is_deleted;
+	bool is_not;       /* True if this entry is "no_keep_domain".        */
+	bool is_last_name; /* True if the domainname is tmy_get_last_name(). */
+};
+
+/* Structure for "alias" keyword. */
+struct alias_entry {
+	struct list1_head list;
+	const struct path_info *original_name;
+	const struct path_info *aliased_name;
+	bool is_deleted;
+};
+
+/**
+ * tmy_set_domain_flag - Set or clear domain's attribute flags.
+ *
+ * @domain:    Pointer to "struct domain_info".
+ * @is_delete: True if it is a delete request.
+ * @flags:     Flags to set or clear.
+ *
+ * Returns nothing.
+ */
+void tmy_set_domain_flag(struct domain_info *domain, const bool is_delete,
+			 const u8 flags)
+{
+	/* We need to serialize because this is bitfield operation. */
+	static DEFINE_SPINLOCK(lock);
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&lock);
+	if (!is_delete)
+		domain->flags |= flags;
+	else
+		domain->flags &= ~flags;
+	spin_unlock(&lock);
+	/***** CRITICAL SECTION END *****/
+}
+
+/**
+ * tmy_get_last_name - Get last component of a domainname.
+ *
+ * @domain: Pointer to "struct domain_info".
+ *
+ * Returns the last component of the domainname.
+ */
+const char *tmy_get_last_name(const struct domain_info *domain)
+{
+	const char *cp0 = domain->domainname->name;
+	const char *cp1 = strrchr(cp0, ' ');
+
+	if (cp1)
+		return cp1 + 1;
+	return cp0;
+}
+
+/*
+ * The list for "struct domain_initializer_entry".
+ *
+ * This list is updated only inside update_domain_initializer_entry(), thus
+ * no global mutex exists.
+ */
+static LIST1_HEAD(domain_initializer_list);
+
+/**
+ * update_domain_initializer_entry - Update "struct 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.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_domain_initializer_entry(const char *domainname,
+					   const char *program,
+					   const bool is_not,
+					   const bool is_delete)
+{
+	struct domain_initializer_entry *new_entry;
+	struct domain_initializer_entry *ptr;
+	static DEFINE_MUTEX(lock);
+	const struct path_info *saved_program;
+	const struct path_info *saved_domainname = NULL;
+	int error = -ENOMEM;
+	bool is_last_name = false;
+
+	if (!tmy_is_correct_path(program, 1, -1, -1, __func__))
+		return -EINVAL; /* No patterns allowed. */
+	if (domainname) {
+		if (!tmy_is_domain_def(domainname) &&
+		    tmy_is_correct_path(domainname, 1, -1, -1, __func__))
+			is_last_name = true;
+		else if (!tmy_is_correct_domain(domainname, __func__))
+			return -EINVAL;
+		saved_domainname = tmy_save_name(domainname);
+		if (!saved_domainname)
+			return -ENOMEM;
+	}
+	saved_program = tmy_save_name(program);
+	if (!saved_program)
+		return -ENOMEM;
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &domain_initializer_list, list) {
+		if (ptr->is_not != is_not ||
+		    ptr->domainname != saved_domainname ||
+		    ptr->program != saved_program)
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = tmy_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->domainname = saved_domainname;
+	new_entry->program = saved_program;
+	new_entry->is_not = is_not;
+	new_entry->is_last_name = is_last_name;
+	list1_add_tail(&new_entry->list, &domain_initializer_list);
+	error = 0;
+out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	tmy_update_counter(TMY_UPDATES_COUNTER_EXCEPTION_POLICY);
+	return error;
+}
+
+/**
+ * tmy_read_domain_initializer_policy - Read "struct domain_initializer_entry" list.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool tmy_read_domain_initializer_policy(struct tmy_io_buffer *head)
+{
+	struct list1_head *pos;
+
+	list1_for_each_cookie(pos, head->read_var2, &domain_initializer_list) {
+		const char *no;
+		const char *from = "";
+		const char *domain = "";
+		struct domain_initializer_entry *ptr;
+		ptr = list1_entry(pos, struct domain_initializer_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		no = ptr->is_not ? "no_" : "";
+		if (ptr->domainname) {
+			from = " from ";
+			domain = ptr->domainname->name;
+		}
+		if (!tmy_io_printf(head,
+				   "%s" KEYWORD_INITIALIZE_DOMAIN "%s%s%s\n",
+				   no, ptr->program->name, from, domain))
+			goto out;
+	}
+	return true;
+out:
+	return false;
+}
+
+/**
+ * tmy_write_domain_initializer_policy - Write "struct 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.
+ */
+int tmy_write_domain_initializer_policy(char *data, const bool is_not,
+					const bool is_delete)
+{
+	char *cp = strstr(data, " from ");
+
+	if (cp) {
+		*cp = '\0';
+		return update_domain_initializer_entry(cp + 6, data, is_not,
+						       is_delete);
+	}
+	return update_domain_initializer_entry(NULL, data, is_not, is_delete);
+}
+
+/**
+ * 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.
+ *
+ * Returns true if executing @program reinitializes domain transition,
+ * false otherwise.
+ */
+static bool is_domain_initializer(const struct path_info *domainname,
+				  const struct path_info *program,
+				  const struct path_info *last_name)
+{
+	struct domain_initializer_entry *ptr;
+	bool flag = false;
+
+	list1_for_each_entry(ptr,  &domain_initializer_list, list) {
+		if (ptr->is_deleted)
+			continue;
+		if (ptr->domainname) {
+			if (!ptr->is_last_name) {
+				if (ptr->domainname != domainname)
+					continue;
+			} else {
+				if (tmy_pathcmp(ptr->domainname, last_name))
+					continue;
+			}
+		}
+		if (tmy_pathcmp(ptr->program, program))
+			continue;
+		if (ptr->is_not)
+			return false;
+		flag = true;
+	}
+	return flag;
+}
+
+/*
+ * The list for "struct domain_keeper_entry".
+ *
+ * This list is updated only inside update_domain_keeper_entry(), thus
+ * no global mutex exists.
+ */
+static LIST1_HEAD(domain_keeper_list);
+
+/**
+ * update_domain_keeper_entry - Update "struct domain_keeper_entry" list.
+ *
+ * @domainname: The name of domain.
+ * @program:    The name of program. May be NULL.
+ * @is_not:     True if it is "no_keep_domain" entry.
+ * @is_delete:  True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_domain_keeper_entry(const char *domainname,
+				      const char *program,
+				      const bool is_not, const bool is_delete)
+{
+	struct domain_keeper_entry *new_entry;
+	struct domain_keeper_entry *ptr;
+	const struct path_info *saved_domainname;
+	const struct path_info *saved_program = NULL;
+	static DEFINE_MUTEX(lock);
+	int error = -ENOMEM;
+	bool is_last_name = false;
+
+	if (!tmy_is_domain_def(domainname) &&
+	    tmy_is_correct_path(domainname, 1, -1, -1, __func__))
+		is_last_name = true;
+	else if (!tmy_is_correct_domain(domainname, __func__))
+		return -EINVAL;
+	if (program) {
+		if (!tmy_is_correct_path(program, 1, -1, -1, __func__))
+			return -EINVAL;
+		saved_program = tmy_save_name(program);
+		if (!saved_program)
+			return -ENOMEM;
+	}
+	saved_domainname = tmy_save_name(domainname);
+	if (!saved_domainname)
+		return -ENOMEM;
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &domain_keeper_list, list) {
+		if (ptr->is_not != is_not ||
+		    ptr->domainname != saved_domainname ||
+		    ptr->program != saved_program)
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = tmy_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->domainname = saved_domainname;
+	new_entry->program = saved_program;
+	new_entry->is_not = is_not;
+	new_entry->is_last_name = is_last_name;
+	list1_add_tail(&new_entry->list, &domain_keeper_list);
+	error = 0;
+out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	tmy_update_counter(TMY_UPDATES_COUNTER_EXCEPTION_POLICY);
+	return error;
+}
+
+/**
+ * tmy_write_domain_keeper_policy - Write "struct domain_keeper_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_not:    True if it is "no_keep_domain" entry.
+ * @is_delete: True if it is a delete request.
+ *
+ */
+int tmy_write_domain_keeper_policy(char *data, const bool is_not,
+				   const bool is_delete)
+{
+	char *cp = strstr(data, " from ");
+
+	if (cp) {
+		*cp = '\0';
+		return update_domain_keeper_entry(cp + 6, data,
+						  is_not, is_delete);
+	}
+	return update_domain_keeper_entry(data, NULL, is_not, is_delete);
+}
+
+/**
+ * tmy_read_domain_keeper_policy - Read "struct domain_keeper_entry" list.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool tmy_read_domain_keeper_policy(struct tmy_io_buffer *head)
+{
+	struct list1_head *pos;
+
+	list1_for_each_cookie(pos, head->read_var2, &domain_keeper_list) {
+		struct domain_keeper_entry *ptr;
+		const char *no;
+		const char *from = "";
+		const char *program = "";
+
+		ptr = list1_entry(pos, struct domain_keeper_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		no = ptr->is_not ? "no_" : "";
+		if (ptr->program) {
+			from = " from ";
+			program = ptr->program->name;
+		}
+		if (!tmy_io_printf(head,
+				   "%s" KEYWORD_KEEP_DOMAIN "%s%s%s\n", no,
+				   program, from, ptr->domainname->name))
+			goto out;
+	}
+	return true;
+out:
+	return false;
+}
+
+/**
+ * is_domain_keeper - Check whether the given program causes domain transition suppression.
+ *
+ * @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.
+ */
+static bool is_domain_keeper(const struct path_info *domainname,
+			     const struct path_info *program,
+			     const struct path_info *last_name)
+{
+	struct domain_keeper_entry *ptr;
+	bool flag = false;
+
+	list1_for_each_entry(ptr, &domain_keeper_list, list) {
+		if (ptr->is_deleted)
+			continue;
+		if (!ptr->is_last_name) {
+			if (ptr->domainname != domainname)
+				continue;
+		} else {
+			if (tmy_pathcmp(ptr->domainname, last_name))
+				continue;
+		}
+		if (ptr->program && tmy_pathcmp(ptr->program, program))
+			continue;
+		if (ptr->is_not)
+			return false;
+		flag = true;
+	}
+	return flag;
+}
+
+/*
+ * The list for "struct alias_entry".
+ *
+ * This list is updated only inside update_alias_entry(), thus
+ * no global mutex exists.
+ */
+static LIST1_HEAD(alias_list);
+
+/**
+ * update_alias_entry - Update "struct alias_entry" 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.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_alias_entry(const char *original_name,
+			      const char *aliased_name,
+			      const bool is_delete)
+{
+	struct alias_entry *new_entry;
+	struct alias_entry *ptr;
+	static DEFINE_MUTEX(lock);
+	const struct path_info *saved_original_name;
+	const struct path_info *saved_aliased_name;
+	int error = -ENOMEM;
+
+	if (!tmy_is_correct_path(original_name, 1, -1, -1, __func__) ||
+	    !tmy_is_correct_path(aliased_name, 1, -1, -1, __func__))
+		return -EINVAL; /* No patterns allowed. */
+	saved_original_name = tmy_save_name(original_name);
+	saved_aliased_name = tmy_save_name(aliased_name);
+	if (!saved_original_name || !saved_aliased_name)
+		return -ENOMEM;
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &alias_list, list) {
+		if (ptr->original_name != saved_original_name ||
+		    ptr->aliased_name != saved_aliased_name)
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = tmy_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->original_name = saved_original_name;
+	new_entry->aliased_name = saved_aliased_name;
+	list1_add_tail(&new_entry->list, &alias_list);
+	error = 0;
+out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	tmy_update_counter(TMY_UPDATES_COUNTER_EXCEPTION_POLICY);
+	return error;
+}
+
+/**
+ * tmy_read_alias_policy - Read "struct alias_entry" list.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool tmy_read_alias_policy(struct tmy_io_buffer *head)
+{
+	struct list1_head *pos;
+
+	list1_for_each_cookie(pos, head->read_var2, &alias_list) {
+		struct alias_entry *ptr;
+
+		ptr = list1_entry(pos, struct alias_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (!tmy_io_printf(head, KEYWORD_ALIAS "%s %s\n",
+				   ptr->original_name->name,
+				   ptr->aliased_name->name))
+			goto out;
+	}
+	return true;
+out:
+	return false;
+}
+
+/**
+ * tmy_write_alias_policy - Write "struct alias_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tmy_write_alias_policy(char *data, const bool is_delete)
+{
+	char *cp = strchr(data, ' ');
+
+	if (!cp)
+		return -EINVAL;
+	*cp++ = '\0';
+	return update_alias_entry(data, cp, is_delete);
+}
+
+/* Domain create/delete/undelete handler. */
+
+/* #define DEBUG_DOMAIN_UNDELETE */
+
+/**
+ * tmy_delete_domain - Delete a domain.
+ *
+ * @domainname: The name of domain.
+ *
+ * Returns 0.
+ */
+int tmy_delete_domain(char *domainname)
+{
+	struct domain_info *domain;
+	struct path_info name;
+
+	name.name = domainname;
+	tmy_fill_path_info(&name);
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&domain_list_lock);
+#ifdef DEBUG_DOMAIN_UNDELETE
+	printk(KERN_DEBUG "tmy_delete_domain %s\n", domainname);
+	list1_for_each_entry(domain, &domain_list, list) {
+		if (tmy_pathcmp(domain->domainname, &name))
+			continue;
+		printk(KERN_DEBUG "List: %p %u\n", domain, domain->is_deleted);
+	}
+#endif
+	/* Is there an active domain? */
+	list1_for_each_entry(domain, &domain_list, list) {
+		struct domain_info *domain2;
+		/* Never delete KERNEL_DOMAIN */
+		if (domain == &KERNEL_DOMAIN)
+			continue;
+		if (domain->is_deleted ||
+		    tmy_pathcmp(domain->domainname, &name))
+			continue;
+		/* Mark already deleted domains as non undeletable. */
+		list1_for_each_entry(domain2, &domain_list, list) {
+			if (!domain2->is_deleted ||
+			    tmy_pathcmp(domain2->domainname, &name))
+				continue;
+#ifdef DEBUG_DOMAIN_UNDELETE
+			if (domain2->is_deleted != 255)
+				printk(KERN_DEBUG
+				       "Marked %p as non undeletable\n",
+				       domain2);
+#endif
+			domain2->is_deleted = 255;
+		}
+		/* Delete and mark active domain as undeletable. */
+		domain->is_deleted = 1;
+#ifdef DEBUG_DOMAIN_UNDELETE
+		printk(KERN_DEBUG "Marked %p as undeletable\n", domain);
+#endif
+		break;
+	}
+	mutex_unlock(&domain_list_lock);
+	/***** EXCLUSIVE SECTION END *****/
+	return 0;
+}
+
+/**
+ * tmy_undelete_domain - Undelete a domain.
+ *
+ * @domainname: The name of domain.
+ *
+ * Returns pointer to "struct domain_info" on success, NULL otherwise.
+ */
+struct domain_info *tmy_undelete_domain(const char *domainname)
+{
+	struct domain_info *domain;
+	struct domain_info *candidate_domain = NULL;
+	struct path_info name;
+
+	name.name = domainname;
+	tmy_fill_path_info(&name);
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&domain_list_lock);
+#ifdef DEBUG_DOMAIN_UNDELETE
+	printk(KERN_DEBUG "tmy_undelete_domain %s\n", domainname);
+	list1_for_each_entry(domain, &domain_list, list) {
+		if (tmy_pathcmp(domain->domainname, &name))
+			continue;
+		printk(KERN_DEBUG "List: %p %u\n", domain, domain->is_deleted);
+	}
+#endif
+	list1_for_each_entry(domain, &domain_list, list) {
+		if (tmy_pathcmp(&name, domain->domainname))
+			continue;
+		if (!domain->is_deleted) {
+			/* This domain is active. I can't undelete. */
+			candidate_domain = NULL;
+#ifdef DEBUG_DOMAIN_UNDELETE
+			printk(KERN_DEBUG "%p is active. I can't undelete.\n",
+			       domain);
+#endif
+			break;
+		}
+		/* Is this domain undeletable? */
+		if (domain->is_deleted == 1)
+			candidate_domain = domain;
+	}
+	if (candidate_domain) {
+		candidate_domain->is_deleted = 0;
+#ifdef DEBUG_DOMAIN_UNDELETE
+		printk(KERN_DEBUG "%p was undeleted.\n", candidate_domain);
+#endif
+	}
+	mutex_unlock(&domain_list_lock);
+	/***** EXCLUSIVE SECTION END *****/
+	return candidate_domain;
+}
+
+/**
+ * tmy_find_or_assign_new_domain - Create a domain.
+ *
+ * @domainname: The name of domain.
+ * @profile:    Profile number to assign if the domain was newly created.
+ *
+ * Returns pointer to "struct domain_info" on success, NULL otherwise.
+ */
+struct domain_info *tmy_find_or_assign_new_domain(const char *domainname,
+						  const u8 profile)
+{
+	struct domain_info *domain = NULL;
+	const struct path_info *saved_domainname;
+
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&domain_list_lock);
+	domain = tmy_find_domain(domainname);
+	if (domain)
+		goto out;
+	if (!tmy_is_correct_domain(domainname, __func__))
+		goto out;
+	saved_domainname = tmy_save_name(domainname);
+	if (!saved_domainname)
+		goto out;
+	/* Can I reuse memory of deleted domain? */
+	list1_for_each_entry(domain, &domain_list, list) {
+		struct task_struct *p;
+		struct acl_info *ptr;
+		bool flag;
+		if (!domain->is_deleted ||
+		    domain->domainname != saved_domainname)
+			continue;
+		flag = false;
+		/***** CRITICAL SECTION START *****/
+		read_lock(&tasklist_lock);
+		for_each_process(p) {
+			if (tmy_real_domain(p) != domain)
+				continue;
+			flag = true;
+			break;
+		}
+		read_unlock(&tasklist_lock);
+		/***** CRITICAL SECTION END *****/
+		if (flag)
+			continue;
+#ifdef DEBUG_DOMAIN_UNDELETE
+		printk(KERN_DEBUG "Reusing %p %s\n", domain,
+		       domain->domainname->name);
+#endif
+		list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+			ptr->type |= ACL_DELETED;
+		}
+		tmy_set_domain_flag(domain, true, domain->flags);
+		domain->profile = profile;
+		domain->quota_warned = false;
+		mb(); /* Avoid out-of-order execution. */
+		domain->is_deleted = 0;
+		goto out;
+	}
+	/* No memory reusable. Create using new memory. */
+	domain = tmy_alloc_element(sizeof(*domain));
+	if (domain) {
+		INIT_LIST1_HEAD(&domain->acl_info_list);
+		domain->domainname = saved_domainname;
+		domain->profile = profile;
+		list1_add_tail(&domain->list, &domain_list);
+	}
+out:
+	mutex_unlock(&domain_list_lock);
+	/***** EXCLUSIVE SECTION END *****/
+	return domain;
+}
+
+/**
+ * tmy_find_next_domain - Find a domain.
+ *
+ * @bprm:           Pointer to "struct linux_binprm".
+ * @next_domain:    Pointer to pointer to "struct domain_info".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tmy_find_next_domain(struct linux_binprm *bprm,
+			 struct domain_info **next_domain)
+{
+	/*
+	 * This function assumes that the size of buffer returned by
+	 * tmy_realpath() = TMY_MAX_PATHNAME_LEN.
+	 */
+	struct tmy_page_buffer *tmp = tmy_alloc(sizeof(*tmp));
+	struct domain_info *old_domain = tmy_domain();
+	struct 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 = tmy_check_flags(old_domain, TMY_TOMOYO_MAC_FOR_FILE);
+	const bool is_enforce = (mode == 3);
+	int retval = -ENOMEM;
+	struct path_info r; /* real name */
+	struct path_info s; /* symlink name */
+	struct path_info l; /* last name */
+
+	if (!tmp)
+		goto out;
+
+	{
+		/*
+		 * Built-in initializers. This is needed because policies are
+		 * not loaded until starting /sbin/init.
+		 */
+		static bool first = true;
+		if (first) {
+			update_domain_initializer_entry(NULL, "/sbin/hotplug",
+							false, false);
+			update_domain_initializer_entry(NULL, "/sbin/modprobe",
+							false, false);
+			first = false;
+		}
+	}
+
+	/* Get tmy_realpath of program. */
+	retval = -ENOENT; /* I hope tmy_realpath() won't fail with -ENOMEM. */
+	real_program_name = tmy_realpath(original_name);
+	if (!real_program_name)
+		goto out;
+	/* Get tmy_realpath of symbolic link. */
+	symlink_program_name = tmy_realpath_nofollow(original_name);
+	if (!symlink_program_name)
+		goto out;
+
+	r.name = real_program_name;
+	tmy_fill_path_info(&r);
+	s.name = symlink_program_name;
+	tmy_fill_path_info(&s);
+	l.name = tmy_get_last_name(old_domain);
+	tmy_fill_path_info(&l);
+
+	/* Check 'alias' directive. */
+	if (tmy_pathcmp(&r, &s)) {
+		struct alias_entry *ptr;
+		/* Is this program allowed to be called via symbolic links? */
+		list1_for_each_entry(ptr, &alias_list, list) {
+			if (ptr->is_deleted ||
+			    tmy_pathcmp(&r, ptr->original_name) ||
+			    tmy_pathcmp(&s, ptr->aliased_name))
+				continue;
+			memset(real_program_name, 0, TMY_MAX_PATHNAME_LEN);
+			strncpy(real_program_name, ptr->aliased_name->name,
+				TMY_MAX_PATHNAME_LEN - 1);
+			tmy_fill_path_info(&r);
+			break;
+		}
+	}
+
+	/* Check execute permission. */
+	retval = tmy_check_exec_perm(old_domain, &r, tmp);
+	if (retval < 0)
+		goto out;
+
+	new_domain_name = tmp->buffer;
+	if (is_domain_initializer(old_domain->domainname, &r, &l)) {
+		/* Transit to the child of KERNEL_DOMAIN domain. */
+		snprintf(new_domain_name, TMY_MAX_PATHNAME_LEN + 1,
+			 ROOT_NAME " " "%s", real_program_name);
+	} else if (old_domain == &KERNEL_DOMAIN && !sbin_init_started) {
+		/*
+		 * 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 (is_domain_keeper(old_domain->domainname, &r, &l)) {
+		/* Keep current domain. */
+		domain = old_domain;
+	} else {
+		/* Normal domain transition. */
+		snprintf(new_domain_name, TMY_MAX_PATHNAME_LEN + 1,
+			 "%s %s", old_domain_name, real_program_name);
+	}
+	if (domain || strlen(new_domain_name) >= TMY_MAX_PATHNAME_LEN)
+		goto done;
+	domain = tmy_find_domain(new_domain_name);
+	if (domain)
+		goto done;
+	if (is_enforce)
+		goto done;
+	domain = tmy_find_or_assign_new_domain(new_domain_name,
+					       old_domain->profile);
+done:
+	if (domain)
+		goto out;
+	printk(KERN_WARNING "TOMOYO-ERROR: Domain '%s' not defined.\n",
+	       new_domain_name);
+	if (is_enforce)
+		retval = -EPERM;
+	else
+		tmy_set_domain_flag(old_domain, false,
+				    DOMAIN_FLAGS_TRANSITION_FAILED);
+out:
+	tmy_free(real_program_name);
+	tmy_free(symlink_program_name);
+	*next_domain = domain ? domain : old_domain;
+	tmy_free(tmp);
+	return retval;
+}

--


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [TOMOYO #12 (2.6.28-rc2-mm1) 09/11] LSM adapter functions.
  2008-11-04  6:08 [TOMOYO #12 (2.6.28-rc2-mm1) 00/11] TOMOYO Linux Kentaro Takeda
                   ` (7 preceding siblings ...)
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 08/11] Domain transition handler Kentaro Takeda
@ 2008-11-04  6:08 ` Kentaro Takeda
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 10/11] Kconfig and Makefile Kentaro Takeda
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 11/11] MAINTAINERS info Kentaro Takeda
  10 siblings, 0 replies; 32+ messages in thread
From: Kentaro Takeda @ 2008-11-04  6:08 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Toshiharu Harada, linux-security-module, linux-kernel,
	Kentaro Takeda, Tetsuo Handa

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 security/tomoyo/tomoyo.c |  376 +++++++++++++++++++++++++++++++++++++++++++++++
 security/tomoyo/tomoyo.h |  105 +++++++++++++
 2 files changed, 481 insertions(+)

--- /dev/null
+++ linux-2.6.28-rc2-mm1/security/tomoyo/tomoyo.c
@@ -0,0 +1,376 @@
+/*
+ * security/tomoyo/tomoyo.c
+ *
+ * LSM hooks for TOMOYO Linux.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 2.2.0-pre   2008/10/10
+ *
+ */
+
+#include <linux/security.h>
+#include "common.h"
+#include "tomoyo.h"
+#include "realpath.h"
+
+static int tmy_cred_prepare(struct cred *new, const struct cred *old, gfp_t gfp)
+{
+	/*
+	 * Since "struct domain_info *" is a sharable pointer, we don't need
+	 * to duplicate.
+	 */
+	new->security = old->security;
+	return 0;
+}
+
+static int tmy_bprm_set_creds(struct linux_binprm *bprm)
+{
+	/*
+	 * Do only if this function is called for the first time of an execve
+	 * operation.
+	 */
+	if (bprm->cred_prepared)
+		return 0;
+	/*
+	 * Load policy if /sbin/tomoyo-init exists and /sbin/init is requested
+	 * for the first time.
+	 */
+	if (!sbin_init_started)
+		tmy_load_policy(bprm->filename);
+	/*
+	 * Tell tmy_bprm_check_security() is called for the first time of an
+	 * execve operation.
+	 */
+	bprm->cred->security = NULL;
+	return 0;
+}
+
+static int tmy_bprm_check_security(struct linux_binprm *bprm)
+{
+	struct domain_info *domain = bprm->cred->security;
+
+	/*
+	 * Execute permission is checked against pathname passed to do_execve()
+	 * using current domain.
+	 */
+	if (!domain) {
+		struct domain_info *next_domain = NULL;
+		int retval = tmy_find_next_domain(bprm, &next_domain);
+
+		if (!retval)
+			bprm->cred->security = next_domain;
+		return retval;
+	}
+	/*
+	 * Read permission is checked against interpreters using next domain.
+	 * '1' is the result of open_to_namei_flags(O_RDONLY).
+	 */
+	return tmy_check_open_permission(domain, &bprm->file->f_path, 1);
+}
+
+static int tmy_sysctl(struct ctl_table *table, int op)
+{
+	int error;
+	char *name;
+
+	op &= MAY_READ | MAY_WRITE;
+	if (!op)
+		return 0;
+	name = sysctlpath_from_table(table);
+	if (!name)
+		return -ENOMEM;
+	error = tmy_check_file_perm(tmy_domain(), name, op);
+	tmy_free(name);
+	return error;
+}
+
+/**
+ * tmy_update_result - Update error code.
+ *
+ * @error: Return code from security_path_*().
+ *
+ * To be able to return DAC's error (if any) to the caller instead of
+ * MAC's error, we don't return MAC's error at security_path_*().
+ *
+ * We remember MAC's error only if security_path_*() returned an error.
+ *
+ * Returns 0 on success, -ENOMEM otherwise if @error != 0.
+ * Returns previously saved error code and clears it if @error == 0.
+ */
+static int tmy_update_result(int error)
+{
+	/* Structure for holding the result of security_path_*(). */
+	struct check_result_entry {
+		struct list_head list;
+		struct task_struct *task; /* = current */
+		int error; /* != 0 */
+	};
+	static LIST_HEAD(list);
+	static DEFINE_SPINLOCK(lock);
+	struct task_struct *task = current;
+	struct check_result_entry *entry;
+	if (!error) {
+		if (!list_empty(&list)) {
+			struct check_result_entry *p;
+			entry = NULL;
+			/***** CRITICAL SECTION START *****/
+			spin_lock(&lock);
+			list_for_each_entry(p, &list, list) {
+				if (p->task != task)
+					continue;
+				list_del(&p->list);
+				entry = p;
+				break;
+			}
+			spin_unlock(&lock);
+			/***** CRITICAL SECTION END *****/
+			if (entry) {
+				error = entry->error;
+				kfree(entry);
+			}
+		}
+		return error;
+	}
+	entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+	if (!entry)
+		return -ENOMEM;
+	entry->task = task;
+	entry->error = error;
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&lock);
+	list_add(&entry->list, &list);
+	spin_unlock(&lock);
+	/***** CRITICAL SECTION END *****/
+	return 0;
+}
+
+/**
+ * tmy_save_result - Remember error code for security_inode_*() if any.
+ *
+ * @error: Return code from security_path_*().
+ *
+ * Returns 0 on success, -ENOMEM otherwise.
+ *
+ * We don't save if @error == 0.
+ */
+static int tmy_save_result(const int error)
+{
+	return error ? tmy_update_result(error) : 0;
+}
+
+/**
+ * tmy_load_result - Fetch error code for security_inode_*().
+ *
+ * Returns error code saved by security_path_*().
+ */
+static int tmy_load_result(void)
+{
+	return tmy_update_result(0);
+}
+
+/* Clear error code in case security_inode_*() was not called. */
+static void tmy_path_clear(void)
+{
+	tmy_load_result();
+}
+
+static int tmy_path_truncate(struct path *path, loff_t length,
+			     unsigned int time_attrs, struct file *filp)
+{
+	return tmy_save_result(tmy_check_1path_perm(tmy_domain(),
+						    TMY_TYPE_TRUNCATE_ACL,
+						    path));
+}
+
+static int tmy_path_unlink(struct path *parent, struct dentry *dentry)
+{
+	struct path path = { parent->mnt, dentry };
+	return tmy_save_result(tmy_check_1path_perm(tmy_domain(),
+						    TMY_TYPE_UNLINK_ACL,
+						    &path));
+}
+
+static int tmy_path_mkdir(struct path *parent, struct dentry *dentry, int mode)
+{
+	struct path path = { parent->mnt, dentry };
+	return tmy_save_result(tmy_check_1path_perm(tmy_domain(),
+						    TMY_TYPE_MKDIR_ACL,
+						    &path));
+}
+
+static int tmy_path_rmdir(struct path *parent, struct dentry *dentry)
+{
+	struct path path = { parent->mnt, dentry };
+	return tmy_save_result(tmy_check_1path_perm(tmy_domain(),
+						    TMY_TYPE_RMDIR_ACL,
+						    &path));
+}
+
+static int tmy_path_symlink(struct path *parent, struct dentry *dentry,
+			    const char *old_name)
+{
+	struct path path = { parent->mnt, dentry };
+	return tmy_save_result(tmy_check_1path_perm(tmy_domain(),
+						    TMY_TYPE_SYMLINK_ACL,
+						    &path));
+}
+
+static int tmy_path_mknod(struct path *parent, struct dentry *dentry, int mode,
+			  unsigned int dev)
+{
+	struct path path = { parent->mnt, dentry };
+	int type = TMY_TYPE_CREATE_ACL;
+
+	switch (mode & S_IFMT) {
+	case S_IFCHR:
+		type = TMY_TYPE_MKCHAR_ACL;
+		break;
+	case S_IFBLK:
+		type = TMY_TYPE_MKBLOCK_ACL;
+		break;
+	case S_IFIFO:
+		type = TMY_TYPE_MKFIFO_ACL;
+		break;
+	case S_IFSOCK:
+		type = TMY_TYPE_MKSOCK_ACL;
+		break;
+	}
+	return tmy_save_result(tmy_check_1path_perm(tmy_domain(),
+						    type, &path));
+}
+
+static int tmy_path_link(struct dentry *old_dentry, struct path *new_dir,
+			 struct dentry *new_dentry)
+{
+	struct path path1 = { new_dir->mnt, old_dentry };
+	struct path path2 = { new_dir->mnt, new_dentry };
+	return tmy_save_result(tmy_check_2path_perm(tmy_domain(),
+						    TMY_TYPE_LINK_ACL,
+						    &path1, &path2));
+}
+
+static int tmy_path_rename(struct path *old_parent, struct dentry *old_dentry,
+			   struct path *new_parent, struct dentry *new_dentry)
+{
+	struct path path1 = { old_parent->mnt, old_dentry };
+	struct path path2 = { new_parent->mnt, new_dentry };
+	return tmy_save_result(tmy_check_2path_perm(tmy_domain(),
+						    TMY_TYPE_RENAME_ACL,
+						    &path1, &path2));
+}
+
+static int tmy_inode_link(struct dentry *old_dentry, struct inode *inode,
+			  struct dentry *new_dentry)
+{
+	return tmy_load_result();
+}
+
+static int tmy_inode_unlink(struct inode *inode, struct dentry *dentry)
+{
+	return tmy_load_result();
+}
+
+static int tmy_inode_symlink(struct inode *inode, struct dentry *dentry,
+			     const char *name)
+{
+	return tmy_load_result();
+}
+
+static int tmy_inode_mkdir(struct inode *inode, struct dentry *dentry,
+			   int mask)
+{
+	return tmy_load_result();
+}
+
+static int tmy_inode_rmdir(struct inode *inode, struct dentry *dentry)
+{
+	return tmy_load_result();
+}
+
+static int tmy_inode_create(struct inode *dir, struct dentry *dentry, int mode)
+{
+	return tmy_load_result();
+}
+
+static int tmy_inode_mknod(struct inode *inode, struct dentry *dentry,
+			   int mode, dev_t dev)
+{
+	return tmy_load_result();
+}
+
+static int tmy_inode_rename(struct inode *old_inode, struct dentry *old_dentry,
+			    struct inode *new_inode, struct dentry *new_dentry)
+{
+	return tmy_load_result();
+}
+
+static int tmy_inode_setattr(struct dentry *dentry, struct iattr *iattr)
+{
+	return tmy_load_result();
+}
+
+static int tmy_file_fcntl(struct file *file, unsigned int cmd,
+			  unsigned long arg)
+{
+	if (cmd == F_SETFL && ((arg ^ file->f_flags) & O_APPEND))
+		return tmy_check_rewrite_permission(tmy_domain(), file);
+	return 0;
+}
+
+static int tmy_dentry_open(struct file *f, const struct cred *cred)
+{
+	int flags = f->f_flags;
+
+	if ((flags + 1) & O_ACCMODE)
+		flags++;
+	flags |= f->f_flags & (O_APPEND | O_TRUNC);
+	/* Don't check read permission here if called from do_execve(). */
+	if (current->in_execve)
+		return 0;
+	return tmy_check_open_permission(tmy_domain(), &f->f_path, flags);
+}
+
+static struct security_operations tomoyo_security_ops = {
+	.name                      = "tomoyo",
+	.cred_prepare              = tmy_cred_prepare,
+	.bprm_set_creds            = tmy_bprm_set_creds,
+	.bprm_check_security       = tmy_bprm_check_security,
+	.sysctl                    = tmy_sysctl,
+	.file_fcntl                = tmy_file_fcntl,
+	.dentry_open               = tmy_dentry_open,
+	.path_truncate             = tmy_path_truncate,
+	.path_unlink               = tmy_path_unlink,
+	.path_mkdir                = tmy_path_mkdir,
+	.path_rmdir                = tmy_path_rmdir,
+	.path_symlink              = tmy_path_symlink,
+	.path_mknod                = tmy_path_mknod,
+	.path_link                 = tmy_path_link,
+	.path_rename               = tmy_path_rename,
+	.inode_create              = tmy_inode_create,
+	.inode_setattr             = tmy_inode_setattr,
+	.inode_unlink              = tmy_inode_unlink,
+	.inode_mkdir               = tmy_inode_mkdir,
+	.inode_rmdir               = tmy_inode_rmdir,
+	.inode_symlink             = tmy_inode_symlink,
+	.inode_mknod               = tmy_inode_mknod,
+	.inode_link                = tmy_inode_link,
+	.inode_rename              = tmy_inode_rename,
+	.path_clear                = tmy_path_clear,
+};
+
+static int __init tmy_init(void)
+{
+	struct cred *cred = (struct cred *) current_cred();
+
+	if (!security_module_enable(&tomoyo_security_ops))
+		return 0;
+	/* register ourselves with the security framework */
+	if (register_security(&tomoyo_security_ops))
+		panic("Failure registering TOMOYO Linux");
+	printk(KERN_INFO "TOMOYO Linux initialized\n");
+	cred->security = &KERNEL_DOMAIN;
+	return 0;
+}
+
+security_initcall(tmy_init);
--- /dev/null
+++ linux-2.6.28-rc2-mm1/security/tomoyo/tomoyo.h
@@ -0,0 +1,105 @@
+/*
+ * security/tomoyo/tomoyo.h
+ *
+ * Implementation of the Domain-Based Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 2.2.0-pre   2008/10/10
+ *
+ */
+
+#ifndef _SECURITY_TOMOYO_TOMOYO_H
+#define _SECURITY_TOMOYO_TOMOYO_H
+
+struct path_info;
+struct path;
+struct inode;
+struct linux_binprm;
+struct pt_regs;
+struct tmy_page_buffer;
+
+int tmy_check_file_perm(struct domain_info *domain, const char *filename,
+			const u8 perm);
+int tmy_check_exec_perm(struct domain_info *domain,
+			const struct path_info *filename,
+			struct tmy_page_buffer *buf);
+int tmy_check_open_permission(struct domain_info *domain, struct path *path,
+			      const int flag);
+int tmy_check_1path_perm(struct domain_info *domain, const u8 operation,
+			 struct path *path);
+int tmy_check_2path_perm(struct domain_info *domain, const u8 operation,
+			 struct path *path1, struct path *path2);
+int tmy_check_rewrite_permission(struct domain_info *domain,
+				 struct file *filp);
+int tmy_find_next_domain(struct linux_binprm *bprm,
+			 struct domain_info **next_domain);
+
+/* Index numbers for Access Controls. */
+
+#define TYPE_SINGLE_PATH_ACL                 0
+#define TYPE_DOUBLE_PATH_ACL                 1
+
+/* 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.
+ */
+
+#define TMY_TYPE_READ_WRITE_ACL    0
+#define TMY_TYPE_EXECUTE_ACL       1
+#define TMY_TYPE_READ_ACL          2
+#define TMY_TYPE_WRITE_ACL         3
+#define TMY_TYPE_CREATE_ACL        4
+#define TMY_TYPE_UNLINK_ACL        5
+#define TMY_TYPE_MKDIR_ACL         6
+#define TMY_TYPE_RMDIR_ACL         7
+#define TMY_TYPE_MKFIFO_ACL        8
+#define TMY_TYPE_MKSOCK_ACL        9
+#define TMY_TYPE_MKBLOCK_ACL      10
+#define TMY_TYPE_MKCHAR_ACL       11
+#define TMY_TYPE_TRUNCATE_ACL     12
+#define TMY_TYPE_SYMLINK_ACL      13
+#define TMY_TYPE_REWRITE_ACL      14
+#define MAX_SINGLE_PATH_OPERATION 15
+
+#define TMY_TYPE_LINK_ACL         0
+#define TMY_TYPE_RENAME_ACL       1
+#define MAX_DOUBLE_PATH_OPERATION 2
+
+#define TMY_DOMAINPOLICY          0
+#define TMY_EXCEPTIONPOLICY       1
+#define TMY_DOMAIN_STATUS         2
+#define TMY_PROCESS_STATUS        3
+#define TMY_MEMINFO               4
+#define TMY_SELFDOMAIN            5
+#define TMY_VERSION               6
+#define TMY_PROFILE               7
+#define TMY_MANAGER               8
+#define TMY_UPDATESCOUNTER        9
+
+extern struct domain_info KERNEL_DOMAIN;
+
+static inline struct domain_info *tmy_domain(void)
+{
+	return current_cred()->security;
+}
+
+/* Caller holds tasklist_lock spinlock. */
+static inline struct domain_info *tmy_real_domain(struct task_struct *task)
+{
+	/***** CRITICAL SECTION START *****/
+	const struct cred *cred = get_task_cred(task);
+	struct domain_info *domain = cred->security;
+
+	put_cred(cred);
+	return domain;
+	/***** CRITICAL SECTION END *****/
+}
+
+#endif /* !defined(_SECURITY_TOMOYO_TOMOYO_H) */

--


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [TOMOYO #12 (2.6.28-rc2-mm1) 10/11] Kconfig and Makefile
  2008-11-04  6:08 [TOMOYO #12 (2.6.28-rc2-mm1) 00/11] TOMOYO Linux Kentaro Takeda
                   ` (8 preceding siblings ...)
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 09/11] LSM adapter functions Kentaro Takeda
@ 2008-11-04  6:08 ` Kentaro Takeda
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 11/11] MAINTAINERS info Kentaro Takeda
  10 siblings, 0 replies; 32+ messages in thread
From: Kentaro Takeda @ 2008-11-04  6:08 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Toshiharu Harada, linux-security-module, linux-kernel,
	Kentaro Takeda, Tetsuo Handa

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
 security/Kconfig         |    1 +
 security/Makefile        |    2 ++
 security/tomoyo/Kconfig  |   11 +++++++++++
 security/tomoyo/Makefile |    1 +
 4 files changed, 15 insertions(+)

--- linux-2.6.28-rc2-mm1.orig/security/Kconfig
+++ linux-2.6.28-rc2-mm1/security/Kconfig
@@ -134,6 +134,7 @@ config SECURITY_DEFAULT_MMAP_MIN_ADDR
 
 source security/selinux/Kconfig
 source security/smack/Kconfig
+source security/tomoyo/Kconfig
 
 endmenu
 
--- linux-2.6.28-rc2-mm1.orig/security/Makefile
+++ linux-2.6.28-rc2-mm1/security/Makefile
@@ -5,6 +5,7 @@
 obj-$(CONFIG_KEYS)			+= keys/
 subdir-$(CONFIG_SECURITY_SELINUX)	+= selinux
 subdir-$(CONFIG_SECURITY_SMACK)		+= smack
+subdir-$(CONFIG_SECURITY_TOMOYO)        += tomoyo
 
 # always enable default capabilities
 obj-y		+= commoncap.o
@@ -17,3 +18,4 @@ obj-$(CONFIG_SECURITY_SELINUX)		+= selin
 obj-$(CONFIG_SECURITY_SMACK)		+= smack/built-in.o
 obj-$(CONFIG_SECURITY_ROOTPLUG)		+= root_plug.o
 obj-$(CONFIG_CGROUP_DEVICE)		+= device_cgroup.o
+obj-$(CONFIG_SECURITY_TOMOYO)		+= tomoyo/built-in.o
--- /dev/null
+++ linux-2.6.28-rc2-mm1/security/tomoyo/Kconfig
@@ -0,0 +1,11 @@
+config SECURITY_TOMOYO
+	bool "TOMOYO Linux Support"
+	depends on SECURITY
+	select SECURITYFS
+	select SECURITY_PATH
+	default n
+	help
+	  This selects TOMOYO Linux, pathname-based access control.
+	  Required userspace tools and further information may be
+          found at <http://tomoyo.sourceforge.jp/>.
+	  If you are unsure how to answer this question, answer N.
--- /dev/null
+++ linux-2.6.28-rc2-mm1/security/tomoyo/Makefile
@@ -0,0 +1 @@
+obj-y = common.o realpath.o tomoyo.o domain.o file.o

--


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [TOMOYO #12 (2.6.28-rc2-mm1) 11/11] MAINTAINERS info
  2008-11-04  6:08 [TOMOYO #12 (2.6.28-rc2-mm1) 00/11] TOMOYO Linux Kentaro Takeda
                   ` (9 preceding siblings ...)
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 10/11] Kconfig and Makefile Kentaro Takeda
@ 2008-11-04  6:08 ` Kentaro Takeda
  10 siblings, 0 replies; 32+ messages in thread
From: Kentaro Takeda @ 2008-11-04  6:08 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Toshiharu Harada, linux-security-module, linux-kernel, Kentaro Takeda

The archive of tomoyo-users-en mailing list is available at
http://lists.sourceforge.jp/mailman/archives/tomoyo-users-en/ .
Mailing lists for Japanese users are at
http://lists.sourceforge.jp/mailman/archives/tomoyo-users/ and
http://lists.sourceforge.jp/mailman/archives/tomoyo-dev/ .

TOMOYO Linux English portal is at
http://elinux.org/TomoyoLinux .

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
---
 MAINTAINERS |   13 +++++++++++++
 1 file changed, 13 insertions(+)

--- linux-2.6.28-rc2-mm1.orig/MAINTAINERS
+++ linux-2.6.28-rc2-mm1/MAINTAINERS
@@ -4130,6 +4130,19 @@ L:	tlan-devel@lists.sourceforge.net (sub
 W:	http://sourceforge.net/projects/tlan/
 S:	Maintained
 
+TOMOYO SECURITY MODULE
+P:	Kentaro Takeda
+M:	takedakn@nttdata.co.jp
+P:	Tetsuo Handa
+M:	penguin-kernel@I-love.SAKURA.ne.jp
+L:	linux-kernel@vger.kernel.org (kernel issues)
+L:	tomoyo-users-en@lists.sourceforge.jp (subscribers-only, for developers and users in English)
+L:	tomoyo-dev@lists.sourceforge.jp (subscribers-only, for developers in Japanese)
+L:	tomoyo-users@lists.sourceforge.jp (subscribers-only, for users in Japanese)
+W:	http://tomoyo.sourceforge.jp/
+T:	quilt http://svn.sourceforge.jp/svnroot/tomoyo/trunk/2.2.x/tomoyo-lsm/patches/
+S:	Maintained
+
 TOSHIBA ACPI EXTRAS DRIVER
 P:	John Belmonte
 M:	toshiba_acpi@memebeam.org

--


^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [TOMOYO #12 (2.6.28-rc2-mm1) 02/11] Add in_execve flag into task_struct.
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 02/11] Add in_execve flag into task_struct Kentaro Takeda
@ 2008-11-05 23:12   ` Andrew Morton
  0 siblings, 0 replies; 32+ messages in thread
From: Andrew Morton @ 2008-11-05 23:12 UTC (permalink / raw)
  To: Kentaro Takeda
  Cc: haradats, linux-security-module, linux-kernel, penguin-kernel, dhowells

On Tue, 04 Nov 2008 15:08:49 +0900
Kentaro Takeda <takedakn@nttdata.co.jp> wrote:

> This patch allows LSM modules to determine whether current process is in an
> execve operation or not so that they can behave differently while an execve
> operation is in progress.
> 
> This allows TOMOYO to dispense with a readability check on a file to be
> executed under the process's current credentials, and to do it instead under
> the proposed credentials.
> 
> This is required with the new COW credentials because TOMOYO is no longer
> allowed to mark the state temporarily in the security struct attached to the
> task_struct.

None of this patch applied.  It seems that some credentials code has
disappeared from linux-next.  So I took a bet shot at reimplementing it
- please check.

If/when that code gets restored to linux-next I get to fix the patch
again.  It's a bit of collateral damage whcih happens when people muck
up their trees.

 fs/compat.c           |    3 +++
 fs/exec.c             |    3 +++
 include/linux/sched.h |    2 ++
 3 files changed, 8 insertions(+)

diff -puN fs/compat.c~tomoyo-add-in_execve-flag-into-task_struct fs/compat.c
--- a/fs/compat.c~tomoyo-add-in_execve-flag-into-task_struct
+++ a/fs/compat.c
@@ -1388,6 +1388,7 @@ int compat_do_execve(char * filename,
 	struct file *file;
 	int retval;
 
+	current->in_execve = 1;
 	retval = -ENOMEM;
 	bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
 	if (!bprm)
@@ -1440,6 +1441,7 @@ int compat_do_execve(char * filename,
 	retval = search_binary_handler(bprm, regs);
 	if (retval >= 0) {
 		/* execve success */
+		current->in_execve = 0;
 		security_bprm_free(bprm);
 		acct_update_integrals(current);
 		free_bprm(bprm);
@@ -1464,6 +1466,7 @@ out_kfree:
 	free_bprm(bprm);
 
 out_ret:
+	current->in_execve = 0;
 	return retval;
 }
 
diff -puN fs/exec.c~tomoyo-add-in_execve-flag-into-task_struct fs/exec.c
--- a/fs/exec.c~tomoyo-add-in_execve-flag-into-task_struct
+++ a/fs/exec.c
@@ -1268,6 +1268,7 @@ int do_execve(char * filename,
 	struct files_struct *displaced;
 	int retval;
 
+	current->in_execve = 1;
 	retval = unshare_files(&displaced);
 	if (retval)
 		goto out_ret;
@@ -1325,6 +1326,7 @@ int do_execve(char * filename,
 	retval = search_binary_handler(bprm,regs);
 	if (retval >= 0) {
 		/* execve success */
+		current->in_execve = 0;
 		security_bprm_free(bprm);
 		acct_update_integrals(current);
 		free_bprm(bprm);
@@ -1353,6 +1355,7 @@ out_files:
 	if (displaced)
 		reset_files_struct(displaced);
 out_ret:
+	current->in_execve = 0;
 	return retval;
 }
 
diff -puN include/linux/sched.h~tomoyo-add-in_execve-flag-into-task_struct include/linux/sched.h
--- a/include/linux/sched.h~tomoyo-add-in_execve-flag-into-task_struct
+++ a/include/linux/sched.h
@@ -1130,6 +1130,8 @@ struct task_struct {
 	/* ??? */
 	unsigned int personality;
 	unsigned did_exec:1;
+	unsigned in_execve:1;	/* Tell the LSMs that the process is doing an
+				 * execve */
 	pid_t pid;
 	pid_t tgid;
 
_


^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [TOMOYO #12 (2.6.28-rc2-mm1) 03/11] Singly linked list implementation.
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 03/11] Singly linked list implementation Kentaro Takeda
@ 2008-11-05 23:12   ` Andrew Morton
  0 siblings, 0 replies; 32+ messages in thread
From: Andrew Morton @ 2008-11-05 23:12 UTC (permalink / raw)
  To: Kentaro Takeda
  Cc: haradats, linux-security-module, linux-kernel, penguin-kernel


This patch needs a changelog which justifies the addition of the new
infrastructure.

Because an obvious question is "why not just use list_heads?", and this
patch didn't provide the answer to that.  It should do so please.


^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [TOMOYO #12 (2.6.28-rc2-mm1) 04/11] Introduce d_realpath().
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 04/11] Introduce d_realpath() Kentaro Takeda
@ 2008-11-05 23:12   ` Andrew Morton
  2008-11-17  6:52     ` Kentaro Takeda
  0 siblings, 1 reply; 32+ messages in thread
From: Andrew Morton @ 2008-11-05 23:12 UTC (permalink / raw)
  To: Kentaro Takeda
  Cc: haradats, linux-security-module, linux-kernel, takedakn, penguin-kernel

On Tue, 04 Nov 2008 15:08:51 +0900
Kentaro Takeda <takedakn@nttdata.co.jp> wrote:

> +		/*
> +		 * Exception: Use /proc/self/ rather than /proc/\$/
> +		 * for current process.
> +		 */
> +		name = dentry->d_name.name;
> +		name_len = dentry->d_name.len;
> +		if (IS_ROOT(parent) &&
> +		    parent->d_sb->s_magic == PROC_SUPER_MAGIC &&
> +		    !strict_strtoul(name, 10, &pid)) {

Well that looks like rather a hack.

It would still be a hack, but a better implementation might be to save
the procfs superblock's address in a global then do


#ifdef CONFIG_PROCFS
static inline bool is_procfs_sb(struct super_block *sb)
{
	return sb == saved_procfs_sb;
}
#else
static inline bool is_procfs_sb(struct super_block *sb)
{
	return false;
}
#endif


^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [TOMOYO #12 (2.6.28-rc2-mm1) 05/11] Memory and pathname management functions.
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 05/11] Memory and pathname management functions Kentaro Takeda
@ 2008-11-05 23:12   ` Andrew Morton
  2008-11-10 10:34     ` Kentaro Takeda
  0 siblings, 1 reply; 32+ messages in thread
From: Andrew Morton @ 2008-11-05 23:12 UTC (permalink / raw)
  To: Kentaro Takeda
  Cc: haradats, linux-security-module, linux-kernel, takedakn, penguin-kernel

On Tue, 04 Nov 2008 15:08:52 +0900
Kentaro Takeda <takedakn@nttdata.co.jp> wrote:

> TOMOYO Linux performs pathname based access control.
> To remove factors that make pathname based access control difficult
> (e.g. symbolic links, "..", "//" etc.), TOMOYO Linux derives realpath
> of requested pathname from "struct dentry" and "struct vfsmount".
> 
> The maximum length of string data is limited to 4000 including trailing '\0'.
> Since TOMOYO Linux uses '\ooo' style representation for non ASCII printable
> characters, may be TOMOYO Linux should be able to support 16336 (which means
> (NAME_MAX * (PATH_MAX / (NAME_MAX + 1)) * 4 + (PATH_MAX / (NAME_MAX + 1)))
> including trailing '\0'), but I think 4000 is enough for practical use.
> 

This code does look a bit hacky.

> 
> --- /dev/null
> +++ linux-2.6.28-rc2-mm1/security/tomoyo/realpath.c
> @@ -0,0 +1,540 @@
> +/*
> + * security/tomoyo/realpath.c
> + *
> + * Get the canonicalized absolute pathnames. The basis for TOMOYO.
> + *
> + * Copyright (C) 2005-2008  NTT DATA CORPORATION
> + *
> + * Version: 2.2.0-pre   2008/10/10
> + *
> + */
> +
> +#include <linux/types.h>
> +#include <linux/mount.h>
> +#include <linux/magic.h>
> +#include <linux/sysctl.h>
> +#include "common.h"
> +#include "realpath.h"
> +
> +/**
> + * tmy_realpath_from_path2 - Returns realpath(3) of the given dentry but ignores chroot'ed root.
> + *
> + * @path:        Pointer to "struct path".
> + * @newname:     Pointer to buffer to return value in.
> + * @newname_len: Size of @newname.
> + *
> + * Returns 0 on success, negative value otherwise.
> + *
> + * If dentry is a directory, trailing '/' is appended.
> + * Characters out of 0x20 < c < 0x7F range are converted to
> + * \ooo style octal string.
> + * Character \ is converted to \\ string.
> + */
> +int tmy_realpath_from_path2(struct path *path, char *newname, int newname_len)
> +{
> +	int error = -ENOMEM;
> +	struct dentry *dentry = path->dentry;
> +	char *sp;
> +
> +	if (!dentry || !path->mnt || !newname || newname_len <= 2048)
> +		return -EINVAL;
> +	if (dentry->d_op && dentry->d_op->d_dname) {
> +		/* For "socket:[\$]" and "pipe:[\$]". */
> +		static const int offset = 1536;
> +		sp = dentry->d_op->d_dname(dentry, newname + offset,
> +					   newname_len - offset);
> +	} else {
> +		path_get(path);
> +		sp = d_realpath(path, newname, newname_len);
> +		path_put(path);
> +	}
> +	if (IS_ERR(sp)) {
> +		error = PTR_ERR(sp);
> +	} else {
> +		char *dp = newname;
> +		newname += newname_len - 5;
> +		while (dp <= newname) {
> +			const unsigned char c = *(unsigned char *) sp++;
> +			*dp++ = c;
> +			if (c == '\\') {
> +				*dp++ = '\\';
> +			} else if (c > ' ' && c < 127) {
> +				continue;
> +			} else if (!c) {
> +				error = 0;
> +				break;
> +			} else {
> +				*dp++ = '\\';
> +				*dp++ = (c >> 6) + '0';
> +				*dp++ = ((c >> 3) & 7) + '0';
> +				*dp++ = (c & 7) + '0';
> +			}
> +		}
> +	}
> +	if (error)
> +		printk(KERN_WARNING "tmy_realpath: Pathname too long.\n");
> +	return error;
> +}
> +
> +/**
> + * tmy_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 tmy_alloc(), so the caller must call tmy_free()
> + * if these functions didn't return NULL.
> + */
> +char *tmy_realpath_from_path(struct path *path)
> +{
> +	char *buf = tmy_alloc(sizeof(struct tmy_page_buffer));
> +
> +	if (buf && tmy_realpath_from_path2(path, buf,
> +					     TMY_MAX_PATHNAME_LEN - 1) == 0)
> +		return buf;
> +	tmy_free(buf);
> +	return NULL;
> +}
> +
> +/**
> + * tmy_realpath - Get realpath of a pathname.
> + *
> + * @pathname: The pathname to solve.
> + *
> + * Returns the realpath of @pathname on success, NULL otherwise.
> + */
> +char *tmy_realpath(const char *pathname)
> +{
> +	struct nameidata nd;
> +
> +	if (pathname && path_lookup(pathname, LOOKUP_FOLLOW, &nd) == 0) {
> +		char *buf = tmy_realpath_from_path(&nd.path);
> +		path_put(&nd.path);
> +		return buf;
> +	}
> +	return NULL;
> +}
> +
> +/**
> + * tmy_realpath_nofollow - Get realpath of a pathname.
> + *
> + * @pathname: The pathname to solve.
> + *
> + * Returns the realpath of @pathname on success, NULL otherwise.
> + */
> +char *tmy_realpath_nofollow(const char *pathname)
> +{
> +	struct nameidata nd;
> +
> +	if (pathname && path_lookup(pathname, 0, &nd) == 0) {
> +		char *buf = tmy_realpath_from_path(&nd.path);
> +		path_put(&nd.path);
> +		return buf;
> +	}
> +	return NULL;
> +}
> +
> +/**
> + * round_up - Round up an integer so that the returned pointers are appropriately aligned.
> + *
> + * @size: Size in bytes.
> + *
> + * Returns rounded value of @size.
> + *
> + * FIXME: Are there more requirements that is needed for assigning value
> + * atomically?
> + */
> +static inline unsigned int round_up(const unsigned int size)
> +{
> +	if (sizeof(void *) >= sizeof(long))
> +		return ((size + sizeof(void *) - 1)
> +			/ sizeof(void *)) * sizeof(void *);
> +	else
> +		return ((size + sizeof(long) - 1)
> +			/ sizeof(long)) * sizeof(long);
> +}

Can PTR_ALIGN be used?

If not, please prefer to avoid implementing generic helpers down in
specific code.  It is better to add the helpers in a kernel-wide
fashion in an early patch, then to use those halpers in the
subsyste-specific patches.


> +/* Memory allocated for non-string data. */
> +static unsigned int allocated_memory_for_elements;
> +/* Quota for holding non-string data. */
> +static unsigned int quota_for_elements;
> +
> +/**
> + * tmy_alloc_element - Allocate permanent memory for structures.
> + *
> + * @size: Size in bytes.
> + *
> + * Returns pointer to allocated memory on success, NULL otherwise.
> + *
> + * The RAM is chunked, so NEVER try to kfree() the returned pointer.
> + */
> +void *tmy_alloc_element(const unsigned int size)
> +{
> +	static char *buf;
> +	static DEFINE_MUTEX(lock);
> +	static unsigned int buf_used_len = PAGE_SIZE;
> +	char *ptr = NULL;
> +	const unsigned int word_aligned_size = round_up(size);
> +
> +	if (word_aligned_size > PAGE_SIZE)
> +		return NULL;
> +	/***** EXCLUSIVE SECTION START *****/
> +	mutex_lock(&lock);
> +	if (buf_used_len + word_aligned_size > PAGE_SIZE) {
> +		if (!quota_for_elements || allocated_memory_for_elements
> +		    + PAGE_SIZE <= quota_for_elements)
> +			ptr = kzalloc(PAGE_SIZE, GFP_KERNEL);
> +		if (!ptr) {
> +			printk(KERN_WARNING "ERROR: Out of memory "
> +			       "for tmy_alloc_element().\n");
> +			if (!sbin_init_started)
> +				panic("MAC Initialization failed.\n");
> +		} else {
> +			buf = ptr;
> +			allocated_memory_for_elements += PAGE_SIZE;
> +			buf_used_len = word_aligned_size;
> +			ptr = buf;
> +		}
> +	} else if (word_aligned_size) {
> +		int i;
> +		ptr = buf + buf_used_len;
> +		buf_used_len += word_aligned_size;
> +		for (i = 0; i < word_aligned_size; i++) {
> +			if (!ptr[i])
> +				continue;
> +			printk(KERN_ERR "WARNING: Reserved memory was tainted! "
> +			       "The system might go wrong.\n");
> +			ptr[i] = '\0';
> +		}
> +	}
> +	mutex_unlock(&lock);
> +	/***** EXCLUSIVE SECTION END *****/
> +	return ptr;
> +}
> +
> +/* Memory allocated for string data. */
> +static unsigned int allocated_memory_for_savename;
> +/* Quota for holding string data. */
> +static unsigned int quota_for_savename;
> +
> +/*
> + * TOMOYO uses this hash only when appending a string into the string
> + * table. Frequency of appending strings is very low. So we don't need
> + * large (e.g. 64k) hash size. 256 will be sufficient.
> + */
> +#define MAX_HASH 256
> +
> +/* Structure for string data. */
> +struct name_entry {
> +	struct list1_head list;
> +	struct path_info entry;
> +};
> +
> +/* Structure for available memory region. */
> +struct free_memory_block_list {
> +	struct list_head list;
> +	char *ptr;             /* Pointer to a free area. */
> +	int len;               /* Length of the area.     */
> +};

You didn't need to invent list1_head for this application.  This is
*exactly* what the existing hlist_head is designed for.

> +/*
> + * The list for "struct name_entry".
> + *
> + * This list is updated only inside tmy_save_name(), thus
> + * no global mutex exists.
> + */
> +static struct list1_head name_list[MAX_HASH];
> +
> +/**
> + * tmy_save_name - Allocate permanent memory for string data.
> + *
> + * @name: The string to store into the permernent memory.
> + *
> + * Returns pointer to "struct path_info" on success, NULL otherwise.
> + *
> + * The RAM is shared, so NEVER try to modify or kfree() the returned name.
> + */
> +const struct path_info *tmy_save_name(const char *name)
> +{
> +	static LIST_HEAD(fmb_list);
> +	static DEFINE_MUTEX(lock);
> +	struct name_entry *ptr;
> +	unsigned int hash;
> +	struct free_memory_block_list *fmb;
> +	int len;
> +	char *cp;
> +
> +	if (!name)
> +		return NULL;
> +	len = strlen(name) + 1;
> +	if (len > TMY_MAX_PATHNAME_LEN) {
> +		printk(KERN_WARNING "ERROR: Name too long "
> +		       "for tmy_save_name().\n");
> +		return NULL;
> +	}
> +	hash = full_name_hash((const unsigned char *) name, len - 1);
> +	/***** EXCLUSIVE SECTION START *****/
> +	mutex_lock(&lock);
> +	list1_for_each_entry(ptr, &name_list[hash % MAX_HASH], list) {
> +		if (hash == ptr->entry.hash && !strcmp(name, ptr->entry.name))
> +			goto out;
> +	}
> +	list_for_each_entry(fmb, &fmb_list, list) {
> +		if (len <= fmb->len)
> +			goto ready;
> +	}
> +	if (!quota_for_savename || allocated_memory_for_savename + PAGE_SIZE
> +	    <= quota_for_savename)
> +		cp = kzalloc(PAGE_SIZE, GFP_KERNEL);
> +	else
> +		cp = NULL;
> +	fmb = kzalloc(sizeof(*fmb), GFP_KERNEL);
> +	if (!cp || !fmb) {
> +		kfree(cp);
> +		kfree(fmb);
> +		printk(KERN_WARNING "ERROR: Out of memory "
> +		       "for tmy_save_name().\n");
> +		if (!sbin_init_started)
> +			panic("MAC Initialization failed.\n");
> +		ptr = NULL;
> +		goto out;
> +	}
> +	allocated_memory_for_savename += PAGE_SIZE;
> +	list_add(&fmb->list, &fmb_list);
> +	fmb->ptr = cp;
> +	fmb->len = PAGE_SIZE;
> +ready:
> +	ptr = tmy_alloc_element(sizeof(*ptr));
> +	if (!ptr)
> +		goto out;
> +	ptr->entry.name = fmb->ptr;
> +	memmove(fmb->ptr, name, len);
> +	tmy_fill_path_info(&ptr->entry);
> +	fmb->ptr += len;
> +	fmb->len -= len;
> +	list1_add_tail(&ptr->list, &name_list[hash % MAX_HASH]);
> +	if (fmb->len == 0) {
> +		list_del(&fmb->list);
> +		kfree(fmb);
> +	}
> +out:
> +	mutex_unlock(&lock);
> +	/***** EXCLUSIVE SECTION END *****/
> +	return ptr ? &ptr->entry : NULL;
> +}

Nothing ever gets removed from fmb_list.  How odd.

If this is not a bug, I'd suggest that a code comment be added
explaining what all this code does and why it does it and how it does
it.

> +/**
> + * tmy_realpath_init - Initialize realpath related code.
> + *
> + * Returns 0.
> + */
> +static int __init tmy_realpath_init(void)
> +{
> +	int i;
> +
> +	if (TMY_MAX_PATHNAME_LEN > PAGE_SIZE)
> +		panic("Bad size.");
> +	for (i = 0; i < MAX_HASH; i++)
> +		INIT_LIST1_HEAD(&name_list[i]);
> +	INIT_LIST1_HEAD(&KERNEL_DOMAIN.acl_info_list);
> +	KERNEL_DOMAIN.domainname = tmy_save_name(ROOT_NAME);
> +	list1_add_tail(&KERNEL_DOMAIN.list, &domain_list);
> +	if (tmy_find_domain(ROOT_NAME) != &KERNEL_DOMAIN)
> +		panic("Can't register KERNEL_DOMAIN");
> +	return 0;
> +}
> +
> +security_initcall(tmy_realpath_init);
> +
> +/* Memory allocated for temporal purpose. */
> +static atomic_t dynamic_memory_size;

The correct word is "temporary".  This needs fixing in at least one
other place.

Is this counter really useful?  If not, I'd suggest that it be removed
and that all calls to tmy_alloc() simply be replaced by calls to
kmalloc().

A better way to perform memory accounting would be to create slab
caches for commonly-used objects and to reply uponthe existing
accounting in /proc/slabinfo.

> +/**
> + * tmy_alloc - Allocate memory for temporal purpose.
> + *
> + * @size: Size in bytes.
> + *
> + * Returns pointer to allocated memory on success, NULL otherwise.
> + */
> +void *tmy_alloc(const size_t size)
> +{
> +	void *p = kzalloc(size, GFP_KERNEL);
> +	if (p)
> +		atomic_add(ksize(p), &dynamic_memory_size);
> +	return p;
> +}

Note that I said "kmalloc", not "kzalloc".  This function zeroes
everything all the time, and surely that is not necessary.  It's just a
waste of CPU time.

> +/**
> + * tmy_free - Release memory allocated by tmy_alloc().
> + *
> + * @p: Pointer returned by tmy_alloc(). May be NULL.
> + *
> + * Returns nothing.
> + */
> +void tmy_free(const void *p)
> +{
> +	if (p)
> +		atomic_sub(ksize(p), &dynamic_memory_size);
> +	kfree(p);
> +}
> +
> +static int tmy_print_ascii(const char *sp, const char *cp,
> +			   int *buflen0, char **end0)
> +{
> +	int error = -ENOMEM;
> +	int buflen = *buflen0;
> +	char *end = *end0;
> +
> +	while (sp <= cp) {
> +		unsigned char c;
> +
> +		c = *(unsigned char *) cp;
> +		if (c == '\\') {
> +			buflen -= 2;
> +			if (buflen < 0)
> +				goto out;
> +			*--end = '\\';
> +			*--end = '\\';
> +		} else if (c > ' ' && c < 127) {
> +			if (--buflen < 0)
> +				goto out;
> +			*--end = (char) c;
> +		} else {
> +			buflen -= 4;
> +			if (buflen < 0)
> +				goto out;
> +			*--end = (c & 7) + '0';
> +			*--end = ((c >> 3) & 7) + '0';
> +			*--end = (c >> 6) + '0';
> +			*--end = '\\';
> +		}
> +		cp--;
> +	}
> +
> +	*buflen0 = buflen;
> +	*end0 = end;
> +	error = 0;
> +out:
> +	return error;
> +}

I look at this and wonder "hm, does that duplicate any facility which
the kernel provides"?  But I can't tell, because I don't know what this
function does, and I shouldn't have to sit down with a pencil and paper
decrypting it.

A function like this should have a comment explaining what it does.

> +
> +/* tmy_realpath_from_path2() for "struct ctl_table". */
> +static int tmy_sysctl_path(struct ctl_table *table, char *buffer, int buflen)
> +{
> +	int error = -ENOMEM;
> +	char *end = buffer + buflen;
> +
> +	if (buflen < 256)
> +		goto out;
> +
> +	*--end = '\0';
> +	buflen--;
> +
> +	buflen -= 9; /* for "/proc/sys" prefix */
> +
> +	while (table) {
> +		char buf[32];
> +		const char *sp = table->procname;
> +		const char *cp;
> +
> +		if (!sp) {
> +			memset(buf, 0, sizeof(buf));
> +			snprintf(buf, sizeof(buf) - 1, "=%d=", table->ctl_name);
> +			sp = buf;
> +		}
> +		cp = strchr(sp, '\0') - 1;
> +
> +		if (tmy_print_ascii(sp, cp, &buflen, &end))
> +			goto out;
> +
> +		if (--buflen < 0)
> +			goto out;
> +
> +		*--end = '/';
> +		table = table->parent;
> +	}
> +
> +	/* Move the pathname to the top of the buffer. */
> +	memmove(buffer, "/proc/sys", 9);
> +	memmove(buffer + 9, end, strlen(end) + 1);
> +	error = 0;
> +out:
> +	return error;
> +}

Is this needed if CONFIG_SYSCTL=n?  Does it compile if CONFIG_SYSCTL=n?

> +/**
> + * sysctlpath_from_table - return the realpath of a ctl_table.
> + * @table: pointer to "struct ctl_table".
> + *
> + * Returns realpath(3) of the @table on success.
> + * Returns NULL on failure.
> + *
> + * This function uses tmy_alloc(), so the caller must call tmy_free()
> + * if this function didn't return NULL.
> + */
> +char *sysctlpath_from_table(struct ctl_table *table)
> +{
> +	char *buf = tmy_alloc(TMY_MAX_PATHNAME_LEN);
> +
> +	if (buf && tmy_sysctl_path(table, buf, TMY_MAX_PATHNAME_LEN - 1) == 0)
> +		return buf;
> +	tmy_free(buf);
> +	return NULL;
> +}

ditto

> +/**
> + * tmy_read_memory_counter - Check for memory usage.
> + *
> + * @head: Pointer to "struct tmy_io_buffer".
> + *
> + * Returns memory usage.

In what units?  Megabytes?

> + */
> +int tmy_read_memory_counter(struct tmy_io_buffer *head)
> +{
> +	if (!head->read_eof) {
> +		const unsigned int shared = allocated_memory_for_savename;
> +		const unsigned int private = allocated_memory_for_elements;
> +		const unsigned int dynamic = atomic_read(&dynamic_memory_size);
> +		char buffer[64];
> +
> +		memset(buffer, 0, sizeof(buffer));
> +		if (quota_for_savename)
> +			snprintf(buffer, sizeof(buffer) - 1,
> +				 "   (Quota: %10u)", quota_for_savename);
> +		else
> +			buffer[0] = '\0';
> +		tmy_io_printf(head, "Shared:  %10u%s\n", shared, buffer);
> +		if (quota_for_elements)
> +			snprintf(buffer, sizeof(buffer) - 1,
> +				 "   (Quota: %10u)", quota_for_elements);
> +		else
> +			buffer[0] = '\0';
> +		tmy_io_printf(head, "Private: %10u%s\n", private, buffer);
> +		tmy_io_printf(head, "Dynamic: %10u\n", dynamic);
> +		tmy_io_printf(head, "Total:   %10u\n",
> +			      shared + private + dynamic);
> +		head->read_eof = true;
> +	}
> +	return 0;
> +}

This (I assume) is part of an implementation of a userspace interface. 
We care a lot about userspace interfaces.  Please describe the Tomoyo
userspace interfaces so that we can review them for suitability and
maintainability.

Surely this function should return a 64-bit quantity?

> +/**
> + * tmy_write_memory_quota - Set memory quota.
> + *
> + * @head: Pointer to "struct tmy_io_buffer".
> + *
> + * Returns 0.
> + */
> +int tmy_write_memory_quota(struct tmy_io_buffer *head)
> +{
> +	char *data = head->write_buf;
> +	unsigned int size;
> +
> +	if (sscanf(data, "Shared: %u", &size) == 1)
> +		quota_for_savename = size;
> +	else if (sscanf(data, "Private: %u", &size) == 1)
> +		quota_for_elements = size;
> +	return 0;
> +}

Again, we would like to see a complete decription of the proposed
userspace ABI.  This one looks fairly ugly.  Do I really have to write
'S' 'h' 'a' 'r' 'e' 'd' ':' ' ' into some pseudo file?

A better interface would be two suitably-named pseudo files each of
which takes a bare integer string.  None of this funny colon-based
prefixing stuff.

> --- /dev/null
> +++ linux-2.6.28-rc2-mm1/security/tomoyo/realpath.h
> @@ -0,0 +1,60 @@
> +/*
> + * security/tomoyo/realpath.h
> + *
> + * Get the canonicalized absolute pathnames. The basis for TOMOYO.
> + *
> + * Copyright (C) 2005-2008  NTT DATA CORPORATION
> + *
> + * Version: 2.2.0-pre   2008/10/10
> + *
> + */
> +
> +#ifndef _SECURITY_TOMOYO_REALPATH_H
> +#define _SECURITY_TOMOYO_REALPATH_H
> +
> +struct path;
> +struct condition_list;
> +struct path_info;
> +struct tmy_io_buffer;
> +
> +/* Returns realpath(3) of the given pathname but ignores chroot'ed root. */
> +int tmy_realpath_from_path2(struct path *path, char *newname, int newname_len);
> +
> +/*
> + * Returns realpath(3) of the given pathname but ignores chroot'ed root.
> + * These functions use tmy_alloc(), so the caller must call tmy_free()
> + * if these functions didn't return NULL.
> + */
> +char *tmy_realpath(const char *pathname);
> +/* Same with tmy_realpath() except that it doesn't follow the final symlink. */
> +char *tmy_realpath_nofollow(const char *pathname);
> +/* Same with tmy_realpath() except that the pathname is already solved. */
> +char *tmy_realpath_from_path(struct path *path);
> +/* Same with tmy_realpath() except that it uses struct ctl_table. */
> +char *sysctlpath_from_table(struct ctl_table *table);
> +
> +/*
> + * Allocate memory for ACL entry.
> + * The RAM is chunked, so NEVER try to kfree() the returned pointer.
> + */
> +void *tmy_alloc_element(const unsigned int size);
> +
> +/*
> + * Keep the given name on the RAM.
> + * The RAM is shared, so NEVER try to modify or kfree() the returned name.
> + */
> +const struct path_info *tmy_save_name(const char *name);
> +
> +/* Allocate memory for temporary use (e.g. permission checks). */
> +void *tmy_alloc(const size_t size);
> +
> +/* Free memory allocated by tmy_alloc(). */
> +void tmy_free(const void *p);
> +
> +/* Check for memory usage. */
> +int tmy_read_memory_counter(struct tmy_io_buffer *head);
> +
> +/* Set memory quota. */
> +int tmy_write_memory_quota(struct tmy_io_buffer *head);
> +
> +#endif /* !defined(_SECURITY_TOMOYO_REALPATH_H) */


^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYO Linux.
  2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYO Linux Kentaro Takeda
@ 2008-11-05 23:12   ` Andrew Morton
  2008-11-06 21:46     ` [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYOLinux Tetsuo Handa
                       ` (3 more replies)
  0 siblings, 4 replies; 32+ messages in thread
From: Andrew Morton @ 2008-11-05 23:12 UTC (permalink / raw)
  To: Kentaro Takeda
  Cc: haradats, linux-security-module, linux-kernel, takedakn, penguin-kernel

On Tue, 04 Nov 2008 15:08:53 +0900
Kentaro Takeda <takedakn@nttdata.co.jp> wrote:

> This file contains common functions (e.g. policy I/O, pattern matching).
> 
> +
> +#include <linux/uaccess.h>
> +#include <linux/security.h>
> +#include <linux/hardirq.h>
> +#include "realpath.h"
> +#include "common.h"
> +#include "tomoyo.h"
> +
> +/* Set default specified by the kernel config. */
> +#define MAX_ACCEPT_ENTRY 2048
> +
> +/* Has /sbin/init started? */
> +bool sbin_init_started;
> +
> +/* String table for functionality that takes 4 modes. */
> +static const char *mode_4[4] = {
> +	"disabled", "learning", "permissive", "enforcing"
> +};
> +/* String table for functionality that takes 2 modes. */
> +static const char *mode_2[4] = {
> +	"disabled", "enabled", "enabled", "enabled"
> +};
> +
> +/* Table for profile. */
> +static struct {
> +	const char *keyword;
> +	unsigned int current_value;
> +	const unsigned int max_value;
> +} tmy_control_array[TMY_MAX_CONTROL_INDEX] = {
> +	[TMY_TOMOYO_MAC_FOR_FILE]        = { "MAC_FOR_FILE",        0, 3 },
> +	[TMY_TOMOYO_MAX_ACCEPT_ENTRY]
> +	= { "MAX_ACCEPT_ENTRY",    MAX_ACCEPT_ENTRY, INT_MAX },
> +	[TMY_TOMOYO_VERBOSE]             = { "TOMOYO_VERBOSE",      1, 1 },
> +};
> +
> +/* Profile table. Memory is allocated as needed. */
> +static struct profile {
> +	unsigned int value[TMY_MAX_CONTROL_INDEX];
> +	const struct path_info *comment;
> +} *profile_ptr[MAX_PROFILES];
> +
> +/* Permit policy management by non-root user? */
> +static bool manage_by_non_root;
> +
> +/* Utility functions. */
> +
> +/* Open operation for /sys/kernel/security/tomoyo/ interface. */
> +static int tmy_open_control(const u8 type, struct file *file);
> +/* Close /sys/kernel/security/tomoyo/ interface. */
> +static int tmy_close_control(struct file *file);
> +/* Read operation for /sys/kernel/security/tomoyo/ interface. */
> +static int tmy_read_control(struct file *file, char __user *buffer,
> +			    const int buffer_len);
> +/* Write operation for /sys/kernel/security/tomoyo/ interface. */
> +static int tmy_write_control(struct file *file, const char __user *buffer,
> +			     const int buffer_len);
> +
> +/**
> + * is_byte_range - Check whether the string isa \ooo style octal value.
> + *
> + * @str: Pointer to the string.
> + *
> + * Returns true if @str is a \ooo style octal value, false otherwise.
> + */
> +static bool is_byte_range(const char *str)
> +{
> +	return *str >= '0' && *str++ <= '3' &&
> +		*str >= '0' && *str++ <= '7' &&
> +		*str >= '0' && *str <= '7';
> +}

Well... why?

I cannot think of any kernel interfaces which use octal strings.  What
is special about Tomoyo?

> +/**
> + * is_decimal - Check whether the character is a decimal character.
> + *
> + * @c: The character to check.
> + *
> + * Returns true if @c is a decimal character, false otherwise.
> + */
> +static bool is_decimal(const char c)
> +{
> +	return c >= '0' && c <= '9';
> +}

This duplicates a standard ctype.h function.

> +/**
> + * is_hexadecimal - Check whether the character is a hexadecimal character.
> + *
> + * @c: The character to check.
> + *
> + * Returns true if @c is a hexadecimal character, false otherwise.
> + */
> +static bool is_hexadecimal(const char c)
> +{
> +	return (c >= '0' && c <= '9') ||
> +		(c >= 'A' && c <= 'F') ||
> +		(c >= 'a' && c <= 'f');
> +}

And so does this.

> +/**
> + * is_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 bool is_alphabet_char(const char c)
> +{
> +	return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
> +}

As does this.

> +/**
> + * 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 u8 make_byte(const u8 c1, const u8 c2, const u8 c3)
> +{
> +	return ((c1 - '0') << 6) + ((c2 - '0') << 3) + (c3 - '0');
> +}
> +
> +/**
> + * 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 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;
> +}

hrm.  Isn't there a standard string.h way of doing this?

If not, it looks like a pretty common thing.  I'd suggest that it a) be
coded to not do two passes across the input and b) proposed as a
generic addition to the kernel's string library functions.

> +/**
> + * 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 normalize_line(unsigned char *buffer)
> +{
> +	unsigned char *sp = buffer;
> +	unsigned char *dp = buffer;
> +	bool first = true;
> +
> +	while (*sp && (*sp <= ' ' || *sp >= 127))
> +		sp++;
> +	while (*sp) {
> +		if (!first)
> +			*dp++ = ' ';
> +		first = false;
> +		while (*sp > ' ' && *sp < 127)
> +			*dp++ = *sp++;
> +		while (*sp && (*sp <= ' ' || *sp >= 127))
> +			sp++;
> +	}
> +	*dp = '\0';
> +}

that looks pretty generic as well.

It seems to have duplicated isprint() in lots of places.

> +/**
> + * tmy_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
> + * @function:     The name of function calling me.
> + *
> + * Check whether the given filename follows the naming rules.
> + * Returns true if @filename follows the naming rules, false otherwise.
> + */
> +bool tmy_is_correct_path(const char *filename, const s8 start_type,
> +			 const s8 pattern_type, const s8 end_type,
> +			 const char *function)
> +{
> +	bool contains_pattern = false;
> +	unsigned char c;
> +	unsigned char d;
> +	unsigned char e;
> +	const char *original_filename = filename;
> +
> +	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 = *(strchr(filename, '\0') - 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 ((c = *filename++) != '\0') {
> +		if (c == '\\') {
> +			switch ((c = *filename++)) {
> +			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 '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 = make_byte(c, d, e);
> +				if (c && (c <= ' ' || c >= 127))
> +					continue; /* pattern is not \000 */
> +			}
> +			goto out;
> +		} else if (c <= ' ' || c >= 127) {
> +			goto out;
> +		}
> +	}
> +	if (pattern_type == 1) { /* Must contain pattern */
> +		if (!contains_pattern)
> +			goto out;
> +	}
> +	return true;
> +out:
> +	printk(KERN_DEBUG "%s: Invalid pathname '%s'\n", function,
> +	       original_filename);
> +	return false;
> +}
> +
> +/**
> + * tmy_is_correct_domain - Check whether the given domainname follows the naming rules.
> + * @domainname:   The domainname to check.
> + * @function:     The name of function calling me.
> + *
> + * Returns true if @domainname follows the naming rules, false otherwise.
> + */
> +bool tmy_is_correct_domain(const unsigned char *domainname,
> +			   const char *function)
> +{
> +	unsigned char c;
> +	unsigned char d;
> +	unsigned char e;
> +	const char *org_domainname = domainname;
> +
> +	if (!domainname || strncmp(domainname, ROOT_NAME, ROOT_NAME_LEN))
> +		goto out;
> +	domainname += 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 = make_byte(c, d, e);
> +					if (c && (c <= ' ' || c >= 127))

isprint?

> +						/* pattern is not \000 */
> +						continue;
> +				}
> +				goto out;
> +			} else if (c < ' ' || c >= 127) {
> +				goto out;
> +			}
> +		}
> +	} while (*domainname);
> +	return true;
> +out:
> +	printk(KERN_DEBUG "%s: Invalid domainname '%s'\n", function,
> +	       org_domainname);
> +	return false;
> +}

Tomoyo does an *amazing* amount of string hacking.  It's weird.

What happens if I have a filename which includes a character in the
128->255 range?

> +/**
> + * tmy_is_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 tmy_is_domain_def(const unsigned char *buffer)
> +{
> +	return !strncmp(buffer, ROOT_NAME, ROOT_NAME_LEN);
> +}
> +
> +/**
> + * tmy_find_domain - Find a domain by the given name.
> + *
> + * @domainname: The domainname to find.
> + *
> + * Returns pointer to "struct domain_info" if found, NULL otherwise.
> + */
> +struct domain_info *tmy_find_domain(const char *domainname)
> +{
> +	struct domain_info *domain;
> +	struct path_info name;
> +
> +	name.name = domainname;
> +	tmy_fill_path_info(&name);
> +	list1_for_each_entry(domain, &domain_list, list) {
> +		if (!domain->is_deleted &&
> +		    !tmy_pathcmp(&name, domain->domainname))
> +			return domain;
> +	}
> +	return NULL;
> +}

No lock was taken to protect that list.

If the caller must take some lock then that precondition should be
documented in the function's comment.

> +/**
> + * path_depth - Evaluate the number of '/' in a string.
> + *
> + * @pathname: The string to evaluate.
> + *
> + * Returns path depth of the string.
> + *
> + * I score 2 for each of the '/' in the @pathname
> + * and score 1 if the @pathname ends with '/'.
> + */
> +static int path_depth(const char *pathname)
> +{
> +	int i = 0;
> +
> +	if (pathname) {
> +		char *ep = strchr(pathname, '\0');

what?  Does that even work?  strchr(p, 0) should always return NULL:

RETURN VALUE
       The strchr() and strrchr() functions return a pointer  to  the  matched
       character or NULL if the character is not found.


Using

	pathname + strlen(pathname)

would be saner, no?

> +		if (pathname < ep--) {
> +			if (*ep != '/')
> +				i++;
> +			while (pathname <= ep)
> +				if (*ep-- == '/')
> +					i += 2;
> +		}
> +	}
> +	return i;
> +}

I cannot imagine why this function exists :(

> [vast amounts of string hacking snipped]

This seems like madness, sorry.

Why the heck is so much string bashing going on in here???

> +/**
> + * tmy_io_printf - Transactional printf() to "struct tmy_io_buffer" structure.
> + *
> + * @head: Pointer to "struct tmy_io_buffer".
> + * @fmt:  The printf()'s format string, followed by parameters.
> + *
> + * Returns true on success, false otherwise.

This comment should explain what the terms "success" and "failure"
refer to.  Perhaps "success"=="the output didn't overflow" or something.

> + * The snprintf() will truncate, but tmy_io_printf() won't.
> + */
> +bool tmy_io_printf(struct tmy_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;
> +}
> +
> +/**
> + * tmy_get_exe - Get tmy_realpath() of current process.
> + *
> + * Returns the tmy_realpath() of current process on success, NULL otherwise.
> + *
> + * This function uses tmy_alloc(), so the caller must call tmy_free()
> + * if this function didn't return NULL.
> + */
> +static const char *tmy_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 = tmy_realpath_from_path(&vma->vm_file->f_path);
> +			break;
> +		}
> +	}
> +	up_read(&mm->mmap_sem);
> +	return cp;
> +}

What guarantees that the first executable mapping in the mapping list
is the correct one for the executable?

What prevents us from accidentally breaking that guarantee in the
future?  I wasn't even aware that this was the case.

What happens if the executable was unlinked?


> +/**
> + * tmy_get_msg - Get warning message.
> + *
> + * @is_enforce: Is it enforcing mode?
> + *
> + * Returns "ERROR" or "WARNING".
> + */
> +const char *tmy_get_msg(const bool is_enforce)
> +{
> +	if (is_enforce)
> +		return "ERROR";
> +	else
> +		return "WARNING";
> +}
> +
> +/**
> + * tmy_check_flags - Check mode for specified functionality.
> + *
> + * @domain: Pointer to "struct domain_info".
> + * @index:  The functionality to check mode.
> + *
> + * Returns the mode of specified functionality.

That description is rather meaningless.

> + */
> +unsigned int tmy_check_flags(const struct domain_info *domain, const u8 index)
> +{
> +	const u8 profile = domain->profile;
> +
> +	if (unlikely(in_interrupt())) {
> +		static u8 count = 20;
> +		if (count) {
> +			count--;
> +			printk(KERN_ERR "BUG: sleeping function called "
> +			       "from invalid context.\n");
> +			dump_stack();
> +		}
> +		return 0;
> +	}

a) WARN_ON is preferred

b) WARN_ON_ONCE might be usable here

c) what on earth is this code doing??

> +	return sbin_init_started && index < TMY_MAX_CONTROL_INDEX
> +#if MAX_PROFILES != 256
> +		&& profile < MAX_PROFILES
> +#endif
> +		&& profile_ptr[profile] ?
> +		profile_ptr[profile]->value[index] : 0;
> +}

And this.  I cannot imagine why Tomoyo cares whether /sbin/init has
started yet.  This sort of thing should be commented!

What happens in a cgroups environment where there will be multiple
/sbin/inits running?

> +/**
> + * tmy_verbose_mode - Check whether TOMOYO is verbose mode.
> + *
> + * @domain: Pointer to "struct domain_info".
> + *
> + * Returns true if domain policy violation warning should be printed to
> + * console.
> + */
> +bool tmy_verbose_mode(const struct domain_info *domain)
> +{
> +	return tmy_check_flags(domain, TMY_TOMOYO_VERBOSE) != 0;
> +}
> +
> +/**
> + * tmy_check_domain_quota - Check for domain's quota.
> + *
> + * @domain: Pointer to "struct domain_info".
> + *
> + * Returns true if the domain is not exceeded quota, false otherwise.
> + */
> +bool tmy_check_domain_quota(struct domain_info * const domain)
> +{
> +	unsigned int count = 0;
> +	struct acl_info *ptr;
> +
> +	if (!domain)
> +		return true;
> +	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
> +		if (ptr->type & ACL_DELETED)
> +			continue;
> +		switch (tmy_acl_type2(ptr)) {
> +			struct single_path_acl_record *acl1;
> +			struct double_path_acl_record *acl2;
> +			u16 perm;
> +		case TYPE_SINGLE_PATH_ACL:
> +			acl1 = container_of(ptr, struct single_path_acl_record,
> +					    head);
> +			perm = acl1->perm;
> +			if (perm & (1 << TMY_TYPE_EXECUTE_ACL))
> +				count++;
> +			if (perm &
> +			    ((1 << TMY_TYPE_READ_ACL) |
> +			     (1 << TMY_TYPE_WRITE_ACL)))
> +				count++;
> +			if (perm & (1 << TMY_TYPE_CREATE_ACL))
> +				count++;
> +			if (perm & (1 << TMY_TYPE_UNLINK_ACL))
> +				count++;
> +			if (perm & (1 << TMY_TYPE_MKDIR_ACL))
> +				count++;
> +			if (perm & (1 << TMY_TYPE_RMDIR_ACL))
> +				count++;
> +			if (perm & (1 << TMY_TYPE_MKFIFO_ACL))
> +				count++;
> +			if (perm & (1 << TMY_TYPE_MKSOCK_ACL))
> +				count++;
> +			if (perm & (1 << TMY_TYPE_MKBLOCK_ACL))
> +				count++;
> +			if (perm & (1 << TMY_TYPE_MKCHAR_ACL))
> +				count++;
> +			if (perm & (1 << TMY_TYPE_TRUNCATE_ACL))
> +				count++;
> +			if (perm & (1 << TMY_TYPE_SYMLINK_ACL))
> +				count++;
> +			if (perm & (1 << TMY_TYPE_REWRITE_ACL))
> +				count++;
> +			break;
> +		case TYPE_DOUBLE_PATH_ACL:
> +			acl2 = container_of(ptr, struct double_path_acl_record,
> +					    head);
> +			perm = acl2->perm;
> +			if (perm & (1 << TMY_TYPE_LINK_ACL))
> +				count++;
> +			if (perm & (1 << TMY_TYPE_RENAME_ACL))
> +				count++;
> +			break;
> +		}
> +	}
> +	if (count < tmy_check_flags(domain, TMY_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;
> +}

This function is poorly named.

If I see code such as

	if (tmy_check_domain_quota(...))
		<something>

then I cannot tell whether <something> will be called when the quota is
exceeded, or whether it is called when the quota is _not_ exceeded. 
This is because the term "check" carries no information about the
results of that check.

Now, if the code was:

	if (tmy_domain_quota_exceeded(...))
		<something>

then I would immediately know what caused <something> to be called.

see?

> +/**
> + * tmy_find_or_assign_new_profile - Create a new profile.
> + *
> + * @profile: Profile number to create.
> + *
> + * Returns pointer to "struct profile" on success, NULL otherwise.
> + */
> +static struct profile *tmy_find_or_assign_new_profile(const unsigned int
> +						      profile)
> +{
> +	static DEFINE_MUTEX(lock);
> +	struct profile *ptr = NULL;
> +
> +	/***** EXCLUSIVE SECTION START *****/
> +	mutex_lock(&lock);
> +	if (profile < MAX_PROFILES) {

This check didn't need to be inside the lock.

> +		ptr = profile_ptr[profile];
> +		if (ptr)
> +			goto ok;
> +		ptr = tmy_alloc_element(sizeof(*ptr));
> +		if (ptr) {
> +			int i;
> +			for (i = 0; i < TMY_MAX_CONTROL_INDEX; i++)
> +				ptr->value[i]
> +					= tmy_control_array[i].current_value;
> +			mb(); /* Avoid out-of-order execution. */

hm.

> +			profile_ptr[profile] = ptr;
> +		}
> +	}
> +ok:
> +	mutex_unlock(&lock);
> +	/***** EXCLUSIVE SECTION END *****/
> +	return ptr;
> +}
> +
> +/**
> + * write_profile - Write profile table.

where to?

> + * @head: Pointer to "struct tmy_io_buffer"
> + *
> + * Returns 0 on success, negative value otherwise.
> + */
> +static int write_profile(struct tmy_io_buffer *head)
> +{
> +	char *data = head->write_buf;
> +	unsigned int i;
> +	unsigned int value;
> +	char *cp;
> +	struct profile *profile;
> +	unsigned long num;
> +
> +	cp = strchr(data, '-');
> +	if (cp)
> +		*cp = '\0';
> +	if (strict_strtoul(data, 10, &num))
> +		return -EINVAL;
> +	if (cp)
> +		data = cp + 1;
> +	profile = tmy_find_or_assign_new_profile(num);
> +	if (!profile)
> +		return -EINVAL;
> +	cp = strchr(data, '=');
> +	if (!cp)
> +		return -EINVAL;
> +	*cp = '\0';
> +	tmy_update_counter(TMY_UPDATES_COUNTER_PROFILE);
> +	if (!strcmp(data, "COMMENT")) {
> +		profile->comment = tmy_save_name(cp + 1);
> +		return 0;
> +	}
> +	for (i = 0; i < TMY_MAX_CONTROL_INDEX; i++) {
> +		if (strcmp(data, tmy_control_array[i].keyword))
> +			continue;
> +		if (sscanf(cp + 1, "%u", &value) != 1) {
> +			int j;
> +			const char **modes;
> +			switch (i) {
> +			case TMY_TOMOYO_VERBOSE:
> +				modes = mode_2;
> +				break;
> +			default:
> +				modes = 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 > tmy_control_array[i].max_value) {
> +			value = tmy_control_array[i].max_value;
> +		}
> +		profile->value[i] = value;
> +		return 0;
> +	}
> +	return -EINVAL;
> +}
> +
> +/**
> + * read_profile - Read profile table.

Where from?

> + * @head: Pointer to "struct tmy_io_buffer"
> + *
> + * Returns 0.
> + */
> +static int read_profile(struct tmy_io_buffer *head)
> +{
> +	static const int total = TMY_MAX_CONTROL_INDEX + 1;
> +	int step;
> +
> +	if (head->read_eof)
> +		return 0;
> +	for (step = head->read_step; step < MAX_PROFILES * total; step++) {
> +		const u8 index = step / total;
> +		u8 type = step % total;
> +		const struct profile *profile = profile_ptr[index];
> +		head->read_step = step;
> +		if (!profile)
> +			continue;
> +		if (!type) { /* Print profile' comment tag. */
> +			if (!tmy_io_printf(head, "%u-COMMENT=%s\n",
> +					   index, profile->comment ?
> +					   profile->comment->name : ""))
> +				break;
> +			continue;
> +		}
> +		type--;
> +		if (type < TMY_MAX_CONTROL_INDEX) {
> +			const unsigned int value = profile->value[type];
> +			const char **modes = NULL;
> +			const char *keyword = tmy_control_array[type].keyword;
> +			switch (tmy_control_array[type].max_value) {
> +			case 3:
> +				modes = mode_4;
> +				break;
> +			case 1:
> +				modes = mode_2;
> +				break;
> +			}
> +			if (modes) {
> +				if (!tmy_io_printf(head, "%u-%s=%s\n", index,
> +						   keyword, modes[value]))
> +					break;
> +			} else {
> +				if (!tmy_io_printf(head, "%u-%s=%u\n", index,
> +						   keyword, value))
> +					break;
> +			}
> +		}
> +	}
> +	if (step == MAX_PROFILES * total)
> +		head->read_eof = true;
> +	return 0;
> +}

These functions appear to be implementing more userspace interfaces.

The userspace interface is the most important part of any kernel code. 
We can change all the internal details, but the interfaces will live
forever.

Hence we should review the proposed interfaces before even looking at
the code.  Indeed, before even writing the code.

What are the Tomoyo kernel interfaces?


> +/* Structure for policy manager. */
> +struct policy_manager_entry {
> +	struct list1_head list;
> +	/* A path to program or a domainname. */
> +	const struct path_info *manager;
> +	bool is_domain;  /* True if manager is a domainname. */
> +	bool is_deleted; /* True if this entry is deleted. */
> +};
> +
> +/*
> + * The list for "struct policy_manager_entry".
> + *
> + * This list is updated only inside update_manager_entry(), thus
> + * no global mutex exists.
> + */
> +static LIST1_HEAD(policy_manager_list);
> +
> +/**
> + * update_manager_entry - Add a manager entry.
> + *
> + * @manager:   The path to manager or the domainnamme.
> + * @is_delete: True if it is a delete request.
> + *
> + * Returns 0 on success, negative value otherwise.
> + */
> +static int update_manager_entry(const char *manager, const bool is_delete)
> +{
> +	struct policy_manager_entry *new_entry;
> +	struct policy_manager_entry *ptr;
> +	static DEFINE_MUTEX(lock);
> +	const struct path_info *saved_manager;
> +	int error = -ENOMEM;
> +	bool is_domain = false;
> +
> +	if (tmy_is_domain_def(manager)) {
> +		if (!tmy_is_correct_domain(manager, __func__))
> +			return -EINVAL;
> +		is_domain = true;
> +	} else {
> +		if (!tmy_is_correct_path(manager, 1, -1, -1, __func__))
> +			return -EINVAL;
> +	}
> +	saved_manager = tmy_save_name(manager);
> +	if (!saved_manager)
> +		return -ENOMEM;
> +	/***** EXCLUSIVE SECTION START *****/
> +	mutex_lock(&lock);
> +	list1_for_each_entry(ptr, &policy_manager_list, list) {
> +		if (ptr->manager != saved_manager)
> +			continue;
> +		ptr->is_deleted = is_delete;
> +		error = 0;
> +		goto out;
> +	}
> +	if (is_delete) {
> +		error = -ENOENT;
> +		goto out;
> +	}
> +	new_entry = tmy_alloc_element(sizeof(*new_entry));
> +	if (!new_entry)
> +		goto out;
> +	new_entry->manager = saved_manager;
> +	new_entry->is_domain = is_domain;
> +	list1_add_tail(&new_entry->list, &policy_manager_list);
> +	error = 0;
> +out:
> +	mutex_unlock(&lock);
> +	/***** EXCLUSIVE SECTION END *****/
> +	if (!error)
> +		tmy_update_counter(TMY_UPDATES_COUNTER_MANAGER);
> +	return error;
> +}

eh?  So deleted entries get their "is_deleted" flag set but they are
never actually removed from the list nor freed?  So over time the list
gets longer and longer and consumes more and more memory?

> +/**
> + * write_manager_policy - Write manager policy.
> + *
> + * @head: Pointer to "struct tmy_io_buffer"
> + *
> + * Returns 0 on success, negative value otherwise.
> + */
> +static int write_manager_policy(struct tmy_io_buffer *head)
> +{
> +	char *data = head->write_buf;
> +	bool is_delete = str_starts(&data, KEYWORD_DELETE);
> +
> +	if (!strcmp(data, "manage_by_non_root")) {
> +		manage_by_non_root = !is_delete;
> +		return 0;
> +	}
> +	return update_manager_entry(data, is_delete);
> +}

More userspace ABI proposals?

> +/**
> + * read_manager_policy - Read manager policy.
> + *
> + * @head: Pointer to "struct tmy_io_buffer"
> + *
> + * Returns 0.
> + */
> +static int read_manager_policy(struct tmy_io_buffer *head)
> +{
> +	struct list1_head *pos;
> +
> +	if (head->read_eof)
> +		return 0;
> +	list1_for_each_cookie(pos, head->read_var2, &policy_manager_list) {
> +		struct policy_manager_entry *ptr;
> +		ptr = list1_entry(pos, struct policy_manager_entry, list);
> +		if (ptr->is_deleted)
> +			continue;
> +		if (!tmy_io_printf(head, "%s\n", ptr->manager->name))
> +			return 0;
> +	}
> +	head->read_eof = true;
> +	return 0;
> +}
> +
> +/**
> + * is_policy_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.
> + */
> +static bool is_policy_manager(void)
> +{
> +	struct policy_manager_entry *ptr;
> +	const char *exe;
> +	const struct task_struct *task = current;
> +	const struct path_info *domainname = tmy_domain()->domainname;
> +	bool found = false;
> +
> +	if (!sbin_init_started)
> +		return true;
> +	if (!manage_by_non_root && (task->cred->uid || task->cred->euid))
> +		return false;

What happens in a containerised environment where uids are non-unique
and where there are multiple /sbin/inits?

> +	list1_for_each_entry(ptr, &policy_manager_list, list) {
> +		if (!ptr->is_deleted && ptr->is_domain
> +		    && !tmy_pathcmp(domainname, ptr->manager))
> +			return true;
> +	}
> +	exe = tmy_get_exe();
> +	if (!exe)
> +		return false;
> +	list1_for_each_entry(ptr, &policy_manager_list, list) {
> +		if (!ptr->is_deleted && !ptr->is_domain
> +		    && !strcmp(exe, ptr->manager->name)) {
> +			found = true;
> +			break;
> +		}
> +	}
> +	if (!found) { /* Reduce error messages. */
> +		static pid_t last_pid;
> +		const pid_t pid = current->pid;
> +		if (last_pid != pid) {
> +			printk(KERN_WARNING "%s ( %s ) is not permitted to "
> +			       "update policies.\n", domainname->name, exe);

It appears that unprivileged userspace can cause this messge to be
printed at will.  That can cause the logs to fill and is considered to
be a small denial of service security hole.

> +			last_pid = pid;
> +		}
> +	}
> +	tmy_free(exe);
> +	return found;
> +}
> +
> +/**
> + * is_select_one - Parse select command.
> + *
> + * @head: Pointer to "struct tmy_io_buffer".
> + * @data: String to parse.
> + *
> + * Returns true on success, false otherwise.
> + */
> +static bool is_select_one(struct tmy_io_buffer *head, const char *data)
> +{
> +	unsigned int pid;
> +	struct domain_info *domain = NULL;
> +
> +	if (sscanf(data, "pid=%u", &pid) == 1) {

PIDs are no longer system-wide unique, and here we appear to be
implementing new userspace ABIs using PIDs.

> +		struct task_struct *p;
> +		/***** CRITICAL SECTION START *****/
> +		read_lock(&tasklist_lock);
> +		p = find_task_by_vpid(pid);
> +		if (p)
> +			domain = tmy_real_domain(p);
> +		read_unlock(&tasklist_lock);
> +		/***** CRITICAL SECTION END *****/
> +	} else if (!strncmp(data, "domain=", 7)) {
> +		if (tmy_is_domain_def(data + 7))
> +			domain = tmy_find_domain(data + 7);
> +	} else
> +		return false;
> +	head->read_avail = 0;
> +	tmy_io_printf(head, "# select %s\n", data);
> +	head->read_single_domain = true;
> +	head->read_eof = !domain;
> +	if (domain) {
> +		struct domain_info *d;
> +		head->read_var1 = NULL;
> +		list1_for_each_entry(d, &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)
> +			tmy_io_printf(head, "# This is a deleted domain.\n");
> +	}
> +	head->write_var1 = domain;
> +	return true;
> +}
> +
> +/**
> + * write_domain_policy - Write domain policy.
> + *
> + * @head: Pointer to "struct tmy_io_buffer".
> + *
> + * Returns 0 on success, negative value otherwise.
> + */
> +static int write_domain_policy(struct tmy_io_buffer *head)
> +{
> +	char *data = head->write_buf;
> +	struct domain_info *domain = head->write_var1;
> +	bool is_delete = false;
> +	bool is_select = false;
> +	bool is_undelete = false;
> +	unsigned int profile;
> +
> +	if (str_starts(&data, KEYWORD_DELETE))
> +		is_delete = true;
> +	else if (str_starts(&data, KEYWORD_SELECT))
> +		is_select = true;
> +	else if (str_starts(&data, KEYWORD_UNDELETE))
> +		is_undelete = true;
> +	if (is_select && is_select_one(head, data))
> +		return 0;
> +	/* Don't allow updating policies by non manager programs. */
> +	if (!is_policy_manager())
> +		return -EPERM;
> +	if (tmy_is_domain_def(data)) {
> +		domain = NULL;
> +		if (is_delete)
> +			tmy_delete_domain(data);
> +		else if (is_select)
> +			domain = tmy_find_domain(data);
> +		else if (is_undelete)
> +			domain = tmy_undelete_domain(data);
> +		else
> +			domain = tmy_find_or_assign_new_domain(data, 0);
> +		head->write_var1 = domain;
> +		tmy_update_counter(TMY_UPDATES_COUNTER_DOMAIN_POLICY);
> +		return 0;
> +	}
> +	if (!domain)
> +		return -EINVAL;
> +
> +	if (sscanf(data, KEYWORD_USE_PROFILE "%u", &profile) == 1
> +	    && profile < MAX_PROFILES) {
> +		if (profile_ptr[profile] || !sbin_init_started)
> +			domain->profile = (u8) profile;
> +		return 0;
> +	}
> +	if (!strcmp(data, KEYWORD_IGNORE_GLOBAL_ALLOW_READ)) {
> +		tmy_set_domain_flag(domain, is_delete,
> +				    DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ);
> +		return 0;
> +	}
> +	return tmy_write_file_policy(data, domain, is_delete);
> +}
> +
> +/**
> + * print_single_path_acl - Print a single path ACL entry.
> + *
> + * @head: Pointer to "struct tmy_io_buffer".
> + * @ptr:  Pointer to "struct single_path_acl_record".
> + *
> + * Returns true on success, false otherwise.
> + */
> +static bool print_single_path_acl(struct tmy_io_buffer *head,
> +				  struct single_path_acl_record *ptr)
> +{
> +	int pos;
> +	u8 bit;
> +	const char *atmark = "";
> +	const char *filename;
> +	const u16 perm = ptr->perm;
> +
> +	filename = ptr->filename->name;
> +	for (bit = head->read_bit; bit < MAX_SINGLE_PATH_OPERATION; bit++) {
> +		const char *msg;
> +		if (!(perm & (1 << bit)))
> +			continue;
> +		/* Print "read/write" instead of "read" and "write". */
> +		if ((bit == TMY_TYPE_READ_ACL || bit == TMY_TYPE_WRITE_ACL)
> +		    && (perm & (1 << TMY_TYPE_READ_WRITE_ACL)))
> +			continue;
> +		msg = tmy_sp2keyword(bit);
> +		pos = head->read_avail;
> +		if (!tmy_io_printf(head, "allow_%s %s%s\n", msg,
> +				   atmark, filename))
> +			goto out;
> +	}
> +	head->read_bit = 0;
> +	return true;
> +out:
> +	head->read_bit = bit;
> +	head->read_avail = pos;
> +	return false;
> +}
> +
> +/**
> + * print_double_path_acl - Print a double path ACL entry.
> + *
> + * @head: Pointer to "struct tmy_io_buffer".
> + * @ptr:  Pointer to "struct double_path_acl_record".
> + *
> + * Returns true on success, false otherwise.
> + */
> +static bool print_double_path_acl(struct tmy_io_buffer *head,
> +				  struct double_path_acl_record *ptr)
> +{
> +	int pos;
> +	const char *atmark1 = "";
> +	const char *atmark2 = "";
> +	const char *filename1;
> +	const char *filename2;
> +	const u8 perm = ptr->perm;
> +	u8 bit;
> +
> +	filename1 = ptr->filename1->name;
> +	filename2 = ptr->filename2->name;
> +	for (bit = head->read_bit; bit < MAX_DOUBLE_PATH_OPERATION; bit++) {
> +		const char *msg;
> +		if (!(perm & (1 << bit)))
> +			continue;
> +		msg = tmy_dp2keyword(bit);
> +		pos = head->read_avail;
> +		if (!tmy_io_printf(head, "allow_%s %s%s %s%s\n", msg,
> +				   atmark1, filename1, atmark2, filename2))
> +			goto out;
> +	}
> +	head->read_bit = 0;
> +	return true;
> +out:
> +	head->read_bit = bit;
> +	head->read_avail = pos;
> +	return false;
> +}
> +
> +/**
> + * print_entry - Print an ACL entry.
> + *
> + * @head: Pointer to "struct tmy_io_buffer".
> + * @ptr:  Pointer to an ACL entry.
> + *
> + * Returns true on success, false otherwise.
> + */
> +static bool print_entry(struct tmy_io_buffer *head, struct acl_info *ptr)
> +{
> +	const u8 acl_type = tmy_acl_type2(ptr);
> +
> +	if (acl_type & ACL_DELETED)
> +		return true;
> +	if (acl_type == TYPE_SINGLE_PATH_ACL) {
> +		struct single_path_acl_record *acl
> +			= container_of(ptr, struct single_path_acl_record,
> +				       head);
> +		return print_single_path_acl(head, acl);
> +	}
> +	if (acl_type == TYPE_DOUBLE_PATH_ACL) {
> +		struct double_path_acl_record *acl
> +			= container_of(ptr, struct double_path_acl_record,
> +				       head);
> +		return print_double_path_acl(head, acl);
> +	}
> +	BUG(); /* This must not happen. */
> +	return false;
> +}
> +
> +/**
> + * read_domain_policy - Read domain policy.
> + *
> + * @head: Pointer to "struct tmy_io_buffer".
> + *
> + * Returns 0.
> + */
> +static int read_domain_policy(struct tmy_io_buffer *head)
> +{
> +	struct list1_head *dpos;
> +	struct list1_head *apos;
> +
> +	if (head->read_eof)
> +		return 0;
> +	if (head->read_step == 0)
> +		head->read_step = 1;
> +	list1_for_each_cookie(dpos, head->read_var1, &domain_list) {
> +		struct domain_info *domain;
> +		const char *quota_exceeded = "";
> +		const char *transition_failed = "";
> +		const char *ignore_global_allow_read = "";
> +		domain = list1_entry(dpos, struct 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->flags & DOMAIN_FLAGS_TRANSITION_FAILED)
> +			transition_failed = "transition_failed\n";
> +		if (domain->flags & DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ)
> +			ignore_global_allow_read
> +				= KEYWORD_IGNORE_GLOBAL_ALLOW_READ "\n";
> +		if (!tmy_io_printf(head, "%s\n" KEYWORD_USE_PROFILE "%u\n"
> +				   "%s%s%s\n", domain->domainname->name,
> +				   domain->profile, quota_exceeded,
> +				   transition_failed, ignore_global_allow_read))
> +			return 0;
> +		head->read_step = 2;
> +acl_loop:
> +		if (head->read_step == 3)
> +			goto tail_mark;
> +		/* Print ACL entries in the domain. */
> +		list1_for_each_cookie(apos, head->read_var2,
> +				      &domain->acl_info_list) {
> +			struct acl_info *ptr
> +				= list1_entry(apos, struct acl_info, list);
> +			if (!print_entry(head, ptr))
> +				return 0;
> +		}
> +		head->read_step = 3;
> +tail_mark:
> +		if (!tmy_io_printf(head, "\n"))
> +			return 0;
> +		head->read_step = 1;
> +		if (head->read_single_domain)
> +			break;
> +	}
> +	head->read_eof = true;
> +	return 0;
> +}
> +
> +/**
> + * write_domain_profile - Assign profile for specified domain.
> + *
> + * @head: Pointer to "struct tmy_io_buffer".
> + *
> + * Returns 0 on success, -EINVAL otherwise.
> + *
> + * This is equivalent to doing
> + *
> + *     ( echo "select " $domainname; echo "use_profile " $profile ) |
> + *     /usr/lib/ccs/loadpolicy -d
> + */
> +static int write_domain_profile(struct tmy_io_buffer *head)
> +{
> +	char *data = head->write_buf;
> +	char *cp = strchr(data, ' ');
> +	struct domain_info *domain;
> +	unsigned long profile;
> +
> +	if (!cp)
> +		return -EINVAL;
> +	*cp = '\0';
> +	domain = tmy_find_domain(cp + 1);
> +	strict_strtoul(data, 10, &profile);

Unchecked return value?

> +	if (domain && profile < MAX_PROFILES
> +	    && (profile_ptr[profile] || !sbin_init_started))
> +		domain->profile = (u8) profile;
> +	tmy_update_counter(TMY_UPDATES_COUNTER_DOMAIN_POLICY);
> +	return 0;
> +}
> +
> +/**
> + * read_domain_profile - Read only domainname and profile.
> + *
> + * @head: Pointer to "struct tmy_io_buffer".
> + *
> + * Returns list of profile number and domainname pairs.
> + *
> + * This is equivalent to doing
> + *
> + *     grep -A 1 '^<kernel>' /sys/kernel/security/tomoyo/domain_policy |
> + *     awk ' { if ( domainname == "" ) { if ( $1 == "<kernel>" )
> + *     domainname = $0; } else if ( $1 == "use_profile" ) {
> + *     print $2 " " domainname; domainname = ""; } } ; '
> + */
> +static int read_domain_profile(struct tmy_io_buffer *head)
> +{
> +	struct list1_head *pos;
> +
> +	if (head->read_eof)
> +		return 0;
> +	list1_for_each_cookie(pos, head->read_var1, &domain_list) {
> +		struct domain_info *domain;
> +		domain = list1_entry(pos, struct domain_info, list);
> +		if (domain->is_deleted)
> +			continue;
> +		if (!tmy_io_printf(head, "%u %s\n", domain->profile,
> +				   domain->domainname->name))
> +			return 0;
> +	}
> +	head->read_eof = true;
> +	return 0;
> +}
> +
> +/**
> + * write_pid: Specify PID to obtain domainname.
> + *
> + * @head: Pointer to "struct tmy_io_buffer".
> + *
> + * Returns 0.
> + */
> +static int write_pid(struct tmy_io_buffer *head)
> +{
> +	unsigned long pid;
> +	strict_strtoul(head->write_buf, 10, &pid);
> +	head->read_step = (int) pid;
> +	head->read_eof = false;
> +	return 0;
> +}

Again, PIDs are not reliable when used for userspace ABI purposes.

> +/**
> + * read_pid - Get domainname of the specified PID.
> + *
> + * @head: Pointer to "struct tmy_io_buffer".
> + *
> + * Returns the domainname which the specified PID is in on success,
> + * empty string otherwise.
> + * The PID is specified by write_pid() so that the user can obtain
> + * using read()/write() interface rather than sysctl() interface.
> + */
> +static int read_pid(struct tmy_io_buffer *head)
> +{
> +	if (head->read_avail == 0 && !head->read_eof) {
> +		const int pid = head->read_step;
> +		struct task_struct *p;
> +		struct domain_info *domain = NULL;
> +		/***** CRITICAL SECTION START *****/
> +		read_lock(&tasklist_lock);
> +		p = find_task_by_vpid(pid);
> +		if (p)
> +			domain = tmy_real_domain(p);
> +		read_unlock(&tasklist_lock);
> +		/***** CRITICAL SECTION END *****/
> +		if (domain)
> +			tmy_io_printf(head, "%d %u %s", pid, domain->profile,
> +				      domain->domainname->name);
> +		head->read_eof = true;
> +	}
> +	return 0;
> +}
> +
> +/**
> + * write_exception_policy - Write exception policy.
> + *
> + * @head: Pointer to "struct tmy_io_buffer".
> + *
> + * Returns 0 on success, negative value otherwise.
> + */
> +static int write_exception_policy(struct tmy_io_buffer *head)
> +{
> +	char *data = head->write_buf;
> +	bool is_delete = str_starts(&data, KEYWORD_DELETE);
> +
> +	if (str_starts(&data, KEYWORD_KEEP_DOMAIN))
> +		return tmy_write_domain_keeper_policy(data, false, is_delete);
> +	if (str_starts(&data, KEYWORD_NO_KEEP_DOMAIN))
> +		return tmy_write_domain_keeper_policy(data, true, is_delete);
> +	if (str_starts(&data, KEYWORD_INITIALIZE_DOMAIN))
> +		return tmy_write_domain_initializer_policy(data, false,
> +							   is_delete);
> +	if (str_starts(&data, KEYWORD_NO_INITIALIZE_DOMAIN))
> +		return tmy_write_domain_initializer_policy(data, true,
> +							   is_delete);
> +	if (str_starts(&data, KEYWORD_ALIAS))
> +		return tmy_write_alias_policy(data, is_delete);
> +	if (str_starts(&data, KEYWORD_ALLOW_READ))
> +		return tmy_write_globally_readable_policy(data, is_delete);
> +	if (str_starts(&data, KEYWORD_FILE_PATTERN))
> +		return tmy_write_pattern_policy(data, is_delete);
> +	if (str_starts(&data, KEYWORD_DENY_REWRITE))
> +		return tmy_write_no_rewrite_policy(data, is_delete);
> +	return -EINVAL;
> +}
> +
> +/**
> + * read_exception_policy - Read exception policy.
> + *
> + * @head: Pointer to "struct tmy_io_buffer".
> + *
> + * Returns 0 on success, -EINVAL otherwise.
> + */
> +static int read_exception_policy(struct tmy_io_buffer *head)
> +{
> +	if (!head->read_eof) {
> +		switch (head->read_step) {
> +		case 0:
> +			head->read_var2 = NULL;
> +			head->read_step = 1;
> +		case 1:
> +			if (!tmy_read_domain_keeper_policy(head))
> +				break;
> +			head->read_var2 = NULL;
> +			head->read_step = 2;
> +		case 2:
> +			if (!tmy_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 (!tmy_read_domain_initializer_policy(head))
> +				break;
> +			head->read_var2 = NULL;
> +			head->read_step = 5;
> +		case 5:
> +			if (!tmy_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 (!tmy_read_file_pattern(head))
> +				break;
> +			head->read_var2 = NULL;
> +			head->read_step = 8;
> +		case 8:
> +			if (!tmy_read_no_rewrite_policy(head))
> +				break;
> +			head->read_var2 = NULL;
> +			head->read_step = 9;
> +		case 9:
> +			head->read_eof = true;
> +			break;
> +		default:
> +			return -EINVAL;
> +		}
> +	}
> +	return 0;
> +}
> +
> +/* path to policy loader */
> +static const char *tmy_loader = "/sbin/tomoyo-init";

hm, hard-wired knowledge of filesytem layout.

We did this in a few places already, reluctantly.  We did at least make
them configurable (eg: /proc/sys/kernel/modprobe).

It's rather ugly to be doing this sort of thing.

> +/**
> + * policy_loader_exists - Check whether /sbin/tomoyo-init exists.
> + *
> + * Returns true if /sbin/tomoyo-init exists, false otherwise.
> + */
> +static bool 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 nameidata nd;
> +
> +	if (path_lookup(tmy_loader, LOOKUP_FOLLOW, &nd)) {
> +		printk(KERN_INFO "Not activating Mandatory Access Control now "
> +		       "since %s doesn't exist.\n", tmy_loader);
> +		return false;
> +	}
> +	path_put(&nd.path);
> +	return true;
> +}

If you really really have to do this then a simple call to sys_access()
might suffice.

But it is of course racy against concurrent rename, unlink, etc.

> +/**
> + * tmy_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 tmy_load_policy(const char *filename)
> +{
> +	char *argv[2];
> +	char *envp[3];
> +
> +	if (sbin_init_started)
> +		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 (!policy_loader_exists())
> +		return;

Why do this?  call_usermodehelper() will simply fail if the file isn't here.

> +	printk(KERN_INFO "Calling %s to load policy. Please wait.\n",
> +	       tmy_loader);
> +	argv[0] = (char *) tmy_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-pre   2008/10/10\n");
> +	printk(KERN_INFO "Mandatory Access Control activated.\n");
> +	sbin_init_started = true;
> +	{ /* Check all profiles currently assigned to domains are defined. */
> +		struct domain_info *domain;
> +		list1_for_each_entry(domain, &domain_list, list) {
> +			const u8 profile = domain->profile;
> +			if (profile_ptr[profile])
> +				continue;
> +			panic("Profile %u (used by '%s') not defined.\n",
> +			      profile, domain->domainname->name);
> +		}
> +	}
> +}
> +
> +/* Policy updates counter. */
> +static atomic_t updates_counter[MAX_TMY_UPDATES_COUNTER];
> +
> +/**
> + * tmy_update_counter - Increment policy change counter.
> + *
> + * @index: Type of policy.
> + *
> + * Returns nothing.
> + */
> +void tmy_update_counter(const unsigned char index)
> +{
> +	if (index < MAX_TMY_UPDATES_COUNTER)
> +		atomic_inc(&updates_counter[index]);
> +}
> +
> +/**
> + * read_updates_counter - Check for policy change counter.
> + *
> + * @head: Pointer to "struct tmy_io_buffer".
> + *
> + * Returns how many times policy has changed since the previous check.
> + */
> +static int read_updates_counter(struct tmy_io_buffer *head)
> +{
> +	if (head->read_eof)
> +		return 0;
> +	tmy_io_printf(head,
> +		      "/sys/kernel/security/tomoyo/domain_policy:    %10u\n"
> +		      "/sys/kernel/security/tomoyo/exception_policy: %10u\n"
> +		      "/sys/kernel/security/tomoyo/profile:          %10u\n"
> +		      "/sys/kernel/security/tomoyo/manager:          %10u\n",
> +		      atomic_xchg(&updates_counter
> +				  [TMY_UPDATES_COUNTER_DOMAIN_POLICY], 0),
> +		      atomic_xchg(&updates_counter
> +				  [TMY_UPDATES_COUNTER_EXCEPTION_POLICY], 0),
> +		      atomic_xchg(&updates_counter
> +				  [TMY_UPDATES_COUNTER_PROFILE], 0),
> +		      atomic_xchg(&updates_counter
> +				  [TMY_UPDATES_COUNTER_MANAGER], 0));
> +	head->read_eof = true;
> +	return 0;
> +}

What is this doing?  We print the absolute pathnames of sysfs files via
another sysfs file?

> +/**
> + * read_version: Get version.
> + *
> + * @head: Pointer to "struct tmy_io_buffer".
> + *
> + * Returns version information.
> + */
> +static int read_version(struct tmy_io_buffer *head)
> +{
> +	if (!head->read_eof) {
> +		tmy_io_printf(head, "2.2.0-pre");
> +		head->read_eof = true;
> +	}
> +	return 0;
> +}
> +
> +/**
> + * read_self_domain - Get the current process's domainname.
> + *
> + * @head: Pointer to "struct tmy_io_buffer".
> + *
> + * Returns the current process's domainname.
> + */
> +static int read_self_domain(struct tmy_io_buffer *head)
> +{
> +	if (!head->read_eof) {
> +		/*
> +		 * tmy_domain()->domainname != NULL
> +		 * because every process belongs to a domain and
> +		 * the domain's name cannot be NULL.
> +		 */
> +		tmy_io_printf(head, "%s", tmy_domain()->domainname->name);
> +		head->read_eof = true;
> +	}
> +	return 0;
> +}
> +
> +/**
> + * tmy_open_control - open() for /sys/kernel/security/tomoyo/ interface.
> + *
> + * @type: Type of interface.
> + * @file: Pointer to "struct file".
> + *
> + * Associates policy handler and returns 0 on success, -ENOMEM otherwise.
> + */
> +static int tmy_open_control(const u8 type, struct file *file)
> +{
> +	struct tmy_io_buffer *head = tmy_alloc(sizeof(*head));
> +
> +	if (!head)
> +		return -ENOMEM;
> +	mutex_init(&head->io_sem);
> +	switch (type) {
> +	case TMY_DOMAINPOLICY:
> +		/* /sys/kernel/security/tomoyo/domain_policy */
> +		head->write = write_domain_policy;
> +		head->read = read_domain_policy;
> +		break;
> +	case TMY_EXCEPTIONPOLICY:
> +		/* /sys/kernel/security/tomoyo/exception_policy */
> +		head->write = write_exception_policy;
> +		head->read = read_exception_policy;
> +		break;
> +	case TMY_SELFDOMAIN:
> +		/* /sys/kernel/security/tomoyo/self_domain */
> +		head->read = read_self_domain;
> +		break;
> +	case TMY_DOMAIN_STATUS:
> +		/* /sys/kernel/security/tomoyo/.domain_status */
> +		head->write = write_domain_profile;
> +		head->read = read_domain_profile;
> +		break;
> +	case TMY_PROCESS_STATUS:
> +		/* /sys/kernel/security/tomoyo/.process_status */
> +		head->write = write_pid;
> +		head->read = read_pid;
> +		break;
> +	case TMY_VERSION:
> +		/* /sys/kernel/security/tomoyo/version */
> +		head->read = read_version;
> +		head->readbuf_size = 128;
> +		break;
> +	case TMY_MEMINFO:
> +		/* /sys/kernel/security/tomoyo/meminfo */
> +		head->write = tmy_write_memory_quota;
> +		head->read = tmy_read_memory_counter;
> +		head->readbuf_size = 512;
> +		break;
> +	case TMY_PROFILE:
> +		/* /sys/kernel/security/tomoyo/profile */
> +		head->write = write_profile;
> +		head->read = read_profile;
> +		break;
> +	case TMY_MANAGER:
> +		/* /sys/kernel/security/tomoyo/manager */
> +		head->write = write_manager_policy;
> +		head->read = read_manager_policy;
> +		break;
> +	case TMY_UPDATESCOUNTER:
> +		/* /sys/kernel/security/tomoyo/.updates_counter */
> +		head->read = read_updates_counter;
> +		break;
> +	}
> +	if (!(file->f_mode & FMODE_READ)) {
> +		/*
> +		 * No need to allocate read_buf since it is not opened
> +		 * for reading.
> +		 */
> +		head->read = NULL;
> +	} else {
> +		if (!head->readbuf_size)
> +			head->readbuf_size = 4096 * 2;
> +		head->read_buf = tmy_alloc(head->readbuf_size);
> +		if (!head->read_buf) {
> +			tmy_free(head);
> +			return -ENOMEM;
> +		}
> +	}
> +	if (!(file->f_mode & FMODE_WRITE)) {
> +		/*
> +		 * No need to allocate write_buf since it is not opened
> +		 * for writing.
> +		 */
> +		head->write = NULL;
> +	} else if (head->write) {
> +		head->writebuf_size = 4096 * 2;
> +		head->write_buf = tmy_alloc(head->writebuf_size);
> +		if (!head->write_buf) {
> +			tmy_free(head->read_buf);
> +			tmy_free(head);
> +			return -ENOMEM;
> +		}
> +	}
> +	file->private_data = head;
> +	/*
> +	 * Call the handler now if the file is
> +	 * /sys/kernel/security/tomoyo/self_domain
> +	 * so that the user can use
> +	 * cat < /sys/kernel/security/tomoyo/self_domain"
> +	 * to know the current process's domainname.
> +	 */
> +	if (type == TMY_SELFDOMAIN)
> +		tmy_read_control(file, NULL, 0);
> +	return 0;
> +}
> +
> +/**
> + * tmy_read_control - read() for /sys/kernel/security/tomoyo/ interface.
> + *
> + * @file:       Pointer to "struct file".
> + * @buffer:     Poiner to buffer to write to.
> + * @buffer_len: Size of @buffer.
> + *
> + * Returns bytes read on success, negative value otherwise.
> + */
> +static int tmy_read_control(struct file *file, char __user *buffer,
> +			    const int buffer_len)
> +{
> +	int len = 0;
> +	struct tmy_io_buffer *head = file->private_data;
> +	char *cp;
> +
> +	if (!head->read)
> +		return -ENOSYS;
> +	if (!access_ok(VERIFY_WRITE, buffer, buffer_len))

Unneeded - copy_to_user() checks this.

> +		return -EFAULT;
> +	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:
> +	mutex_unlock(&head->io_sem);
> +	return len;
> +}
> +
> +/**
> + * tmy_write_control - write() for /sys/kernel/security/tomoyo/ interface.
> + *
> + * @file:       Pointer to "struct file".
> + * @buffer:     Pointer to buffer to read from.
> + * @buffer_len: Size of @buffer.
> + *
> + * Returns @buffer_len on success, negative value otherwise.
> + */
> +static int tmy_write_control(struct file *file, const char __user *buffer,
> +			     const int buffer_len)
> +{
> +	struct tmy_io_buffer *head = file->private_data;
> +	int error = buffer_len;
> +	int avail_len = buffer_len;
> +	char *cp0 = head->write_buf;
> +
> +	if (!head->write)
> +		return -ENOSYS;
> +	if (!access_ok(VERIFY_READ, buffer, buffer_len))

Unneeded.

> +		return -EFAULT;
> +	/* Don't allow updating policies by non manager programs. */
> +	if (head->write != write_pid && head->write != write_domain_policy &&
> +	    !is_policy_manager())
> +		return -EPERM;
> +	if (mutex_lock_interruptible(&head->io_sem))
> +		return -EINTR;
> +	/* Read a line and dispatch it to the policy handler. */
> +	while (avail_len > 0) {
> +		char c;
> +		if (head->write_avail >= head->writebuf_size - 1) {
> +			error = -ENOMEM;
> +			break;
> +		} else if (get_user(c, buffer)) {
> +			error = -EFAULT;
> +			break;
> +		}
> +		buffer++;
> +		avail_len--;
> +		cp0[head->write_avail++] = c;
> +		if (c != '\n')
> +			continue;
> +		cp0[head->write_avail - 1] = '\0';
> +		head->write_avail = 0;
> +		normalize_line(cp0);
> +		head->write(head);
> +	}
> +	mutex_unlock(&head->io_sem);
> +	return error;
> +}
> +
> +/**
> + * tmy_close_control - close() for /sys/kernel/security/tomoyo/ interface.
> + *
> + * @file: Pointer to "struct file".
> + *
> + * Releases memory and returns 0.
> + */
> +static int tmy_close_control(struct file *file)
> +{
> +	struct tmy_io_buffer *head = file->private_data;
> +
> +	/* Release memory used for policy I/O. */
> +	tmy_free(head->read_buf);
> +	head->read_buf = NULL;
> +	tmy_free(head->write_buf);
> +	head->write_buf = NULL;
> +	tmy_free(head);
> +	head = NULL;
> +	file->private_data = NULL;
> +	return 0;
> +}
> +
> +/**
> + * tmy_alloc_acl_element - Allocate permanent memory for ACL entry.
> + *
> + * @acl_type:  Type of ACL entry.
> + *
> + * Returns pointer to the ACL entry on success, NULL otherwise.
> + */
> +void *tmy_alloc_acl_element(const u8 acl_type)
> +{
> +	int len;
> +	struct acl_info *ptr;
> +
> +	switch (acl_type) {
> +	case TYPE_SINGLE_PATH_ACL:
> +		len = sizeof(struct single_path_acl_record);
> +		break;
> +	case TYPE_DOUBLE_PATH_ACL:
> +		len = sizeof(struct double_path_acl_record);
> +		break;
> +	default:
> +		return NULL;
> +	}
> +	ptr = tmy_alloc_element(len);
> +	if (!ptr)
> +		return NULL;
> +	ptr->type = acl_type;
> +	return ptr;
> +}
> +
> +/**
> + * tmy_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 tmy_open(struct inode *inode, struct file *file)
> +{
> +	return tmy_open_control(((u8 *) file->f_path.dentry->d_inode->i_private)
> +				- ((u8 *) NULL), file);
> +}
> +
> +/**
> + * tmy_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 tmy_release(struct inode *inode, struct file *file)
> +{
> +	return tmy_close_control(file);
> +}
> +
> +/**
> + * tmy_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 tmy_read(struct file *file, char __user *buf, size_t count,
> +			loff_t *ppos)
> +{
> +	return tmy_read_control(file, buf, count);
> +}
> +
> +/**
> + * tmy_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 tmy_write(struct file *file, const char __user *buf,
> +			 size_t count, loff_t *ppos)
> +{
> +	return tmy_write_control(file, buf, count);
> +}
> +
> +/* Operations for /sys/kernel/security/tomoyo/ interface. */
> +static struct file_operations tmy_operations = {
> +	.open    = tmy_open,
> +	.release = tmy_release,
> +	.read    = tmy_read,
> +	.write   = tmy_write,
> +};
> +
> +/**
> + * 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 create_entry(const char *name, const mode_t mode,
> +				struct dentry *parent, const u8 key)
> +{
> +	securityfs_create_file(name, mode, parent, ((u8 *) NULL) + key,
> +			       &tmy_operations);
> +}
> +
> +/**
> + * tmy_initerface_init - Initialize /sys/kernel/security/tomoyo/ interface.
> + *
> + * Returns 0.
> + */
> +static int __init tmy_initerface_init(void)
> +{
> +	struct dentry *tmy_dir;
> +
> +	tmy_dir = securityfs_create_dir("tomoyo", NULL);
> +	create_entry("domain_policy",    0600, tmy_dir, TMY_DOMAINPOLICY);
> +	create_entry("exception_policy", 0600, tmy_dir, TMY_EXCEPTIONPOLICY);
> +	create_entry("self_domain",      0400, tmy_dir, TMY_SELFDOMAIN);
> +	create_entry(".domain_status",   0600, tmy_dir, TMY_DOMAIN_STATUS);
> +	create_entry(".process_status",  0600, tmy_dir, TMY_PROCESS_STATUS);
> +	create_entry("meminfo",          0600, tmy_dir, TMY_MEMINFO);
> +	create_entry("profile",          0600, tmy_dir, TMY_PROFILE);
> +	create_entry("manager",          0600, tmy_dir, TMY_MANAGER);
> +	create_entry(".updates_counter", 0400, tmy_dir, TMY_UPDATESCOUNTER);
> +	create_entry("version",          0400, tmy_dir, TMY_VERSION);
> +	return 0;
> +}
> +
> +fs_initcall(tmy_initerface_init);
> --- /dev/null
> +++ linux-2.6.28-rc2-mm1/security/tomoyo/common.h
> @@ -0,0 +1,320 @@
> +/*
> + * security/tomoyo/common.h
> + *
> + * Common functions for TOMOYO.
> + *
> + * Copyright (C) 2005-2008  NTT DATA CORPORATION
> + *
> + * Version: 2.2.0-pre   2008/10/10
> + *
> + */
> +
> +#ifndef _SECURITY_TOMOYO_COMMON_H
> +#define _SECURITY_TOMOYO_COMMON_H
> +
> +#include <linux/string.h>
> +#include <linux/mm.h>
> +#include <linux/file.h>
> +#include <linux/kmod.h>
> +#include <linux/fs.h>
> +#include <linux/sched.h>
> +#include <linux/namei.h>
> +#include <linux/mount.h>
> +#include <linux/list1.h>
> +
> +struct dentry;
> +struct vfsmount;
> +
> +#define false 0
> +#define true 1

The kernel already defines true and false.

> +/* Temporary buffer for holding pathnames. */
> +struct tmy_page_buffer {
> +	char buffer[4096];
> +};
> +
> +/* Structure for attribute checks in addition to pathname checks. */
> +struct obj_info {
> +	struct tmy_page_buffer *tmp;
> +};
> +
> +/* Structure for holding a token. */
> +struct path_info {
> +	const char *name;
> +	u32 hash;          /* = full_name_hash(name, strlen(name)) */
> +	u16 total_len;     /* = strlen(name)                       */
> +	u16 const_len;     /* = const_part_length(name)            */
> +	bool is_dir;       /* = strendswith(name, "/")             */
> +	bool is_patterned; /* = path_contains_pattern(name)        */
> +	u16 depth;         /* = path_depth(name)                   */
> +};
> +
> +/*
> + * 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 TMY_MAX_PATHNAME_LEN 4000
> +
> +/* Structure for holding requested pathname. */
> +struct path_info_with_data {
> +	/* Keep "head" first, for this pointer is passed to tmy_free(). */
> +	struct path_info head;
> +	char bariier1[16]; /* Safeguard for overrun. */
> +	char body[TMY_MAX_PATHNAME_LEN];
> +	char barrier2[16]; /* Safeguard for overrun. */
> +};
> +
> +/* Common header for holding ACL entries. */
> +struct acl_info {
> +	struct list1_head list;
> +	/*
> +	 * Type of this ACL entry.
> +	 *
> +	 * MSB is is_deleted flag.
> +	 */
> +	u8 type;
> +} __attribute__((__packed__));

I cannot tell from reading the code why this is packed.  Hence a comment
is needed.

Please use __packed.

> +/* This ACL entry is deleted.           */
> +#define ACL_DELETED        0x80
> +
> +/* Structure for domain information. */
> +struct domain_info {
> +	struct list1_head list;
> +	struct list1_head acl_info_list;
> +	/* Name of this domain. Never NULL.          */
> +	const struct path_info *domainname;
> +	u8 profile;        /* Profile number to use. */
> +	u8 is_deleted;     /* Delete flag.
> +			      0 = active.
> +			      1 = deleted but undeletable.
> +			      255 = deleted and no longer undeletable. */
> +	bool quota_warned; /* Quota warnning flag.   */
> +	/* DOMAIN_FLAGS_*. Use tmy_set_domain_flag() to modify. */
> +	u8 flags;
> +};
> +
> +/* Profile number is an integer between 0 and 255. */
> +#define MAX_PROFILES 256
> +
> +/* Ignore "allow_read" directive in exception policy. */
> +#define DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ 1
> +/*
> + * This domain was unable to create a new domain at tmy_find_next_domain()
> + * because the name of the domain to be created was too long or
> + * it could not allocate memory.
> + * More than one process continued execve() without domain transition.
> + */
> +#define DOMAIN_FLAGS_TRANSITION_FAILED        2
> +
> +/*
> + * Structure for "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" and "allow_rewrite" directive.
> + */
> +struct single_path_acl_record {
> +	struct acl_info head; /* type = TYPE_SINGLE_PATH_ACL */
> +	u16 perm;
> +	/* Pointer to single pathname. */
> +	const struct path_info *filename;
> +};
> +
> +/* Structure for "allow_rename" and "allow_link" directive. */
> +struct double_path_acl_record {
> +	struct acl_info head; /* type = TYPE_DOUBLE_PATH_ACL */
> +	u8 perm;
> +	/* Pointer to single pathname. */
> +	const struct path_info *filename1;
> +	/* Pointer to single pathname. */
> +	const struct path_info *filename2;
> +};
> +
> +/* Keywords for ACLs. */
> +#define KEYWORD_ALIAS                     "alias "
> +#define KEYWORD_ALLOW_READ                "allow_read "
> +#define KEYWORD_DELETE                    "delete "
> +#define KEYWORD_DENY_REWRITE              "deny_rewrite "
> +#define KEYWORD_FILE_PATTERN              "file_pattern "
> +#define KEYWORD_INITIALIZE_DOMAIN         "initialize_domain "
> +#define KEYWORD_KEEP_DOMAIN               "keep_domain "
> +#define KEYWORD_NO_INITIALIZE_DOMAIN      "no_initialize_domain "
> +#define KEYWORD_NO_KEEP_DOMAIN            "no_keep_domain "
> +#define KEYWORD_SELECT                    "select "
> +#define KEYWORD_UNDELETE                  "undelete "
> +#define KEYWORD_USE_PROFILE               "use_profile "
> +#define KEYWORD_IGNORE_GLOBAL_ALLOW_READ  "ignore_global_allow_read"
> +/* A domain definition starts with <kernel>. */
> +#define ROOT_NAME                         "<kernel>"
> +#define ROOT_NAME_LEN                     (sizeof(ROOT_NAME) - 1)
> +
> +/* Index numbers for Access Controls. */
> +#define TMY_TOMOYO_MAC_FOR_FILE                  0  /* domain_policy.conf */
> +#define TMY_TOMOYO_MAX_ACCEPT_ENTRY              1
> +#define TMY_TOMOYO_VERBOSE                       2
> +#define TMY_MAX_CONTROL_INDEX                    3
> +
> +/* Index numbers for updates counter. */
> +#define TMY_UPDATES_COUNTER_DOMAIN_POLICY    0
> +#define TMY_UPDATES_COUNTER_EXCEPTION_POLICY 1
> +#define TMY_UPDATES_COUNTER_PROFILE          2
> +#define TMY_UPDATES_COUNTER_MANAGER          3
> +#define MAX_TMY_UPDATES_COUNTER              4
> +
> +/* Structure for reading/writing policy via securityfs interfaces. */
> +struct tmy_io_buffer {
> +	int (*read) (struct tmy_io_buffer *);
> +	int (*write) (struct tmy_io_buffer *);
> +	/* Exclusive lock for this structure.   */
> +	struct mutex io_sem;
> +	/* The position currently reading from. */
> +	struct list1_head *read_var1;
> +	/* Extra variables for reading.         */
> +	struct list1_head *read_var2;
> +	/* The position currently writing to.   */
> +	struct 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.                  */
> +	char *write_buf;
> +	/* Bytes available for writing.         */
> +	int write_avail;
> +	/* Size of write buffer.                */
> +	int writebuf_size;
> +};
> +
> +/* Check whether the domain has too many ACL entries to hold. */
> +bool tmy_check_domain_quota(struct domain_info * const domain);
> +/* Transactional sprintf() for policy dump. */
> +bool tmy_io_printf(struct tmy_io_buffer *head, const char *fmt, ...)
> +	__attribute__ ((format(printf, 2, 3)));
> +/* Check whether the domainname is correct. */
> +bool tmy_is_correct_domain(const unsigned char *domainname,
> +			   const char *function);
> +/* Check whether the token is correct. */
> +bool tmy_is_correct_path(const char *filename, const s8 start_type,
> +			 const s8 pattern_type, const s8 end_type,
> +			 const char *function);
> +/* Check whether the token can be a domainname. */
> +bool tmy_is_domain_def(const unsigned char *buffer);
> +/* Check whether the given filename matches the given pattern. */
> +bool tmy_path_matches_pattern(const struct path_info *filename,
> +			      const struct path_info *pattern);
> +/* Read "alias" entry in exception policy. */
> +bool tmy_read_alias_policy(struct tmy_io_buffer *head);
> +/*
> + * Read "initialize_domain" and "no_initialize_domain" entry
> + * in exception policy.
> + */
> +bool tmy_read_domain_initializer_policy(struct tmy_io_buffer *head);
> +/* Read "keep_domain" and "no_keep_domain" entry in exception policy. */
> +bool tmy_read_domain_keeper_policy(struct tmy_io_buffer *head);
> +/* Read "file_pattern" entry in exception policy. */
> +bool tmy_read_file_pattern(struct tmy_io_buffer *head);
> +/* Read "allow_read" entry in exception policy. */
> +bool tmy_read_globally_readable_policy(struct tmy_io_buffer *head);
> +/* Read "deny_rewrite" entry in exception policy. */
> +bool tmy_read_no_rewrite_policy(struct tmy_io_buffer *head);
> +/* Write domain policy violation warning message to console? */
> +bool tmy_verbose_mode(const struct domain_info *domain);
> +/* Convert double path operation to operation name. */
> +const char *tmy_dp2keyword(const u8 operation);
> +/* Get the last component of the given domainname. */
> +const char *tmy_get_last_name(const struct domain_info *domain);
> +/* Get warning message. */
> +const char *tmy_get_msg(const bool is_enforce);
> +/* Convert single path operation to operation name. */
> +const char *tmy_sp2keyword(const u8 operation);
> +/* Delete a domain. */
> +int tmy_delete_domain(char *data);
> +/* Create "alias" entry in exception policy. */
> +int tmy_write_alias_policy(char *data, const bool is_delete);
> +/*
> + * Create "initialize_domain" and "no_initialize_domain" entry
> + * in exception policy.
> + */
> +int tmy_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 tmy_write_domain_keeper_policy(char *data, const bool is_not,
> +				   const bool is_delete);
> +/*
> + * Create "allow_read/write", "allow_execute", "allow_read", "allow_write",
> + * "allow_create", "allow_unlink", "allow_mkdir", "allow_rmdir",
> + * "allow_mkfifo", "allow_mksock", "allow_mkblock", "allow_mkchar",
> + * "allow_truncate", "allow_symlink", "allow_rewrite", "allow_rename" and
> + * "allow_link" entry in domain policy.
> + */
> +int tmy_write_file_policy(char *data, struct domain_info *domain,
> +			  const bool is_delete);
> +/* Create "allow_read" entry in exception policy. */
> +int tmy_write_globally_readable_policy(char *data, const bool is_delete);
> +/* Create "deny_rewrite" entry in exception policy. */
> +int tmy_write_no_rewrite_policy(char *data, const bool is_delete);
> +/* Create "file_pattern" entry in exception policy. */
> +int tmy_write_pattern_policy(char *data, const bool is_delete);
> +/* Find a domain by the given name. */
> +struct domain_info *tmy_find_domain(const char *domainname);
> +/* Find or create a domain by the given name. */
> +struct domain_info *tmy_find_or_assign_new_domain(const char *domainname,
> +						  const u8 profile);
> +/* Undelete a domain. */
> +struct domain_info *tmy_undelete_domain(const char *domainname);
> +/* Check mode for specified functionality. */
> +unsigned int tmy_check_flags(const struct domain_info *domain, const u8 index);
> +/* Allocate memory for structures. */
> +void *tmy_alloc_acl_element(const u8 acl_type);
> +/* Fill in "struct path_info" members. */
> +void tmy_fill_path_info(struct path_info *ptr);
> +/* Run policy loader when /sbin/init starts. */
> +void tmy_load_policy(const char *filename);
> +/* Change "struct domain_info"->flags. */
> +void tmy_set_domain_flag(struct domain_info *domain, const bool is_delete,
> +			 const u8 flags);
> +/* Update the policy change counter. */
> +void tmy_update_counter(const unsigned char index);
> +
> +/* strcmp() for "struct path_info" structure. */
> +static inline bool tmy_pathcmp(const struct path_info *a,
> +			       const struct path_info *b)
> +{
> +	return a->hash != b->hash || strcmp(a->name, b->name);
> +}
> +
> +/* Get type of an ACL entry. */
> +static inline u8 tmy_acl_type1(struct acl_info *ptr)
> +{
> +	return ptr->type & ~ACL_DELETED;
> +}
> +
> +/* Get type of an ACL entry. */
> +static inline u8 tmy_acl_type2(struct acl_info *ptr)
> +{
> +	return ptr->type;
> +}
> +
> +/* The list for "struct domain_info". */
> +extern struct list1_head domain_list;
> +
> +/* Has /sbin/init started? */
> +extern bool sbin_init_started;
> +
> +/* The kernel's domain. */
> +extern struct domain_info KERNEL_DOMAIN;

Why upper-case?

> +#endif /* !defined(_SECURITY_TOMOYO_COMMON_H) */

Many of the symbols which this header defines have quite
generic-sounding names.  it would be better if their names were to
identify the symbols as being part of Tomoyo.


(That's two hours of tomoyo-reading for me.  I need to stop now)

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYOLinux.
  2008-11-05 23:12   ` Andrew Morton
@ 2008-11-06 21:46     ` Tetsuo Handa
  2008-11-08 16:38     ` Tetsuo Handa
                       ` (2 subsequent siblings)
  3 siblings, 0 replies; 32+ messages in thread
From: Tetsuo Handa @ 2008-11-06 21:46 UTC (permalink / raw)
  To: akpm
  Cc: takedakn, haradats, linux-security-module, linux-kernel, penguin-kernel

Hello.

Andrew Morton wrote:
> (That's two hours of tomoyo-reading for me.  I need to stop now)
Thank you very much for reviewing.
Before I answer for individual comments, I'd like to show three basic outlines.

(1) The way TOMOYO handles string data.

To be able to handle any characters correctly, TOMOYO Linux follows the rules
shown below to represent a word. A word means all tokens that are treated as
string data, such as pathnames and comments.

 * NUL character (0x00) is used for indicating end of string.
   Thus you cannot include \000 in a word.
 * \ character (0x5C) is used for indicating octal expression.
   Thus, you need to use \\ to represent a \.
 * Characters 0x01 - 0x20 and 0x7F - 0xFF are represented using octal
   expression \ooo .
 * The rest characters (i.e. 0x21 - 0x5B and 0x5D - 0x7E) are represented
   as is.

 * Space character (0x20) is used as a delimiter that separates words.
   Line feed character (0x0A) is used as a delimiter that separates lines.
 * Only words that follow the rule above and the delimiters (i.e. space
   character and line feed characters) are valid. All other characters are
   regarded as space character. Multiple spaces are automatically compressed
   into one space. Leading and trailing spaces are automatically deleted.

(2) The way TOMOYO allocates memory.

In TOMOYO Linux, memory allocated for holding access permissions and words are
never freed. There is no way except rebooting the system that can free unneeded
memory.

But don't worry. The policy seldom changes after you start production mode.
By tuning policy before starting production mode, you can reduce memory usage
to (usually) less than 1 Mega Bytes. You can also enable memory quota.

(3) The kernel-userspace interface of TOMOYO.

Policy files are automatically loaded into the kernel upon boot.
When a system boots, /sbin/init is executed. When the execution of /sbin/init
is requested and if /sbin/tomoyo-init exists, /sbin/tomoyo-init is executed,
and /sbin/init is executed after /sbin/tomoyo-init terminates.
/sbin/tomoyo-init is called only once.

TOMOYO requires no modifications of existing userland applications.
The pathname /sbin/tomoyo-init is embedded into the kernel so that we don't
need to modify /sbin/init for loading policy.

/sbin/tomoyo-init loads policy via /sys/kernel/security/tomoyo/ interface.
All data passed through this interface consists of only ASCII printable
characters, for all words consist of only ASCII printable characters.

Regards.

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYOLinux.
  2008-11-05 23:12   ` Andrew Morton
  2008-11-06 21:46     ` [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYOLinux Tetsuo Handa
@ 2008-11-08 16:38     ` Tetsuo Handa
  2008-11-10  0:41       ` Serge E. Hallyn
  2008-11-10 10:35     ` [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYO Linux Kentaro Takeda
  2008-11-14  9:22     ` Kentaro Takeda
  3 siblings, 1 reply; 32+ messages in thread
From: Tetsuo Handa @ 2008-11-08 16:38 UTC (permalink / raw)
  To: akpm
  Cc: linux-security-module, linux-kernel, takedakn, haradats, penguin-kernel

Hello.

Andrew Morton wrote:
> > +static bool is_select_one(struct tmy_io_buffer *head, const char *data)
> > +{
> > +	unsigned int pid;
> > +	struct domain_info *domain = NULL;
> > +
> > +	if (sscanf(data, "pid=%u", &pid) == 1) {
> 
> PIDs are no longer system-wide unique, and here we appear to be
> implementing new userspace ABIs using PIDs.
> 
I'm not familiar with virtualized environment.

There are two PIDs, PID seen from inside virtualized environment and
PID seen from outside virtualized environment. To clarify, let me call
the former "PIDv" and the latter "PIDg".

PIDv is not system-wide unique. But PIDg is system-wide unique, aren't they?
The PID received from outside virtualized environment is PIDg and they are
system-wide unique, am I right?

This interface is designed to be accessed from outside virtualized environment.
Maybe some checks to prevent processes inside virtualized environment from
accessing this interface are needed.

Regards.

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYOLinux.
  2008-11-08 16:38     ` Tetsuo Handa
@ 2008-11-10  0:41       ` Serge E. Hallyn
  2008-11-10  2:24         ` Tetsuo Handa
  0 siblings, 1 reply; 32+ messages in thread
From: Serge E. Hallyn @ 2008-11-10  0:41 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: akpm, linux-security-module, linux-kernel, takedakn, haradats

Quoting Tetsuo Handa (penguin-kernel@I-love.SAKURA.ne.jp):
> Hello.
> 
> Andrew Morton wrote:
> > > +static bool is_select_one(struct tmy_io_buffer *head, const char *data)
> > > +{
> > > +	unsigned int pid;
> > > +	struct domain_info *domain = NULL;
> > > +
> > > +	if (sscanf(data, "pid=%u", &pid) == 1) {
> > 
> > PIDs are no longer system-wide unique, and here we appear to be
> > implementing new userspace ABIs using PIDs.
> > 
> I'm not familiar with virtualized environment.
> 
> There are two PIDs, PID seen from inside virtualized environment and
> PID seen from outside virtualized environment. To clarify, let me call
> the former "PIDv" and the latter "PIDg".
> 
> PIDv is not system-wide unique. But PIDg is system-wide unique, aren't they?
> The PID received from outside virtualized environment is PIDg and they are
> system-wide unique, am I right?

You are doing find_task_by_vpid(), so you are not looking up a task by
global pid.

-serge

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYOLinux.
  2008-11-10  0:41       ` Serge E. Hallyn
@ 2008-11-10  2:24         ` Tetsuo Handa
  2008-11-10  2:52           ` Serge E. Hallyn
  0 siblings, 1 reply; 32+ messages in thread
From: Tetsuo Handa @ 2008-11-10  2:24 UTC (permalink / raw)
  To: serue
  Cc: akpm, linux-security-module, linux-kernel, takedakn, haradats,
	penguin-kernel

Hello.

Serge E. Hallyn wrote:
> > There are two PIDs, PID seen from inside virtualized environment and
> > PID seen from outside virtualized environment. To clarify, let me call
> > the former "PIDv" and the latter "PIDg".
> > 
> > PIDv is not system-wide unique. But PIDg is system-wide unique, aren't they?
> > The PID received from outside virtualized environment is PIDg and they are
> > system-wide unique, am I right?
> 
> You are doing find_task_by_vpid(), so you are not looking up a task by
> global pid.
> 
I need to clarify reachability of "struct task_struct".

A process inside a virtualized environment cannot reach "struct task_struct"
which belongs to outside the virtualized environment.

A process outside virtualized environments can reach "struct task_struct"
which belongs to inside virtualized environments, can't it?

Regards.

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYOLinux.
  2008-11-10  2:24         ` Tetsuo Handa
@ 2008-11-10  2:52           ` Serge E. Hallyn
  2008-11-10  3:30             ` Tetsuo Handa
  0 siblings, 1 reply; 32+ messages in thread
From: Serge E. Hallyn @ 2008-11-10  2:52 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: akpm, linux-security-module, linux-kernel, takedakn, haradats

Quoting Tetsuo Handa (penguin-kernel@i-love.sakura.ne.jp):
> Hello.
> 
> Serge E. Hallyn wrote:
> > > There are two PIDs, PID seen from inside virtualized environment and
> > > PID seen from outside virtualized environment. To clarify, let me call
> > > the former "PIDv" and the latter "PIDg".
> > > 
> > > PIDv is not system-wide unique. But PIDg is system-wide unique, aren't they?
> > > The PID received from outside virtualized environment is PIDg and they are
> > > system-wide unique, am I right?
> > 
> > You are doing find_task_by_vpid(), so you are not looking up a task by
> > global pid.
> > 
> I need to clarify reachability of "struct task_struct".
> 
> A process inside a virtualized environment cannot reach "struct task_struct"
> which belongs to outside the virtualized environment.
> 
> A process outside virtualized environments can reach "struct task_struct"
> which belongs to inside virtualized environments, can't it?

To be precise, there isn't a real 'inside' and 'outside' virtualized
environements.  Rather pid namespaces are hierarchical.

(Taking another look) it looks like In is_select_one() you're doing the
right thing - you look up the domain of a task based on
find_task_by_vpid() on a passed-in pid.  Seems correct.

thanks,
-serge

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYOLinux.
  2008-11-10  2:52           ` Serge E. Hallyn
@ 2008-11-10  3:30             ` Tetsuo Handa
  2008-11-10 14:00               ` Serge E. Hallyn
  0 siblings, 1 reply; 32+ messages in thread
From: Tetsuo Handa @ 2008-11-10  3:30 UTC (permalink / raw)
  To: serue
  Cc: akpm, linux-security-module, linux-kernel, takedakn, haradats,
	penguin-kernel

Hello.

Serge E. Hallyn wrote:
> > I need to clarify reachability of "struct task_struct".
> > 
> > A process inside a virtualized environment cannot reach "struct task_struct"
> > which belongs to outside the virtualized environment.
> > 
> > A process outside virtualized environments can reach "struct task_struct"
> > which belongs to inside virtualized environments, can't it?
> 
> To be precise, there isn't a real 'inside' and 'outside' virtualized
> environements.  Rather pid namespaces are hierarchical.
> 
So, processes which have non-topmost namespace cannot see processes which have
topmost namespace (like chroot()).
Then, it might be preferable if TOMOYO can prevent processes which have
non-topmost namespace from modifying policy information.
Do you think TOMOYO should do "current->nsproxy->pid_ns == &init_pid_ns"
checking like below one?

static bool tomoyo_is_policy_manager(void)
{
	struct tomoyo_policy_manager_entry *ptr;
	const char *exe;
	const struct task_struct *task = current;
	const struct tomoyo_path_info *domainname = tomoyo_domain()->domainname;
	bool found = false;

	if (!tomoyo_policy_loaded)
	        return true;
	if (!tomoyo_manage_by_non_root && (task->cred->uid || task->cred->euid))
	        return false;
	/* Don't allow modifying policy by processes not having init_pid_ns. */
	if (task->nsproxy->pid_ns != &init_pid_ns)
		return false;
	list1_for_each_entry(ptr, &tomoyo_policy_manager_list, list) {
		if (!ptr->is_deleted && ptr->is_domain
		    && !tomoyo_pathcmp(domainname, ptr->manager))
			return true;
	}

> (Taking another look) it looks like In is_select_one() you're doing the
> right thing - you look up the domain of a task based on
> find_task_by_vpid() on a passed-in pid.  Seems correct.
> 
I see, thanks.

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [TOMOYO #12 (2.6.28-rc2-mm1) 05/11] Memory and pathname management functions.
  2008-11-05 23:12   ` Andrew Morton
@ 2008-11-10 10:34     ` Kentaro Takeda
  2008-11-11  5:04       ` Andrew Morton
  0 siblings, 1 reply; 32+ messages in thread
From: Kentaro Takeda @ 2008-11-10 10:34 UTC (permalink / raw)
  To: akpm; +Cc: haradats, linux-security-module, linux-kernel, penguin-kernel

Andrew Morton wrote:
> > +/**
> > + * round_up - Round up an integer so that the returned pointers are appropriately aligned.
> > + *
> > + * @size: Size in bytes.
> > + *
> > + * Returns rounded value of @size.
> > + *
> > + * FIXME: Are there more requirements that is needed for assigning value
> > + * atomically?
> > + */
> 
> Can PTR_ALIGN be used?
> 
> If not, please prefer to avoid implementing generic helpers down in
> specific code.  It is better to add the helpers in a kernel-wide
> fashion in an early patch, then to use those halpers in the
> subsyste-specific patches.

Replaced by "roundup(size, max(sizeof(void *), sizeof(long)))".

> > +/* Structure for string data. */
> > +struct name_entry {
> > +	struct list1_head list;
> > +	struct path_info entry;
> > +};
> > +
> 
> You didn't need to invent list1_head for this application.  This is
> *exactly* what the existing hlist_head is designed for.

hlist_head omits ->pprev, but hlist_node doesn't.
Since TOMOYO uses this list as Write-Once-Read-Many,
TOMOYO doesn't use ->pprev for list elements.

> > +/**
> > + * tmy_save_name - Allocate permanent memory for string data.
> > + *
> > + * @name: The string to store into the permernent memory.
> > + *
> > + * Returns pointer to "struct path_info" on success, NULL otherwise.
> > + *
> > + * The RAM is shared, so NEVER try to modify or kfree() the returned name.
> > + */
> 
> Nothing ever gets removed from fmb_list.  How odd.
> 
> If this is not a bug, I'd suggest that a code comment be added
> explaining what all this code does and why it does it and how it does
> it.

fmb contains memory reserved by TOMOYO for future requests.
fmb is removed from the fmb_list when fmb->len becomes 0.
So, this is not a bug. I added a comment.

> > +/* Memory allocated for temporal purpose. */
> > +static atomic_t dynamic_memory_size;
> 
> The correct word is "temporary".  This needs fixing in at least one
> other place.

Replaced "temporal" by "temporary". Thanks.

> Is this counter really useful?  If not, I'd suggest that it be removed
> and that all calls to tmy_alloc() simply be replaced by calls to
> kmalloc().
> 
This counter was introduced by user's request so that the administrator can
know how much memory is used by TOMOYO module. So, I want to keep this counter.

> A better way to perform memory accounting would be to create slab
> caches for commonly-used objects and to reply uponthe existing
> accounting in /proc/slabinfo.
> 
Hmm, memory allocated for temporary purpose is not a fixed size.

> > +/**
> > + * tmy_alloc - Allocate memory for temporal purpose.
> > + *
> > + * @size: Size in bytes.
> > + *
> > + * Returns pointer to allocated memory on success, NULL otherwise.
> > + */
> > +void *tmy_alloc(const size_t size)
> > +{
> > +	void *p = kzalloc(size, GFP_KERNEL);
> > +	if (p)
> > +		atomic_add(ksize(p), &dynamic_memory_size);
> > +	return p;
> > +}
> 
> Note that I said "kmalloc", not "kzalloc".  This function zeroes
> everything all the time, and surely that is not necessary.  It's just a
> waste of CPU time.
> 
Callers of tmy_alloc assume that allocated memory is zeroed.

> > +static int tmy_print_ascii(const char *sp, const char *cp,
> > +			   int *buflen0, char **end0)
> 
> I look at this and wonder "hm, does that duplicate any facility which
> the kernel provides"?  But I can't tell, because I don't know what this
> function does, and I shouldn't have to sit down with a pencil and paper
> decrypting it.
> 
I splitted this function into "prepend()" part and "convert a string to
TOMOYO's string representation rule" part. And I renamed the latter from
"tmy_print_ascii" to "tmy_encode".

> > +/* tmy_realpath_from_path2() for "struct ctl_table". */
> > +static int tmy_sysctl_path(struct ctl_table *table, char *buffer, int buflen)
> 
> Is this needed if CONFIG_SYSCTL=n?  Does it compile if CONFIG_SYSCTL=n?
> 
Added "#ifdef CONFIG_SYSCTL" and moved to security/tomoyo/tomoyo.c .

> > +/**
> > + * tmy_read_memory_counter - Check for memory usage.
> > + *
> > + * @head: Pointer to "struct tmy_io_buffer".
> > + *
> > + * Returns memory usage.
> 
> In what units?  Megabytes?
> 
In bytes.

> > +int tmy_read_memory_counter(struct tmy_io_buffer *head)
> 
> This (I assume) is part of an implementation of a userspace interface. 
> We care a lot about userspace interfaces.  Please describe the Tomoyo
> userspace interfaces so that we can review them for suitability and
> maintainability.
> 
I'll describe it in other posting.

> Surely this function should return a 64-bit quantity?
> 
I believe 32-bit is enough. TOMOYO uses only 1MB or so. Never 4GB or more.

> Again, we would like to see a complete decription of the proposed
> userspace ABI.  This one looks fairly ugly.  Do I really have to write
> 'S' 'h' 'a' 'r' 'e' 'd' ':' ' ' into some pseudo file?
> 
> A better interface would be two suitably-named pseudo files each of
> which takes a bare integer string.  None of this funny colon-based
> prefixing stuff.
> 
Creating pseudo files for each variables is fine, though I don't see
advantage by changing from
"echo Shared: 16777216 > /sys/kernel/security/tomoyo/meminfo" to
"echo 16777216 > /sys/kernel/security/tomoyo/quota/shared_memory".



^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYO Linux.
  2008-11-05 23:12   ` Andrew Morton
  2008-11-06 21:46     ` [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYOLinux Tetsuo Handa
  2008-11-08 16:38     ` Tetsuo Handa
@ 2008-11-10 10:35     ` Kentaro Takeda
  2008-11-14  9:22     ` Kentaro Takeda
  3 siblings, 0 replies; 32+ messages in thread
From: Kentaro Takeda @ 2008-11-10 10:35 UTC (permalink / raw)
  To: akpm; +Cc: haradats, linux-security-module, linux-kernel, penguin-kernel

Andrew Morton wrote:
> > +static bool is_byte_range(const char *str)
> > +{
> > +	return *str >= '0' && *str++ <= '3' &&
> > +		*str >= '0' && *str++ <= '7' &&
> > +		*str >= '0' && *str <= '7';
> > +}
> 
> Well... why?
> 
> I cannot think of any kernel interfaces which use octal strings.  What
> is special about Tomoyo?
> 
TOMOYO uses \ooo style representation for 0x01 - 0x20 and 0x7F - 0xFF.
This function verifies that \ooo is in valid range.

> > +static bool is_decimal(const char c)
> > +{
> > +	return c >= '0' && c <= '9';
> > +}
> 
> This duplicates a standard ctype.h function.
> 
Replaced "is_decimal" by "isdigit".

> > +static bool is_hexadecimal(const char c)
> > +{
> > +	return (c >= '0' && c <= '9') ||
> > +		(c >= 'A' && c <= 'F') ||
> > +		(c >= 'a' && c <= 'f');
> > +}
> 
> And so does this.
> 
Replaced "is_hexadecimal" by "isxdigit".

> > +static bool is_alphabet_char(const char c)
> > +{
> > +	return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
> > +}
> 
> As does this.
> 
Oops! "(c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');" was wrong. X-p
It is "(c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');".
But, not found in a standard ctype.h function.

> > +static bool 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;
> > +}
> 
> hrm.  Isn't there a standard string.h way of doing this?
> 
> If not, it looks like a pretty common thing.  I'd suggest that it a) be
> coded to not do two passes across the input and b) proposed as a
> generic addition to the kernel's string library functions.
> 
> > +static void normalize_line(unsigned char *buffer)
> > +{
> > +	unsigned char *sp = buffer;
> > +	unsigned char *dp = buffer;
> > +	bool first = true;
> > +
> > +	while (*sp && (*sp <= ' ' || *sp >= 127))
> > +		sp++;
> > +	while (*sp) {
> > +		if (!first)
> > +			*dp++ = ' ';
> > +		first = false;
> > +		while (*sp > ' ' && *sp < 127)
> > +			*dp++ = *sp++;
> > +		while (*sp && (*sp <= ' ' || *sp >= 127))
> > +			sp++;
> > +	}
> > +	*dp = '\0';
> > +}
> 
> that looks pretty generic as well.
> 
If you think TOMOYO's way of string representation (described below) is useful,
I'd like to propose these functions as generic functions.

> It seems to have duplicated isprint() in lots of places.
> 
It is different from "isprint()".

TOMOYO uses only 0x21 - 0x7E (as printable characters) and 0x20 (as word
delimiter) and 0x0A (as line delimiter).

> What happens if I have a filename which includes a character in the
> 128->255 range?

0x01 - 0x20 and 0x80 - 0xFF will be handled in \ooo style representation.
The reason to use \ooo is to guarantee that "%s" won't damage logs.
Userland program can request

 open("/tmp/file granted.\nAccess /tmp/file ", O_WRONLY | O_CREAT)

and auditing such crazy pathname using "Access %s denied.\n" format
will results in "fabrication of audit logs" like

 Access /tmp/file granted.
 Access /tmp/file rejected.

TOMOYO converts such characters to \ooo so that the auditing will generate

 Access /tmp/file\040granted.\012Access\040/tmp/file rejected.

and the administrator can read the audited logs safely using /bin/cat .

> > +struct domain_info *tmy_find_domain(const char *domainname)
> > +{
> > +	struct domain_info *domain;
> > +	struct path_info name;
> > +
> > +	name.name = domainname;
> > +	tmy_fill_path_info(&name);
> > +	list1_for_each_entry(domain, &domain_list, list) {
> > +		if (!domain->is_deleted &&
> > +		    !tmy_pathcmp(&name, domain->domainname))
> > +			return domain;
> > +	}
> > +	return NULL;
> > +}
> 
> No lock was taken to protect that list.
> 
No lock needed for protecting "list1" list.
list1 was reviewed by Paul E. McKenney. ( http://lkml.org/lkml/2008/10/20/4 ).

> If the caller must take some lock then that precondition should be
> documented in the function's comment.
> 
To your surprise, most functions are lock free, due to use of
"append only singly linked list" named "list1".
Only tmy_real_domain() requires the caller to take "tasklist_lock".
tmy_read_control() and tmy_write_control take "struct tmy_io_buffer"->io_sem
before calling "struct tmy_io_buffer"->read and "struct tmy_io_buffer"->write
methods.
All other functions don't require the caller to take some lock.

> > +/**
> > + * path_depth - Evaluate the number of '/' in a string.
> > + *
> > + * @pathname: The string to evaluate.
> > + *
> > + * Returns path depth of the string.
> > + *
> > + * I score 2 for each of the '/' in the @pathname
> > + * and score 1 if the @pathname ends with '/'.
> > + */
> > +static int path_depth(const char *pathname)
> > +{
> > +	int i = 0;
> > +
> > +	if (pathname) {
> > +		char *ep = strchr(pathname, '\0');
> 
> what?  Does that even work?  strchr(p, 0) should always return NULL:
> 
> RETURN VALUE
>        The strchr() and strrchr() functions return a pointer  to  the  matched
>        character or NULL if the character is not found.
> 
> 
> Using
> 
> 	pathname + strlen(pathname)
> 
> would be saner, no?
> 
strchr(p, 0) returns the location of '\0', not NULL.
But, "p + strlen(p)" could generate faster assembly code than "strchr(p, 0)".
Replaced "strchr(p, 0)" by "p + strlen(p)".

> > +		if (pathname < ep--) {
> > +			if (*ep != '/')
> > +				i++;
> > +			while (pathname <= ep)
> > +				if (*ep-- == '/')
> > +					i += 2;
> > +		}
> > +	}
> > +	return i;
> > +}
> 
> I cannot imagine why this function exists :(
> 
To hash string for faster comparison.

> > [vast amounts of string hacking snipped]
> 
> This seems like madness, sorry.
> 
> Why the heck is so much string bashing going on in here???
> 
Yeah, You would go crazy with functions that handle string data.
But these functions are needed to stay inside the kernel for validating,
hashing and comparing string data.

> > +/**
> > + * tmy_io_printf - Transactional printf() to "struct tmy_io_buffer" structure.
> > + *
> > + * @head: Pointer to "struct tmy_io_buffer".
> > + * @fmt:  The printf()'s format string, followed by parameters.
> > + *
> > + * Returns true on success, false otherwise.
> 
> This comment should explain what the terms "success" and "failure"
> refer to.  Perhaps "success"=="the output didn't overflow" or something.
> 
Replaced "Returns true on success" by "Returns true if output was written".

> > +static const char *tmy_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 = tmy_realpath_from_path(&vma->vm_file->f_path);
> > +			break;
> > +		}
> > +	}
> > +	up_read(&mm->mmap_sem);
> > +	return cp;
> > +}
> 
> What guarantees that the first executable mapping in the mapping list
> is the correct one for the executable?
> 
It is just for auditing. Not for security decision.

> What prevents us from accidentally breaking that guarantee in the
> future?  I wasn't even aware that this was the case.
> 
> What happens if the executable was unlinked?
> 
audit_log_task_info() is doing the same thing.

> > +/**
> > + * tmy_check_flags - Check mode for specified functionality.
> > + *
> > + * @domain: Pointer to "struct domain_info".
> > + * @index:  The functionality to check mode.
> > + *
> > + * Returns the mode of specified functionality.
> 
> That description is rather meaningless.
> 
Removed the description.

> > +unsigned int tmy_check_flags(const struct domain_info *domain, const u8 index)
> > +{
> > +	const u8 profile = domain->profile;
> > +
> > +	if (unlikely(in_interrupt())) {
> > +		static u8 count = 20;
> > +		if (count) {
> > +			count--;
> > +			printk(KERN_ERR "BUG: sleeping function called "
> > +			       "from invalid context.\n");
> > +			dump_stack();
> > +		}
> > +		return 0;
> > +	}
> 
> a) WARN_ON is preferred
> 
> b) WARN_ON_ONCE might be usable here
> 
> c) what on earth is this code doing??
> 
Replaced "count" with "WARN_ON".

TOMOYO checks only process context.
This code disables TOMOYO's enforcement in case the function is called from
interrupt context.

> > +	return sbin_init_started && index < TMY_MAX_CONTROL_INDEX
> > +#if MAX_PROFILES != 256
> > +		&& profile < MAX_PROFILES
> > +#endif
> > +		&& profile_ptr[profile] ?
> > +		profile_ptr[profile]->value[index] : 0;
> > +}
> 
> And this.  I cannot imagine why Tomoyo cares whether /sbin/init has
> started yet.  This sort of thing should be commented!
> 
TOMOYO loads policy using /sbin/tomoyo-init when /sbin/init starts.
Replaced "sbin_init_started" by "tmy_policy_loaded".

> What happens in a cgroups environment where there will be multiple
> /sbin/inits running?
Nothing. /sbin/tomoyo-init is called only for the first time /sbin/init
is requested and /sbin/tomoyo-init exists.

> > +/**
> > + * tmy_check_domain_quota - Check for domain's quota.
> > + *
> > + * @domain: Pointer to "struct domain_info".
> > + *
> > + * Returns true if the domain is not exceeded quota, false otherwise.
> > + */
> 
> This function is poorly named.
> 
Replaced "tmy_check_domain_quota" by "tomoyo_domain_quota_is_ok".

> > +/**
> > + * tmy_find_or_assign_new_profile - Create a new profile.
> > + *
> > + * @profile: Profile number to create.
> > + *
> > + * Returns pointer to "struct profile" on success, NULL otherwise.
> > + */
> > +static struct profile *tmy_find_or_assign_new_profile(const unsigned int
> > +						      profile)
> > +{
> > +	static DEFINE_MUTEX(lock);
> > +	struct profile *ptr = NULL;
> > +
> > +	/***** EXCLUSIVE SECTION START *****/
> > +	mutex_lock(&lock);
> > +	if (profile < MAX_PROFILES) {
> 
> This check didn't need to be inside the lock.
> 
Moved the "if" to before the lock.

> > +/**
> > + * write_profile - Write profile table.
> 
> where to?
> 
Write to profile table. Fixed.

> > +/**
> > + * read_profile - Read profile table.
> 
> Where from?
> 
Read from profile table. Fixed.

> These functions appear to be implementing more userspace interfaces.
> 
> The userspace interface is the most important part of any kernel code. 
> We can change all the internal details, but the interfaces will live
> forever.
> 
> Hence we should review the proposed interfaces before even looking at
> the code.  Indeed, before even writing the code.
> 
> What are the Tomoyo kernel interfaces?

I'll describe it in other posting.

> > +/* Structure for policy manager. */
> > +struct policy_manager_entry {
> > +	struct list1_head list;
> > +	/* A path to program or a domainname. */
> > +	const struct path_info *manager;
> > +	bool is_domain;  /* True if manager is a domainname. */
> > +	bool is_deleted; /* True if this entry is deleted. */
> > +};
> > +
> > +/*
> > + * The list for "struct policy_manager_entry".
> > + *
> > + * This list is updated only inside update_manager_entry(), thus
> > + * no global mutex exists.
> > + */
> > +static LIST1_HEAD(policy_manager_list);
> > +
> > +/**
> > + * update_manager_entry - Add a manager entry.
> > + *
> > + * @manager:   The path to manager or the domainnamme.
> > + * @is_delete: True if it is a delete request.
> > + *
> > + * Returns 0 on success, negative value otherwise.
> > + */
> 
> eh?  So deleted entries get their "is_deleted" flag set but they are
> never actually removed from the list nor freed?  So over time the list
> gets longer and longer and consumes more and more memory?
> 
Right. Most of elements are allocated when /sbin/init starts, and they 
are referred during lifetime of the kernel. Deleted entries get their 
"is_deleted" flag set but they are never actually removed from the list 
nor freed. But don't worry. The amount of memory used by TOMOYO is quite 
small (about 1MB or so).

> > +/**
> > + * write_manager_policy - Write manager policy.
> > + *
> > + * @head: Pointer to "struct tmy_io_buffer"
> > + *
> > + * Returns 0 on success, negative value otherwise.
> > + */
> 
> More userspace ABI proposals?
> 
Yes. But, it is only for TOMOYO's management tools.
TOMOYO requires no modification of existing userland programs
and provides no API for existing userland programs.

> > +/**
> > + * is_policy_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.
> > + */
> > +static bool is_policy_manager(void)
> > +{
> > +	struct policy_manager_entry *ptr;
> > +	const char *exe;
> > +	const struct task_struct *task = current;
> > +	const struct path_info *domainname = tmy_domain()->domainname;
> > +	bool found = false;
> > +
> > +	if (!sbin_init_started)
> > +		return true;
> > +	if (!manage_by_non_root && (task->cred->uid || task->cred->euid))
> > +		return false;
> 
> What happens in a containerised environment where uids are non-unique
> and where there are multiple /sbin/inits?
> 
This interface is designed to be accessed by processes having init_pid_ns
namespace.

> > +	if (!found) { /* Reduce error messages. */
> > +		static pid_t last_pid;
> > +		const pid_t pid = current->pid;
> > +		if (last_pid != pid) {
> > +			printk(KERN_WARNING "%s ( %s ) is not permitted to "
> > +			       "update policies.\n", domainname->name, exe);
> 
> It appears that unprivileged userspace can cause this messge to be
> printed at will.  That can cause the logs to fill and is considered to
> be a small denial of service security hole.
> 
By default, this pseudo file is "root:root" and it's permission is 0600.

> > +static bool is_select_one(struct tmy_io_buffer *head, const char *data)
> > +{
> > +	unsigned int pid;
> > +	struct domain_info *domain = NULL;
> > +
> > +	if (sscanf(data, "pid=%u", &pid) == 1) {
> 
> PIDs are no longer system-wide unique, and here we appear to be
> implementing new userspace ABIs using PIDs.
> 
This interface is designed to be accessed by processes having init_pid_ns
namespace.

> > +static int write_domain_profile(struct tmy_io_buffer *head)
> > +{
> > +	char *data = head->write_buf;
> > +	char *cp = strchr(data, ' ');
> > +	struct domain_info *domain;
> > +	unsigned long profile;
> > +
> > +	if (!cp)
> > +		return -EINVAL;
> > +	*cp = '\0';
> > +	domain = tmy_find_domain(cp + 1);
> > +	strict_strtoul(data, 10, &profile);
> 
> Unchecked return value?
> 
Added checking.

> > +/* path to policy loader */
> > +static const char *tmy_loader = "/sbin/tomoyo-init";
> 
> hm, hard-wired knowledge of filesytem layout.
> 
> We did this in a few places already, reluctantly.  We did at least make
> them configurable (eg: /proc/sys/kernel/modprobe).
> 
> It's rather ugly to be doing this sort of thing.
> 
This pathname is embedded into the kernel to avoid modification of
userland program.
/proc/sys/kernel/tmy_loader seems redundant. Should we use __setup()?

> > +static bool 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 nameidata nd;
> > +
> > +	if (path_lookup(tmy_loader, LOOKUP_FOLLOW, &nd)) {
> > +		printk(KERN_INFO "Not activating Mandatory Access Control now "
> > +		       "since %s doesn't exist.\n", tmy_loader);
> > +		return false;
> > +	}
> > +	path_put(&nd.path);
> > +	return true;
> > +}
> 
> If you really really have to do this then a simple call to sys_access()
> might suffice.
> 
To use sys_access(), we need to add get_fs()/set_fs() stuff.
It's not simple.

> But it is of course racy against concurrent rename, unlink, etc.
> 
Racing is not a problem. Policy loader is called *only once* upon boot.

> > +void tmy_load_policy(const char *filename)
> > +{
> > +	char *argv[2];
> > +	char *envp[3];
> > +
> > +	if (sbin_init_started)
> > +		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 (!policy_loader_exists())
> > +		return;
> 
> Why do this?  call_usermodehelper() will simply fail if the file isn't here.
> 
Without policy_loader_exists(), the system will panic() if
/sbin/init is requested but the policy loader doesn't exist.

> > +	printk(KERN_INFO "Calling %s to load policy. Please wait.\n",
> > +	       tmy_loader);
> > +	argv[0] = (char *) tmy_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-pre   2008/10/10\n");
> > +	printk(KERN_INFO "Mandatory Access Control activated.\n");
> > +	sbin_init_started = true;
> > +	{ /* Check all profiles currently assigned to domains are defined. */
> > +		struct domain_info *domain;
> > +		list1_for_each_entry(domain, &domain_list, list) {
> > +			const u8 profile = domain->profile;
> > +			if (profile_ptr[profile])
> > +				continue;
> > +			panic("Profile %u (used by '%s') not defined.\n",
> > +			      profile, domain->domainname->name);
> > +		}
> > +	}
> > +}

> > +/**
> > + * read_updates_counter - Check for policy change counter.
> > + *
> > + * @head: Pointer to "struct tmy_io_buffer".
> > + *
> > + * Returns how many times policy has changed since the previous check.
> > + */
> > +static int read_updates_counter(struct tmy_io_buffer *head)
> > +{
> > +	if (head->read_eof)
> > +		return 0;
> > +	tmy_io_printf(head,
> > +		      "/sys/kernel/security/tomoyo/domain_policy:    %10u\n"
> > +		      "/sys/kernel/security/tomoyo/exception_policy: %10u\n"
> > +		      "/sys/kernel/security/tomoyo/profile:          %10u\n"
> > +		      "/sys/kernel/security/tomoyo/manager:          %10u\n",
> > +		      atomic_xchg(&updates_counter
> > +				  [TMY_UPDATES_COUNTER_DOMAIN_POLICY], 0),
> > +		      atomic_xchg(&updates_counter
> > +				  [TMY_UPDATES_COUNTER_EXCEPTION_POLICY], 0),
> > +		      atomic_xchg(&updates_counter
> > +				  [TMY_UPDATES_COUNTER_PROFILE], 0),
> > +		      atomic_xchg(&updates_counter
> > +				  [TMY_UPDATES_COUNTER_MANAGER], 0));
> > +	head->read_eof = true;
> > +	return 0;
> > +}
> 
> What is this doing?  We print the absolute pathnames of sysfs files via
> another sysfs file?
> 
This is an interface to allow GUI management tool to examine policy changes.
But removed because GUI management tool is not ready to support this version.

> > +static int tmy_read_control(struct file *file, char __user *buffer,
> > +			    const int buffer_len)
> > +{
> > +	int len = 0;
> > +	struct tmy_io_buffer *head = file->private_data;
> > +	char *cp;
> > +
> > +	if (!head->read)
> > +		return -ENOSYS;
> > +	if (!access_ok(VERIFY_WRITE, buffer, buffer_len))
> 
> Unneeded - copy_to_user() checks this.
> 
Removed.

> > +/**
> > + * tmy_write_control - write() for /sys/kernel/security/tomoyo/ interface.
> > + *
> > + * @file:       Pointer to "struct file".
> > + * @buffer:     Pointer to buffer to read from.
> > + * @buffer_len: Size of @buffer.
> > + *
> > + * Returns @buffer_len on success, negative value otherwise.
> > + */
> > +static int tmy_write_control(struct file *file, const char __user *buffer,
> > +			     const int buffer_len)
> > +{
> > +	struct tmy_io_buffer *head = file->private_data;
> > +	int error = buffer_len;
> > +	int avail_len = buffer_len;
> > +	char *cp0 = head->write_buf;
> > +
> > +	if (!head->write)
> > +		return -ENOSYS;
> > +	if (!access_ok(VERIFY_READ, buffer, buffer_len))
> 
> Unneeded.
> 
I know. But to avoid partial copy, I check here too.

> > +/* Common header for holding ACL entries. */
> > +struct acl_info {
> > +	struct list1_head list;
> > +	/*
> > +	 * Type of this ACL entry.
> > +	 *
> > +	 * MSB is is_deleted flag.
> > +	 */
> > +	u8 type;
> > +} __attribute__((__packed__));
> 
> I cannot tell from reading the code why this is packed.  Hence a comment
> is needed.
> 
Packing "struct acl_info" allows "single_path_acl_record" to embed "u16" and
"struct double_path_acl_record" to embed "u8" without enlarging their structure
size. I added a comment.

> Please use __packed.
Replaced "__attribute__((__packed__))" with "__packed".

> > +/* The kernel's domain. */
> > +extern struct domain_info KERNEL_DOMAIN;
> 
> Why upper-case?
> 
Replaced "KERNEL_DOMAIN" by "kernel_domain".

> Many of the symbols which this header defines have quite
> generic-sounding names.  it would be better if their names were to
> identify the symbols as being part of Tomoyo.
> 
Added "tomoyo_" to all symbols.



^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYOLinux.
  2008-11-10  3:30             ` Tetsuo Handa
@ 2008-11-10 14:00               ` Serge E. Hallyn
  0 siblings, 0 replies; 32+ messages in thread
From: Serge E. Hallyn @ 2008-11-10 14:00 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: akpm, linux-security-module, linux-kernel, takedakn, haradats

Quoting Tetsuo Handa (penguin-kernel@i-love.sakura.ne.jp):
> Hello.
> 
> Serge E. Hallyn wrote:
> > > I need to clarify reachability of "struct task_struct".
> > > 
> > > A process inside a virtualized environment cannot reach "struct task_struct"
> > > which belongs to outside the virtualized environment.
> > > 
> > > A process outside virtualized environments can reach "struct task_struct"
> > > which belongs to inside virtualized environments, can't it?
> > 
> > To be precise, there isn't a real 'inside' and 'outside' virtualized
> > environements.  Rather pid namespaces are hierarchical.
> > 
> So, processes which have non-topmost namespace cannot see processes which have
> topmost namespace (like chroot()).
> Then, it might be preferable if TOMOYO can prevent processes which have
> non-topmost namespace from modifying policy information.
> Do you think TOMOYO should do "current->nsproxy->pid_ns == &init_pid_ns"
> checking like below one?

Nah, I actually don't.  There is nothing to stop init or getty from
doing an unshare(CLONE_NEWNS|CLONE_NEWPID) so noone would be able to
make modifications.  The important thing is that the kernel won't
use another task than what userspace intended, so I think you're ok.

> static bool tomoyo_is_policy_manager(void)
> {
> 	struct tomoyo_policy_manager_entry *ptr;
> 	const char *exe;
> 	const struct task_struct *task = current;
> 	const struct tomoyo_path_info *domainname = tomoyo_domain()->domainname;
> 	bool found = false;
> 
> 	if (!tomoyo_policy_loaded)
> 	        return true;
> 	if (!tomoyo_manage_by_non_root && (task->cred->uid || task->cred->euid))

Now the point of LSM is to let you decide on your own policy, so this
is entirely up to you, but wouldn't it be nicer to use CAP_MAC_ADMIN's
presence like SMACK does?

> 	        return false;
> 	/* Don't allow modifying policy by processes not having init_pid_ns. */
> 	if (task->nsproxy->pid_ns != &init_pid_ns)
> 		return false;

No, I think it's far better to keep this out.  (Plus you'd have to use
task_nsproxy() under rcu_read_lock.)

I know at this point it might seem like I'm changing my mind left and
right (sorry), so here is the guiding principle:  pids are appropriate
for userspace to communicate to userspace and, if done right, to the
kernel.  But the kernel must not cache them internally.

(...and, if getting or sending them from/to user-space, must be careful
about the pidns, of course.)

So you're fine.

thanks,
-serge


^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [TOMOYO #12 (2.6.28-rc2-mm1) 05/11] Memory and pathname management functions.
  2008-11-10 10:34     ` Kentaro Takeda
@ 2008-11-11  5:04       ` Andrew Morton
  2008-11-11  6:34         ` Kentaro Takeda
  0 siblings, 1 reply; 32+ messages in thread
From: Andrew Morton @ 2008-11-11  5:04 UTC (permalink / raw)
  To: Kentaro Takeda
  Cc: haradats, linux-security-module, linux-kernel, penguin-kernel

On Mon, 10 Nov 2008 19:34:17 +0900 Kentaro Takeda <takedakn@nttdata.co.jp> wrote:

> 
> ...
>
> > > +/**
> > > + * tmy_alloc - Allocate memory for temporal purpose.
> > > + *
> > > + * @size: Size in bytes.
> > > + *
> > > + * Returns pointer to allocated memory on success, NULL otherwise.
> > > + */
> > > +void *tmy_alloc(const size_t size)
> > > +{
> > > +	void *p = kzalloc(size, GFP_KERNEL);
> > > +	if (p)
> > > +		atomic_add(ksize(p), &dynamic_memory_size);
> > > +	return p;
> > > +}
> > 
> > Note that I said "kmalloc", not "kzalloc".  This function zeroes
> > everything all the time, and surely that is not necessary.  It's just a
> > waste of CPU time.
> > 
> Callers of tmy_alloc assume that allocated memory is zeroed.

That isn't the point.  For programmer convenience we could make
__alloc_pages() and kmalloc() zero all the memory too.  But we don't
because it is slow.

> > > +/**
> > > + * tmy_read_memory_counter - Check for memory usage.
> > > + *
> > > + * @head: Pointer to "struct tmy_io_buffer".
> > > + *
> > > + * Returns memory usage.
> > 
> > In what units?  Megabytes?
> > 
> In bytes.

Let me rephrase:

The comment over tmy_read_memory_counter() fails to tell the reader
what units are used for the return value.  It should do so.

> > Again, we would like to see a complete decription of the proposed
> > userspace ABI.  This one looks fairly ugly.  Do I really have to write
> > 'S' 'h' 'a' 'r' 'e' 'd' ':' ' ' into some pseudo file?
> > 
> > A better interface would be two suitably-named pseudo files each of
> > which takes a bare integer string.  None of this funny colon-based
> > prefixing stuff.
> > 
> Creating pseudo files for each variables is fine, though I don't see
> advantage by changing from
> "echo Shared: 16777216 > /sys/kernel/security/tomoyo/meminfo" to
> "echo 16777216 > /sys/kernel/security/tomoyo/quota/shared_memory".

Well for starters, the existing interface is ugly as sin and will make
kernel developers unhappy.

There is a pretty strict one-value-per-file rule in sysfs files, and
"multiple tagged values in one file" violates that a lot.


^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [TOMOYO #12 (2.6.28-rc2-mm1) 05/11] Memory and pathname management functions.
  2008-11-11  5:04       ` Andrew Morton
@ 2008-11-11  6:34         ` Kentaro Takeda
  2008-11-11  6:46           ` Andrew Morton
  0 siblings, 1 reply; 32+ messages in thread
From: Kentaro Takeda @ 2008-11-11  6:34 UTC (permalink / raw)
  To: akpm; +Cc: haradats, linux-security-module, linux-kernel, penguin-kernel

Andrew Morton wrote:
>>> Note that I said "kmalloc", not "kzalloc".  This function zeroes
>>> everything all the time, and surely that is not necessary.  It's just a
>>> waste of CPU time.
>>>
>> Callers of tmy_alloc assume that allocated memory is zeroed.
> 
> That isn't the point.  For programmer convenience we could make
> __alloc_pages() and kmalloc() zero all the memory too.  But we don't
> because it is slow.
Are you saying "make the callers of tmy_alloc() tolerable with
uninitialized memory"?

>>>> +/**
>>>> + * tmy_read_memory_counter - Check for memory usage.
>>>> + *
>>>> + * @head: Pointer to "struct tmy_io_buffer".
>>>> + *
>>>> + * Returns memory usage.
>>> In what units?  Megabytes?
>>>
>> In bytes.
> 
> Let me rephrase:
> 
> The comment over tmy_read_memory_counter() fails to tell the reader
> what units are used for the return value.  It should do so.
I see. Replaced "Check for memory usage." by "Check for memory usage in bytes".
Thanks.

>> Creating pseudo files for each variables is fine, though I don't see
>> advantage by changing from
>> "echo Shared: 16777216 > /sys/kernel/security/tomoyo/meminfo" to
>> "echo 16777216 > /sys/kernel/security/tomoyo/quota/shared_memory".
> 
> Well for starters, the existing interface is ugly as sin and will make
> kernel developers unhappy.
> 
> There is a pretty strict one-value-per-file rule in sysfs files, and
> "multiple tagged values in one file" violates that a lot.
/sys/kernel/security/ is not sysfs but securityfs.
Does "one-value-per-file rule" also apply to securityfs?

Regards,



^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [TOMOYO #12 (2.6.28-rc2-mm1) 05/11] Memory and pathname management functions.
  2008-11-11  6:34         ` Kentaro Takeda
@ 2008-11-11  6:46           ` Andrew Morton
  2008-11-11  7:32             ` Kentaro Takeda
  0 siblings, 1 reply; 32+ messages in thread
From: Andrew Morton @ 2008-11-11  6:46 UTC (permalink / raw)
  To: Kentaro Takeda
  Cc: haradats, linux-security-module, linux-kernel, penguin-kernel

On Tue, 11 Nov 2008 15:34:39 +0900 Kentaro Takeda <takedakn@nttdata.co.jp> wrote:

> Andrew Morton wrote:
> >>> Note that I said "kmalloc", not "kzalloc".  This function zeroes
> >>> everything all the time, and surely that is not necessary.  It's just a
> >>> waste of CPU time.
> >>>
> >> Callers of tmy_alloc assume that allocated memory is zeroed.
> > 
> > That isn't the point.  For programmer convenience we could make
> > __alloc_pages() and kmalloc() zero all the memory too.  But we don't
> > because it is slow.
> Are you saying "make the callers of tmy_alloc() tolerable with
> uninitialized memory"?

Well.  That would be a desirable objective.  I can understand the
reasons for taking the easy way out.  Given that Tomoyo doesn't seem to
ever free memory again, one hopes that this function doesn't get called
a lot, so the performance impact of zeroing out all that memory should
be negligible.

I think.  Maybe I misinterpreted tmy_alloc(), and perhaps it _is_
called frequently?

> >> Creating pseudo files for each variables is fine, though I don't see
> >> advantage by changing from
> >> "echo Shared: 16777216 > /sys/kernel/security/tomoyo/meminfo" to
> >> "echo 16777216 > /sys/kernel/security/tomoyo/quota/shared_memory".
> > 
> > Well for starters, the existing interface is ugly as sin and will make
> > kernel developers unhappy.
> > 
> > There is a pretty strict one-value-per-file rule in sysfs files, and
> > "multiple tagged values in one file" violates that a lot.
> /sys/kernel/security/ is not sysfs but securityfs.
> Does "one-value-per-file rule" also apply to securityfs?

It should apply.  It's not so much a matter of rules and regulations. 
One needs to look at the underlying _reasons_ why those rules came
about.  We got ourselves into a sticky mess with procfs with all sorts
of ad-hoc data presentation and input formatting.  It's inconsistent,
complex, makes tool writing harder, etc.

So we recognised our mistakes and when sysfs (otherwise known as procfs
V2 :)) came about we decided that sysfs files should not make the same
mistakes.

So, logically, that thinking should apply to all new pseudo-fs files. 
Even, in fact, ones which are in /proc!

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [TOMOYO #12 (2.6.28-rc2-mm1) 05/11] Memory and pathname management functions.
  2008-11-11  6:46           ` Andrew Morton
@ 2008-11-11  7:32             ` Kentaro Takeda
  0 siblings, 0 replies; 32+ messages in thread
From: Kentaro Takeda @ 2008-11-11  7:32 UTC (permalink / raw)
  To: akpm; +Cc: haradats, linux-security-module, linux-kernel, penguin-kernel

Andrew Morton wrote:
>> Are you saying "make the callers of tmy_alloc() tolerable with
>> uninitialized memory"?
> 
> Well.  That would be a desirable objective.  I can understand the
> reasons for taking the easy way out.  Given that Tomoyo doesn't seem to
> ever free memory again, one hopes that this function doesn't get called
> a lot, so the performance impact of zeroing out all that memory should
> be negligible.
> 
> I think.  Maybe I misinterpreted tmy_alloc(), and perhaps it _is_
> called frequently?
It is called whenever open() / mkdir() / unlink() etc. are called,
but not when read() / write() are called.
Frequency of open() / mkdir() / unlink() etc. are much lower than frequency of
read() / write().
Main cost of pathname based access control is strcmp()ing (or even regexp()ing)
over the list of strings, therefore zeroing buffer for pathname is relatively
negligible.

>>>> Creating pseudo files for each variables is fine, though I don't see
>>>> advantage by changing from
>>>> "echo Shared: 16777216 > /sys/kernel/security/tomoyo/meminfo" to
>>>> "echo 16777216 > /sys/kernel/security/tomoyo/quota/shared_memory".
>>> Well for starters, the existing interface is ugly as sin and will make
>>> kernel developers unhappy.
>>>
>>> There is a pretty strict one-value-per-file rule in sysfs files, and
>>> "multiple tagged values in one file" violates that a lot.
>> /sys/kernel/security/ is not sysfs but securityfs.
>> Does "one-value-per-file rule" also apply to securityfs?
> 
> It should apply.  It's not so much a matter of rules and regulations. 
> One needs to look at the underlying _reasons_ why those rules came
> about.  We got ourselves into a sticky mess with procfs with all sorts
> of ad-hoc data presentation and input formatting.  It's inconsistent,
> complex, makes tool writing harder, etc.
> 
> So we recognised our mistakes and when sysfs (otherwise known as procfs
> V2 :)) came about we decided that sysfs files should not make the same
> mistakes.
> 
> So, logically, that thinking should apply to all new pseudo-fs files. 
> Even, in fact, ones which are in /proc!
Well, regarding memory usage, it is easy to follow "one-value-per-file rule".
But regarding policy information (which is managed as lists),
"one-value-per-file rule" is not suitable. I think none of SELinux, SMACK,
AppArmor, TOMOYO create "one pseudo file for one value".
This /sys/kernel/security/tomoyo/ interface is used by only TOMOYO's management
programs, and not by generic programs.

Regards,


^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYO Linux.
  2008-11-05 23:12   ` Andrew Morton
                       ` (2 preceding siblings ...)
  2008-11-10 10:35     ` [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYO Linux Kentaro Takeda
@ 2008-11-14  9:22     ` Kentaro Takeda
  3 siblings, 0 replies; 32+ messages in thread
From: Kentaro Takeda @ 2008-11-14  9:22 UTC (permalink / raw)
  To: akpm; +Cc: haradats, linux-security-module, linux-kernel, penguin-kernel

Andrew Morton wrote:
> These functions appear to be implementing more userspace interfaces.
> 
> The userspace interface is the most important part of any kernel code. 
> We can change all the internal details, but the interfaces will live
> forever.
> 
> Hence we should review the proposed interfaces before even looking at
> the code.  Indeed, before even writing the code.
> 
> What are the Tomoyo kernel interfaces?
TOMOYO Linux creates the following files on securityfs (normally 
mounted on /sys/kernel/security) as interfaces between kernel and 
userspace. These files are for TOMOYO Linux management tools *only*, 
not for general programs.

  * profile
  * exception_policy
  * domain_policy
  * manager
  * meminfo
  * self_domain
  * version
  * .domain_status
  * .process_status

*** /sys/kernel/security/tomoyo/profile ***

This file is used to read or write profiles.

"profile" means a running mode of process. A profile lists up 
functions and their modes in "$number-$variable=$value" format. The 
$number is profile number between 0 and 255. Each domain is assigned 
one profile. To assign profile to domains, use "ccs-setprofile" or 
"ccs-editpolicy" or "ccs-loadpolicy" commands.

(Example)
[root@tomoyo]# cat /sys/kernel/security/tomoyo/profile
0-COMMENT=-----Disabled Mode-----
0-MAC_FOR_FILE=disabled
0-MAX_ACCEPT_ENTRY=2048
0-TOMOYO_VERBOSE=disabled
1-COMMENT=-----Learning Mode-----
1-MAC_FOR_FILE=learning
1-MAX_ACCEPT_ENTRY=2048
1-TOMOYO_VERBOSE=disabled
2-COMMENT=-----Permissive Mode-----
2-MAC_FOR_FILE=permissive
2-MAX_ACCEPT_ENTRY=2048
2-TOMOYO_VERBOSE=enabled
3-COMMENT=-----Enforcing Mode-----
3-MAC_FOR_FILE=enforcing
3-MAX_ACCEPT_ENTRY=2048
3-TOMOYO_VERBOSE=enabled

- MAC_FOR_FILE:
Specifies access control level regarding file access requests.
- MAX_ACCEPT_ENTRY:
Limits the max number of ACL entries that are automatically appended 
during learning mode. Default is 2048.
- TOMOYO_VERBOSE:
Specifies whether to print domain policy violation messages or not.

*** /sys/kernel/security/tomoyo/manager ***

This file is used to read or append the list of programs or domains 
that can write to /sys/kernel/security/tomoyo interface. By default, 
only processes with both UID = 0 and EUID = 0 can modify policy via 
/sys/kernel/security/tomoyo interface. You can use keyword 
"manage_by_non_root" to allow policy modification by non root user.

(Example)
[root@tomoyo]# cat /sys/kernel/security/tomoyo/manager
/usr/lib/ccs/loadpolicy
/usr/lib/ccs/editpolicy
/usr/lib/ccs/setlevel
/usr/lib/ccs/setprofile
/usr/lib/ccs/ld-watch
/usr/lib/ccs/ccs-queryd

*** /sys/kernel/security/tomoyo/exception_policy ***

This file is used to read and write system global settings. Each line 
has a directive and operand pair. Directives are listed below.

- initialize_domain:
To initialize domain transition when specific program is executed, 
use initialize_domain directive.
  * initialize_domain "program" from "domain"
  * initialize_domain "program" from "the last program part of domain"
  * initialize_domain "program"
If the part "from" and after is not given, the entry is applied to 
all domain. If the "domain" doesn't start with "<kernel>", the entry 
is applied to all domain whose domainname ends with "the last program 
part of domain".
This directive is intended to aggregate domain transitions for daemon 
program and program that are invoked by the kernel on demand, by 
transiting to different domain.

- keep_domain
To prevent domain transition when program is executed from specific 
domain, use keep_domain directive.
  * keep_domain "program" from "domain"
  * keep_domain "program" from "the last program part of domain"
  * keep_domain "domain"
  * keep_domain "the last program part of domain" 
If the part "from" and before is not given, this entry is applied to 
all program. If the "domain" doesn't start with "<kernel>", the entry 
is applied to all domain whose domainname ends with "the last program 
part of domain".
This directive is intended to reduce total number of domains and 
memory usage by suppressing unneeded domain transitions.
To declare domain keepers, use keep_domain directive followed by 
domain definition.
Any process that belongs to any domain declared with this directive, 
the process stays at the same domain unless any program registered 
with initialize_domain directive is executed.

In order to control domain transition in detail, you can use 
no_keep_domain/no_initialize_domain keywrods.

- alias: 
To allow executing programs using the name of symbolic links, use 
alias keyword followed by dereferenced pathname and reference 
pathname. For example, /sbin/pidof is a symbolic link to 
/sbin/killall5 . In normal case, if /sbin/pidof is executed, the 
domain is defined as if /sbin/killall5 is executed. By specifying 
"alias /sbin/killall5 /sbin/pidof", you can run /sbin/pidof in the 
domain for /sbin/pidof .
(Example)
alias /sbin/killall5 /sbin/pidof

- allow_read:
To grant unconditionally readable permissions, use allow_read keyword 
followed by canonicalized file. This keyword is intended to reduce 
size of domain policy by granting read access to library files such 
as GLIBC and locale files. Exception is, if ignore_global_allow_read 
keyword is given to a domain, entries specified by this keyword are 
ignored.
(Example)
allow_read /lib/libc-2.5.so

- file_pattern:
To declare pathname pattern, use file_pattern keyword followed by 
pathname pattern. The pathname pattern must be a canonicalized 
Pathname. This keyword is not applicable to neither granting execute 
permissions nor domain definitions.
For example, canonicalized pathname that contains a process ID 
(i.e. /proc/PID/ files) needs to be grouped in order to make access 
control work well.
(Example)
file_pattern /proc/\$/cmdline

- path_group
To declare pathname group, use path_group keyword followed by name of 
the group and pathname pattern. For example, if you want to group all 
files under home directory, you can define
   path_group HOME-DIR-FILE /home/\*/\*
   path_group HOME-DIR-FILE /home/\*/\*/\*
   path_group HOME-DIR-FILE /home/\*/\*/\*/\*
in the exception policy and use like
   allow_read @HOME-DIR-FILE
to grant file access permission.

- deny_rewrite:
To deny overwriting already written contents of file (such as log 
files) by default, use deny_rewrite keyword followed by pathname 
pattern. Files whose pathname match the patterns are not permitted to 
open for writing without append mode or truncate unless the pathnames 
are explicitly granted using allow_rewrite keyword in domain policy.
(Example)
deny_rewrite /var/log/\*

- aggregator
To deal multiple programs as a single program, use aggregator keyword 
followed by name of original program and aggregated program. This 
keyword is intended to aggregate similar programs.
For example, /usr/bin/tac and /bin/cat are similar. By specifying 
"aggregator /usr/bin/tac /bin/cat", you can run /usr/bin/tac in the 
domain for /bin/cat .
For example, /usr/sbin/logrotate for Fedora Core 3 generates programs 
like /tmp/logrotate.\?\?\?\?\?\? and run them, but TOMOYO Linux 
doesn't allow using patterns for granting execute permission and 
defining domains. By specifying 
"aggregator /tmp/logrotate.\?\?\?\?\?\? /tmp/logrotate.tmp", you can 
run /tmp/logrotate.\?\?\?\?\?\? as if /tmp/logrotate.tmp is running.

*** /sys/kernel/security/tomoyo/domain_policy ***

This file contains definition of all domains and permissions that are 
granted to each domain.

Lines from the next line to a domain definition ( any lines starting 
with "<kernel>") to the previous line to the next domain definitions 
are interpreted as access permissions for that domain.

*** /sys/kernel/security/tomoyo/meminfo ***

This file is to show the total RAM used to keep policy in the kernel 
by TOMOYO Linux in bytes.
(Example)
[root@tomoyo]# cat /sys/kernel/security/tomoyo/meminfo
Shared:       61440
Private:      69632
Dynamic:        768
Total:       131840

You can set memory quota by writing to this file.
(Example)
[root@tomoyo]# echo Shared: 2097152 > /sys/kernel/security/tomoyo/meminfo
[root@tomoyo]# echo Private: 2097152 > /sys/kernel/security/tomoyo/meminfo

*** /sys/kernel/security/tomoyo/self_domain ***

This file is to show the name of domain the caller process belongs to.
(Example)
[root@etch]# cat /sys/kernel/security/tomoyo/self_domain
<kernel> /usr/sbin/sshd /bin/zsh /bin/cat

*** /sys/kernel/security/tomoyo/version ***

This file is used for getting TOMOYO Linux's version.
(Example)
[root@etch]# cat /sys/kernel/security/tomoyo/version
2.2.0-pre

*** /sys/kernel/security/tomoyo/.domain_status ***

This is a view (of a DBMS) that contains only profile number and 
domainnames of domain so that "ccs-setprofile" command can do 
line-oriented processing easily.

*** /sys/kernel/security/tomoyo/.process_status ***

This file is used by "ccs-ccstree" command to show "list of processes 
currently running" and "domains which each process belongs to" and 
"profile number which the domain is currently assigned" like "pstree" 
command. This file is writable by programs that aren't registered as 
policy manager.

Regards,



^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [TOMOYO #12 (2.6.28-rc2-mm1) 04/11] Introduce d_realpath().
  2008-11-05 23:12   ` Andrew Morton
@ 2008-11-17  6:52     ` Kentaro Takeda
  0 siblings, 0 replies; 32+ messages in thread
From: Kentaro Takeda @ 2008-11-17  6:52 UTC (permalink / raw)
  To: akpm; +Cc: haradats, linux-security-module, linux-kernel, penguin-kernel

Andrew Morton wrote:
> On Tue, 04 Nov 2008 15:08:51 +0900
> Kentaro Takeda <takedakn@nttdata.co.jp> wrote:
> 
>> +		/*
>> +		 * Exception: Use /proc/self/ rather than /proc/\$/
>> +		 * for current process.
>> +		 */
>> +		name = dentry->d_name.name;
>> +		name_len = dentry->d_name.len;
>> +		if (IS_ROOT(parent) &&
>> +		    parent->d_sb->s_magic == PROC_SUPER_MAGIC &&
>> +		    !strict_strtoul(name, 10, &pid)) {
> 
> Well that looks like rather a hack.
> 
> It would still be a hack, but a better implementation might be to save
> the procfs superblock's address in a global then do
> 
> 
> #ifdef CONFIG_PROCFS
> static inline bool is_procfs_sb(struct super_block *sb)
> {
> 	return sb == saved_procfs_sb;
> }
> #else
> static inline bool is_procfs_sb(struct super_block *sb)
> {
> 	return false;
> }
> #endif
It seems to me that the procfs superblock's address is not a single value
because proc_get_sb() in fs/proc/root.c could be called for multiple times.
Thus, I'd like to continue using "parent->d_sb->s_magic == PROC_SUPER_MAGIC"
rather than "is_procfs_sb(parent->d_sb)".

I think I've replied to most of your comments.
Is there anything we can do before reposting TOMOYO #13 ?

Regards,



^ permalink raw reply	[flat|nested] 32+ messages in thread

end of thread, other threads:[~2008-11-17  6:52 UTC | newest]

Thread overview: 32+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2008-11-04  6:08 [TOMOYO #12 (2.6.28-rc2-mm1) 00/11] TOMOYO Linux Kentaro Takeda
2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 01/11] Introduce security_path_clear() hook Kentaro Takeda
2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 02/11] Add in_execve flag into task_struct Kentaro Takeda
2008-11-05 23:12   ` Andrew Morton
2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 03/11] Singly linked list implementation Kentaro Takeda
2008-11-05 23:12   ` Andrew Morton
2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 04/11] Introduce d_realpath() Kentaro Takeda
2008-11-05 23:12   ` Andrew Morton
2008-11-17  6:52     ` Kentaro Takeda
2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 05/11] Memory and pathname management functions Kentaro Takeda
2008-11-05 23:12   ` Andrew Morton
2008-11-10 10:34     ` Kentaro Takeda
2008-11-11  5:04       ` Andrew Morton
2008-11-11  6:34         ` Kentaro Takeda
2008-11-11  6:46           ` Andrew Morton
2008-11-11  7:32             ` Kentaro Takeda
2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYO Linux Kentaro Takeda
2008-11-05 23:12   ` Andrew Morton
2008-11-06 21:46     ` [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYOLinux Tetsuo Handa
2008-11-08 16:38     ` Tetsuo Handa
2008-11-10  0:41       ` Serge E. Hallyn
2008-11-10  2:24         ` Tetsuo Handa
2008-11-10  2:52           ` Serge E. Hallyn
2008-11-10  3:30             ` Tetsuo Handa
2008-11-10 14:00               ` Serge E. Hallyn
2008-11-10 10:35     ` [TOMOYO #12 (2.6.28-rc2-mm1) 06/11] Common functions for TOMOYO Linux Kentaro Takeda
2008-11-14  9:22     ` Kentaro Takeda
2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 07/11] File operation restriction part Kentaro Takeda
2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 08/11] Domain transition handler Kentaro Takeda
2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 09/11] LSM adapter functions Kentaro Takeda
2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 10/11] Kconfig and Makefile Kentaro Takeda
2008-11-04  6:08 ` [TOMOYO #12 (2.6.28-rc2-mm1) 11/11] MAINTAINERS info Kentaro Takeda

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).