public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [TOMOYO #13 (mmotm 2008-11-19-02-19) 00/11] TOMOYO Linux
@ 2008-11-20 11:25 Tetsuo Handa
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 01/11] Introduce security_path_clear() hook Tetsuo Handa
                   ` (11 more replies)
  0 siblings, 12 replies; 29+ messages in thread
From: Tetsuo Handa @ 2008-11-20 11:25 UTC (permalink / raw)
  To: Andrew Morton; +Cc: linux-security-module, linux-kernel

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

This patchset is for mmotm 2008-11-19-02-19.

Andrew's comments and our answers on #12 are at
http://lkml.org/lkml/2008/11/6/409
http://lkml.org/lkml/2008/11/10/106
http://lkml.org/lkml/2008/11/10/107
http://lkml.org/lkml/2008/11/11/41
http://lkml.org/lkml/2008/11/17/18 .

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's command line.)
5. Run /usr/sbin/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] 29+ messages in thread

* [TOMOYO #13 (mmotm 2008-11-19-02-19) 01/11] Introduce security_path_clear() hook.
  2008-11-20 11:25 [TOMOYO #13 (mmotm 2008-11-19-02-19) 00/11] TOMOYO Linux Tetsuo Handa
@ 2008-11-20 11:25 ` Tetsuo Handa
  2008-12-01 20:00   ` Stephen Smalley
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 02/11] Add in_execve flag into task_struct Tetsuo Handa
                   ` (10 subsequent siblings)
  11 siblings, 1 reply; 29+ messages in thread
From: Tetsuo Handa @ 2008-11-20 11:25 UTC (permalink / raw)
  To: Andrew Morton
  Cc: linux-security-module, linux-kernel, Kentaro Takeda, Tetsuo Handa,
	Toshiharu Harada, Al Viro, Christoph Hellwig, Crispin Cowan,
	Stephen Smalley, Casey Schaufler, James Morris

[-- Attachment #1: introduce-security_path_clear.patch --]
[-- Type: text/plain, Size: 6850 bytes --]

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-rc5-mm1.orig/fs/namei.c
+++ linux-2.6.28-rc5-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-rc5-mm1.orig/fs/open.c
+++ linux-2.6.28-rc5-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-rc5-mm1.orig/include/linux/security.h
+++ linux-2.6.28-rc5-mm1/include/linux/security.h
@@ -527,6 +527,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
  *
@@ -1402,6 +1408,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);
@@ -2792,6 +2799,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)
 {
@@ -2842,6 +2850,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-rc5-mm1.orig/net/unix/af_unix.c
+++ linux-2.6.28-rc5-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-rc5-mm1.orig/security/capability.c
+++ linux-2.6.28-rc5-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-rc5-mm1.orig/security/security.c
+++ linux-2.6.28-rc5-mm1/security/security.c
@@ -419,6 +419,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] 29+ messages in thread

* [TOMOYO #13 (mmotm 2008-11-19-02-19) 02/11] Add in_execve flag into task_struct.
  2008-11-20 11:25 [TOMOYO #13 (mmotm 2008-11-19-02-19) 00/11] TOMOYO Linux Tetsuo Handa
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 01/11] Introduce security_path_clear() hook Tetsuo Handa
@ 2008-11-20 11:25 ` Tetsuo Handa
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 03/11] Singly linked list implementation Tetsuo Handa
                   ` (9 subsequent siblings)
  11 siblings, 0 replies; 29+ messages in thread
From: Tetsuo Handa @ 2008-11-20 11:25 UTC (permalink / raw)
  To: Andrew Morton
  Cc: linux-security-module, linux-kernel, Tetsuo Handa, David Howells

[-- Attachment #1: add-in_execve-flag-into-task_struct.patch --]
[-- Type: text/plain, Size: 2443 bytes --]

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-rc5-mm1.orig/fs/compat.c
+++ linux-2.6.28-rc5-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-rc5-mm1.orig/fs/exec.c
+++ linux-2.6.28-rc5-mm1/fs/exec.c
@@ -1293,6 +1293,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();
@@ -1346,6 +1347,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);
@@ -1364,6 +1366,7 @@ out_file:
 	}
 
 out_unlock:
+	current->in_execve = 0;
 	mutex_unlock(&current->cred_exec_mutex);
 
 out_free:
--- linux-2.6.28-rc5-mm1.orig/include/linux/sched.h
+++ linux-2.6.28-rc5-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] 29+ messages in thread

* [TOMOYO #13 (mmotm 2008-11-19-02-19) 03/11] Singly linked list implementation.
  2008-11-20 11:25 [TOMOYO #13 (mmotm 2008-11-19-02-19) 00/11] TOMOYO Linux Tetsuo Handa
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 01/11] Introduce security_path_clear() hook Tetsuo Handa
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 02/11] Add in_execve flag into task_struct Tetsuo Handa
@ 2008-11-20 11:25 ` Tetsuo Handa
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 04/11] Introduce d_realpath() Tetsuo Handa
                   ` (8 subsequent siblings)
  11 siblings, 0 replies; 29+ messages in thread
From: Tetsuo Handa @ 2008-11-20 11:25 UTC (permalink / raw)
  To: Andrew Morton; +Cc: linux-security-module, linux-kernel, Tetsuo Handa

[-- Attachment #1: introduce-write-once-read-many-linked-list.patch --]
[-- Type: text/plain, Size: 3646 bytes --]

This patch introduces new list structure named "list1".

TOMOYO wants to use "write once read many" list.
Since only two operations

 (1) Append an entry to the tail of the list.
 (2) Read all entries starting from the head of the list.

are needed for that purpose, this list doesn't have pointer to previous
element.

While "hlist" is defined as

 struct hlist_head {
 	struct hlist_node *first;
 };

 struct hlist_node {
 	struct hlist_node *next, **pprev;
 };

I don't use "hlist_node" because I don't need pointer to previous element.

By ommiting pointer to previous element, the reader becomes read lock free,
which is good thing for implementing "write once read many" list.

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-rc5-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] 29+ messages in thread

* [TOMOYO #13 (mmotm 2008-11-19-02-19) 04/11] Introduce d_realpath().
  2008-11-20 11:25 [TOMOYO #13 (mmotm 2008-11-19-02-19) 00/11] TOMOYO Linux Tetsuo Handa
                   ` (2 preceding siblings ...)
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 03/11] Singly linked list implementation Tetsuo Handa
@ 2008-11-20 11:25 ` Tetsuo Handa
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 05/11] Memory and pathname management functions Tetsuo Handa
                   ` (7 subsequent siblings)
  11 siblings, 0 replies; 29+ messages in thread
From: Tetsuo Handa @ 2008-11-20 11:25 UTC (permalink / raw)
  To: Andrew Morton
  Cc: linux-security-module, linux-kernel, Kentaro Takeda, Tetsuo Handa,
	Toshiharu Harada

[-- Attachment #1: introduce-d_realpath.patch --]
[-- Type: text/plain, Size: 3867 bytes --]

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.

This patch introduces d_realpath(), a variant of d_path().
While d_path() stops traversing at current->fs->root,
d_realpath() doesn't stop traversiong at current->fs->root.

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-rc5-mm1.orig/fs/dcache.c
+++ linux-2.6.28-rc5-mm1/fs/dcache.c
@@ -33,6 +33,7 @@
 #include <linux/swap.h>
 #include <linux/bootmem.h>
 #include <linux/backing-dev.h>
+#include <linux/magic.h>
 #include "internal.h"
 
 
@@ -1982,6 +1983,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-rc5-mm1.orig/include/linux/dcache.h
+++ linux-2.6.28-rc5-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] 29+ messages in thread

* [TOMOYO #13 (mmotm 2008-11-19-02-19) 05/11] Memory and pathname management functions.
  2008-11-20 11:25 [TOMOYO #13 (mmotm 2008-11-19-02-19) 00/11] TOMOYO Linux Tetsuo Handa
                   ` (3 preceding siblings ...)
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 04/11] Introduce d_realpath() Tetsuo Handa
@ 2008-11-20 11:25 ` Tetsuo Handa
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 06/11] Common functions for TOMOYO Linux Tetsuo Handa
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 29+ messages in thread
From: Tetsuo Handa @ 2008-11-20 11:25 UTC (permalink / raw)
  To: Andrew Morton
  Cc: linux-security-module, linux-kernel, Kentaro Takeda, Tetsuo Handa,
	Toshiharu Harada

[-- Attachment #1: tomoyo-realpath.patch --]
[-- Type: text/plain, Size: 16216 bytes --]

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.

TOMOYO uses only 0x21 - 0x7E (as printable characters) and 0x20 (as word
delimiter) and 0x0A (as line delimiter).
0x01 - 0x20 and 0x80 - 0xFF is 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, 0600)

and logging such crazy pathname using "Access %s denied.\n" format will cause
"fabrication of logs" like

 Access /tmp/file granted.
 Access /tmp/file denied.

TOMOYO converts such characters to \ooo so that the logs will become

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

and the administrator can read the logs safely using /bin/cat .
Likewise, a crazy request like

 open("/tmp/\x01\x02\x03\x04\x05\x06\x07\x08\x09", O_WRONLY | O_CREAT, 0600)

will be processed safely by converting to

 Access /tmp/\001\002\003\004\005\006\007\010\011 denied.

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 |  453 +++++++++++++++++++++++++++++++++++++++++++++
 security/tomoyo/realpath.h |   63 ++++++
 2 files changed, 516 insertions(+)

--- /dev/null
+++ linux-2.6.28-rc5-mm1/security/tomoyo/realpath.c
@@ -0,0 +1,453 @@
+/*
+ * 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/11/11
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/mount.h>
+#include "common.h"
+#include "realpath.h"
+
+/**
+ * tomoyo_encode: Convert binary string to ascii string.
+ *
+ * @buffer:  Buffer for ASCII string.
+ * @buflen:  Size of @buffer.
+ * @str:     Binary string.
+ *
+ * Returns 0 on success, -ENOMEM otherwise.
+ */
+int tomoyo_encode(char *buffer, int buflen, const char *str)
+{
+	while (1) {
+		const unsigned char c = *(unsigned char *) str++;
+
+		if (tomoyo_is_valid(c)) {
+			if (--buflen <= 0)
+				break;
+			*buffer++ = (char) c;
+			if (c != '\\')
+				continue;
+			if (--buflen <= 0)
+				break;
+			*buffer++ = (char) c;
+			continue;
+		}
+		if (!c) {
+			if (--buflen <= 0)
+				break;
+			*buffer = '\0';
+			return 0;
+		}
+		buflen -= 4;
+		if (buflen <= 0)
+			break;
+		*buffer++ = '\\';
+		*buffer++ = (c >> 6) + '0';
+		*buffer++ = ((c >> 3) & 7) + '0';
+		*buffer++ = (c & 7) + '0';
+	}
+	return -ENOMEM;
+}
+
+/**
+ * tomoyo_realpath_from_path2 - Returns realpath(3) of the given dentry but ignores chroot'ed root.
+ *
+ * @path:        Pointer to "struct path".
+ * @newname:     Pointer to buffer to return value in.
+ * @newname_len: Size of @newname.
+ *
+ * Returns 0 on success, negative value otherwise.
+ *
+ * If dentry is a directory, trailing '/' is appended.
+ * Characters out of 0x20 < c < 0x7F range are converted to
+ * \ooo style octal string.
+ * Character \ is converted to \\ string.
+ */
+int tomoyo_realpath_from_path2(struct path *path, char *newname,
+			       int newname_len)
+{
+	int error = -ENOMEM;
+	struct dentry *dentry = path->dentry;
+	char *sp;
+
+	if (!dentry || !path->mnt || !newname || newname_len <= 2048)
+		return -EINVAL;
+	if (dentry->d_op && dentry->d_op->d_dname) {
+		/* For "socket:[\$]" and "pipe:[\$]". */
+		static const int offset = 1536;
+		sp = dentry->d_op->d_dname(dentry, newname + offset,
+					   newname_len - offset);
+	} else {
+		path_get(path);
+		sp = d_realpath(path, newname, newname_len);
+		path_put(path);
+	}
+	if (IS_ERR(sp))
+		error = PTR_ERR(sp);
+	else
+		error = tomoyo_encode(newname, sp - newname, sp);
+	if (error)
+		printk(KERN_WARNING "tomoyo_realpath: Pathname too long.\n");
+	return error;
+}
+
+/**
+ * tomoyo_realpath_from_path - Returns realpath(3) of the given pathname but ignores chroot'ed root.
+ *
+ * @path: Pointer to "struct path".
+ *
+ * Returns the realpath of the given @path on success, NULL otherwise.
+ *
+ * These functions use tomoyo_alloc(), so the caller must call tomoyo_free()
+ * if these functions didn't return NULL.
+ */
+char *tomoyo_realpath_from_path(struct path *path)
+{
+	char *buf = tomoyo_alloc(sizeof(struct tomoyo_page_buffer));
+
+	BUILD_BUG_ON(sizeof(struct tomoyo_page_buffer)
+		     <= TOMOYO_MAX_PATHNAME_LEN - 1);
+	if (!buf)
+		return NULL;
+	if (tomoyo_realpath_from_path2(path, buf,
+				       TOMOYO_MAX_PATHNAME_LEN - 1) == 0)
+		return buf;
+	tomoyo_free(buf);
+	return NULL;
+}
+
+/**
+ * tomoyo_realpath - Get realpath of a pathname.
+ *
+ * @pathname: The pathname to solve.
+ *
+ * Returns the realpath of @pathname on success, NULL otherwise.
+ */
+char *tomoyo_realpath(const char *pathname)
+{
+	struct nameidata nd;
+
+	if (pathname && path_lookup(pathname, LOOKUP_FOLLOW, &nd) == 0) {
+		char *buf = tomoyo_realpath_from_path(&nd.path);
+		path_put(&nd.path);
+		return buf;
+	}
+	return NULL;
+}
+
+/**
+ * tomoyo_realpath_nofollow - Get realpath of a pathname.
+ *
+ * @pathname: The pathname to solve.
+ *
+ * Returns the realpath of @pathname on success, NULL otherwise.
+ */
+char *tomoyo_realpath_nofollow(const char *pathname)
+{
+	struct nameidata nd;
+
+	if (pathname && path_lookup(pathname, 0, &nd) == 0) {
+		char *buf = tomoyo_realpath_from_path(&nd.path);
+		path_put(&nd.path);
+		return buf;
+	}
+	return NULL;
+}
+
+/* Memory allocated for non-string data. */
+static unsigned int tomoyo_allocated_memory_for_elements;
+/* Quota for holding non-string data. */
+static unsigned int tomoyo_quota_for_elements;
+
+/**
+ * tomoyo_alloc_element - Allocate permanent memory for structures.
+ *
+ * @size: Size in bytes.
+ *
+ * Returns pointer to allocated memory on success, NULL otherwise.
+ *
+ * Memory has to be zeroed.
+ * The RAM is chunked, so NEVER try to kfree() the returned pointer.
+ */
+void *tomoyo_alloc_element(const unsigned int size)
+{
+	static char *buf;
+	static DEFINE_MUTEX(lock);
+	static unsigned int buf_used_len = PATH_MAX;
+	char *ptr = NULL;
+	/*Assumes sizeof(void *) >= sizeof(long) is true. */
+	const unsigned int word_aligned_size
+		= roundup(size, max(sizeof(void *), sizeof(long)));
+	if (word_aligned_size > PATH_MAX)
+		return NULL;
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&lock);
+	if (buf_used_len + word_aligned_size > PATH_MAX) {
+		if (!tomoyo_quota_for_elements ||
+		    tomoyo_allocated_memory_for_elements
+		    + PATH_MAX <= tomoyo_quota_for_elements)
+			ptr = kzalloc(PATH_MAX, GFP_KERNEL);
+		if (!ptr) {
+			printk(KERN_WARNING "ERROR: Out of memory "
+			       "for tomoyo_alloc_element().\n");
+			if (!tomoyo_policy_loaded)
+				panic("MAC Initialization failed.\n");
+		} else {
+			buf = ptr;
+			tomoyo_allocated_memory_for_elements += PATH_MAX;
+			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 in bytes. */
+static unsigned int tomoyo_allocated_memory_for_savename;
+/* Quota for holding string data in bytes. */
+static unsigned int tomoyo_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 TOMOYO_MAX_HASH 256
+
+/* Structure for string data. */
+struct tomoyo_name_entry {
+	struct list1_head list;
+	struct tomoyo_path_info entry;
+};
+
+/* Structure for available memory region. */
+struct tomoyo_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 tomoyo_name_entry".
+ *
+ * This list is updated only inside tomoyo_save_name(), thus
+ * no global mutex exists.
+ */
+static struct list1_head tomoyo_name_list[TOMOYO_MAX_HASH];
+
+/**
+ * tomoyo_save_name - Allocate permanent memory for string data.
+ *
+ * @name: The string to store into the permernent memory.
+ *
+ * Returns pointer to "struct tomoyo_path_info" on success, NULL otherwise.
+ *
+ * The RAM is shared, so NEVER try to modify or kfree() the returned name.
+ */
+const struct tomoyo_path_info *tomoyo_save_name(const char *name)
+{
+	static LIST_HEAD(fmb_list);
+	static DEFINE_MUTEX(lock);
+	struct tomoyo_name_entry *ptr;
+	unsigned int hash;
+	/* fmb contains available size in bytes.
+	   fmb is removed from the fmb_list when fmb->len becomes 0. */
+	struct tomoyo_free_memory_block_list *fmb;
+	int len;
+	char *cp;
+
+	if (!name)
+		return NULL;
+	len = strlen(name) + 1;
+	if (len > TOMOYO_MAX_PATHNAME_LEN) {
+		printk(KERN_WARNING "ERROR: Name too long "
+		       "for tomoyo_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, &tomoyo_name_list[hash % TOMOYO_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 (!tomoyo_quota_for_savename ||
+	    tomoyo_allocated_memory_for_savename + PATH_MAX
+	    <= tomoyo_quota_for_savename)
+		cp = kzalloc(PATH_MAX, 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 tomoyo_save_name().\n");
+		if (!tomoyo_policy_loaded)
+			panic("MAC Initialization failed.\n");
+		ptr = NULL;
+		goto out;
+	}
+	tomoyo_allocated_memory_for_savename += PATH_MAX;
+	list_add(&fmb->list, &fmb_list);
+	fmb->ptr = cp;
+	fmb->len = PATH_MAX;
+ ready:
+	ptr = tomoyo_alloc_element(sizeof(*ptr));
+	if (!ptr)
+		goto out;
+	ptr->entry.name = fmb->ptr;
+	memmove(fmb->ptr, name, len);
+	tomoyo_fill_path_info(&ptr->entry);
+	fmb->ptr += len;
+	fmb->len -= len;
+	list1_add_tail(&ptr->list, &tomoyo_name_list[hash % TOMOYO_MAX_HASH]);
+	if (fmb->len == 0) {
+		list_del(&fmb->list);
+		kfree(fmb);
+	}
+ out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	return ptr ? &ptr->entry : NULL;
+}
+
+/**
+ * tomoyo_realpath_init - Initialize realpath related code.
+ *
+ * Returns 0.
+ */
+static int __init tomoyo_realpath_init(void)
+{
+	int i;
+
+	BUILD_BUG_ON(TOMOYO_MAX_PATHNAME_LEN > PATH_MAX);
+	for (i = 0; i < TOMOYO_MAX_HASH; i++)
+		INIT_LIST1_HEAD(&tomoyo_name_list[i]);
+	INIT_LIST1_HEAD(&tomoyo_kernel_domain.acl_info_list);
+	tomoyo_kernel_domain.domainname = tomoyo_save_name(TOMOYO_ROOT_NAME);
+	list1_add_tail(&tomoyo_kernel_domain.list, &tomoyo_domain_list);
+	if (tomoyo_find_domain(TOMOYO_ROOT_NAME) != &tomoyo_kernel_domain)
+		panic("Can't register tomoyo_kernel_domain");
+	return 0;
+}
+
+security_initcall(tomoyo_realpath_init);
+
+/* Memory allocated for temporary purpose. */
+static atomic_t tomoyo_dynamic_memory_size;
+
+/**
+ * tomoyo_alloc - Allocate memory for temporary purpose.
+ *
+ * @size: Size in bytes.
+ *
+ * Returns pointer to allocated memory on success, NULL otherwise.
+ */
+void *tomoyo_alloc(const size_t size)
+{
+	void *p = kzalloc(size, GFP_KERNEL);
+	if (p)
+		atomic_add(ksize(p), &tomoyo_dynamic_memory_size);
+	return p;
+}
+
+/**
+ * tomoyo_free - Release memory allocated by tomoyo_alloc().
+ *
+ * @p: Pointer returned by tomoyo_alloc(). May be NULL.
+ *
+ * Returns nothing.
+ */
+void tomoyo_free(const void *p)
+{
+	if (p) {
+		atomic_sub(ksize(p), &tomoyo_dynamic_memory_size);
+		kfree(p);
+	}
+}
+
+/**
+ * tomoyo_read_memory_counter - Check for memory usage in bytes.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns memory usage.
+ */
+int tomoyo_read_memory_counter(struct tomoyo_io_buffer *head)
+{
+	if (!head->read_eof) {
+		const unsigned int shared
+			= tomoyo_allocated_memory_for_savename;
+		const unsigned int private
+			= tomoyo_allocated_memory_for_elements;
+		const unsigned int dynamic
+			= atomic_read(&tomoyo_dynamic_memory_size);
+		char buffer[64];
+
+		memset(buffer, 0, sizeof(buffer));
+		if (tomoyo_quota_for_savename)
+			snprintf(buffer, sizeof(buffer) - 1,
+				 "   (Quota: %10u)",
+				 tomoyo_quota_for_savename);
+		else
+			buffer[0] = '\0';
+		tomoyo_io_printf(head, "Shared:  %10u%s\n", shared, buffer);
+		if (tomoyo_quota_for_elements)
+			snprintf(buffer, sizeof(buffer) - 1,
+				 "   (Quota: %10u)",
+				 tomoyo_quota_for_elements);
+		else
+			buffer[0] = '\0';
+		tomoyo_io_printf(head, "Private: %10u%s\n", private, buffer);
+		tomoyo_io_printf(head, "Dynamic: %10u\n", dynamic);
+		tomoyo_io_printf(head, "Total:   %10u\n",
+				 shared + private + dynamic);
+		head->read_eof = true;
+	}
+	return 0;
+}
+
+/**
+ * tomoyo_write_memory_quota - Set memory quota.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns 0.
+ */
+int tomoyo_write_memory_quota(struct tomoyo_io_buffer *head)
+{
+	char *data = head->write_buf;
+	unsigned int size;
+
+	if (sscanf(data, "Shared: %u", &size) == 1)
+		tomoyo_quota_for_savename = size;
+	else if (sscanf(data, "Private: %u", &size) == 1)
+		tomoyo_quota_for_elements = size;
+	return 0;
+}
--- /dev/null
+++ linux-2.6.28-rc5-mm1/security/tomoyo/realpath.h
@@ -0,0 +1,63 @@
+/*
+ * 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/11/11
+ *
+ */
+
+#ifndef _SECURITY_TOMOYO_REALPATH_H
+#define _SECURITY_TOMOYO_REALPATH_H
+
+struct path;
+struct tomoyo_path_info;
+struct tomoyo_io_buffer;
+
+/* Convert binary string to ascii string. */
+int tomoyo_encode(char *buffer, int buflen, const char *str);
+
+/* Returns realpath(3) of the given pathname but ignores chroot'ed root. */
+int tomoyo_realpath_from_path2(struct path *path, char *newname,
+			       int newname_len);
+
+/*
+ * Returns realpath(3) of the given pathname but ignores chroot'ed root.
+ * These functions use tomoyo_alloc(), so the caller must call tomoyo_free()
+ * if these functions didn't return NULL.
+ */
+char *tomoyo_realpath(const char *pathname);
+/*
+ * Same with tomoyo_realpath() except that it doesn't follow the final symlink.
+ */
+char *tomoyo_realpath_nofollow(const char *pathname);
+/* Same with tomoyo_realpath() except that the pathname is already solved. */
+char *tomoyo_realpath_from_path(struct path *path);
+
+/*
+ * Allocate memory for ACL entry.
+ * The RAM is chunked, so NEVER try to kfree() the returned pointer.
+ */
+void *tomoyo_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 tomoyo_path_info *tomoyo_save_name(const char *name);
+
+/* Allocate memory for temporary use (e.g. permission checks). */
+void *tomoyo_alloc(const size_t size);
+
+/* Free memory allocated by tomoyo_alloc(). */
+void tomoyo_free(const void *p);
+
+/* Check for memory usage. */
+int tomoyo_read_memory_counter(struct tomoyo_io_buffer *head);
+
+/* Set memory quota. */
+int tomoyo_write_memory_quota(struct tomoyo_io_buffer *head);
+
+#endif /* !defined(_SECURITY_TOMOYO_REALPATH_H) */

-- 

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

* [TOMOYO #13 (mmotm 2008-11-19-02-19) 06/11] Common functions for TOMOYO Linux.
  2008-11-20 11:25 [TOMOYO #13 (mmotm 2008-11-19-02-19) 00/11] TOMOYO Linux Tetsuo Handa
                   ` (4 preceding siblings ...)
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 05/11] Memory and pathname management functions Tetsuo Handa
@ 2008-11-20 11:25 ` Tetsuo Handa
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 07/11] File operation restriction part Tetsuo Handa
                   ` (5 subsequent siblings)
  11 siblings, 0 replies; 29+ messages in thread
From: Tetsuo Handa @ 2008-11-20 11:25 UTC (permalink / raw)
  To: Andrew Morton
  Cc: linux-security-module, linux-kernel, Kentaro Takeda, Tetsuo Handa,
	Toshiharu Harada

[-- Attachment #1: tomoyo-common.patch --]
[-- Type: text/plain, Size: 82797 bytes --]

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

----- About pattern matching -----

In TOMOYO, all wildcard characters are in the form of '\' + one character.
Many programs interpret a character with a backslash as literal character, but
TOMOYO interprets all characters without a backslash as literal characters.

Since the characters which are interpreted as wildcard are program dependent
(e.g. For shells, '*' and '?' are wildcards but '.' isn't. For sed, '.' is
a wildcard). Those who worry about some characters being interpreted as
wildcard characters will add a backslash to every characters
(e.g. \/\t\m\p\/\* ) without knowing whether '/' 't' 'm' 'p' '*' are wildcard
characters or not.
Not only this approach requires the interpreter to remove unneeded backslashes
but also this approach keeps introduction of new wildcard characters away.
For example, until yesterday, 'p' was a literal character and users had been
allowed to add a backslash before 'p'. But, from today, '\p' is now a wildcard
character and users are no longer allowed to add a backslash before 'p' if the
users want to represent literal 'p'.
If the interpreter forces users not to add a backslash to represent literal
character, users must know all wildcard characters beforehand.

On the contrary, TOMOYO's approach allows introduction of new wildcard
characters as needed, for users won't add a backslash to represent literal
characters. If the interpreter forces users to add a backslash to represent
wildcard character, users needn't to know all wildcard characters beforehand.
Users can learn the meaning of new wildcard characters on first encounter.


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.

To speeds up string comparison, TOMOYO hashes strings using "depth" (number of
'/' characters). This is calculated by tomoyo_path_depth() and used by
tomoyo_path_matches_pattern().

To be able to use strncmp() where possible, TOMOYO remembers initial length of
strings without wildcard characters. This is calculated by
tomoyo_const_part_length().

To be able to use strcmp() where possible, TOMOYO remembers whether strings
contains wildcard character or not.

----- About policy interface -----

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.

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 | 2160 +++++++++++++++++++++++++++++++++++++++++++++++
 security/tomoyo/common.h |  337 +++++++
 2 files changed, 2497 insertions(+)

--- /dev/null
+++ linux-2.6.28-rc5-mm1/security/tomoyo/common.c
@@ -0,0 +1,2160 @@
+/*
+ * security/tomoyo/common.c
+ *
+ * Common functions for TOMOYO.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 2.2.0-pre   2008/11/11
+ *
+ */
+
+#include <linux/uaccess.h>
+#include <linux/security.h>
+#include <linux/hardirq.h>
+#include "realpath.h"
+#include "common.h"
+#include "tomoyo.h"
+
+/* Has loading policy done? */
+bool tomoyo_policy_loaded;
+
+/* String table for functionality that takes 4 modes. */
+static const char *tomoyo_mode_4[4] = {
+	"disabled", "learning", "permissive", "enforcing"
+};
+/* String table for functionality that takes 2 modes. */
+static const char *tomoyo_mode_2[4] = {
+	"disabled", "enabled", "enabled", "enabled"
+};
+
+/* Table for profile. */
+static struct {
+	const char *keyword;
+	unsigned int current_value;
+	const unsigned int max_value;
+} tomoyo_control_array[TOMOYO_MAX_CONTROL_INDEX] = {
+	[TOMOYO_MAC_FOR_FILE]     = { "MAC_FOR_FILE",        0,       3 },
+	[TOMOYO_MAX_ACCEPT_ENTRY] = { "MAX_ACCEPT_ENTRY", 2048, INT_MAX },
+	[TOMOYO_VERBOSE]          = { "TOMOYO_VERBOSE",      1,       1 },
+};
+
+/* Profile table. Memory is allocated as needed. */
+static struct tomoyo_profile {
+	unsigned int value[TOMOYO_MAX_CONTROL_INDEX];
+	const struct tomoyo_path_info *comment;
+} *tomoyo_profile_ptr[TOMOYO_MAX_PROFILES];
+
+/* Permit policy management by non-root user? */
+static bool tomoyo_manage_by_non_root;
+
+/* Utility functions. */
+
+/* Open operation for /sys/kernel/security/tomoyo/ interface. */
+static int tomoyo_open_control(const u8 type, struct file *file);
+/* Close /sys/kernel/security/tomoyo/ interface. */
+static int tomoyo_close_control(struct file *file);
+/* Read operation for /sys/kernel/security/tomoyo/ interface. */
+static int tomoyo_read_control(struct file *file, char __user *buffer,
+			       const int buffer_len);
+/* Write operation for /sys/kernel/security/tomoyo/ interface. */
+static int tomoyo_write_control(struct file *file, const char __user *buffer,
+				const int buffer_len);
+
+/**
+ * tomoyo_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.
+ *
+ * TOMOYO uses \ooo style representation for 0x01 - 0x20 and 0x7F - 0xFF.
+ * This function verifies that \ooo is in valid range.
+ */
+static inline bool tomoyo_is_byte_range(const char *str)
+{
+	return *str >= '0' && *str++ <= '3' &&
+		*str >= '0' && *str++ <= '7' &&
+		*str >= '0' && *str <= '7';
+}
+
+/**
+ * tomoyo_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 inline bool tomoyo_is_alphabet_char(const char c)
+{
+	return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
+}
+
+/**
+ * tomoyo_make_byte - Make byte value from three octal characters.
+ *
+ * @c1: The first character.
+ * @c2: The second character.
+ * @c3: The third character.
+ *
+ * Returns byte value.
+ */
+static inline u8 tomoyo_make_byte(const u8 c1, const u8 c2, const u8 c3)
+{
+	return ((c1 - '0') << 6) + ((c2 - '0') << 3) + (c3 - '0');
+}
+
+/**
+ * tomoyo_str_starts - Check whether the given string starts with the given keyword.
+ *
+ * @src:  Pointer to pointer to the string.
+ * @find: Pointer to the keyword.
+ *
+ * Returns true if @src starts with @find, false otherwise.
+ *
+ * The @src is updated to point the first character after the @find
+ * if @src starts with @find.
+ */
+static bool tomoyo_str_starts(char **src, const char *find)
+{
+	const int len = strlen(find);
+	char *tmp = *src;
+
+	if (strncmp(tmp, find, len))
+		return false;
+	tmp += len;
+	*src = tmp;
+	return true;
+}
+
+/**
+ * tomoyo_normalize_line - Format string.
+ *
+ * @buffer: The line to normalize.
+ *
+ * Leading and trailing whitespaces are removed.
+ * Multiple whitespaces are packed into single space.
+ *
+ * Returns nothing.
+ */
+static void tomoyo_normalize_line(unsigned char *buffer)
+{
+	unsigned char *sp = buffer;
+	unsigned char *dp = buffer;
+	bool first = true;
+
+	while (tomoyo_is_invalid(*sp))
+		sp++;
+	while (*sp) {
+		if (!first)
+			*dp++ = ' ';
+		first = false;
+		while (tomoyo_is_valid(*sp))
+			*dp++ = *sp++;
+		while (tomoyo_is_invalid(*sp))
+			sp++;
+	}
+	*dp = '\0';
+}
+
+/**
+ * tomoyo_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 tomoyo_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 = *(filename + strlen(filename) - 1);
+	if (end_type == 1) { /* Must end with '/' */
+		if (c != '/')
+			goto out;
+	} else if (end_type == -1) { /* Must not end with '/' */
+		if (c == '/')
+			goto out;
+	}
+	while ((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 = tomoyo_make_byte(c, d, e);
+				if (tomoyo_is_invalid(c))
+					continue; /* pattern is not \000 */
+			}
+			goto out;
+		} else if (tomoyo_is_invalid(c)) {
+			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;
+}
+
+/**
+ * tomoyo_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 tomoyo_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, TOMOYO_ROOT_NAME,
+				   TOMOYO_ROOT_NAME_LEN))
+		goto out;
+	domainname += TOMOYO_ROOT_NAME_LEN;
+	if (!*domainname)
+		return true;
+	do {
+		if (*domainname++ != ' ')
+			goto out;
+		if (*domainname++ != '/')
+			goto out;
+		while ((c = *domainname) != '\0' && c != ' ') {
+			domainname++;
+			if (c == '\\') {
+				c = *domainname++;
+				switch ((c)) {
+				case '\\':  /* "\\" */
+					continue;
+				case '0':   /* "\ooo" */
+				case '1':
+				case '2':
+				case '3':
+					d = *domainname++;
+					if (d < '0' || d > '7')
+						break;
+					e = *domainname++;
+					if (e < '0' || e > '7')
+						break;
+					c = tomoyo_make_byte(c, d, e);
+					if (tomoyo_is_invalid(c))
+						/* pattern is not \000 */
+						continue;
+				}
+				goto out;
+			} else if (tomoyo_is_invalid(c)) {
+				goto out;
+			}
+		}
+	} while (*domainname);
+	return true;
+ out:
+	printk(KERN_DEBUG "%s: Invalid domainname '%s'\n", function,
+	       org_domainname);
+	return false;
+}
+
+/**
+ * tomoyo_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 tomoyo_is_domain_def(const unsigned char *buffer)
+{
+	return !strncmp(buffer, TOMOYO_ROOT_NAME, TOMOYO_ROOT_NAME_LEN);
+}
+
+/**
+ * tomoyo_find_domain - Find a domain by the given name.
+ *
+ * @domainname: The domainname to find.
+ *
+ * Returns pointer to "struct tomoyo_domain_info" if found, NULL otherwise.
+ */
+struct tomoyo_domain_info *tomoyo_find_domain(const char *domainname)
+{
+	struct tomoyo_domain_info *domain;
+	struct tomoyo_path_info name;
+
+	name.name = domainname;
+	tomoyo_fill_path_info(&name);
+	list1_for_each_entry(domain, &tomoyo_domain_list, list) {
+		if (!domain->is_deleted &&
+		    !tomoyo_pathcmp(&name, domain->domainname))
+			return domain;
+	}
+	return NULL;
+}
+
+/**
+ * tomoyo_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 tomoyo_path_depth(const char *pathname)
+{
+	int i = 0;
+
+	if (pathname) {
+		const char *ep = pathname + strlen(pathname);
+		if (pathname < ep--) {
+			if (*ep != '/')
+				i++;
+			while (pathname <= ep)
+				if (*ep-- == '/')
+					i += 2;
+		}
+	}
+	return i;
+}
+
+/**
+ * tomoyo_const_part_length - Evaluate the initial length without a pattern in a token.
+ *
+ * @filename: The string to evaluate.
+ *
+ * Returns the initial length without a pattern in @filename.
+ */
+static int tomoyo_const_part_length(const char *filename)
+{
+	char c;
+	int len = 0;
+
+	if (!filename)
+		return 0;
+	while ((c = *filename++) != '\0') {
+		if (c != '\\') {
+			len++;
+			continue;
+		}
+		c = *filename++;
+		switch (c) {
+		case '\\':  /* "\\" */
+			len += 2;
+			continue;
+		case '0':   /* "\ooo" */
+		case '1':
+		case '2':
+		case '3':
+			c = *filename++;
+			if (c < '0' || c > '7')
+				break;
+			c = *filename++;
+			if (c < '0' || c > '7')
+				break;
+			len += 4;
+			continue;
+		}
+		break;
+	}
+	return len;
+}
+
+/**
+ * tomoyo_fill_path_info - Fill in "struct tomoyo_path_info" members.
+ *
+ * @ptr: Pointer to "struct tomoyo_path_info" to fill in.
+ *
+ * The caller sets "struct tomoyo_path_info"->name.
+ */
+void tomoyo_fill_path_info(struct tomoyo_path_info *ptr)
+{
+	const char *name = ptr->name;
+	const int len = strlen(name);
+
+	ptr->total_len = len;
+	ptr->const_len = tomoyo_const_part_length(name);
+	ptr->is_dir = len && (name[len - 1] == '/');
+	ptr->is_patterned = (ptr->const_len < len);
+	ptr->hash = full_name_hash(name, len);
+	ptr->depth = tomoyo_path_depth(name);
+}
+
+/**
+ * tomoyo_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 tomoyo_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 (tomoyo_is_byte_range(filename + 1))
+					filename += 3;
+				else
+					return false;
+			}
+			break;
+		case '\\':
+			if (c != '\\')
+				return false;
+			if (*++filename != '\\')
+				return false;
+			break;
+		case '+':
+			if (!isdigit(c))
+				return false;
+			break;
+		case 'x':
+			if (!isxdigit(c))
+				return false;
+			break;
+		case 'a':
+			if (!tomoyo_is_alphabet_char(c))
+				return false;
+			break;
+		case '0':
+		case '1':
+		case '2':
+		case '3':
+			if (c == '\\' && tomoyo_is_byte_range(filename + 1)
+			    && strncmp(filename + 1, pattern, 3) == 0) {
+				filename += 3;
+				pattern += 2;
+				break;
+			}
+			return false; /* Not matched. */
+		case '*':
+		case '@':
+			for (i = 0; i <= filename_end - filename; i++) {
+				if (tomoyo_file_matches_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 (tomoyo_is_byte_range(filename + i + 1))
+					i += 3;
+				else
+					break; /* Bad pattern. */
+			}
+			return false; /* Not matched. */
+		default:
+			j = 0;
+			c = *pattern;
+			if (c == '$') {
+				while (isdigit(filename[j]))
+					j++;
+			} else if (c == 'X') {
+				while (isxdigit(filename[j]))
+					j++;
+			} else if (c == 'A') {
+				while (tomoyo_is_alphabet_char(filename[j]))
+					j++;
+			}
+			for (i = 1; i <= j; i++) {
+				if (tomoyo_file_matches_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;
+}
+
+/**
+ * tomoyo_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 tomoyo_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 = tomoyo_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 = tomoyo_file_matches_to_pattern2(filename, filename_end,
+						 pattern_start, pattern_end);
+	return first ? result : !result;
+}
+
+/**
+ * tomoyo_path_matches_pattern - Check whether the given filename matches the given pattern.
+ * @filename: The filename to check.
+ * @pattern:  The pattern to compare.
+ *
+ * Returns true if matches, false otherwise.
+ *
+ * The following patterns are available.
+ *   \\     \ itself.
+ *   \ooo   Octal representation of a byte.
+ *   \*     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 tomoyo_path_matches_pattern(const struct tomoyo_path_info *filename,
+				 const struct tomoyo_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 !tomoyo_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 = f + strlen(f);
+		if (!p_delimiter)
+			p_delimiter = p + strlen(p);
+		if (!tomoyo_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;
+}
+
+/**
+ * tomoyo_io_printf - Transactional printf() to "struct tomoyo_io_buffer" structure.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ * @fmt:  The printf()'s format string, followed by parameters.
+ *
+ * Returns true if output was written, false otherwise.
+ *
+ * The snprintf() will truncate, but tomoyo_io_printf() won't.
+ */
+bool tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt, ...)
+{
+	va_list args;
+	int len;
+	int pos = head->read_avail;
+	int size = head->readbuf_size - pos;
+
+	if (size <= 0)
+		return false;
+	va_start(args, fmt);
+	len = vsnprintf(head->read_buf + pos, size, fmt, args);
+	va_end(args);
+	if (pos + len >= head->readbuf_size)
+		return false;
+	head->read_avail += len;
+	return true;
+}
+
+/**
+ * tomoyo_get_exe - Get tomoyo_realpath() of current process.
+ *
+ * Returns the tomoyo_realpath() of current process on success, NULL otherwise.
+ *
+ * This function uses tomoyo_alloc(), so the caller must call tomoyo_free()
+ * if this function didn't return NULL.
+ */
+static const char *tomoyo_get_exe(void)
+{
+	struct mm_struct *mm = current->mm;
+	struct vm_area_struct *vma;
+	const char *cp = NULL;
+
+	if (!mm)
+		return NULL;
+	down_read(&mm->mmap_sem);
+	for (vma = mm->mmap; vma; vma = vma->vm_next) {
+		if ((vma->vm_flags & VM_EXECUTABLE) && vma->vm_file) {
+			cp = tomoyo_realpath_from_path(&vma->vm_file->f_path);
+			break;
+		}
+	}
+	up_read(&mm->mmap_sem);
+	return cp;
+}
+
+/**
+ * tomoyo_get_msg - Get warning message.
+ *
+ * @is_enforce: Is it enforcing mode?
+ *
+ * Returns "ERROR" or "WARNING".
+ */
+const char *tomoyo_get_msg(const bool is_enforce)
+{
+	if (is_enforce)
+		return "ERROR";
+	else
+		return "WARNING";
+}
+
+/**
+ * tomoyo_check_flags - Check mode for specified functionality.
+ *
+ * @domain: Pointer to "struct tomoyo_domain_info".
+ * @index:  The functionality to check mode.
+ *
+ * TOMOYO checks only process context.
+ * This code disables TOMOYO's enforcement in case the function is called from
+ * interrupt context.
+ */
+unsigned int tomoyo_check_flags(const struct tomoyo_domain_info *domain,
+				const u8 index)
+{
+	const u8 profile = domain->profile;
+
+	if (WARN_ON(in_interrupt()))
+		return 0;
+	return tomoyo_policy_loaded && index < TOMOYO_MAX_CONTROL_INDEX
+#if TOMOYO_MAX_PROFILES != 256
+		&& profile < TOMOYO_MAX_PROFILES
+#endif
+		&& tomoyo_profile_ptr[profile] ?
+		tomoyo_profile_ptr[profile]->value[index] : 0;
+}
+
+/**
+ * tomoyo_verbose_mode - Check whether TOMOYO is verbose mode.
+ *
+ * @domain: Pointer to "struct tomoyo_domain_info".
+ *
+ * Returns true if domain policy violation warning should be printed to
+ * console.
+ */
+bool tomoyo_verbose_mode(const struct tomoyo_domain_info *domain)
+{
+	return tomoyo_check_flags(domain, TOMOYO_VERBOSE) != 0;
+}
+
+/**
+ * tomoyo_domain_quota_is_ok - Check for domain's quota.
+ *
+ * @domain: Pointer to "struct tomoyo_domain_info".
+ *
+ * Returns true if the domain is not exceeded quota, false otherwise.
+ */
+bool tomoyo_domain_quota_is_ok(struct tomoyo_domain_info * const domain)
+{
+	unsigned int count = 0;
+	struct tomoyo_acl_info *ptr;
+
+	if (!domain)
+		return true;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (ptr->type & TOMOYO_ACL_DELETED)
+			continue;
+		switch (tomoyo_acl_type2(ptr)) {
+			struct tomoyo_single_path_acl_record *acl1;
+			struct tomoyo_double_path_acl_record *acl2;
+			u16 perm;
+		case TOMOYO_TYPE_SINGLE_PATH_ACL:
+			acl1 = container_of(ptr,
+				    struct tomoyo_single_path_acl_record,
+					    head);
+			perm = acl1->perm;
+			if (perm & (1 << TOMOYO_TYPE_EXECUTE_ACL))
+				count++;
+			if (perm &
+			    ((1 << TOMOYO_TYPE_READ_ACL) |
+			     (1 << TOMOYO_TYPE_WRITE_ACL)))
+				count++;
+			if (perm & (1 << TOMOYO_TYPE_CREATE_ACL))
+				count++;
+			if (perm & (1 << TOMOYO_TYPE_UNLINK_ACL))
+				count++;
+			if (perm & (1 << TOMOYO_TYPE_MKDIR_ACL))
+				count++;
+			if (perm & (1 << TOMOYO_TYPE_RMDIR_ACL))
+				count++;
+			if (perm & (1 << TOMOYO_TYPE_MKFIFO_ACL))
+				count++;
+			if (perm & (1 << TOMOYO_TYPE_MKSOCK_ACL))
+				count++;
+			if (perm & (1 << TOMOYO_TYPE_MKBLOCK_ACL))
+				count++;
+			if (perm & (1 << TOMOYO_TYPE_MKCHAR_ACL))
+				count++;
+			if (perm & (1 << TOMOYO_TYPE_TRUNCATE_ACL))
+				count++;
+			if (perm & (1 << TOMOYO_TYPE_SYMLINK_ACL))
+				count++;
+			if (perm & (1 << TOMOYO_TYPE_REWRITE_ACL))
+				count++;
+			break;
+		case TOMOYO_TYPE_DOUBLE_PATH_ACL:
+			acl2 = container_of(ptr,
+				    struct tomoyo_double_path_acl_record,
+					    head);
+			perm = acl2->perm;
+			if (perm & (1 << TOMOYO_TYPE_LINK_ACL))
+				count++;
+			if (perm & (1 << TOMOYO_TYPE_RENAME_ACL))
+				count++;
+			break;
+		}
+	}
+	if (count < tomoyo_check_flags(domain, TOMOYO_MAX_ACCEPT_ENTRY))
+		return true;
+	if (!domain->quota_warned) {
+		domain->quota_warned = true;
+		printk(KERN_WARNING "TOMOYO-WARNING: "
+		       "Domain '%s' has so many ACLs to hold. "
+		       "Stopped learning mode.\n", domain->domainname->name);
+	}
+	return false;
+}
+
+/**
+ * tomoyo_find_or_assign_new_profile - Create a new profile.
+ *
+ * @profile: Profile number to create.
+ *
+ * Returns pointer to "struct tomoyo_profile" on success, NULL otherwise.
+ */
+static struct tomoyo_profile *tomoyo_find_or_assign_new_profile(const unsigned
+								int profile)
+{
+	static DEFINE_MUTEX(lock);
+	struct tomoyo_profile *ptr = NULL;
+	int i;
+
+	if (profile >= TOMOYO_MAX_PROFILES)
+		return NULL;
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&lock);
+	ptr = tomoyo_profile_ptr[profile];
+	if (ptr)
+		goto ok;
+	ptr = tomoyo_alloc_element(sizeof(*ptr));
+	if (!ptr)
+		goto ok;
+	for (i = 0; i < TOMOYO_MAX_CONTROL_INDEX; i++)
+		ptr->value[i] = tomoyo_control_array[i].current_value;
+	mb(); /* Avoid out-of-order execution. */
+	tomoyo_profile_ptr[profile] = ptr;
+ ok:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	return ptr;
+}
+
+/**
+ * tomoyo_write_profile - Write to profile table.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_write_profile(struct tomoyo_io_buffer *head)
+{
+	char *data = head->write_buf;
+	unsigned int i;
+	unsigned int value;
+	char *cp;
+	struct tomoyo_profile *profile;
+	unsigned long num;
+
+	cp = strchr(data, '-');
+	if (cp)
+		*cp = '\0';
+	if (strict_strtoul(data, 10, &num))
+		return -EINVAL;
+	if (cp)
+		data = cp + 1;
+	profile = tomoyo_find_or_assign_new_profile(num);
+	if (!profile)
+		return -EINVAL;
+	cp = strchr(data, '=');
+	if (!cp)
+		return -EINVAL;
+	*cp = '\0';
+	if (!strcmp(data, "COMMENT")) {
+		profile->comment = tomoyo_save_name(cp + 1);
+		return 0;
+	}
+	for (i = 0; i < TOMOYO_MAX_CONTROL_INDEX; i++) {
+		if (strcmp(data, tomoyo_control_array[i].keyword))
+			continue;
+		if (sscanf(cp + 1, "%u", &value) != 1) {
+			int j;
+			const char **modes;
+			switch (i) {
+			case TOMOYO_VERBOSE:
+				modes = tomoyo_mode_2;
+				break;
+			default:
+				modes = tomoyo_mode_4;
+				break;
+			}
+			for (j = 0; j < 4; j++) {
+				if (strcmp(cp + 1, modes[j]))
+					continue;
+				value = j;
+				break;
+			}
+			if (j == 4)
+				return -EINVAL;
+		} else if (value > tomoyo_control_array[i].max_value) {
+			value = tomoyo_control_array[i].max_value;
+		}
+		profile->value[i] = value;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+/**
+ * tomoyo_read_profile - Read from profile table.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns 0.
+ */
+static int tomoyo_read_profile(struct tomoyo_io_buffer *head)
+{
+	static const int total = TOMOYO_MAX_CONTROL_INDEX + 1;
+	int step;
+
+	if (head->read_eof)
+		return 0;
+	for (step = head->read_step; step < TOMOYO_MAX_PROFILES * total;
+	     step++) {
+		const u8 index = step / total;
+		u8 type = step % total;
+		const struct tomoyo_profile *profile
+			= tomoyo_profile_ptr[index];
+		head->read_step = step;
+		if (!profile)
+			continue;
+		if (!type) { /* Print profile' comment tag. */
+			if (!tomoyo_io_printf(head, "%u-COMMENT=%s\n",
+					      index, profile->comment ?
+					      profile->comment->name : ""))
+				break;
+			continue;
+		}
+		type--;
+		if (type < TOMOYO_MAX_CONTROL_INDEX) {
+			const unsigned int value = profile->value[type];
+			const char **modes = NULL;
+			const char *keyword
+				= tomoyo_control_array[type].keyword;
+			switch (tomoyo_control_array[type].max_value) {
+			case 3:
+				modes = tomoyo_mode_4;
+				break;
+			case 1:
+				modes = tomoyo_mode_2;
+				break;
+			}
+			if (modes) {
+				if (!tomoyo_io_printf(head, "%u-%s=%s\n", index,
+						      keyword, modes[value]))
+					break;
+			} else {
+				if (!tomoyo_io_printf(head, "%u-%s=%u\n", index,
+						      keyword, value))
+					break;
+			}
+		}
+	}
+	if (step == TOMOYO_MAX_PROFILES * total)
+		head->read_eof = true;
+	return 0;
+}
+
+/* Structure for policy manager. */
+struct tomoyo_policy_manager_entry {
+	struct list1_head list;
+	/* A path to program or a domainname. */
+	const struct tomoyo_path_info *manager;
+	bool is_domain;  /* True if manager is a domainname. */
+	bool is_deleted; /* True if this entry is deleted. */
+};
+
+/*
+ * The list for "struct tomoyo_policy_manager_entry".
+ *
+ * This list is updated only inside update_manager_entry(), thus
+ * no global mutex exists.
+ */
+static LIST1_HEAD(tomoyo_policy_manager_list);
+
+/**
+ * tomoyo_update_manager_entry - Add a manager entry.
+ *
+ * @manager:   The path to manager or the domainnamme.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_update_manager_entry(const char *manager,
+				       const bool is_delete)
+{
+	struct tomoyo_policy_manager_entry *new_entry;
+	struct tomoyo_policy_manager_entry *ptr;
+	static DEFINE_MUTEX(lock);
+	const struct tomoyo_path_info *saved_manager;
+	int error = -ENOMEM;
+	bool is_domain = false;
+
+	if (tomoyo_is_domain_def(manager)) {
+		if (!tomoyo_is_correct_domain(manager, __func__))
+			return -EINVAL;
+		is_domain = true;
+	} else {
+		if (!tomoyo_is_correct_path(manager, 1, -1, -1, __func__))
+			return -EINVAL;
+	}
+	saved_manager = tomoyo_save_name(manager);
+	if (!saved_manager)
+		return -ENOMEM;
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &tomoyo_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 = tomoyo_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, &tomoyo_policy_manager_list);
+	error = 0;
+ out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	return error;
+}
+
+/**
+ * tomoyo_write_manager_policy - Write manager policy.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_write_manager_policy(struct tomoyo_io_buffer *head)
+{
+	char *data = head->write_buf;
+	bool is_delete = tomoyo_str_starts(&data, TOMOYO_KEYWORD_DELETE);
+
+	if (!strcmp(data, "manage_by_non_root")) {
+		tomoyo_manage_by_non_root = !is_delete;
+		return 0;
+	}
+	return tomoyo_update_manager_entry(data, is_delete);
+}
+
+/**
+ * tomoyo_read_manager_policy - Read manager policy.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns 0.
+ */
+static int tomoyo_read_manager_policy(struct tomoyo_io_buffer *head)
+{
+	struct list1_head *pos;
+
+	if (head->read_eof)
+		return 0;
+	list1_for_each_cookie(pos, head->read_var2,
+			      &tomoyo_policy_manager_list) {
+		struct tomoyo_policy_manager_entry *ptr;
+		ptr = list1_entry(pos, struct tomoyo_policy_manager_entry,
+				  list);
+		if (ptr->is_deleted)
+			continue;
+		if (!tomoyo_io_printf(head, "%s\n", ptr->manager->name))
+			return 0;
+	}
+	head->read_eof = true;
+	return 0;
+}
+
+/**
+ * tomoyo_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 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;
+	list1_for_each_entry(ptr, &tomoyo_policy_manager_list, list) {
+		if (!ptr->is_deleted && ptr->is_domain
+		    && !tomoyo_pathcmp(domainname, ptr->manager))
+			return true;
+	}
+	exe = tomoyo_get_exe();
+	if (!exe)
+		return false;
+	list1_for_each_entry(ptr, &tomoyo_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;
+		}
+	}
+	tomoyo_free(exe);
+	return found;
+}
+
+/**
+ * tomoyo_is_select_one - Parse select command.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ * @data: String to parse.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool tomoyo_is_select_one(struct tomoyo_io_buffer *head,
+				 const char *data)
+{
+	unsigned int pid;
+	struct tomoyo_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 = tomoyo_real_domain(p);
+		read_unlock(&tasklist_lock);
+		/***** CRITICAL SECTION END *****/
+	} else if (!strncmp(data, "domain=", 7)) {
+		if (tomoyo_is_domain_def(data + 7))
+			domain = tomoyo_find_domain(data + 7);
+	} else
+		return false;
+	head->write_var1 = domain;
+	/* Accessing read_buf is safe because head->io_sem is held. */
+	if (!head->read_buf)
+		return true; /* Do nothing if open(O_WRONLY). */
+	head->read_avail = 0;
+	tomoyo_io_printf(head, "# select %s\n", data);
+	head->read_single_domain = true;
+	head->read_eof = !domain;
+	if (domain) {
+		struct tomoyo_domain_info *d;
+		head->read_var1 = NULL;
+		list1_for_each_entry(d, &tomoyo_domain_list, list) {
+			if (d == domain)
+				break;
+			head->read_var1 = &d->list;
+		}
+		head->read_var2 = NULL;
+		head->read_bit = 0;
+		head->read_step = 0;
+		if (domain->is_deleted)
+			tomoyo_io_printf(head, "# This is a deleted domain.\n");
+	}
+	return true;
+}
+
+/**
+ * tomoyo_write_domain_policy - Write domain policy.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_write_domain_policy(struct tomoyo_io_buffer *head)
+{
+	char *data = head->write_buf;
+	struct tomoyo_domain_info *domain = head->write_var1;
+	bool is_delete = false;
+	bool is_select = false;
+	bool is_undelete = false;
+	unsigned int profile;
+
+	if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_DELETE))
+		is_delete = true;
+	else if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_SELECT))
+		is_select = true;
+	else if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_UNDELETE))
+		is_undelete = true;
+	if (is_select && tomoyo_is_select_one(head, data))
+		return 0;
+	/* Don't allow updating policies by non manager programs. */
+	if (!tomoyo_is_policy_manager())
+		return -EPERM;
+	if (tomoyo_is_domain_def(data)) {
+		domain = NULL;
+		if (is_delete)
+			tomoyo_delete_domain(data);
+		else if (is_select)
+			domain = tomoyo_find_domain(data);
+		else if (is_undelete)
+			domain = tomoyo_undelete_domain(data);
+		else
+			domain = tomoyo_find_or_assign_new_domain(data, 0);
+		head->write_var1 = domain;
+		return 0;
+	}
+	if (!domain)
+		return -EINVAL;
+
+	if (sscanf(data, TOMOYO_KEYWORD_USE_PROFILE "%u", &profile) == 1
+	    && profile < TOMOYO_MAX_PROFILES) {
+		if (tomoyo_profile_ptr[profile] || !tomoyo_policy_loaded)
+			domain->profile = (u8) profile;
+		return 0;
+	}
+	if (!strcmp(data, TOMOYO_KEYWORD_IGNORE_GLOBAL_ALLOW_READ)) {
+		tomoyo_set_domain_flag(domain, is_delete,
+			       TOMOYO_DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ);
+		return 0;
+	}
+	return tomoyo_write_file_policy(data, domain, is_delete);
+}
+
+/**
+ * tomoyo_print_single_path_acl - Print a single path ACL entry.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ * @ptr:  Pointer to "struct tomoyo_single_path_acl_record".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool tomoyo_print_single_path_acl(struct tomoyo_io_buffer *head,
+					 struct tomoyo_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 < TOMOYO_MAX_SINGLE_PATH_OPERATION;
+	     bit++) {
+		const char *msg;
+		if (!(perm & (1 << bit)))
+			continue;
+		/* Print "read/write" instead of "read" and "write". */
+		if ((bit == TOMOYO_TYPE_READ_ACL ||
+		     bit == TOMOYO_TYPE_WRITE_ACL)
+		    && (perm & (1 << TOMOYO_TYPE_READ_WRITE_ACL)))
+			continue;
+		msg = tomoyo_sp2keyword(bit);
+		pos = head->read_avail;
+		if (!tomoyo_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;
+}
+
+/**
+ * tomoyo_print_double_path_acl - Print a double path ACL entry.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ * @ptr:  Pointer to "struct tomoyo_double_path_acl_record".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool tomoyo_print_double_path_acl(struct tomoyo_io_buffer *head,
+					 struct tomoyo_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 < TOMOYO_MAX_DOUBLE_PATH_OPERATION;
+	     bit++) {
+		const char *msg;
+		if (!(perm & (1 << bit)))
+			continue;
+		msg = tomoyo_dp2keyword(bit);
+		pos = head->read_avail;
+		if (!tomoyo_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;
+}
+
+/**
+ * tomoyo_print_entry - Print an ACL entry.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ * @ptr:  Pointer to an ACL entry.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool tomoyo_print_entry(struct tomoyo_io_buffer *head,
+			       struct tomoyo_acl_info *ptr)
+{
+	const u8 acl_type = tomoyo_acl_type2(ptr);
+
+	if (acl_type & TOMOYO_ACL_DELETED)
+		return true;
+	if (acl_type == TOMOYO_TYPE_SINGLE_PATH_ACL) {
+		struct tomoyo_single_path_acl_record *acl
+			= container_of(ptr,
+				       struct tomoyo_single_path_acl_record,
+				       head);
+		return tomoyo_print_single_path_acl(head, acl);
+	}
+	if (acl_type == TOMOYO_TYPE_DOUBLE_PATH_ACL) {
+		struct tomoyo_double_path_acl_record *acl
+			= container_of(ptr,
+				       struct tomoyo_double_path_acl_record,
+				       head);
+		return tomoyo_print_double_path_acl(head, acl);
+	}
+	BUG(); /* This must not happen. */
+	return false;
+}
+
+/**
+ * tomoyo_read_domain_policy - Read domain policy.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns 0.
+ */
+static int tomoyo_read_domain_policy(struct tomoyo_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, &tomoyo_domain_list) {
+		struct tomoyo_domain_info *domain;
+		const char *quota_exceeded = "";
+		const char *transition_failed = "";
+		const char *ignore_global_allow_read = "";
+		domain = list1_entry(dpos, struct tomoyo_domain_info, list);
+		if (head->read_step != 1)
+			goto acl_loop;
+		if (domain->is_deleted && !head->read_single_domain)
+			continue;
+		/* Print domainname and flags. */
+		if (domain->quota_warned)
+			quota_exceeded = "quota_exceeded\n";
+		if (domain->flags & TOMOYO_DOMAIN_FLAGS_TRANSITION_FAILED)
+			transition_failed = "transition_failed\n";
+		if (domain->flags &
+		    TOMOYO_DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ)
+			ignore_global_allow_read
+				= TOMOYO_KEYWORD_IGNORE_GLOBAL_ALLOW_READ "\n";
+		if (!tomoyo_io_printf(head,
+				      "%s\n" TOMOYO_KEYWORD_USE_PROFILE "%u\n"
+				      "%s%s%s\n", domain->domainname->name,
+				      domain->profile, quota_exceeded,
+				      transition_failed,
+				      ignore_global_allow_read))
+			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 tomoyo_acl_info *ptr
+				= list1_entry(apos, struct tomoyo_acl_info,
+					      list);
+			if (!tomoyo_print_entry(head, ptr))
+				return 0;
+		}
+		head->read_step = 3;
+tail_mark:
+		if (!tomoyo_io_printf(head, "\n"))
+			return 0;
+		head->read_step = 1;
+		if (head->read_single_domain)
+			break;
+	}
+	head->read_eof = true;
+	return 0;
+}
+
+/**
+ *tomoyo_write_domain_profile - Assign profile for specified domain.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns 0 on success, -EINVAL otherwise.
+ *
+ * This is equivalent to doing
+ *
+ *     ( echo "select " $domainname; echo "use_profile " $profile ) |
+ *     /usr/lib/ccs/loadpolicy -d
+ */
+static int tomoyo_write_domain_profile(struct tomoyo_io_buffer *head)
+{
+	char *data = head->write_buf;
+	char *cp = strchr(data, ' ');
+	struct tomoyo_domain_info *domain;
+	unsigned long profile;
+
+	if (!cp)
+		return -EINVAL;
+	*cp = '\0';
+	domain = tomoyo_find_domain(cp + 1);
+	if (strict_strtoul(data, 10, &profile))
+		return -EINVAL;
+	if (domain && profile < TOMOYO_MAX_PROFILES
+	    && (tomoyo_profile_ptr[profile] || !tomoyo_policy_loaded))
+		domain->profile = (u8) profile;
+	return 0;
+}
+
+/**
+ * tomoyo_read_domain_profile - Read only domainname and profile.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns list of profile number and domainname pairs.
+ *
+ * This is equivalent to doing
+ *
+ *     grep -A 1 '^<kernel>' /sys/kernel/security/tomoyo/domain_policy |
+ *     awk ' { if ( domainname == "" ) { if ( $1 == "<kernel>" )
+ *     domainname = $0; } else if ( $1 == "use_profile" ) {
+ *     print $2 " " domainname; domainname = ""; } } ; '
+ */
+static int tomoyo_read_domain_profile(struct tomoyo_io_buffer *head)
+{
+	struct list1_head *pos;
+
+	if (head->read_eof)
+		return 0;
+	list1_for_each_cookie(pos, head->read_var1, &tomoyo_domain_list) {
+		struct tomoyo_domain_info *domain;
+		domain = list1_entry(pos, struct tomoyo_domain_info, list);
+		if (domain->is_deleted)
+			continue;
+		if (!tomoyo_io_printf(head, "%u %s\n", domain->profile,
+				      domain->domainname->name))
+			return 0;
+	}
+	head->read_eof = true;
+	return 0;
+}
+
+/**
+ * tomoyo_write_pid: Specify PID to obtain domainname.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns 0.
+ */
+static int tomoyo_write_pid(struct tomoyo_io_buffer *head)
+{
+	unsigned long pid;
+	/* No error check. */
+	strict_strtoul(head->write_buf, 10, &pid);
+	head->read_step = (int) pid;
+	head->read_eof = false;
+	return 0;
+}
+
+/**
+ * tomoyo_read_pid - Get domainname of the specified PID.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns the domainname which the specified PID is in on success,
+ * empty string otherwise.
+ * The PID is specified by tomoyo_write_pid() so that the user can obtain
+ * using read()/write() interface rather than sysctl() interface.
+ */
+static int tomoyo_read_pid(struct tomoyo_io_buffer *head)
+{
+	if (head->read_avail == 0 && !head->read_eof) {
+		const int pid = head->read_step;
+		struct task_struct *p;
+		struct tomoyo_domain_info *domain = NULL;
+		/***** CRITICAL SECTION START *****/
+		read_lock(&tasklist_lock);
+		p = find_task_by_vpid(pid);
+		if (p)
+			domain = tomoyo_real_domain(p);
+		read_unlock(&tasklist_lock);
+		/***** CRITICAL SECTION END *****/
+		if (domain)
+			tomoyo_io_printf(head, "%d %u %s", pid, domain->profile,
+					 domain->domainname->name);
+		head->read_eof = true;
+	}
+	return 0;
+}
+
+/**
+ * tomoyo_write_exception_policy - Write exception policy.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_write_exception_policy(struct tomoyo_io_buffer *head)
+{
+	char *data = head->write_buf;
+	bool is_delete = tomoyo_str_starts(&data, TOMOYO_KEYWORD_DELETE);
+
+	if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_KEEP_DOMAIN))
+		return tomoyo_write_domain_keeper_policy(data, false,
+							 is_delete);
+	if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_NO_KEEP_DOMAIN))
+		return tomoyo_write_domain_keeper_policy(data, true, is_delete);
+	if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_INITIALIZE_DOMAIN))
+		return tomoyo_write_domain_initializer_policy(data, false,
+							      is_delete);
+	if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_NO_INITIALIZE_DOMAIN))
+		return tomoyo_write_domain_initializer_policy(data, true,
+							      is_delete);
+	if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_ALIAS))
+		return tomoyo_write_alias_policy(data, is_delete);
+	if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_ALLOW_READ))
+		return tomoyo_write_globally_readable_policy(data, is_delete);
+	if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_FILE_PATTERN))
+		return tomoyo_write_pattern_policy(data, is_delete);
+	if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_DENY_REWRITE))
+		return tomoyo_write_no_rewrite_policy(data, is_delete);
+	return -EINVAL;
+}
+
+/**
+ * tomoyo_read_exception_policy - Read exception policy.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns 0 on success, -EINVAL otherwise.
+ */
+static int tomoyo_read_exception_policy(struct tomoyo_io_buffer *head)
+{
+	if (!head->read_eof) {
+		switch (head->read_step) {
+		case 0:
+			head->read_var2 = NULL;
+			head->read_step = 1;
+		case 1:
+			if (!tomoyo_read_domain_keeper_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 2;
+		case 2:
+			if (!tomoyo_read_globally_readable_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 3;
+		case 3:
+			head->read_var2 = NULL;
+			head->read_step = 4;
+		case 4:
+			if (!tomoyo_read_domain_initializer_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 5;
+		case 5:
+			if (!tomoyo_read_alias_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 6;
+		case 6:
+			head->read_var2 = NULL;
+			head->read_step = 7;
+		case 7:
+			if (!tomoyo_read_file_pattern(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 8;
+		case 8:
+			if (!tomoyo_read_no_rewrite_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 9;
+		case 9:
+			head->read_eof = true;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+/* path to policy loader */
+static const char *tomoyo_loader = "/sbin/tomoyo-init";
+
+/**
+ * tomoyo_policy_loader_exists - Check whether /sbin/tomoyo-init exists.
+ *
+ * Returns true if /sbin/tomoyo-init exists, false otherwise.
+ */
+static bool tomoyo_policy_loader_exists(void)
+{
+	/*
+	 * Don't activate MAC if the policy loader doesn't exist.
+	 * If the initrd includes /sbin/init but real-root-dev has not
+	 * mounted on / yet, activating MAC will block the system since
+	 * policies are not loaded yet.
+	 * Thus, let do_execve() call this function everytime.
+	 */
+	struct nameidata nd;
+
+	if (path_lookup(tomoyo_loader, LOOKUP_FOLLOW, &nd)) {
+		printk(KERN_INFO "Not activating Mandatory Access Control now "
+		       "since %s doesn't exist.\n", tomoyo_loader);
+		return false;
+	}
+	path_put(&nd.path);
+	return true;
+}
+
+/**
+ * tomoyo_load_policy - Run external policy loader to load policy.
+ *
+ * @filename: The program about to start.
+ *
+ * This function checks whether @filename is /sbin/init , and if so
+ * invoke /sbin/tomoyo-init and wait for the termination of /sbin/tomoyo-init
+ * and then continues invocation of /sbin/init.
+ * /sbin/tomoyo-init reads policy files in /etc/tomoyo/ directory and
+ * writes to /sys/kernel/security/tomoyo/ interfaces.
+ *
+ * Returns nothing.
+ */
+void tomoyo_load_policy(const char *filename)
+{
+	char *argv[2];
+	char *envp[3];
+
+	if (tomoyo_policy_loaded)
+		return;
+	/*
+	 * Check filename is /sbin/init or /sbin/tomoyo-start.
+	 * /sbin/tomoyo-start is a dummy filename in case where /sbin/init can't
+	 * be passed.
+	 * You can create /sbin/tomoyo-start by
+	 * "ln -s /bin/true /sbin/tomoyo-start".
+	 */
+	if (strcmp(filename, "/sbin/init") &&
+	    strcmp(filename, "/sbin/tomoyo-start"))
+		return;
+	if (!tomoyo_policy_loader_exists())
+		return;
+
+	printk(KERN_INFO "Calling %s to load policy. Please wait.\n",
+	       tomoyo_loader);
+	argv[0] = (char *) tomoyo_loader;
+	argv[1] = NULL;
+	envp[0] = "HOME=/";
+	envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
+	envp[2] = NULL;
+	call_usermodehelper(argv[0], argv, envp, 1);
+
+	printk(KERN_INFO "TOMOYO: 2.2.0-pre   2008/11/11\n");
+	printk(KERN_INFO "Mandatory Access Control activated.\n");
+	tomoyo_policy_loaded = true;
+	{ /* Check all profiles currently assigned to domains are defined. */
+		struct tomoyo_domain_info *domain;
+		list1_for_each_entry(domain, &tomoyo_domain_list, list) {
+			const u8 profile = domain->profile;
+			if (tomoyo_profile_ptr[profile])
+				continue;
+			panic("Profile %u (used by '%s') not defined.\n",
+			      profile, domain->domainname->name);
+		}
+	}
+}
+
+/**
+ * tomoyo_read_version: Get version.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns version information.
+ */
+static int tomoyo_read_version(struct tomoyo_io_buffer *head)
+{
+	if (!head->read_eof) {
+		tomoyo_io_printf(head, "2.2.0-pre");
+		head->read_eof = true;
+	}
+	return 0;
+}
+
+/**
+ * tomoyo_read_self_domain - Get the current process's domainname.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns the current process's domainname.
+ */
+static int tomoyo_read_self_domain(struct tomoyo_io_buffer *head)
+{
+	if (!head->read_eof) {
+		/*
+		 * tomoyo_domain()->domainname != NULL
+		 * because every process belongs to a domain and
+		 * the domain's name cannot be NULL.
+		 */
+		tomoyo_io_printf(head, "%s", tomoyo_domain()->domainname->name);
+		head->read_eof = true;
+	}
+	return 0;
+}
+
+/**
+ * tomoyo_open_control - open() for /sys/kernel/security/tomoyo/ interface.
+ *
+ * @type: Type of interface.
+ * @file: Pointer to "struct file".
+ *
+ * Associates policy handler and returns 0 on success, -ENOMEM otherwise.
+ */
+static int tomoyo_open_control(const u8 type, struct file *file)
+{
+	struct tomoyo_io_buffer *head = tomoyo_alloc(sizeof(*head));
+
+	if (!head)
+		return -ENOMEM;
+	mutex_init(&head->io_sem);
+	switch (type) {
+	case TOMOYO_DOMAINPOLICY:
+		/* /sys/kernel/security/tomoyo/domain_policy */
+		head->write = tomoyo_write_domain_policy;
+		head->read = tomoyo_read_domain_policy;
+		break;
+	case TOMOYO_EXCEPTIONPOLICY:
+		/* /sys/kernel/security/tomoyo/exception_policy */
+		head->write = tomoyo_write_exception_policy;
+		head->read = tomoyo_read_exception_policy;
+		break;
+	case TOMOYO_SELFDOMAIN:
+		/* /sys/kernel/security/tomoyo/self_domain */
+		head->read = tomoyo_read_self_domain;
+		break;
+	case TOMOYO_DOMAIN_STATUS:
+		/* /sys/kernel/security/tomoyo/.domain_status */
+		head->write = tomoyo_write_domain_profile;
+		head->read = tomoyo_read_domain_profile;
+		break;
+	case TOMOYO_PROCESS_STATUS:
+		/* /sys/kernel/security/tomoyo/.process_status */
+		head->write = tomoyo_write_pid;
+		head->read = tomoyo_read_pid;
+		break;
+	case TOMOYO_VERSION:
+		/* /sys/kernel/security/tomoyo/version */
+		head->read = tomoyo_read_version;
+		head->readbuf_size = 128;
+		break;
+	case TOMOYO_MEMINFO:
+		/* /sys/kernel/security/tomoyo/meminfo */
+		head->write = tomoyo_write_memory_quota;
+		head->read = tomoyo_read_memory_counter;
+		head->readbuf_size = 512;
+		break;
+	case TOMOYO_PROFILE:
+		/* /sys/kernel/security/tomoyo/profile */
+		head->write = tomoyo_write_profile;
+		head->read = tomoyo_read_profile;
+		break;
+	case TOMOYO_MANAGER:
+		/* /sys/kernel/security/tomoyo/manager */
+		head->write = tomoyo_write_manager_policy;
+		head->read = tomoyo_read_manager_policy;
+		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 = tomoyo_alloc(head->readbuf_size);
+		if (!head->read_buf) {
+			tomoyo_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 = tomoyo_alloc(head->writebuf_size);
+		if (!head->write_buf) {
+			tomoyo_free(head->read_buf);
+			tomoyo_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 == TOMOYO_SELFDOMAIN)
+		tomoyo_read_control(file, NULL, 0);
+	return 0;
+}
+
+/**
+ * tomoyo_read_control - read() for /sys/kernel/security/tomoyo/ interface.
+ *
+ * @file:       Pointer to "struct file".
+ * @buffer:     Poiner to buffer to write to.
+ * @buffer_len: Size of @buffer.
+ *
+ * Returns bytes read on success, negative value otherwise.
+ */
+static int tomoyo_read_control(struct file *file, char __user *buffer,
+			       const int buffer_len)
+{
+	int len = 0;
+	struct tomoyo_io_buffer *head = file->private_data;
+	char *cp;
+
+	if (!head->read)
+		return -ENOSYS;
+	if (mutex_lock_interruptible(&head->io_sem))
+		return -EINTR;
+	/* Call the policy handler. */
+	len = head->read(head);
+	if (len < 0)
+		goto out;
+	/* Write to buffer. */
+	len = head->read_avail;
+	if (len > buffer_len)
+		len = buffer_len;
+	if (!len)
+		goto out;
+	/* head->read_buf changes by some functions. */
+	cp = head->read_buf;
+	if (copy_to_user(buffer, cp, len)) {
+		len = -EFAULT;
+		goto out;
+	}
+	head->read_avail -= len;
+	memmove(cp, cp + len, head->read_avail);
+ out:
+	mutex_unlock(&head->io_sem);
+	return len;
+}
+
+/**
+ * tomoyo_write_control - write() for /sys/kernel/security/tomoyo/ interface.
+ *
+ * @file:       Pointer to "struct file".
+ * @buffer:     Pointer to buffer to read from.
+ * @buffer_len: Size of @buffer.
+ *
+ * Returns @buffer_len on success, negative value otherwise.
+ */
+static int tomoyo_write_control(struct file *file, const char __user *buffer,
+				const int buffer_len)
+{
+	struct tomoyo_io_buffer *head = file->private_data;
+	int error = buffer_len;
+	int avail_len = buffer_len;
+	char *cp0 = head->write_buf;
+
+	if (!head->write)
+		return -ENOSYS;
+	if (!access_ok(VERIFY_READ, buffer, buffer_len))
+		return -EFAULT;
+	/* Don't allow updating policies by non manager programs. */
+	if (head->write != tomoyo_write_pid &&
+	    head->write != tomoyo_write_domain_policy &&
+	    !tomoyo_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;
+		tomoyo_normalize_line(cp0);
+		head->write(head);
+	}
+	mutex_unlock(&head->io_sem);
+	return error;
+}
+
+/**
+ * tomoyo_close_control - close() for /sys/kernel/security/tomoyo/ interface.
+ *
+ * @file: Pointer to "struct file".
+ *
+ * Releases memory and returns 0.
+ */
+static int tomoyo_close_control(struct file *file)
+{
+	struct tomoyo_io_buffer *head = file->private_data;
+
+	/* Release memory used for policy I/O. */
+	tomoyo_free(head->read_buf);
+	head->read_buf = NULL;
+	tomoyo_free(head->write_buf);
+	head->write_buf = NULL;
+	tomoyo_free(head);
+	head = NULL;
+	file->private_data = NULL;
+	return 0;
+}
+
+/**
+ * tomoyo_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 *tomoyo_alloc_acl_element(const u8 acl_type)
+{
+	int len;
+	struct tomoyo_acl_info *ptr;
+
+	switch (acl_type) {
+	case TOMOYO_TYPE_SINGLE_PATH_ACL:
+		len = sizeof(struct tomoyo_single_path_acl_record);
+		break;
+	case TOMOYO_TYPE_DOUBLE_PATH_ACL:
+		len = sizeof(struct tomoyo_double_path_acl_record);
+		break;
+	default:
+		return NULL;
+	}
+	ptr = tomoyo_alloc_element(len);
+	if (!ptr)
+		return NULL;
+	ptr->type = acl_type;
+	return ptr;
+}
+
+/**
+ * tomoyo_open - open() for /sys/kernel/security/tomoyo/ interface.
+ *
+ * @inode: Pointer to "struct inode".
+ * @file:  Pointer to "struct file".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_open(struct inode *inode, struct file *file)
+{
+	const int key = ((u8 *) file->f_path.dentry->d_inode->i_private)
+		- ((u8 *) NULL);
+	return tomoyo_open_control(key, file);
+}
+
+/**
+ * tomoyo_release - close() for /sys/kernel/security/tomoyo/ interface.
+ *
+ * @inode: Pointer to "struct inode".
+ * @file:  Pointer to "struct file".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_release(struct inode *inode, struct file *file)
+{
+	return tomoyo_close_control(file);
+}
+
+/**
+ * tomoyo_read - read() for /sys/kernel/security/tomoyo/ interface.
+ *
+ * @file:  Pointer to "struct file".
+ * @buf:   Pointer to buffer.
+ * @count: Size of @buf.
+ * @ppos:  Unused.
+ *
+ * Returns bytes read on success, negative value otherwise.
+ */
+static ssize_t tomoyo_read(struct file *file, char __user *buf, size_t count,
+			   loff_t *ppos)
+{
+	return tomoyo_read_control(file, buf, count);
+}
+
+/**
+ * tomoyo_write - write() for /sys/kernel/security/tomoyo/ interface.
+ *
+ * @file:  Pointer to "struct file".
+ * @buf:   Pointer to buffer.
+ * @count: Size of @buf.
+ * @ppos:  Unused.
+ *
+ * Returns @count on success, negative value otherwise.
+ */
+static ssize_t tomoyo_write(struct file *file, const char __user *buf,
+			    size_t count, loff_t *ppos)
+{
+	return tomoyo_write_control(file, buf, count);
+}
+
+/* Operations for /sys/kernel/security/tomoyo/ interface. */
+static struct file_operations tomoyo_operations = {
+	.open    = tomoyo_open,
+	.release = tomoyo_release,
+	.read    = tomoyo_read,
+	.write   = tomoyo_write,
+};
+
+/**
+ * tomoyo_create_entry - Create interface files under /sys/kernel/security/tomoyo/ directory.
+ *
+ * @name:   The name of the interface file.
+ * @mode:   The permission of the interface file.
+ * @parent: The parent directory.
+ * @key:    Type of interface.
+ *
+ * Returns nothing.
+ */
+static void __init tomoyo_create_entry(const char *name, const mode_t mode,
+				       struct dentry *parent, const u8 key)
+{
+	securityfs_create_file(name, mode, parent, ((u8 *) NULL) + key,
+			       &tomoyo_operations);
+}
+
+/**
+ * tomoyo_initerface_init - Initialize /sys/kernel/security/tomoyo/ interface.
+ *
+ * Returns 0.
+ */
+static int __init tomoyo_initerface_init(void)
+{
+	struct dentry *tomoyo_dir;
+
+	tomoyo_dir = securityfs_create_dir("tomoyo", NULL);
+	tomoyo_create_entry("domain_policy",    0600, tomoyo_dir,
+			    TOMOYO_DOMAINPOLICY);
+	tomoyo_create_entry("exception_policy", 0600, tomoyo_dir,
+			    TOMOYO_EXCEPTIONPOLICY);
+	tomoyo_create_entry("self_domain",      0400, tomoyo_dir,
+			    TOMOYO_SELFDOMAIN);
+	tomoyo_create_entry(".domain_status",   0600, tomoyo_dir,
+			    TOMOYO_DOMAIN_STATUS);
+	tomoyo_create_entry(".process_status",  0600, tomoyo_dir,
+			    TOMOYO_PROCESS_STATUS);
+	tomoyo_create_entry("meminfo",          0600, tomoyo_dir,
+			    TOMOYO_MEMINFO);
+	tomoyo_create_entry("profile",          0600, tomoyo_dir,
+			    TOMOYO_PROFILE);
+	tomoyo_create_entry("manager",          0600, tomoyo_dir,
+			    TOMOYO_MANAGER);
+	tomoyo_create_entry("version",          0400, tomoyo_dir,
+			    TOMOYO_VERSION);
+	return 0;
+}
+
+fs_initcall(tomoyo_initerface_init);
--- /dev/null
+++ linux-2.6.28-rc5-mm1/security/tomoyo/common.h
@@ -0,0 +1,337 @@
+/*
+ * security/tomoyo/common.h
+ *
+ * Common functions for TOMOYO.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 2.2.0-pre   2008/11/11
+ *
+ */
+
+#ifndef _SECURITY_TOMOYO_COMMON_H
+#define _SECURITY_TOMOYO_COMMON_H
+
+#include <linux/ctype.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/file.h>
+#include <linux/kmod.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/namei.h>
+#include <linux/mount.h>
+#include <linux/list1.h>
+
+struct dentry;
+struct vfsmount;
+
+/* Temporary buffer for holding pathnames. */
+struct tomoyo_page_buffer {
+	char buffer[4096];
+};
+
+/* Structure for holding a token. */
+struct tomoyo_path_info {
+	const char *name;
+	u32 hash;          /* = full_name_hash(name, strlen(name)) */
+	u16 total_len;     /* = strlen(name)                       */
+	u16 const_len;     /* = tomoyo_const_part_length(name)     */
+	bool is_dir;       /* = tomoyo_strendswith(name, "/")      */
+	bool is_patterned; /* = tomoyo_path_contains_pattern(name) */
+	u16 depth;         /* = tomoyo_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 TOMOYO_MAX_PATHNAME_LEN 4000
+
+/* Structure for holding requested pathname. */
+struct tomoyo_path_info_with_data {
+	/* Keep "head" first, for this pointer is passed to tomoyo_free(). */
+	struct tomoyo_path_info head;
+	char bariier1[16]; /* Safeguard for overrun. */
+	char body[TOMOYO_MAX_PATHNAME_LEN];
+	char barrier2[16]; /* Safeguard for overrun. */
+};
+
+/*
+ * Common header for holding ACL entries.
+ *
+ * Packing "struct tomoyo_acl_info" allows
+ * "struct tomoyo_single_path_acl_record" to embed "u16" and
+ * "struct tomoyo_double_path_acl_record" to embed "u8"
+ * without enlarging their structure size.
+ */
+struct tomoyo_acl_info {
+	struct list1_head list;
+	/*
+	 * Type of this ACL entry.
+	 *
+	 * MSB is is_deleted flag.
+	 */
+	u8 type;
+} __packed;
+
+/* This ACL entry is deleted.           */
+#define TOMOYO_ACL_DELETED        0x80
+
+/* Structure for domain information. */
+struct tomoyo_domain_info {
+	struct list1_head list;
+	struct list1_head acl_info_list;
+	/* Name of this domain. Never NULL.          */
+	const struct tomoyo_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 tomoyo_set_domain_flag() to modify. */
+	u8 flags;
+};
+
+/* Profile number is an integer between 0 and 255. */
+#define TOMOYO_MAX_PROFILES 256
+
+/* Ignore "allow_read" directive in exception policy. */
+#define TOMOYO_DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ 1
+/*
+ * This domain was unable to create a new domain at tomoyo_find_next_domain()
+ * because the name of the domain to be created was too long or
+ * it could not allocate memory.
+ * More than one process continued execve() without domain transition.
+ */
+#define TOMOYO_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 tomoyo_single_path_acl_record {
+	struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_SINGLE_PATH_ACL */
+	u16 perm;
+	/* Pointer to single pathname. */
+	const struct tomoyo_path_info *filename;
+};
+
+/* Structure for "allow_rename" and "allow_link" directive. */
+struct tomoyo_double_path_acl_record {
+	struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_DOUBLE_PATH_ACL */
+	u8 perm;
+	/* Pointer to single pathname. */
+	const struct tomoyo_path_info *filename1;
+	/* Pointer to single pathname. */
+	const struct tomoyo_path_info *filename2;
+};
+
+/* Keywords for ACLs. */
+#define TOMOYO_KEYWORD_ALIAS                     "alias "
+#define TOMOYO_KEYWORD_ALLOW_READ                "allow_read "
+#define TOMOYO_KEYWORD_DELETE                    "delete "
+#define TOMOYO_KEYWORD_DENY_REWRITE              "deny_rewrite "
+#define TOMOYO_KEYWORD_FILE_PATTERN              "file_pattern "
+#define TOMOYO_KEYWORD_INITIALIZE_DOMAIN         "initialize_domain "
+#define TOMOYO_KEYWORD_KEEP_DOMAIN               "keep_domain "
+#define TOMOYO_KEYWORD_NO_INITIALIZE_DOMAIN      "no_initialize_domain "
+#define TOMOYO_KEYWORD_NO_KEEP_DOMAIN            "no_keep_domain "
+#define TOMOYO_KEYWORD_SELECT                    "select "
+#define TOMOYO_KEYWORD_UNDELETE                  "undelete "
+#define TOMOYO_KEYWORD_USE_PROFILE               "use_profile "
+#define TOMOYO_KEYWORD_IGNORE_GLOBAL_ALLOW_READ  "ignore_global_allow_read"
+/* A domain definition starts with <kernel>. */
+#define TOMOYO_ROOT_NAME                         "<kernel>"
+#define TOMOYO_ROOT_NAME_LEN                     (sizeof(TOMOYO_ROOT_NAME) - 1)
+
+/* Index numbers for Access Controls. */
+#define TOMOYO_MAC_FOR_FILE                  0  /* domain_policy.conf */
+#define TOMOYO_MAX_ACCEPT_ENTRY              1
+#define TOMOYO_VERBOSE                       2
+#define TOMOYO_MAX_CONTROL_INDEX             3
+
+/* Structure for reading/writing policy via securityfs interfaces. */
+struct tomoyo_io_buffer {
+	int (*read) (struct tomoyo_io_buffer *);
+	int (*write) (struct tomoyo_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 tomoyo_domain_info *write_var1;
+	/* The step for reading.                */
+	int read_step;
+	/* Buffer for reading.                  */
+	char *read_buf;
+	/* EOF flag for reading.                */
+	bool read_eof;
+	/* Read domain ACL of specified PID?    */
+	bool read_single_domain;
+	/* Extra variable for reading.          */
+	u8 read_bit;
+	/* Bytes available for reading.         */
+	int read_avail;
+	/* Size of read buffer.                 */
+	int readbuf_size;
+	/* Buffer for writing.                  */
+	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 tomoyo_domain_quota_is_ok(struct tomoyo_domain_info * const domain);
+/* Transactional sprintf() for policy dump. */
+bool tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt, ...)
+	__attribute__ ((format(printf, 2, 3)));
+/* Check whether the domainname is correct. */
+bool tomoyo_is_correct_domain(const unsigned char *domainname,
+			      const char *function);
+/* Check whether the token is correct. */
+bool tomoyo_is_correct_path(const char *filename, const s8 start_type,
+			    const s8 pattern_type, const s8 end_type,
+			    const char *function);
+/* Check whether the token can be a domainname. */
+bool tomoyo_is_domain_def(const unsigned char *buffer);
+/* Check whether the given filename matches the given pattern. */
+bool tomoyo_path_matches_pattern(const struct tomoyo_path_info *filename,
+				 const struct tomoyo_path_info *pattern);
+/* Read "alias" entry in exception policy. */
+bool tomoyo_read_alias_policy(struct tomoyo_io_buffer *head);
+/*
+ * Read "initialize_domain" and "no_initialize_domain" entry
+ * in exception policy.
+ */
+bool tomoyo_read_domain_initializer_policy(struct tomoyo_io_buffer *head);
+/* Read "keep_domain" and "no_keep_domain" entry in exception policy. */
+bool tomoyo_read_domain_keeper_policy(struct tomoyo_io_buffer *head);
+/* Read "file_pattern" entry in exception policy. */
+bool tomoyo_read_file_pattern(struct tomoyo_io_buffer *head);
+/* Read "allow_read" entry in exception policy. */
+bool tomoyo_read_globally_readable_policy(struct tomoyo_io_buffer *head);
+/* Read "deny_rewrite" entry in exception policy. */
+bool tomoyo_read_no_rewrite_policy(struct tomoyo_io_buffer *head);
+/* Write domain policy violation warning message to console? */
+bool tomoyo_verbose_mode(const struct tomoyo_domain_info *domain);
+/* Convert double path operation to operation name. */
+const char *tomoyo_dp2keyword(const u8 operation);
+/* Get the last component of the given domainname. */
+const char *tomoyo_get_last_name(const struct tomoyo_domain_info *domain);
+/* Get warning message. */
+const char *tomoyo_get_msg(const bool is_enforce);
+/* Convert single path operation to operation name. */
+const char *tomoyo_sp2keyword(const u8 operation);
+/* Delete a domain. */
+int tomoyo_delete_domain(char *data);
+/* Create "alias" entry in exception policy. */
+int tomoyo_write_alias_policy(char *data, const bool is_delete);
+/*
+ * Create "initialize_domain" and "no_initialize_domain" entry
+ * in exception policy.
+ */
+int tomoyo_write_domain_initializer_policy(char *data, const bool is_not,
+					   const bool is_delete);
+/* Create "keep_domain" and "no_keep_domain" entry in exception policy. */
+int tomoyo_write_domain_keeper_policy(char *data, const bool is_not,
+				      const bool is_delete);
+/*
+ * Create "allow_read/write", "allow_execute", "allow_read", "allow_write",
+ * "allow_create", "allow_unlink", "allow_mkdir", "allow_rmdir",
+ * "allow_mkfifo", "allow_mksock", "allow_mkblock", "allow_mkchar",
+ * "allow_truncate", "allow_symlink", "allow_rewrite", "allow_rename" and
+ * "allow_link" entry in domain policy.
+ */
+int tomoyo_write_file_policy(char *data, struct tomoyo_domain_info *domain,
+			     const bool is_delete);
+/* Create "allow_read" entry in exception policy. */
+int tomoyo_write_globally_readable_policy(char *data, const bool is_delete);
+/* Create "deny_rewrite" entry in exception policy. */
+int tomoyo_write_no_rewrite_policy(char *data, const bool is_delete);
+/* Create "file_pattern" entry in exception policy. */
+int tomoyo_write_pattern_policy(char *data, const bool is_delete);
+/* Find a domain by the given name. */
+struct tomoyo_domain_info *tomoyo_find_domain(const char *domainname);
+/* Find or create a domain by the given name. */
+struct tomoyo_domain_info *tomoyo_find_or_assign_new_domain(const char *
+							    domainname,
+							    const u8 profile);
+/* Undelete a domain. */
+struct tomoyo_domain_info *tomoyo_undelete_domain(const char *domainname);
+/* Check mode for specified functionality. */
+unsigned int tomoyo_check_flags(const struct tomoyo_domain_info *domain,
+				const u8 index);
+/* Allocate memory for structures. */
+void *tomoyo_alloc_acl_element(const u8 acl_type);
+/* Fill in "struct tomoyo_path_info" members. */
+void tomoyo_fill_path_info(struct tomoyo_path_info *ptr);
+/* Run policy loader when /sbin/init starts. */
+void tomoyo_load_policy(const char *filename);
+/* Change "struct tomoyo_domain_info"->flags. */
+void tomoyo_set_domain_flag(struct tomoyo_domain_info *domain,
+			    const bool is_delete, const u8 flags);
+
+/* strcmp() for "struct tomoyo_path_info" structure. */
+static inline bool tomoyo_pathcmp(const struct tomoyo_path_info *a,
+				  const struct tomoyo_path_info *b)
+{
+	return a->hash != b->hash || strcmp(a->name, b->name);
+}
+
+/* Get type of an ACL entry. */
+static inline u8 tomoyo_acl_type1(struct tomoyo_acl_info *ptr)
+{
+	return ptr->type & ~TOMOYO_ACL_DELETED;
+}
+
+/* Get type of an ACL entry. */
+static inline u8 tomoyo_acl_type2(struct tomoyo_acl_info *ptr)
+{
+	return ptr->type;
+}
+
+/**
+ * tomoyo_is_valid - Check whether the character is a valid char.
+ *
+ * @c: The character to check.
+ *
+ * Returns true if @c is a valid character, false otherwise.
+ */
+static inline bool tomoyo_is_valid(const unsigned char c)
+{
+	return c > ' ' && c < 127;
+}
+
+/**
+ * tomoyo_is_invalid - Check whether the character is an invalid char.
+ *
+ * @c: The character to check.
+ *
+ * Returns true if @c is an invalid character, false otherwise.
+ */
+static inline bool tomoyo_is_invalid(const unsigned char c)
+{
+	return c && (c <= ' ' || c >= 127);
+}
+
+/* The list for "struct tomoyo_domain_info". */
+extern struct list1_head tomoyo_domain_list;
+
+/* Has /sbin/init started? */
+extern bool tomoyo_policy_loaded;
+
+/* The kernel's domain. */
+extern struct tomoyo_domain_info tomoyo_kernel_domain;
+
+#endif /* !defined(_SECURITY_TOMOYO_COMMON_H) */

-- 

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

* [TOMOYO #13 (mmotm 2008-11-19-02-19) 07/11] File operation restriction part.
  2008-11-20 11:25 [TOMOYO #13 (mmotm 2008-11-19-02-19) 00/11] TOMOYO Linux Tetsuo Handa
                   ` (5 preceding siblings ...)
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 06/11] Common functions for TOMOYO Linux Tetsuo Handa
@ 2008-11-20 11:25 ` Tetsuo Handa
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 08/11] Domain transition handler Tetsuo Handa
                   ` (4 subsequent siblings)
  11 siblings, 0 replies; 29+ messages in thread
From: Tetsuo Handa @ 2008-11-20 11:25 UTC (permalink / raw)
  To: Andrew Morton
  Cc: linux-security-module, linux-kernel, Kentaro Takeda, Tetsuo Handa,
	Toshiharu Harada

[-- Attachment #1: tomoyo-file-restriction-part.patch --]
[-- Type: text/plain, Size: 36973 bytes --]

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 | 1258 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1258 insertions(+)

--- /dev/null
+++ linux-2.6.28-rc5-mm1/security/tomoyo/file.c
@@ -0,0 +1,1258 @@
+/*
+ * 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/11/11
+ *
+ */
+
+#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 tomoyo_globally_readable_file_entry {
+	struct list1_head list;
+	const struct tomoyo_path_info *filename;
+	bool is_deleted;
+};
+
+/* Structure for "file_pattern" keyword. */
+struct tomoyo_pattern_entry {
+	struct list1_head list;
+	const struct tomoyo_path_info *pattern;
+	bool is_deleted;
+};
+
+/* Structure for "deny_rewrite" keyword. */
+struct tomoyo_no_rewrite_entry {
+	struct list1_head list;
+	const struct tomoyo_path_info *pattern;
+	bool is_deleted;
+};
+
+/* Keyword array for single path operations. */
+static const char *tomoyo_sp_keyword[TOMOYO_MAX_SINGLE_PATH_OPERATION] = {
+	[TOMOYO_TYPE_READ_WRITE_ACL] = "read/write",
+	[TOMOYO_TYPE_EXECUTE_ACL]    = "execute",
+	[TOMOYO_TYPE_READ_ACL]       = "read",
+	[TOMOYO_TYPE_WRITE_ACL]      = "write",
+	[TOMOYO_TYPE_CREATE_ACL]     = "create",
+	[TOMOYO_TYPE_UNLINK_ACL]     = "unlink",
+	[TOMOYO_TYPE_MKDIR_ACL]      = "mkdir",
+	[TOMOYO_TYPE_RMDIR_ACL]      = "rmdir",
+	[TOMOYO_TYPE_MKFIFO_ACL]     = "mkfifo",
+	[TOMOYO_TYPE_MKSOCK_ACL]     = "mksock",
+	[TOMOYO_TYPE_MKBLOCK_ACL]    = "mkblock",
+	[TOMOYO_TYPE_MKCHAR_ACL]     = "mkchar",
+	[TOMOYO_TYPE_TRUNCATE_ACL]   = "truncate",
+	[TOMOYO_TYPE_SYMLINK_ACL]    = "symlink",
+	[TOMOYO_TYPE_REWRITE_ACL]    = "rewrite",
+};
+
+/* Keyword array for double path operations. */
+static const char *tomoyo_dp_keyword[TOMOYO_MAX_DOUBLE_PATH_OPERATION] = {
+	[TOMOYO_TYPE_LINK_ACL]    = "link",
+	[TOMOYO_TYPE_RENAME_ACL]  = "rename",
+};
+
+/**
+ * tomoyo_sp2keyword - Get the name of single path operation.
+ *
+ * @operation: Type of operation.
+ *
+ * Returns the name of single path operation.
+ */
+const char *tomoyo_sp2keyword(const u8 operation)
+{
+	return (operation < TOMOYO_MAX_SINGLE_PATH_OPERATION)
+		? tomoyo_sp_keyword[operation] : NULL;
+}
+
+/**
+ * tomoyo_dp2keyword - Get the name of double path operation.
+ *
+ * @operation: Type of operation.
+ *
+ * Returns the name of double path operation.
+ */
+const char *tomoyo_dp2keyword(const u8 operation)
+{
+	return (operation < TOMOYO_MAX_DOUBLE_PATH_OPERATION)
+		? tomoyo_dp_keyword[operation] : NULL;
+}
+
+/**
+ * tomoyo_strendswith - Check whether the token ends with the given token.
+ *
+ * @name: The token to check.
+ * @tail: The token to find.
+ *
+ * Returns true if @name ends with @tail, false otherwise.
+ */
+static bool tomoyo_strendswith(const char *name, const char *tail)
+{
+	int len;
+
+	if (!name || !tail)
+		return false;
+	len = strlen(name) - strlen(tail);
+	return len >= 0 && !strcmp(name + len, tail);
+}
+
+/**
+ * tomoyo_get_path - Get realpath.
+ *
+ * @path: Pointer to "struct path".
+ *
+ * Returns pointer to "struct tomoyo_path_info" on success, NULL otherwise.
+ */
+static struct tomoyo_path_info *tomoyo_get_path(struct path *path)
+{
+	int error;
+	struct tomoyo_path_info_with_data *buf = tomoyo_alloc(sizeof(*buf));
+
+	if (!buf)
+		return NULL;
+	/* Reserve one byte for appending "/". */
+	error = tomoyo_realpath_from_path2(path, buf->body,
+					   sizeof(buf->body) - 2);
+	if (!error) {
+		buf->head.name = buf->body;
+		tomoyo_fill_path_info(&buf->head);
+		return &buf->head;
+	}
+	tomoyo_free(buf);
+	return NULL;
+}
+
+static int tomoyo_update_double_path_acl(const u8 type, const char *filename1,
+					 const char *filename2,
+					 struct tomoyo_domain_info *
+					 const domain, const bool is_delete);
+static int tomoyo_update_single_path_acl(const u8 type, const char *filename,
+					 struct tomoyo_domain_info *
+					 const domain, const bool is_delete);
+
+/**
+ * tomoyo_add_domain_acl - Add the given ACL to the given domain.
+ *
+ * @domain: Pointer to "struct tomoyo_domain_info". May be NULL.
+ * @acl:    Pointer to "struct tomoyo_acl_info".
+ *
+ * Returns 0.
+ */
+static int tomoyo_add_domain_acl(struct tomoyo_domain_info *domain,
+				 struct tomoyo_acl_info *acl)
+{
+	if (domain) {
+		/*
+		 * We need to serialize because this function is called by
+		 * tomoyo_update_single_path_acl() and
+		 * tomoyo_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 &= ~TOMOYO_ACL_DELETED;
+	}
+	return 0;
+}
+
+/**
+ * tomoyo_del_domain_acl - Delete the given ACL from the domain.
+ *
+ * @acl: Pointer to "struct tomoyo_acl_info". May be NULL.
+ *
+ * Returns 0.
+ */
+static int tomoyo_del_domain_acl(struct tomoyo_acl_info *acl)
+{
+	if (acl)
+		acl->type |= TOMOYO_ACL_DELETED;
+	return 0;
+}
+
+/*
+ * The list for "struct tomoyo_globally_readable_file_entry".
+ *
+ * This list is updated only inside update_globally_readable_entry(), thus
+ * no global mutex exists.
+ */
+static LIST1_HEAD(tomoyo_globally_readable_list);
+
+/**
+ * update_globally_readable_entry - Update "struct tomoyo_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 tomoyo_globally_readable_file_entry *new_entry;
+	struct tomoyo_globally_readable_file_entry *ptr;
+	static DEFINE_MUTEX(lock);
+	const struct tomoyo_path_info *saved_filename;
+	int error = -ENOMEM;
+
+	if (!tomoyo_is_correct_path(filename, 1, 0, -1, __func__))
+		return -EINVAL;
+	saved_filename = tomoyo_save_name(filename);
+	if (!saved_filename)
+		return -ENOMEM;
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &tomoyo_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 = tomoyo_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->filename = saved_filename;
+	list1_add_tail(&new_entry->list, &tomoyo_globally_readable_list);
+	error = 0;
+ out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	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 tomoyo_path_info *filename)
+{
+	struct tomoyo_globally_readable_file_entry *ptr;
+
+	list1_for_each_entry(ptr, &tomoyo_globally_readable_list, list) {
+		if (!ptr->is_deleted &&
+		    tomoyo_path_matches_pattern(filename, ptr->filename))
+			return true;
+	}
+	return false;
+}
+
+/**
+ * tomoyo_write_globally_readable_policy - Write "struct tomoyo_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 tomoyo_write_globally_readable_policy(char *data, const bool is_delete)
+{
+	return update_globally_readable_entry(data, is_delete);
+}
+
+/**
+ * tomoyo_read_globally_readable_policy - Read "struct tomoyo_globally_readable_file_entry" list.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool tomoyo_read_globally_readable_policy(struct tomoyo_io_buffer *head)
+{
+	struct list1_head *pos;
+
+	list1_for_each_cookie(pos, head->read_var2,
+			      &tomoyo_globally_readable_list) {
+		struct tomoyo_globally_readable_file_entry *ptr;
+		ptr = list1_entry(pos,
+				  struct tomoyo_globally_readable_file_entry,
+				  list);
+		if (ptr->is_deleted)
+			continue;
+		if (!tomoyo_io_printf(head, TOMOYO_KEYWORD_ALLOW_READ "%s\n",
+				      ptr->filename->name))
+			goto out;
+	}
+	return true;
+ out:
+	return false;
+}
+
+/*
+ * The list for "struct tomoyo_pattern_entry".
+ *
+ * This list is updated only inside tomoyo_update_file_pattern_entry(), thus
+ * no global mutex exists.
+ */
+static LIST1_HEAD(tomoyo_pattern_list);
+
+/**
+ * tomoyo_update_file_pattern_entry - Update "struct tomoyo_pattern_entry" list.
+ *
+ * @pattern:   Pathname pattern.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_update_file_pattern_entry(const char *pattern,
+					    const bool is_delete)
+{
+	struct tomoyo_pattern_entry *new_entry;
+	struct tomoyo_pattern_entry *ptr;
+	static DEFINE_MUTEX(lock);
+	const struct tomoyo_path_info *saved_pattern;
+	int error = -ENOMEM;
+
+	if (!tomoyo_is_correct_path(pattern, 0, 1, 0, __func__))
+		return -EINVAL;
+	saved_pattern = tomoyo_save_name(pattern);
+	if (!saved_pattern)
+		return -ENOMEM;
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &tomoyo_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 = tomoyo_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->pattern = saved_pattern;
+	list1_add_tail(&new_entry->list, &tomoyo_pattern_list);
+	error = 0;
+ out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	return error;
+}
+
+/**
+ * tomoyo_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 tomoyo_path_info *
+tomoyo_get_file_pattern(const struct tomoyo_path_info *filename)
+{
+	struct tomoyo_pattern_entry *ptr;
+	const struct tomoyo_path_info *pattern = NULL;
+
+	list1_for_each_entry(ptr, &tomoyo_pattern_list, list) {
+		if (ptr->is_deleted)
+			continue;
+		if (!tomoyo_path_matches_pattern(filename, ptr->pattern))
+			continue;
+		pattern = ptr->pattern;
+		if (tomoyo_strendswith(pattern->name, "/\\*")) {
+			/* Do nothing. Try to find the better match. */
+		} else {
+			/* This would be the better match. Use this. */
+			break;
+		}
+	}
+	if (pattern)
+		filename = pattern;
+	return filename;
+}
+
+/**
+ * tomoyo_write_pattern_policy - Write "struct tomoyo_pattern_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tomoyo_write_pattern_policy(char *data, const bool is_delete)
+{
+	return tomoyo_update_file_pattern_entry(data, is_delete);
+}
+
+/**
+ * tomoyo_read_file_pattern - Read "struct tomoyo_pattern_entry" list.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool tomoyo_read_file_pattern(struct tomoyo_io_buffer *head)
+{
+	struct list1_head *pos;
+
+	list1_for_each_cookie(pos, head->read_var2, &tomoyo_pattern_list) {
+		struct tomoyo_pattern_entry *ptr;
+		ptr = list1_entry(pos, struct tomoyo_pattern_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (!tomoyo_io_printf(head, TOMOYO_KEYWORD_FILE_PATTERN "%s\n",
+				      ptr->pattern->name))
+			goto out;
+	}
+	return true;
+ out:
+	return false;
+}
+
+/*
+ * The list for "struct tomoyo_no_rewrite_entry".
+ *
+ * This list is updated only inside tomoyo_update_no_rewrite_entry(), thus
+ * no global mutex exists.
+ */
+static LIST1_HEAD(tomoyo_no_rewrite_list);
+
+/**
+ * tomoyo_update_no_rewrite_entry - Update "struct tomoyo_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 tomoyo_update_no_rewrite_entry(const char *pattern,
+					  const bool is_delete)
+{
+	struct tomoyo_no_rewrite_entry *new_entry, *ptr;
+	static DEFINE_MUTEX(lock);
+	const struct tomoyo_path_info *saved_pattern;
+	int error = -ENOMEM;
+
+	if (!tomoyo_is_correct_path(pattern, 0, 0, 0, __func__))
+		return -EINVAL;
+	saved_pattern = tomoyo_save_name(pattern);
+	if (!saved_pattern)
+		return -ENOMEM;
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &tomoyo_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 = tomoyo_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->pattern = saved_pattern;
+	list1_add_tail(&new_entry->list, &tomoyo_no_rewrite_list);
+	error = 0;
+ out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	return error;
+}
+
+/**
+ * tomoyo_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 tomoyo_is_no_rewrite_file(const struct tomoyo_path_info *filename)
+{
+	struct tomoyo_no_rewrite_entry *ptr;
+
+	list1_for_each_entry(ptr, &tomoyo_no_rewrite_list, list) {
+		if (ptr->is_deleted)
+			continue;
+		if (!tomoyo_path_matches_pattern(filename, ptr->pattern))
+			continue;
+		return true;
+	}
+	return false;
+}
+
+/**
+ * tomoyo_write_no_rewrite_policy - Write "struct tomoyo_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 tomoyo_write_no_rewrite_policy(char *data, const bool is_delete)
+{
+	return tomoyo_update_no_rewrite_entry(data, is_delete);
+}
+
+/**
+ * tomoyo_read_no_rewrite_policy - Read "struct tomoyo_no_rewrite_entry" list.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool tomoyo_read_no_rewrite_policy(struct tomoyo_io_buffer *head)
+{
+	struct list1_head *pos;
+
+	list1_for_each_cookie(pos, head->read_var2, &tomoyo_no_rewrite_list) {
+		struct tomoyo_no_rewrite_entry *ptr;
+		ptr = list1_entry(pos, struct tomoyo_no_rewrite_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (!tomoyo_io_printf(head, TOMOYO_KEYWORD_DENY_REWRITE "%s\n",
+				      ptr->pattern->name))
+			goto out;
+	}
+	return true;
+ out:
+	return false;
+}
+
+/**
+ * tomoyo_update_file_acl - Update file's read/write/execute ACL.
+ *
+ * @filename:  Filename.
+ * @perm:      Permission (between 1 to 7).
+ * @domain:    Pointer to "struct tomoyo_domain_info".
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ *
+ * This is legacy support interface for older policy syntax.
+ * Current policy syntax uses "allow_read/write" instead of "6",
+ * "allow_read" instead of "4", "allow_write" instead of "2",
+ * "allow_execute" instead of "1".
+ */
+static int tomoyo_update_file_acl(const char *filename, u8 perm,
+				  struct tomoyo_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] != '@' && tomoyo_strendswith(filename, "/"))
+		/*
+		 * Only 'allow_mkdir' and 'allow_rmdir' are valid for
+		 * directory permissions.
+		 */
+		return 0;
+	if (perm & 4)
+		tomoyo_update_single_path_acl(TOMOYO_TYPE_READ_ACL, filename,
+					      domain, is_delete);
+	if (perm & 2)
+		tomoyo_update_single_path_acl(TOMOYO_TYPE_WRITE_ACL, filename,
+					      domain, is_delete);
+	if (perm & 1)
+		tomoyo_update_single_path_acl(TOMOYO_TYPE_EXECUTE_ACL,
+					      filename, domain, is_delete);
+	return 0;
+}
+
+/**
+ * tomoyo_check_single_path_acl2 - Check permission for single path operation.
+ *
+ * @domain:          Pointer to "struct tomoyo_domain_info".
+ * @filename:        Filename to check.
+ * @perm:            Permission.
+ * @may_use_pattern: True if patterned ACL is permitted.
+ *
+ * Returns 0 on success, -EPERM otherwise.
+ */
+static int tomoyo_check_single_path_acl2(const struct tomoyo_domain_info *
+					 domain,
+					 const struct tomoyo_path_info *
+					 filename,
+					 const u16 perm,
+					 const bool may_use_pattern)
+{
+	struct tomoyo_acl_info *ptr;
+
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		struct tomoyo_single_path_acl_record *acl;
+		if (tomoyo_acl_type2(ptr) != TOMOYO_TYPE_SINGLE_PATH_ACL)
+			continue;
+		acl = container_of(ptr, struct tomoyo_single_path_acl_record,
+				   head);
+		if (!(acl->perm & perm))
+			continue;
+		if (may_use_pattern || !acl->filename->is_patterned) {
+			if (!tomoyo_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 tomoyo_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 tomoyo_domain_info *domain,
+			  const struct tomoyo_path_info *filename,
+			  const u8 operation)
+{
+	u16 perm = 0;
+
+	if (!tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE))
+		return 0;
+	if (operation == 6)
+		perm = 1 << TOMOYO_TYPE_READ_WRITE_ACL;
+	else if (operation == 4)
+		perm = 1 << TOMOYO_TYPE_READ_ACL;
+	else if (operation == 2)
+		perm = 1 << TOMOYO_TYPE_WRITE_ACL;
+	else if (operation == 1)
+		perm = 1 << TOMOYO_TYPE_EXECUTE_ACL;
+	else
+		BUG();
+	return tomoyo_check_single_path_acl2(domain, filename, perm,
+					     operation != 1);
+}
+
+/**
+ * tomoyo_check_file_perm2 - Check permission for opening files.
+ *
+ * @domain:    Pointer to "struct tomoyo_domain_info".
+ * @filename:  Filename to check.
+ * @perm:      Mode ("read" or "write" or "read/write" or "execute").
+ * @operation: Operation name passed used for verbose mode.
+ * @mode:      Access control mode.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_check_file_perm2(struct tomoyo_domain_info * const domain,
+				   const struct tomoyo_path_info *filename,
+				   const u8 perm, const char *operation,
+				   const u8 mode)
+{
+	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 & TOMOYO_DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ) == 0
+	    && is_globally_readable_file(filename))
+		error = 0;
+	if (perm == 6)
+		msg = tomoyo_sp2keyword(TOMOYO_TYPE_READ_WRITE_ACL);
+	else if (perm == 4)
+		msg = tomoyo_sp2keyword(TOMOYO_TYPE_READ_ACL);
+	else if (perm == 2)
+		msg = tomoyo_sp2keyword(TOMOYO_TYPE_WRITE_ACL);
+	else if (perm == 1)
+		msg = tomoyo_sp2keyword(TOMOYO_TYPE_EXECUTE_ACL);
+	else
+		BUG();
+	if (!error)
+		return 0;
+	if (tomoyo_verbose_mode(domain))
+		printk(KERN_WARNING "TOMOYO-%s: Access '%s(%s) %s' denied "
+		       "for %s\n", tomoyo_get_msg(is_enforce), msg, operation,
+		       filename->name, tomoyo_get_last_name(domain));
+	if (is_enforce)
+		return error;
+	if (mode == 1 && tomoyo_domain_quota_is_ok(domain)) {
+		/* Don't use patterns for execute permission. */
+		const struct tomoyo_path_info *patterned_file = (perm != 1) ?
+			tomoyo_get_file_pattern(filename) : filename;
+		tomoyo_update_file_acl(patterned_file->name, perm,
+				       domain, false);
+	}
+	return 0;
+}
+
+/**
+ * tomoyo_write_file_policy - Update file related list.
+ *
+ * @data:      String to parse.
+ * @domain:    Pointer to "struct tomoyo_domain_info".
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tomoyo_write_file_policy(char *data, struct tomoyo_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 tomoyo_update_file_acl(filename, (u8) perm, domain,
+					      is_delete);
+	if (strncmp(data, "allow_", 6))
+		goto out;
+	data += 6;
+	for (type = 0; type < TOMOYO_MAX_SINGLE_PATH_OPERATION; type++) {
+		if (strcmp(data, tomoyo_sp_keyword[type]))
+			continue;
+		return tomoyo_update_single_path_acl(type, filename,
+						     domain, is_delete);
+	}
+	filename2 = strchr(filename, ' ');
+	if (!filename2)
+		goto out;
+	*filename2++ = '\0';
+	for (type = 0; type < TOMOYO_MAX_DOUBLE_PATH_OPERATION; type++) {
+		if (strcmp(data, tomoyo_dp_keyword[type]))
+			continue;
+		return tomoyo_update_double_path_acl(type, filename, filename2,
+						     domain, is_delete);
+	}
+ out:
+	return -EINVAL;
+}
+
+/**
+ * tomoyo_update_single_path_acl - Update "struct tomoyo_single_path_acl_record" list.
+ *
+ * @type:      Type of operation.
+ * @filename:  Filename.
+ * @domain:    Pointer to "struct tomoyo_domain_info".
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_update_single_path_acl(const u8 type, const char *filename,
+					 struct tomoyo_domain_info *
+					 const domain, const bool is_delete)
+{
+	static DEFINE_MUTEX(lock);
+	static const u16 rw_mask =
+		(1 << TOMOYO_TYPE_READ_ACL) | (1 << TOMOYO_TYPE_WRITE_ACL);
+	const struct tomoyo_path_info *saved_filename;
+	struct tomoyo_acl_info *ptr;
+	struct tomoyo_single_path_acl_record *acl;
+	int error = -ENOMEM;
+	const u16 perm = 1 << type;
+
+	if (!domain)
+		return -EINVAL;
+	if (!tomoyo_is_correct_path(filename, 0, 0, 0, __func__))
+		return -EINVAL;
+	saved_filename = tomoyo_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 (tomoyo_acl_type1(ptr) != TOMOYO_TYPE_SINGLE_PATH_ACL)
+			continue;
+		acl = container_of(ptr, struct tomoyo_single_path_acl_record,
+				   head);
+		if (acl->filename != saved_filename)
+			continue;
+		/* Special case. Clear all bits if marked as deleted. */
+		if (ptr->type & TOMOYO_ACL_DELETED)
+			acl->perm = 0;
+		acl->perm |= perm;
+		if ((acl->perm & rw_mask) == rw_mask)
+			acl->perm |= 1 << TOMOYO_TYPE_READ_WRITE_ACL;
+		else if (acl->perm & (1 << TOMOYO_TYPE_READ_WRITE_ACL))
+			acl->perm |= rw_mask;
+		error = tomoyo_add_domain_acl(NULL, ptr);
+		goto out;
+	}
+	/* Not found. Append it to the tail. */
+	acl = tomoyo_alloc_acl_element(TOMOYO_TYPE_SINGLE_PATH_ACL);
+	if (!acl)
+		goto out;
+	acl->perm = perm;
+	acl->filename = saved_filename;
+	error = tomoyo_add_domain_acl(domain, &acl->head);
+	goto out;
+ delete:
+	error = -ENOENT;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (tomoyo_acl_type2(ptr) != TOMOYO_TYPE_SINGLE_PATH_ACL)
+			continue;
+		acl = container_of(ptr, struct tomoyo_single_path_acl_record,
+				   head);
+		if (acl->filename != saved_filename)
+			continue;
+		acl->perm &= ~perm;
+		if ((acl->perm & rw_mask) != rw_mask)
+			acl->perm &= ~(1 << TOMOYO_TYPE_READ_WRITE_ACL);
+		else if (!(acl->perm & (1 << TOMOYO_TYPE_READ_WRITE_ACL)))
+			acl->perm &= ~rw_mask;
+		error = tomoyo_del_domain_acl(acl->perm ? NULL : ptr);
+		break;
+	}
+ out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	return error;
+}
+
+/**
+ * tomoyo_update_double_path_acl - Update "struct tomoyo_double_path_acl_record" list.
+ *
+ * @type:      Type of operation.
+ * @filename1: First filename.
+ * @filename2: Second filename.
+ * @domain:    Pointer to "struct tomoyo_domain_info".
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_update_double_path_acl(const u8 type, const char *filename1,
+					 const char *filename2,
+					 struct tomoyo_domain_info *
+					 const domain, const bool is_delete)
+{
+	static DEFINE_MUTEX(lock);
+	const struct tomoyo_path_info *saved_filename1;
+	const struct tomoyo_path_info *saved_filename2;
+	struct tomoyo_acl_info *ptr;
+	struct tomoyo_double_path_acl_record *acl;
+	int error = -ENOMEM;
+	const u8 perm = 1 << type;
+
+	if (!domain)
+		return -EINVAL;
+	if (!tomoyo_is_correct_path(filename1, 0, 0, 0, __func__) ||
+	    !tomoyo_is_correct_path(filename2, 0, 0, 0, __func__))
+		return -EINVAL;
+	saved_filename1 = tomoyo_save_name(filename1);
+	saved_filename2 = tomoyo_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 (tomoyo_acl_type1(ptr) != TOMOYO_TYPE_DOUBLE_PATH_ACL)
+			continue;
+		acl = container_of(ptr, struct tomoyo_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 & TOMOYO_ACL_DELETED)
+			acl->perm = 0;
+		acl->perm |= perm;
+		error = tomoyo_add_domain_acl(NULL, ptr);
+		goto out;
+	}
+	/* Not found. Append it to the tail. */
+	acl = tomoyo_alloc_acl_element(TOMOYO_TYPE_DOUBLE_PATH_ACL);
+	if (!acl)
+		goto out;
+	acl->perm = perm;
+	acl->filename1 = saved_filename1;
+	acl->filename2 = saved_filename2;
+	error = tomoyo_add_domain_acl(domain, &acl->head);
+	goto out;
+ delete:
+	error = -ENOENT;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (tomoyo_acl_type2(ptr) != TOMOYO_TYPE_DOUBLE_PATH_ACL)
+			continue;
+		acl = container_of(ptr, struct tomoyo_double_path_acl_record,
+				   head);
+		if (acl->filename1 != saved_filename1 ||
+		    acl->filename2 != saved_filename2)
+			continue;
+		acl->perm &= ~perm;
+		error = tomoyo_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 tomoyo_domain_info".
+ * @type:     Type of operation.
+ * @filename: Filename to check.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int check_single_path_acl(struct tomoyo_domain_info *domain,
+				 const u8 type,
+				 const struct tomoyo_path_info *filename)
+{
+	if (!tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE))
+		return 0;
+	return tomoyo_check_single_path_acl2(domain, filename, 1 << type, 1);
+}
+
+/**
+ * check_double_path_acl - Check permission for double path operation.
+ *
+ * @domain:    Pointer to "struct tomoyo_domain_info".
+ * @type:      Type of operation.
+ * @filename1: First filename to check.
+ * @filename2: Second filename to check.
+ *
+ * Returns 0 on success, -EPERM otherwise.
+ */
+static int check_double_path_acl(const struct tomoyo_domain_info *domain,
+				 const u8 type,
+				 const struct tomoyo_path_info *filename1,
+				 const struct tomoyo_path_info *filename2)
+{
+	struct tomoyo_acl_info *ptr;
+	const u8 perm = 1 << type;
+
+	if (!tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE))
+		return 0;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		struct tomoyo_double_path_acl_record *acl;
+		if (tomoyo_acl_type2(ptr) != TOMOYO_TYPE_DOUBLE_PATH_ACL)
+			continue;
+		acl = container_of(ptr, struct tomoyo_double_path_acl_record,
+				   head);
+		if (!(acl->perm & perm))
+			continue;
+		if (!tomoyo_path_matches_pattern(filename1, acl->filename1))
+			continue;
+		if (!tomoyo_path_matches_pattern(filename2, acl->filename2))
+			continue;
+		return 0;
+	}
+	return -EPERM;
+}
+
+/**
+ * tomoyo_check_single_path_permission2 - Check permission for single path operation.
+ *
+ * @domain:    Pointer to "struct tomoyo_domain_info".
+ * @operation: Type of operation.
+ * @filename:  Filename to check.
+ * @mode:      Access control mode.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_check_single_path_permission2(struct tomoyo_domain_info *
+						const domain, u8 operation,
+						const struct tomoyo_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 = tomoyo_sp2keyword(operation);
+	if (!error)
+		goto ok;
+	if (tomoyo_verbose_mode(domain))
+		printk(KERN_WARNING "TOMOYO-%s: Access '%s %s' denied for %s\n",
+		       tomoyo_get_msg(is_enforce), msg, filename->name,
+		       tomoyo_get_last_name(domain));
+	if (mode == 1 && tomoyo_domain_quota_is_ok(domain)) {
+		const char *name = tomoyo_get_file_pattern(filename)->name;
+		tomoyo_update_single_path_acl(operation, 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 == TOMOYO_TYPE_TRUNCATE_ACL &&
+	    tomoyo_is_no_rewrite_file(filename)) {
+		operation = TOMOYO_TYPE_REWRITE_ACL;
+		goto next;
+	}
+	return error;
+}
+
+/**
+ * tomoyo_check_file_perm - Check permission for sysctl()'s "read" and "write".
+ *
+ * @domain:    Pointer to "struct tomoyo_domain_info".
+ * @filename:  Filename to check.
+ * @perm:      Mode ("read" or "write" or "read/write").
+ * Returns 0 on success, negative value otherwise.
+ */
+int tomoyo_check_file_perm(struct tomoyo_domain_info *domain,
+			   const char *filename, const u8 perm)
+{
+	struct tomoyo_path_info name;
+	const u8 mode = tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE);
+
+	if (!mode)
+		return 0;
+	name.name = filename;
+	tomoyo_fill_path_info(&name);
+	return tomoyo_check_file_perm2(domain, &name, perm, "sysctl", mode);
+}
+
+/**
+ * tomoyo_check_exec_perm - Check permission for "execute".
+ *
+ * @domain:   Pointer to "struct tomoyo_domain_info".
+ * @filename: Check permission for "execute".
+ * @tmp:      Buffer for temporary use.
+ *
+ * Returns 0 on success, negativevalue otherwise.
+ */
+int tomoyo_check_exec_perm(struct tomoyo_domain_info *domain,
+			   const struct tomoyo_path_info *filename,
+			   struct tomoyo_page_buffer *tmp)
+{
+	const u8 mode = tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE);
+
+	if (!mode)
+		return 0;
+	return tomoyo_check_file_perm2(domain, filename, 1, "do_execve", mode);
+}
+
+/**
+ * tomoyo_check_open_permission - Check permission for "read" and "write".
+ *
+ * @domain: Pointer to "struct tomoyo_domain_info".
+ * @path:   Pointer to "struct path".
+ * @flag:   Flags for open().
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tomoyo_check_open_permission(struct tomoyo_domain_info *domain,
+				 struct path *path, const int flag)
+{
+	const u8 acc_mode = ACC_MODE(flag);
+	int error = -ENOMEM;
+	struct tomoyo_path_info *buf;
+	const u8 mode = tomoyo_check_flags(domain, 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 = tomoyo_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)) &&
+	    (tomoyo_is_no_rewrite_file(buf))) {
+		error = tomoyo_check_single_path_permission2(domain,
+						     TOMOYO_TYPE_REWRITE_ACL,
+							     buf, mode);
+	}
+	if (!error)
+		error = tomoyo_check_file_perm2(domain, buf, acc_mode, "open",
+						mode);
+	if (!error && (flag & O_TRUNC))
+		error = tomoyo_check_single_path_permission2(domain,
+						     TOMOYO_TYPE_TRUNCATE_ACL,
+							     buf, mode);
+ out:
+	tomoyo_free(buf);
+	if (!is_enforce)
+		error = 0;
+	return error;
+}
+
+/**
+ * tomoyo_check_1path_perm - Check permission for "create", "unlink", "mkdir", "rmdir", "mkfifo", "mksock", "mkblock", "mkchar", "truncate" and "symlink".
+ *
+ * @domain:    Pointer to "struct tomoyo_domain_info".
+ * @operation: Type of operation.
+ * @path:      Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tomoyo_check_1path_perm(struct tomoyo_domain_info *domain,
+			    const u8 operation, struct path *path)
+{
+	int error = -ENOMEM;
+	struct tomoyo_path_info *buf;
+	const u8 mode = tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE);
+	const bool is_enforce = (mode == 3);
+
+	if (!mode || !path->mnt)
+		return 0;
+	buf = tomoyo_get_path(path);
+	if (!buf)
+		goto out;
+	switch (operation) {
+	case TOMOYO_TYPE_MKDIR_ACL:
+	case TOMOYO_TYPE_RMDIR_ACL:
+		if (!buf->is_dir) {
+			/*
+			 * tomoyo_get_path() reserves space for appending "/."
+			 */
+			strcat((char *) buf->name, "/");
+			tomoyo_fill_path_info(buf);
+		}
+	}
+	error = tomoyo_check_single_path_permission2(domain, operation, buf,
+						     mode);
+ out:
+	tomoyo_free(buf);
+	if (!is_enforce)
+		error = 0;
+	return error;
+}
+
+/**
+ * tomoyo_check_rewrite_permission - Check permission for "rewrite".
+ *
+ * @domain: Pointer to "struct tomoyo_domain_info".
+ * @filp: Pointer to "struct file".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tomoyo_check_rewrite_permission(struct tomoyo_domain_info *domain,
+				    struct file *filp)
+{
+	int error = -ENOMEM;
+	const u8 mode = tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE);
+	const bool is_enforce = (mode == 3);
+	struct tomoyo_path_info *buf;
+
+	if (!mode || !filp->f_path.mnt)
+		return 0;
+	buf = tomoyo_get_path(&filp->f_path);
+	if (!buf)
+		goto out;
+	if (!tomoyo_is_no_rewrite_file(buf)) {
+		error = 0;
+		goto out;
+	}
+	error = tomoyo_check_single_path_permission2(domain,
+						     TOMOYO_TYPE_REWRITE_ACL,
+						     buf, mode);
+ out:
+	tomoyo_free(buf);
+	if (!is_enforce)
+		error = 0;
+	return error;
+}
+
+/**
+ * tomoyo_check_2path_perm - Check permission for "rename" and "link".
+ *
+ * @domain:    Pointer to "struct tomoyo_domain_info".
+ * @operation: Type of operation.
+ * @path1:      Pointer to "struct path".
+ * @path2:      Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tomoyo_check_2path_perm(struct tomoyo_domain_info * const domain,
+			    const u8 operation, struct path *path1,
+			    struct path *path2)
+{
+	int error = -ENOMEM;
+	struct tomoyo_path_info *buf1, *buf2;
+	const u8 mode = tomoyo_check_flags(domain, TOMOYO_MAC_FOR_FILE);
+	const bool is_enforce = (mode == 3);
+	const char *msg;
+
+	if (!mode || !path1->mnt || !path2->mnt)
+		return 0;
+	buf1 = tomoyo_get_path(path1);
+	buf2 = tomoyo_get_path(path2);
+	if (!buf1 || !buf2)
+		goto out;
+	{
+		struct dentry *dentry = path1->dentry;
+		if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)) {
+			/*
+			 * tomoyo_get_path() reserves space for appending "/."
+			 */
+			if (!buf1->is_dir) {
+				strcat((char *) buf1->name, "/");
+				tomoyo_fill_path_info(buf1);
+			}
+			if (!buf2->is_dir) {
+				strcat((char *) buf2->name, "/");
+				tomoyo_fill_path_info(buf2);
+			}
+		}
+	}
+	error = check_double_path_acl(domain, operation, buf1, buf2);
+	msg = tomoyo_dp2keyword(operation);
+	if (!error)
+		goto out;
+	if (tomoyo_verbose_mode(domain))
+		printk(KERN_WARNING "TOMOYO-%s: Access '%s %s %s' "
+		       "denied for %s\n", tomoyo_get_msg(is_enforce),
+		       msg, buf1->name, buf2->name,
+		       tomoyo_get_last_name(domain));
+	if (mode == 1 && tomoyo_domain_quota_is_ok(domain)) {
+		const char *name1 = tomoyo_get_file_pattern(buf1)->name;
+		const char *name2 = tomoyo_get_file_pattern(buf2)->name;
+		tomoyo_update_double_path_acl(operation, name1, name2, domain,
+					      false);
+	}
+ out:
+	tomoyo_free(buf1);
+	tomoyo_free(buf2);
+	if (!is_enforce)
+		error = 0;
+	return error;
+}

-- 

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

* [TOMOYO #13 (mmotm 2008-11-19-02-19) 08/11] Domain transition handler.
  2008-11-20 11:25 [TOMOYO #13 (mmotm 2008-11-19-02-19) 00/11] TOMOYO Linux Tetsuo Handa
                   ` (6 preceding siblings ...)
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 07/11] File operation restriction part Tetsuo Handa
@ 2008-11-20 11:25 ` Tetsuo Handa
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 09/11] LSM adapter functions Tetsuo Handa
                   ` (3 subsequent siblings)
  11 siblings, 0 replies; 29+ messages in thread
From: Tetsuo Handa @ 2008-11-20 11:25 UTC (permalink / raw)
  To: Andrew Morton
  Cc: linux-security-module, linux-kernel, Kentaro Takeda, Tetsuo Handa,
	Toshiharu Harada

[-- Attachment #1: tomoyo-domain-transition-part.patch --]
[-- Type: text/plain, Size: 26411 bytes --]

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 current->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 |  874 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 874 insertions(+)

--- /dev/null
+++ linux-2.6.28-rc5-mm1/security/tomoyo/domain.c
@@ -0,0 +1,874 @@
+/*
+ * 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/11/11
+ *
+ */
+
+#include "common.h"
+#include "tomoyo.h"
+#include "realpath.h"
+#include <linux/binfmts.h>
+
+/* Variables definitions.*/
+
+/* The initial domain. */
+struct tomoyo_domain_info tomoyo_kernel_domain;
+
+/*
+ * The list for "struct tomoyo_domain_info".
+ *
+ * The tomoyo_domain_list_lock mutex protects the tomoyo_domain_list list.
+ */
+LIST1_HEAD(tomoyo_domain_list);
+static DEFINE_MUTEX(tomoyo_domain_list_lock);
+
+/* Structure for "initialize_domain" and "no_initialize_domain" keyword. */
+struct tomoyo_domain_initializer_entry {
+	struct list1_head list;
+	const struct tomoyo_path_info *domainname;    /* This may be NULL */
+	const struct tomoyo_path_info *program;
+	bool is_deleted;
+	bool is_not;       /* True if this entry is "no_initialize_domain".  */
+	/* True if the domainname is tomoyo_get_last_name(). */
+	bool is_last_name;
+};
+
+/* Structure for "keep_domain" and "no_keep_domain" keyword. */
+struct tomoyo_domain_keeper_entry {
+	struct list1_head list;
+	const struct tomoyo_path_info *domainname;
+	const struct tomoyo_path_info *program;       /* This may be NULL */
+	bool is_deleted;
+	bool is_not;       /* True if this entry is "no_keep_domain".        */
+	/* True if the domainname is tomoyo_get_last_name(). */
+	bool is_last_name;
+};
+
+/* Structure for "alias" keyword. */
+struct tomoyo_alias_entry {
+	struct list1_head list;
+	const struct tomoyo_path_info *original_name;
+	const struct tomoyo_path_info *aliased_name;
+	bool is_deleted;
+};
+
+/**
+ * tomoyo_set_domain_flag - Set or clear domain's attribute flags.
+ *
+ * @domain:    Pointer to "struct tomoyo_domain_info".
+ * @is_delete: True if it is a delete request.
+ * @flags:     Flags to set or clear.
+ *
+ * Returns nothing.
+ */
+void tomoyo_set_domain_flag(struct tomoyo_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 *****/
+}
+
+/**
+ * tomoyo_get_last_name - Get last component of a domainname.
+ *
+ * @domain: Pointer to "struct tomoyo_domain_info".
+ *
+ * Returns the last component of the domainname.
+ */
+const char *tomoyo_get_last_name(const struct tomoyo_domain_info *domain)
+{
+	const char *cp0 = domain->domainname->name;
+	const char *cp1 = strrchr(cp0, ' ');
+
+	if (cp1)
+		return cp1 + 1;
+	return cp0;
+}
+
+/*
+ * The list for "struct tomoyo_domain_initializer_entry".
+ *
+ * This list is updated only inside
+ * tomoyo_update_domain_initializer_entry(),
+ * thus no global mutex exists.
+ */
+static LIST1_HEAD(tomoyo_domain_initializer_list);
+
+/**
+ * tomoyo_update_domain_initializer_entry - Update "struct tomoyo_domain_initializer_entry" list.
+ *
+ * @domainname: The name of domain. May be NULL.
+ * @program:    The name of program.
+ * @is_not:     True if it is "no_initialize_domain" entry.
+ * @is_delete:  True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tomoyo_update_domain_initializer_entry(const char *domainname,
+						  const char *program,
+						  const bool is_not,
+						  const bool is_delete)
+{
+	struct tomoyo_domain_initializer_entry *new_entry;
+	struct tomoyo_domain_initializer_entry *ptr;
+	static DEFINE_MUTEX(lock);
+	const struct tomoyo_path_info *saved_program;
+	const struct tomoyo_path_info *saved_domainname = NULL;
+	int error = -ENOMEM;
+	bool is_last_name = false;
+
+	if (!tomoyo_is_correct_path(program, 1, -1, -1, __func__))
+		return -EINVAL; /* No patterns allowed. */
+	if (domainname) {
+		if (!tomoyo_is_domain_def(domainname) &&
+		    tomoyo_is_correct_path(domainname, 1, -1, -1, __func__))
+			is_last_name = true;
+		else if (!tomoyo_is_correct_domain(domainname, __func__))
+			return -EINVAL;
+		saved_domainname = tomoyo_save_name(domainname);
+		if (!saved_domainname)
+			return -ENOMEM;
+	}
+	saved_program = tomoyo_save_name(program);
+	if (!saved_program)
+		return -ENOMEM;
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &tomoyo_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 = tomoyo_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, &tomoyo_domain_initializer_list);
+	error = 0;
+ out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	return error;
+}
+
+/**
+ * tomoyo_read_domain_initializer_policy - Read "struct tomoyo_domain_initializer_entry" list.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool tomoyo_read_domain_initializer_policy(struct tomoyo_io_buffer *head)
+{
+	struct list1_head *pos;
+
+	list1_for_each_cookie(pos, head->read_var2,
+			      &tomoyo_domain_initializer_list) {
+		const char *no;
+		const char *from = "";
+		const char *domain = "";
+		struct tomoyo_domain_initializer_entry *ptr;
+		ptr = list1_entry(pos, struct tomoyo_domain_initializer_entry,
+				  list);
+		if (ptr->is_deleted)
+			continue;
+		no = ptr->is_not ? "no_" : "";
+		if (ptr->domainname) {
+			from = " from ";
+			domain = ptr->domainname->name;
+		}
+		if (!tomoyo_io_printf(head,
+				      "%s" TOMOYO_KEYWORD_INITIALIZE_DOMAIN
+				      "%s%s%s\n", no, ptr->program->name, from,
+				      domain))
+			goto out;
+	}
+	return true;
+ out:
+	return false;
+}
+
+/**
+ * tomoyo_write_domain_initializer_policy - Write "struct tomoyo_domain_initializer_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_not:    True if it is "no_initialize_domain" entry.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tomoyo_write_domain_initializer_policy(char *data, const bool is_not,
+					   const bool is_delete)
+{
+	char *cp = strstr(data, " from ");
+
+	if (cp) {
+		*cp = '\0';
+		return tomoyo_update_domain_initializer_entry(cp + 6, data,
+							      is_not,
+							      is_delete);
+	}
+	return tomoyo_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 tomoyo_path_info *domainname,
+				  const struct tomoyo_path_info *program,
+				  const struct tomoyo_path_info *last_name)
+{
+	struct tomoyo_domain_initializer_entry *ptr;
+	bool flag = false;
+
+	list1_for_each_entry(ptr,  &tomoyo_domain_initializer_list, list) {
+		if (ptr->is_deleted)
+			continue;
+		if (ptr->domainname) {
+			if (!ptr->is_last_name) {
+				if (ptr->domainname != domainname)
+					continue;
+			} else {
+				if (tomoyo_pathcmp(ptr->domainname, last_name))
+					continue;
+			}
+		}
+		if (tomoyo_pathcmp(ptr->program, program))
+			continue;
+		if (ptr->is_not)
+			return false;
+		flag = true;
+	}
+	return flag;
+}
+
+/*
+ * The list for "struct tomoyo_domain_keeper_entry".
+ *
+ * This list is updated only inside tomoyo_update_domain_keeper_entry(),
+ * thus no global mutex exists.
+ */
+static LIST1_HEAD(tomoyo_domain_keeper_list);
+
+/**
+ * tomoyo_update_domain_keeper_entry - Update "struct tomoyo_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 tomoyo_update_domain_keeper_entry(const char *domainname,
+					     const char *program,
+					     const bool is_not,
+					     const bool is_delete)
+{
+	struct tomoyo_domain_keeper_entry *new_entry;
+	struct tomoyo_domain_keeper_entry *ptr;
+	const struct tomoyo_path_info *saved_domainname;
+	const struct tomoyo_path_info *saved_program = NULL;
+	static DEFINE_MUTEX(lock);
+	int error = -ENOMEM;
+	bool is_last_name = false;
+
+	if (!tomoyo_is_domain_def(domainname) &&
+	    tomoyo_is_correct_path(domainname, 1, -1, -1, __func__))
+		is_last_name = true;
+	else if (!tomoyo_is_correct_domain(domainname, __func__))
+		return -EINVAL;
+	if (program) {
+		if (!tomoyo_is_correct_path(program, 1, -1, -1, __func__))
+			return -EINVAL;
+		saved_program = tomoyo_save_name(program);
+		if (!saved_program)
+			return -ENOMEM;
+	}
+	saved_domainname = tomoyo_save_name(domainname);
+	if (!saved_domainname)
+		return -ENOMEM;
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &tomoyo_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 = tomoyo_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, &tomoyo_domain_keeper_list);
+	error = 0;
+ out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	return error;
+}
+
+/**
+ * tomoyo_write_domain_keeper_policy - Write "struct tomoyo_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 tomoyo_write_domain_keeper_policy(char *data, const bool is_not,
+				      const bool is_delete)
+{
+	char *cp = strstr(data, " from ");
+
+	if (cp) {
+		*cp = '\0';
+		return tomoyo_update_domain_keeper_entry(cp + 6, data, is_not,
+							 is_delete);
+	}
+	return tomoyo_update_domain_keeper_entry(data, NULL, is_not, is_delete);
+}
+
+/**
+ * tomoyo_read_domain_keeper_policy - Read "struct tomoyo_domain_keeper_entry" list.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool tomoyo_read_domain_keeper_policy(struct tomoyo_io_buffer *head)
+{
+	struct list1_head *pos;
+
+	list1_for_each_cookie(pos, head->read_var2,
+			      &tomoyo_domain_keeper_list) {
+		struct tomoyo_domain_keeper_entry *ptr;
+		const char *no;
+		const char *from = "";
+		const char *program = "";
+
+		ptr = list1_entry(pos, struct tomoyo_domain_keeper_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		no = ptr->is_not ? "no_" : "";
+		if (ptr->program) {
+			from = " from ";
+			program = ptr->program->name;
+		}
+		if (!tomoyo_io_printf(head,
+				      "%s" TOMOYO_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 tomoyo_path_info *domainname,
+			     const struct tomoyo_path_info *program,
+			     const struct tomoyo_path_info *last_name)
+{
+	struct tomoyo_domain_keeper_entry *ptr;
+	bool flag = false;
+
+	list1_for_each_entry(ptr, &tomoyo_domain_keeper_list, list) {
+		if (ptr->is_deleted)
+			continue;
+		if (!ptr->is_last_name) {
+			if (ptr->domainname != domainname)
+				continue;
+		} else {
+			if (tomoyo_pathcmp(ptr->domainname, last_name))
+				continue;
+		}
+		if (ptr->program && tomoyo_pathcmp(ptr->program, program))
+			continue;
+		if (ptr->is_not)
+			return false;
+		flag = true;
+	}
+	return flag;
+}
+
+/*
+ * The list for "struct tomoyo_alias_entry".
+ *
+ * This list is updated only inside tomoyo_update_alias_entry(), thus
+ * no global mutex exists.
+ */
+static LIST1_HEAD(tomoyo_alias_list);
+
+/**
+ * tomoyo_update_alias_entry - Update "struct tomoyo_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 tomoyo_update_alias_entry(const char *original_name,
+				     const char *aliased_name,
+				     const bool is_delete)
+{
+	struct tomoyo_alias_entry *new_entry;
+	struct tomoyo_alias_entry *ptr;
+	static DEFINE_MUTEX(lock);
+	const struct tomoyo_path_info *saved_original_name;
+	const struct tomoyo_path_info *saved_aliased_name;
+	int error = -ENOMEM;
+
+	if (!tomoyo_is_correct_path(original_name, 1, -1, -1, __func__) ||
+	    !tomoyo_is_correct_path(aliased_name, 1, -1, -1, __func__))
+		return -EINVAL; /* No patterns allowed. */
+	saved_original_name = tomoyo_save_name(original_name);
+	saved_aliased_name = tomoyo_save_name(aliased_name);
+	if (!saved_original_name || !saved_aliased_name)
+		return -ENOMEM;
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &tomoyo_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 = tomoyo_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, &tomoyo_alias_list);
+	error = 0;
+ out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	return error;
+}
+
+/**
+ * tomoyo_read_alias_policy - Read "struct tomoyo_alias_entry" list.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool tomoyo_read_alias_policy(struct tomoyo_io_buffer *head)
+{
+	struct list1_head *pos;
+
+	list1_for_each_cookie(pos, head->read_var2, &tomoyo_alias_list) {
+		struct tomoyo_alias_entry *ptr;
+
+		ptr = list1_entry(pos, struct tomoyo_alias_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (!tomoyo_io_printf(head, TOMOYO_KEYWORD_ALIAS "%s %s\n",
+				      ptr->original_name->name,
+				      ptr->aliased_name->name))
+			goto out;
+	}
+	return true;
+ out:
+	return false;
+}
+
+/**
+ * tomoyo_write_alias_policy - Write "struct tomoyo_alias_entry" list.
+ *
+ * @data:      String to parse.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tomoyo_write_alias_policy(char *data, const bool is_delete)
+{
+	char *cp = strchr(data, ' ');
+
+	if (!cp)
+		return -EINVAL;
+	*cp++ = '\0';
+	return tomoyo_update_alias_entry(data, cp, is_delete);
+}
+
+/* Domain create/delete/undelete handler. */
+
+/* #define TOMOYO_DEBUG_DOMAIN_UNDELETE */
+
+/**
+ * tomoyo_delete_domain - Delete a domain.
+ *
+ * @domainname: The name of domain.
+ *
+ * Returns 0.
+ */
+int tomoyo_delete_domain(char *domainname)
+{
+	struct tomoyo_domain_info *domain;
+	struct tomoyo_path_info name;
+
+	name.name = domainname;
+	tomoyo_fill_path_info(&name);
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&tomoyo_domain_list_lock);
+#ifdef TOMOYO_DEBUG_DOMAIN_UNDELETE
+	printk(KERN_DEBUG "tomoyo_delete_domain %s\n", domainname);
+	list1_for_each_entry(domain, &tomoyo_domain_list, list) {
+		if (tomoyo_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, &tomoyo_domain_list, list) {
+		struct tomoyo_domain_info *domain2;
+		/* Never delete tomoyo_kernel_domain */
+		if (domain == &tomoyo_kernel_domain)
+			continue;
+		if (domain->is_deleted ||
+		    tomoyo_pathcmp(domain->domainname, &name))
+			continue;
+		/* Mark already deleted domains as non undeletable. */
+		list1_for_each_entry(domain2, &tomoyo_domain_list, list) {
+			if (!domain2->is_deleted ||
+			    tomoyo_pathcmp(domain2->domainname, &name))
+				continue;
+#ifdef TOMOYO_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 TOMOYO_DEBUG_DOMAIN_UNDELETE
+		printk(KERN_DEBUG "Marked %p as undeletable\n", domain);
+#endif
+		break;
+	}
+	mutex_unlock(&tomoyo_domain_list_lock);
+	/***** EXCLUSIVE SECTION END *****/
+	return 0;
+}
+
+/**
+ * tomoyo_undelete_domain - Undelete a domain.
+ *
+ * @domainname: The name of domain.
+ *
+ * Returns pointer to "struct tomoyo_domain_info" on success, NULL otherwise.
+ */
+struct tomoyo_domain_info *tomoyo_undelete_domain(const char *domainname)
+{
+	struct tomoyo_domain_info *domain;
+	struct tomoyo_domain_info *candidate_domain = NULL;
+	struct tomoyo_path_info name;
+
+	name.name = domainname;
+	tomoyo_fill_path_info(&name);
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&tomoyo_domain_list_lock);
+#ifdef TOMOYO_DEBUG_DOMAIN_UNDELETE
+	printk(KERN_DEBUG "tomoyo_undelete_domain %s\n", domainname);
+	list1_for_each_entry(domain, &tomoyo_domain_list, list) {
+		if (tomoyo_pathcmp(domain->domainname, &name))
+			continue;
+		printk(KERN_DEBUG "List: %p %u\n", domain, domain->is_deleted);
+	}
+#endif
+	list1_for_each_entry(domain, &tomoyo_domain_list, list) {
+		if (tomoyo_pathcmp(&name, domain->domainname))
+			continue;
+		if (!domain->is_deleted) {
+			/* This domain is active. I can't undelete. */
+			candidate_domain = NULL;
+#ifdef TOMOYO_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 TOMOYO_DEBUG_DOMAIN_UNDELETE
+		printk(KERN_DEBUG "%p was undeleted.\n", candidate_domain);
+#endif
+	}
+	mutex_unlock(&tomoyo_domain_list_lock);
+	/***** EXCLUSIVE SECTION END *****/
+	return candidate_domain;
+}
+
+/**
+ * tomoyo_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 tomoyo_domain_info" on success, NULL otherwise.
+ */
+struct tomoyo_domain_info *tomoyo_find_or_assign_new_domain(const char *
+							    domainname,
+							    const u8 profile)
+{
+	struct tomoyo_domain_info *domain = NULL;
+	const struct tomoyo_path_info *saved_domainname;
+
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&tomoyo_domain_list_lock);
+	domain = tomoyo_find_domain(domainname);
+	if (domain)
+		goto out;
+	if (!tomoyo_is_correct_domain(domainname, __func__))
+		goto out;
+	saved_domainname = tomoyo_save_name(domainname);
+	if (!saved_domainname)
+		goto out;
+	/* Can I reuse memory of deleted domain? */
+	list1_for_each_entry(domain, &tomoyo_domain_list, list) {
+		struct task_struct *p;
+		struct tomoyo_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 (tomoyo_real_domain(p) != domain)
+				continue;
+			flag = true;
+			break;
+		}
+		read_unlock(&tasklist_lock);
+		/***** CRITICAL SECTION END *****/
+		if (flag)
+			continue;
+#ifdef TOMOYO_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 |= TOMOYO_ACL_DELETED;
+		}
+		tomoyo_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 = tomoyo_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, &tomoyo_domain_list);
+	}
+ out:
+	mutex_unlock(&tomoyo_domain_list_lock);
+	/***** EXCLUSIVE SECTION END *****/
+	return domain;
+}
+
+/**
+ * tomoyo_find_next_domain - Find a domain.
+ *
+ * @bprm:           Pointer to "struct linux_binprm".
+ * @next_domain:    Pointer to pointer to "struct tomoyo_domain_info".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tomoyo_find_next_domain(struct linux_binprm *bprm,
+			    struct tomoyo_domain_info **next_domain)
+{
+	/*
+	 * This function assumes that the size of buffer returned by
+	 * tomoyo_realpath() = TOMOYO_MAX_PATHNAME_LEN.
+	 */
+	struct tomoyo_page_buffer *tmp = tomoyo_alloc(sizeof(*tmp));
+	struct tomoyo_domain_info *old_domain = tomoyo_domain();
+	struct tomoyo_domain_info *domain = NULL;
+	const char *old_domain_name = old_domain->domainname->name;
+	const char *original_name = bprm->filename;
+	char *new_domain_name = NULL;
+	char *real_program_name = NULL;
+	char *symlink_program_name = NULL;
+	const u8 mode = tomoyo_check_flags(old_domain, TOMOYO_MAC_FOR_FILE);
+	const bool is_enforce = (mode == 3);
+	int retval = -ENOMEM;
+	struct tomoyo_path_info r; /* real name */
+	struct tomoyo_path_info s; /* symlink name */
+	struct tomoyo_path_info l; /* last name */
+	static bool initialized;
+
+	if (!tmp)
+		goto out;
+
+	if (!initialized) {
+		/*
+		 * Built-in initializers. This is needed because policies are
+		 * not loaded until starting /sbin/init.
+		 */
+		tomoyo_update_domain_initializer_entry(NULL, "/sbin/hotplug",
+						       false, false);
+		tomoyo_update_domain_initializer_entry(NULL, "/sbin/modprobe",
+						       false, false);
+		initialized = true;
+	}
+
+	/* Get tomoyo_realpath of program. */
+	retval = -ENOENT;
+	/* I hope tomoyo_realpath() won't fail with -ENOMEM. */
+	real_program_name = tomoyo_realpath(original_name);
+	if (!real_program_name)
+		goto out;
+	/* Get tomoyo_realpath of symbolic link. */
+	symlink_program_name = tomoyo_realpath_nofollow(original_name);
+	if (!symlink_program_name)
+		goto out;
+
+	r.name = real_program_name;
+	tomoyo_fill_path_info(&r);
+	s.name = symlink_program_name;
+	tomoyo_fill_path_info(&s);
+	l.name = tomoyo_get_last_name(old_domain);
+	tomoyo_fill_path_info(&l);
+
+	/* Check 'alias' directive. */
+	if (tomoyo_pathcmp(&r, &s)) {
+		struct tomoyo_alias_entry *ptr;
+		/* Is this program allowed to be called via symbolic links? */
+		list1_for_each_entry(ptr, &tomoyo_alias_list, list) {
+			if (ptr->is_deleted ||
+			    tomoyo_pathcmp(&r, ptr->original_name) ||
+			    tomoyo_pathcmp(&s, ptr->aliased_name))
+				continue;
+			memset(real_program_name, 0, TOMOYO_MAX_PATHNAME_LEN);
+			strncpy(real_program_name, ptr->aliased_name->name,
+				TOMOYO_MAX_PATHNAME_LEN - 1);
+			tomoyo_fill_path_info(&r);
+			break;
+		}
+	}
+
+	/* Check execute permission. */
+	retval = tomoyo_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 tomoyo_kernel_domain domain. */
+		snprintf(new_domain_name, TOMOYO_MAX_PATHNAME_LEN + 1,
+			 TOMOYO_ROOT_NAME " " "%s", real_program_name);
+	} else if (old_domain == &tomoyo_kernel_domain &&
+		   !tomoyo_policy_loaded) {
+		/*
+		 * Needn't to transit from kernel domain before starting
+		 * /sbin/init. But transit from kernel domain if executing
+		 * initializers because they might start before /sbin/init.
+		 */
+		domain = old_domain;
+	} else if (is_domain_keeper(old_domain->domainname, &r, &l)) {
+		/* Keep current domain. */
+		domain = old_domain;
+	} else {
+		/* Normal domain transition. */
+		snprintf(new_domain_name, TOMOYO_MAX_PATHNAME_LEN + 1,
+			 "%s %s", old_domain_name, real_program_name);
+	}
+	if (domain || strlen(new_domain_name) >= TOMOYO_MAX_PATHNAME_LEN)
+		goto done;
+	domain = tomoyo_find_domain(new_domain_name);
+	if (domain)
+		goto done;
+	if (is_enforce)
+		goto done;
+	domain = tomoyo_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
+		tomoyo_set_domain_flag(old_domain, false,
+				       TOMOYO_DOMAIN_FLAGS_TRANSITION_FAILED);
+ out:
+	tomoyo_free(real_program_name);
+	tomoyo_free(symlink_program_name);
+	*next_domain = domain ? domain : old_domain;
+	tomoyo_free(tmp);
+	return retval;
+}

-- 

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

* [TOMOYO #13 (mmotm 2008-11-19-02-19) 09/11] LSM adapter functions.
  2008-11-20 11:25 [TOMOYO #13 (mmotm 2008-11-19-02-19) 00/11] TOMOYO Linux Tetsuo Handa
                   ` (7 preceding siblings ...)
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 08/11] Domain transition handler Tetsuo Handa
@ 2008-11-20 11:25 ` Tetsuo Handa
  2008-12-01 20:10   ` Stephen Smalley
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 10/11] Kconfig and Makefile Tetsuo Handa
                   ` (2 subsequent siblings)
  11 siblings, 1 reply; 29+ messages in thread
From: Tetsuo Handa @ 2008-11-20 11:25 UTC (permalink / raw)
  To: Andrew Morton
  Cc: linux-security-module, linux-kernel, Kentaro Takeda, Tetsuo Handa,
	Toshiharu Harada

[-- Attachment #1: tomoyo-hooks.patch --]
[-- Type: text/plain, Size: 17033 bytes --]

This is LSM adapter for TOMOYO.
This version of TOMOYO assigns only one variable "struct tomoyo_domain_info *"
to "current->cred->security".

Future version will require two variables "struct tomoyo_domain_info *" and
"u32".
http://tomoyo.sourceforge.jp/cgi-bin/lxr/source/include/linux/sched.h#L1311
Well, how to implement this while "current->cred->security" cannot be modified
freely?

To be honest, the only object which TOMOYO needs to assign variables is
"struct task_struct". TOMOYO's "T" means "task".
TOMOYO utilizes characteristics of "struct task_struct".
For TOMOYO, use of per task variables is vital.

Removal of security_task_alloc() and security_task_free() by introduction of
COW credentials will become a nightmare when TOMOYO tries to add "u32".
Of cource, if TOMOYO is allowed to add "u32" to "struct task_struct",
COW credentials won't become a nightmare.

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 |  453 +++++++++++++++++++++++++++++++++++++++++++++++
 security/tomoyo/tomoyo.h |  107 +++++++++++
 2 files changed, 560 insertions(+)

--- /dev/null
+++ linux-2.6.28-rc5-mm1/security/tomoyo/tomoyo.c
@@ -0,0 +1,453 @@
+/*
+ * security/tomoyo/tomoyo.c
+ *
+ * LSM hooks for TOMOYO Linux.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 2.2.0-pre   2008/11/11
+ *
+ */
+
+#include <linux/security.h>
+#include "common.h"
+#include "tomoyo.h"
+#include "realpath.h"
+
+static int tomoyo_cred_prepare(struct cred *new, const struct cred *old,
+			       gfp_t gfp)
+{
+	/*
+	 * Since "struct tomoyo_domain_info *" is a sharable pointer,
+	 * we don't need to duplicate.
+	 */
+	new->security = old->security;
+	return 0;
+}
+
+static int tomoyo_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 (!tomoyo_policy_loaded)
+		tomoyo_load_policy(bprm->filename);
+	/*
+	 * Tell tomoyo_bprm_check_security() is called for the first time of an
+	 * execve operation.
+	 */
+	bprm->cred->security = NULL;
+	return 0;
+}
+
+static int tomoyo_bprm_check_security(struct linux_binprm *bprm)
+{
+	struct tomoyo_domain_info *domain = bprm->cred->security;
+
+	/*
+	 * Execute permission is checked against pathname passed to do_execve()
+	 * using current domain.
+	 */
+	if (!domain) {
+		struct tomoyo_domain_info *next_domain = NULL;
+		int retval = tomoyo_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 tomoyo_check_open_permission(domain, &bprm->file->f_path, 1);
+}
+
+#ifdef CONFIG_SYSCTL
+
+static int tomoyo_prepend(char **buffer, int *buflen, const char *str)
+{
+	int namelen = strlen(str);
+
+	if (*buflen < namelen)
+		return -ENOMEM;
+	*buflen -= namelen;
+	*buffer -= namelen;
+	memcpy(*buffer, str, namelen);
+	return 0;
+}
+
+/**
+ * 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 tomoyo_alloc(), so the caller must call tomoyo_free()
+ * if this function didn't return NULL.
+ */
+static char *sysctl_path(struct ctl_table *table)
+{
+	int buflen = TOMOYO_MAX_PATHNAME_LEN;
+	char *buf = tomoyo_alloc(buflen);
+	char *end = buf + buflen;
+	int error = -ENOMEM;
+
+	if (!buf)
+		return NULL;
+
+	*--end = '\0';
+	buflen--;
+	while (table) {
+		char buf[32];
+		const char *sp = table->procname;
+
+		if (!sp) {
+			memset(buf, 0, sizeof(buf));
+			snprintf(buf, sizeof(buf) - 1, "=%d=", table->ctl_name);
+			sp = buf;
+		}
+		if (tomoyo_prepend(&end, &buflen, sp) ||
+		    tomoyo_prepend(&end, &buflen, "/"))
+			goto out;
+		table = table->parent;
+	}
+	if (tomoyo_prepend(&end, &buflen, "/proc/sys"))
+		goto out;
+	error = tomoyo_encode(buf, end - buf, end);
+ out:
+	if (!error)
+		return buf;
+	tomoyo_free(buf);
+	return NULL;
+}
+
+static int tomoyo_sysctl(struct ctl_table *table, int op)
+{
+	int error;
+	char *name;
+
+	op &= MAY_READ | MAY_WRITE;
+	if (!op)
+		return 0;
+	name = sysctl_path(table);
+	if (!name)
+		return -ENOMEM;
+	error = tomoyo_check_file_perm(tomoyo_domain(), name, op);
+	tomoyo_free(name);
+	return error;
+}
+#endif
+
+/**
+ * tomoyo_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 tomoyo_update_result(int error)
+{
+	/* Structure for holding the result of security_path_*(). */
+	struct tomoyo_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 tomoyo_check_result_entry *entry;
+	if (!error) {
+		if (!list_empty(&list)) {
+			struct tomoyo_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;
+}
+
+/**
+ * tomoyo_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 tomoyo_save_result(const int error)
+{
+	return error ? tomoyo_update_result(error) : 0;
+}
+
+/**
+ * tomoyo_load_result - Fetch error code for security_inode_*().
+ *
+ * Returns error code saved by security_path_*().
+ */
+static int tomoyo_load_result(void)
+{
+	return tomoyo_update_result(0);
+}
+
+/* Clear error code in case security_inode_*() was not called. */
+static void tomoyo_path_clear(void)
+{
+	tomoyo_load_result();
+}
+
+static int tomoyo_path_truncate(struct path *path, loff_t length,
+				unsigned int time_attrs, struct file *filp)
+{
+	const int ret = tomoyo_check_1path_perm(tomoyo_domain(),
+						TOMOYO_TYPE_TRUNCATE_ACL,
+						path);
+	return tomoyo_save_result(ret);
+}
+
+static int tomoyo_path_unlink(struct path *parent, struct dentry *dentry)
+{
+	struct path path = { parent->mnt, dentry };
+	const int ret = tomoyo_check_1path_perm(tomoyo_domain(),
+						TOMOYO_TYPE_UNLINK_ACL,
+						&path);
+	return tomoyo_save_result(ret);
+}
+
+static int tomoyo_path_mkdir(struct path *parent, struct dentry *dentry,
+			     int mode)
+{
+	struct path path = { parent->mnt, dentry };
+	const int ret = tomoyo_check_1path_perm(tomoyo_domain(),
+						TOMOYO_TYPE_MKDIR_ACL,
+						&path);
+	return tomoyo_save_result(ret);
+}
+
+static int tomoyo_path_rmdir(struct path *parent, struct dentry *dentry)
+{
+	struct path path = { parent->mnt, dentry };
+	const int ret = tomoyo_check_1path_perm(tomoyo_domain(),
+						TOMOYO_TYPE_RMDIR_ACL,
+						&path);
+	return tomoyo_save_result(ret);
+}
+
+static int tomoyo_path_symlink(struct path *parent, struct dentry *dentry,
+			       const char *old_name)
+{
+	struct path path = { parent->mnt, dentry };
+	const int ret = tomoyo_check_1path_perm(tomoyo_domain(),
+						TOMOYO_TYPE_SYMLINK_ACL,
+						&path);
+	return tomoyo_save_result(ret);
+}
+
+static int tomoyo_path_mknod(struct path *parent, struct dentry *dentry,
+			     int mode, unsigned int dev)
+{
+	struct path path = { parent->mnt, dentry };
+	int type = TOMOYO_TYPE_CREATE_ACL;
+
+	switch (mode & S_IFMT) {
+	case S_IFCHR:
+		type = TOMOYO_TYPE_MKCHAR_ACL;
+		break;
+	case S_IFBLK:
+		type = TOMOYO_TYPE_MKBLOCK_ACL;
+		break;
+	case S_IFIFO:
+		type = TOMOYO_TYPE_MKFIFO_ACL;
+		break;
+	case S_IFSOCK:
+		type = TOMOYO_TYPE_MKSOCK_ACL;
+		break;
+	}
+	return tomoyo_save_result(tomoyo_check_1path_perm(tomoyo_domain(),
+							  type, &path));
+}
+
+static int tomoyo_path_link(struct dentry *old_dentry, struct path *new_dir,
+			    struct dentry *new_dentry)
+{
+	struct path path1 = { new_dir->mnt, old_dentry };
+	struct path path2 = { new_dir->mnt, new_dentry };
+	const int ret = tomoyo_check_2path_perm(tomoyo_domain(),
+						TOMOYO_TYPE_LINK_ACL,
+						&path1, &path2);
+	return tomoyo_save_result(ret);
+}
+
+static int tomoyo_path_rename(struct path *old_parent,
+			      struct dentry *old_dentry,
+			      struct path *new_parent,
+			      struct dentry *new_dentry)
+{
+	struct path path1 = { old_parent->mnt, old_dentry };
+	struct path path2 = { new_parent->mnt, new_dentry };
+	const int ret = tomoyo_check_2path_perm(tomoyo_domain(),
+						TOMOYO_TYPE_RENAME_ACL,
+						&path1, &path2);
+	return tomoyo_save_result(ret);
+}
+
+static int tomoyo_inode_link(struct dentry *old_dentry, struct inode *inode,
+			     struct dentry *new_dentry)
+{
+	return tomoyo_load_result();
+}
+
+static int tomoyo_inode_unlink(struct inode *inode, struct dentry *dentry)
+{
+	return tomoyo_load_result();
+}
+
+static int tomoyo_inode_symlink(struct inode *inode, struct dentry *dentry,
+				const char *name)
+{
+	return tomoyo_load_result();
+}
+
+static int tomoyo_inode_mkdir(struct inode *inode, struct dentry *dentry,
+			      int mask)
+{
+	return tomoyo_load_result();
+}
+
+static int tomoyo_inode_rmdir(struct inode *inode, struct dentry *dentry)
+{
+	return tomoyo_load_result();
+}
+
+static int tomoyo_inode_create(struct inode *dir, struct dentry *dentry,
+			       int mode)
+{
+	return tomoyo_load_result();
+}
+
+static int tomoyo_inode_mknod(struct inode *inode, struct dentry *dentry,
+			      int mode, dev_t dev)
+{
+	return tomoyo_load_result();
+}
+
+static int tomoyo_inode_rename(struct inode *old_inode,
+			       struct dentry *old_dentry,
+			       struct inode *new_inode,
+			       struct dentry *new_dentry)
+{
+	return tomoyo_load_result();
+}
+
+static int tomoyo_inode_setattr(struct dentry *dentry, struct iattr *iattr)
+{
+	return tomoyo_load_result();
+}
+
+static int tomoyo_file_fcntl(struct file *file, unsigned int cmd,
+			     unsigned long arg)
+{
+	if (cmd == F_SETFL && ((arg ^ file->f_flags) & O_APPEND))
+		return tomoyo_check_rewrite_permission(tomoyo_domain(), file);
+	return 0;
+}
+
+static int tomoyo_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 tomoyo_check_open_permission(tomoyo_domain(), &f->f_path, flags);
+}
+
+static struct security_operations tomoyo_security_ops = {
+	.name                = "tomoyo",
+	.cred_prepare        = tomoyo_cred_prepare,
+	.bprm_set_creds      = tomoyo_bprm_set_creds,
+	.bprm_check_security = tomoyo_bprm_check_security,
+#ifdef CONFIG_SYSCTL
+	.sysctl              = tomoyo_sysctl,
+#endif
+	.file_fcntl          = tomoyo_file_fcntl,
+	.dentry_open         = tomoyo_dentry_open,
+	.path_truncate       = tomoyo_path_truncate,
+	.path_unlink         = tomoyo_path_unlink,
+	.path_mkdir          = tomoyo_path_mkdir,
+	.path_rmdir          = tomoyo_path_rmdir,
+	.path_symlink        = tomoyo_path_symlink,
+	.path_mknod          = tomoyo_path_mknod,
+	.path_link           = tomoyo_path_link,
+	.path_rename         = tomoyo_path_rename,
+	.inode_create        = tomoyo_inode_create,
+	.inode_setattr       = tomoyo_inode_setattr,
+	.inode_unlink        = tomoyo_inode_unlink,
+	.inode_mkdir         = tomoyo_inode_mkdir,
+	.inode_rmdir         = tomoyo_inode_rmdir,
+	.inode_symlink       = tomoyo_inode_symlink,
+	.inode_mknod         = tomoyo_inode_mknod,
+	.inode_link          = tomoyo_inode_link,
+	.inode_rename        = tomoyo_inode_rename,
+	.path_clear          = tomoyo_path_clear,
+};
+
+static int __init tomoyo_init(void)
+{
+	struct cred *cred = (struct cred *) current_cred();
+
+	if (!security_module_enable(&tomoyo_security_ops))
+		return 0;
+	/* register ourselves with the security framework */
+	if (register_security(&tomoyo_security_ops))
+		panic("Failure registering TOMOYO Linux");
+	printk(KERN_INFO "TOMOYO Linux initialized\n");
+	cred->security = &tomoyo_kernel_domain;
+	return 0;
+}
+
+security_initcall(tomoyo_init);
--- /dev/null
+++ linux-2.6.28-rc5-mm1/security/tomoyo/tomoyo.h
@@ -0,0 +1,107 @@
+/*
+ * 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/11/11
+ *
+ */
+
+#ifndef _SECURITY_TOMOYO_TOMOYO_H
+#define _SECURITY_TOMOYO_TOMOYO_H
+
+struct tomoyo_path_info;
+struct path;
+struct inode;
+struct linux_binprm;
+struct pt_regs;
+struct tomoyo_page_buffer;
+
+int tomoyo_check_file_perm(struct tomoyo_domain_info *domain,
+			   const char *filename, const u8 perm);
+int tomoyo_check_exec_perm(struct tomoyo_domain_info *domain,
+			   const struct tomoyo_path_info *filename,
+			   struct tomoyo_page_buffer *buf);
+int tomoyo_check_open_permission(struct tomoyo_domain_info *domain,
+				 struct path *path, const int flag);
+int tomoyo_check_1path_perm(struct tomoyo_domain_info *domain,
+			    const u8 operation, struct path *path);
+int tomoyo_check_2path_perm(struct tomoyo_domain_info *domain,
+			    const u8 operation, struct path *path1,
+			    struct path *path2);
+int tomoyo_check_rewrite_permission(struct tomoyo_domain_info *domain,
+				    struct file *filp);
+int tomoyo_find_next_domain(struct linux_binprm *bprm,
+			    struct tomoyo_domain_info **next_domain);
+
+/* Index numbers for Access Controls. */
+
+#define TOMOYO_TYPE_SINGLE_PATH_ACL                 0
+#define TOMOYO_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 TOMOYO_TYPE_READ_WRITE_ACL    0
+#define TOMOYO_TYPE_EXECUTE_ACL       1
+#define TOMOYO_TYPE_READ_ACL          2
+#define TOMOYO_TYPE_WRITE_ACL         3
+#define TOMOYO_TYPE_CREATE_ACL        4
+#define TOMOYO_TYPE_UNLINK_ACL        5
+#define TOMOYO_TYPE_MKDIR_ACL         6
+#define TOMOYO_TYPE_RMDIR_ACL         7
+#define TOMOYO_TYPE_MKFIFO_ACL        8
+#define TOMOYO_TYPE_MKSOCK_ACL        9
+#define TOMOYO_TYPE_MKBLOCK_ACL      10
+#define TOMOYO_TYPE_MKCHAR_ACL       11
+#define TOMOYO_TYPE_TRUNCATE_ACL     12
+#define TOMOYO_TYPE_SYMLINK_ACL      13
+#define TOMOYO_TYPE_REWRITE_ACL      14
+#define TOMOYO_MAX_SINGLE_PATH_OPERATION 15
+
+#define TOMOYO_TYPE_LINK_ACL         0
+#define TOMOYO_TYPE_RENAME_ACL       1
+#define TOMOYO_MAX_DOUBLE_PATH_OPERATION 2
+
+#define TOMOYO_DOMAINPOLICY          0
+#define TOMOYO_EXCEPTIONPOLICY       1
+#define TOMOYO_DOMAIN_STATUS         2
+#define TOMOYO_PROCESS_STATUS        3
+#define TOMOYO_MEMINFO               4
+#define TOMOYO_SELFDOMAIN            5
+#define TOMOYO_VERSION               6
+#define TOMOYO_PROFILE               7
+#define TOMOYO_MANAGER               8
+#define TOMOYO_UPDATESCOUNTER        9
+
+extern struct tomoyo_domain_info tomoyo_kernel_domain;
+
+static inline struct tomoyo_domain_info *tomoyo_domain(void)
+{
+	return current_cred()->security;
+}
+
+/* Caller holds tasklist_lock spinlock. */
+static inline struct tomoyo_domain_info *tomoyo_real_domain(struct task_struct
+							    *task)
+{
+	/***** CRITICAL SECTION START *****/
+	const struct cred *cred = get_task_cred(task);
+	struct tomoyo_domain_info *domain = cred->security;
+
+	put_cred(cred);
+	return domain;
+	/***** CRITICAL SECTION END *****/
+}
+
+#endif /* !defined(_SECURITY_TOMOYO_TOMOYO_H) */

-- 

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

* [TOMOYO #13 (mmotm 2008-11-19-02-19) 10/11] Kconfig and Makefile
  2008-11-20 11:25 [TOMOYO #13 (mmotm 2008-11-19-02-19) 00/11] TOMOYO Linux Tetsuo Handa
                   ` (8 preceding siblings ...)
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 09/11] LSM adapter functions Tetsuo Handa
@ 2008-11-20 11:25 ` Tetsuo Handa
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 11/11] MAINTAINERS info Tetsuo Handa
  2008-11-29 11:59 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 00/11] TOMOYO Linux Tetsuo Handa
  11 siblings, 0 replies; 29+ messages in thread
From: Tetsuo Handa @ 2008-11-20 11:25 UTC (permalink / raw)
  To: Andrew Morton
  Cc: linux-security-module, linux-kernel, Kentaro Takeda, Tetsuo Handa

[-- Attachment #1: tomoyo-makefile.patch --]
[-- Type: text/plain, Size: 1836 bytes --]

TOMOYO uses LSM hooks for pathname based access control and securityfs support.

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-rc5-mm1.orig/security/Kconfig
+++ linux-2.6.28-rc5-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-rc5-mm1.orig/security/Makefile
+++ linux-2.6.28-rc5-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-rc5-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-rc5-mm1/security/tomoyo/Makefile
@@ -0,0 +1 @@
+obj-y = common.o realpath.o tomoyo.o domain.o file.o

-- 

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

* [TOMOYO #13 (mmotm 2008-11-19-02-19) 11/11] MAINTAINERS info
  2008-11-20 11:25 [TOMOYO #13 (mmotm 2008-11-19-02-19) 00/11] TOMOYO Linux Tetsuo Handa
                   ` (9 preceding siblings ...)
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 10/11] Kconfig and Makefile Tetsuo Handa
@ 2008-11-20 11:25 ` Tetsuo Handa
  2008-11-29 11:59 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 00/11] TOMOYO Linux Tetsuo Handa
  11 siblings, 0 replies; 29+ messages in thread
From: Tetsuo Handa @ 2008-11-20 11:25 UTC (permalink / raw)
  To: Andrew Morton; +Cc: linux-security-module, linux-kernel, Kentaro Takeda

[-- Attachment #1: tomoyo-maintainers.patch --]
[-- Type: text/plain, Size: 1334 bytes --]

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-rc5-mm1.orig/MAINTAINERS
+++ linux-2.6.28-rc5-mm1/MAINTAINERS
@@ -4193,6 +4193,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] 29+ messages in thread

* Re: [TOMOYO #13 (mmotm 2008-11-19-02-19) 00/11] TOMOYO Linux
  2008-11-20 11:25 [TOMOYO #13 (mmotm 2008-11-19-02-19) 00/11] TOMOYO Linux Tetsuo Handa
                   ` (10 preceding siblings ...)
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 11/11] MAINTAINERS info Tetsuo Handa
@ 2008-11-29 11:59 ` Tetsuo Handa
  11 siblings, 0 replies; 29+ messages in thread
From: Tetsuo Handa @ 2008-11-29 11:59 UTC (permalink / raw)
  To: chrisw, jmorris, sds, serue, dhowells, paulmck, viro, hch,
	crispin, casey
  Cc: akpm, linux-security-module, linux-kernel

Hello.

As I see no comments on #13, I'd like to ask Andrew Morton to merge #13
and start testing on -mm tree.
Please respond soon if you have any problems/objections.
Also, please respond if you can give #13 "Acked-by:" response.

Regards.

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

* Re: [TOMOYO #13 (mmotm 2008-11-19-02-19) 01/11] Introduce security_path_clear() hook.
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 01/11] Introduce security_path_clear() hook Tetsuo Handa
@ 2008-12-01 20:00   ` Stephen Smalley
  2008-12-02 10:39     ` Tetsuo Handa
  0 siblings, 1 reply; 29+ messages in thread
From: Stephen Smalley @ 2008-12-01 20:00 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: Andrew Morton, linux-security-module, linux-kernel,
	Kentaro Takeda, Toshiharu Harada, Al Viro, Christoph Hellwig,
	Crispin Cowan, Casey Schaufler, James Morris

On Thu, 2008-11-20 at 20:25 +0900, Tetsuo Handa wrote:
> plain text document attachment (introduce-security_path_clear.patch)
> 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.

This seems very fragile and unmaintainable to me.  The fact that you
even need a security_path_clear() hook suggests that something is wrong
with the other security_path* hooks. I'd suggest that you explicitly
pass the result of the security_path* hooks down to the security_inode*
hooks instead.  What do others think?

-- 
Stephen Smalley
National Security Agency


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

* Re: [TOMOYO #13 (mmotm 2008-11-19-02-19) 09/11] LSM adapter functions.
  2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 09/11] LSM adapter functions Tetsuo Handa
@ 2008-12-01 20:10   ` Stephen Smalley
  2008-12-02 10:40     ` Tetsuo Handa
  0 siblings, 1 reply; 29+ messages in thread
From: Stephen Smalley @ 2008-12-01 20:10 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: Andrew Morton, linux-security-module, linux-kernel,
	Kentaro Takeda, Toshiharu Harada, David Howells, James Morris

On Thu, 2008-11-20 at 20:25 +0900, Tetsuo Handa wrote:
> plain text document attachment (tomoyo-hooks.patch)
> This is LSM adapter for TOMOYO.
> This version of TOMOYO assigns only one variable "struct tomoyo_domain_info *"
> to "current->cred->security".
> 
> Future version will require two variables "struct tomoyo_domain_info *" and
> "u32".
> http://tomoyo.sourceforge.jp/cgi-bin/lxr/source/include/linux/sched.h#L1311
> Well, how to implement this while "current->cred->security" cannot be modified
> freely?
> 
> To be honest, the only object which TOMOYO needs to assign variables is
> "struct task_struct". TOMOYO's "T" means "task".
> TOMOYO utilizes characteristics of "struct task_struct".
> For TOMOYO, use of per task variables is vital.
> 
> Removal of security_task_alloc() and security_task_free() by introduction of
> COW credentials will become a nightmare when TOMOYO tries to add "u32".
> Of cource, if TOMOYO is allowed to add "u32" to "struct task_struct",
> COW credentials won't become a nightmare.

Can you explain exactly how you are using this per-task state and why it
is a problem to create a new cred in the situations where you need to
modify it?  I think that this issue needs to be addressed up front.

-- 
Stephen Smalley
National Security Agency


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

* Re: [TOMOYO #13 (mmotm 2008-11-19-02-19) 01/11] Introduce security_path_clear() hook.
  2008-12-01 20:00   ` Stephen Smalley
@ 2008-12-02 10:39     ` Tetsuo Handa
  2008-12-02 13:48       ` Stephen Smalley
  0 siblings, 1 reply; 29+ messages in thread
From: Tetsuo Handa @ 2008-12-02 10:39 UTC (permalink / raw)
  To: sds
  Cc: akpm, linux-security-module, linux-kernel, viro, hch, crispin,
	casey, jmorris, takedakn, haradats, penguin-kernel

Hello.

Stephen Smalley wrote:
> On Thu, 2008-11-20 at 20:25 +0900, Tetsuo Handa wrote:
> > plain text document attachment (introduce-security_path_clear.patch)
> > 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.
> 
> This seems very fragile and unmaintainable to me.  The fact that you
> even need a security_path_clear() hook suggests that something is wrong
> with the other security_path* hooks. I'd suggest that you explicitly
> pass the result of the security_path* hooks down to the security_inode*
> hooks instead.  What do others think?

You are recommending us to pass variables required for security_inode_*() via
stack memory rather than private hash, aren't you?
I think there are two problems.

One is that the variable passed via stack memory won't be used by SELinux and
SMACK and "CONFIG_SECURITY=n kernels", which will be a waste of stack memory.

The other one is that TOMOYO will need another variable for telling how the
security_inode_*() are called. Passing the variable via stack memory requires
modification of all vfs_*() calls, but TOMOYO doesn't check requests issued
by (e.g.) stackable filesystems.

By the way, this security_path_clear() is intended to be able to return DAC's
error code in priority to MAC's error code, but there are two problems for
TOMOYO.
One is that pathnames which will be later denied by DAC are appended by
TOMOYO's learning mode (i.e. garbage entries appears in the learned policy).
The other is that warning messages on pathnames which will be later denied by
DAC are generated by TOMOYO's enforcing mode.

Thus, it will be preferable for TOMOYO to "do MAC checks after DAC checks"
rather than to "return DAC's error in priority to MAC's error while doing MAC
checks before DAC checks".

To do so, "security_path_*() should be replaced by security_path_set(vfsmount)"
and "let security_inode_*() do MAC checks using the result of
security_path_set()" and "let security_path_clear() clear the result of
security_path_set() in case security_inode_*() was not called".

So, I think storing the pathname of "struct vfsmount" in the form of "char *"
into private hash at security_path_set() and clearing the private hash at
security_path_clear() should be most preferable.

Regards.

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

* Re: [TOMOYO #13 (mmotm 2008-11-19-02-19) 09/11] LSM adapter functions.
  2008-12-01 20:10   ` Stephen Smalley
@ 2008-12-02 10:40     ` Tetsuo Handa
  0 siblings, 0 replies; 29+ messages in thread
From: Tetsuo Handa @ 2008-12-02 10:40 UTC (permalink / raw)
  To: sds
  Cc: akpm, linux-security-module, linux-kernel, dhowells, jmorris,
	takedakn, haradats, penguin-kernel

Hello.

Stephen Smalley wrote:
> > Removal of security_task_alloc() and security_task_free() by introduction of
> > COW credentials will become a nightmare when TOMOYO tries to add "u32".
> > Of cource, if TOMOYO is allowed to add "u32" to "struct task_struct",
> > COW credentials won't become a nightmare.
> 
> Can you explain exactly how you are using this per-task state and why it
> is a problem to create a new cred in the situations where you need to
> modify it?  I think that this issue needs to be addressed up front.
> 
Use of COW credentials saves memory usage as long as per-task variable doesn't
change, but creating a new cred after fork() introduces new error paths
(i.e. memory allocation failure) when per-task variable changes, and COW
credentials will no longer save memory if per-task variable differs each other.

I use this per-task variable for tracking and restricting access requests
issued by a process.
http://tomoyo.sourceforge.jp/cgi-bin/lxr/source/fs/tomoyo_audit.c#L284
TOMOYO (version 1.6.x) provides "ability to change what request a process can
issue without involving execve()" according to "requests that process issued in
the past".
It is similar to AppArmor's chhat(), but it is done by the kernel and it is
done without modifying userland programs.

I use this per-task variable for remembering "whether the process is allowed to
modify policy or not". Since "the pathname of a program which is allowed to
modify policy" can be changed by package manager (e.g. "rpm" and "dpkg"),
I have to remember "whether the process ever had a pathname which is allowed to
modify policy or not".
http://tomoyo.sourceforge.jp/cgi-bin/lxr/source/fs/ccs_common.c#L1420
http://tomoyo.sourceforge.jp/cgi-bin/lxr/source/fs/tomoyo_domain.c#L1700

I use this per-task variable for holding "type of the process".
TOMOYO (version 1.6.x) provides "ability to record/verify/modify parameters
passed to execve()" by executing a userland helper instead of a program
passed to execve().
http://tomoyo.sourceforge.jp/cgi-bin/lxr/source/fs/tomoyo_domain.c#L1736

And maybe more in future.
Worrying about memory allocation failure complicates things. Thus I want to
allocate memory on fork() and not after fork(). For that purpose, directly
assigning "u32" to "struct task_struct" is preferable over allocating on
demand.

Regards.

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

* Re: [TOMOYO #13 (mmotm 2008-11-19-02-19) 01/11] Introduce security_path_clear() hook.
  2008-12-02 10:39     ` Tetsuo Handa
@ 2008-12-02 13:48       ` Stephen Smalley
  2008-12-03  8:49         ` Kentaro Takeda
  0 siblings, 1 reply; 29+ messages in thread
From: Stephen Smalley @ 2008-12-02 13:48 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: akpm, linux-security-module, linux-kernel, viro, hch, crispin,
	casey, jmorris, takedakn, haradats

On Tue, 2008-12-02 at 19:39 +0900, Tetsuo Handa wrote:
> Hello.
> 
> Stephen Smalley wrote:
> > On Thu, 2008-11-20 at 20:25 +0900, Tetsuo Handa wrote:
> > > plain text document attachment (introduce-security_path_clear.patch)
> > > 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.
> > 
> > This seems very fragile and unmaintainable to me.  The fact that you
> > even need a security_path_clear() hook suggests that something is wrong
> > with the other security_path* hooks. I'd suggest that you explicitly
> > pass the result of the security_path* hooks down to the security_inode*
> > hooks instead.  What do others think?
> 
> You are recommending us to pass variables required for security_inode_*() via
> stack memory rather than private hash, aren't you?

To be precise, I was recommending passing the return value of
security_path* down to security_inode* explicitly rather than doing it
implicitly as you presently do.  Thereby making the actual control flow
and relationship between the security_path* and security_inode* hooks
evident.  However, I guess that is moot given your statements below.

> I think there are two problems.
> 
> One is that the variable passed via stack memory won't be used by SELinux and
> SMACK and "CONFIG_SECURITY=n kernels", which will be a waste of stack memory.

I'm more concerned with the hook interface being understandable and
maintainable.

> The other one is that TOMOYO will need another variable for telling how the
> security_inode_*() are called. Passing the variable via stack memory requires
> modification of all vfs_*() calls, but TOMOYO doesn't check requests issued
> by (e.g.) stackable filesystems.

I'm not clear on why that requires a separate argument; if the caller is
passing in the access decision result as an input, then certain callers
(e.g. stackable filesystems) can always pass 0 (success).

> By the way, this security_path_clear() is intended to be able to return DAC's
> error code in priority to MAC's error code, but there are two problems for
> TOMOYO.
> One is that pathnames which will be later denied by DAC are appended by
> TOMOYO's learning mode (i.e. garbage entries appears in the learned policy).
> The other is that warning messages on pathnames which will be later denied by
> DAC are generated by TOMOYO's enforcing mode.
> 
> Thus, it will be preferable for TOMOYO to "do MAC checks after DAC checks"
> rather than to "return DAC's error in priority to MAC's error while doing MAC
> checks before DAC checks".

It sounds like the existing security_path* hooks are not adequate for
your needs then, and that patch should not in fact be merged.  Yes?

> To do so, "security_path_*() should be replaced by security_path_set(vfsmount)"
> and "let security_inode_*() do MAC checks using the result of
> security_path_set()" and "let security_path_clear() clear the result of
> security_path_set() in case security_inode_*() was not called".
> 
> So, I think storing the pathname of "struct vfsmount" in the form of "char *"
> into private hash at security_path_set() and clearing the private hash at
> security_path_clear() should be most preferable.

Then I guess you need to redo your patches along those lines and
re-submit them.  Likely starting with just a patch adding the
security_path_set/clear hooks, posted to lsm and fsdevel.

-- 
Stephen Smalley
National Security Agency


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

* Re: [TOMOYO #13 (mmotm 2008-11-19-02-19) 01/11] Introduce security_path_clear() hook.
  2008-12-02 13:48       ` Stephen Smalley
@ 2008-12-03  8:49         ` Kentaro Takeda
  2008-12-03  8:56           ` [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks Kentaro Takeda
  0 siblings, 1 reply; 29+ messages in thread
From: Kentaro Takeda @ 2008-12-03  8:49 UTC (permalink / raw)
  To: sds
  Cc: penguin-kernel, akpm, linux-security-module, linux-kernel, viro,
	hch, crispin, casey, jmorris, haradats

Stephen Smalley wrote:
> To be precise, I was recommending passing the return value of
> security_path* down to security_inode* explicitly rather than doing it
> implicitly as you presently do.  Thereby making the actual control flow
> and relationship between the security_path* and security_inode* hooks
> evident.  However, I guess that is moot given your statements below.
I think so too.

>> I think there are two problems.
>>
>> One is that the variable passed via stack memory won't be used by SELinux and
>> SMACK and "CONFIG_SECURITY=n kernels", which will be a waste of stack memory.
> 
> I'm more concerned with the hook interface being understandable and
> maintainable.
I see.

>> The other one is that TOMOYO will need another variable for telling how the
>> security_inode_*() are called. Passing the variable via stack memory requires
>> modification of all vfs_*() calls, but TOMOYO doesn't check requests issued
>> by (e.g.) stackable filesystems.
> 
> I'm not clear on why that requires a separate argument; if the caller is
> passing in the access decision result as an input, then certain callers
> (e.g. stackable filesystems) can always pass 0 (success).
If we use stack memory to pass the access decision result from security_path_*() 
to security_inode_*(), this method seems possible.

>> By the way, this security_path_clear() is intended to be able to return DAC's
>> error code in priority to MAC's error code, but there are two problems for
>> TOMOYO.
>> One is that pathnames which will be later denied by DAC are appended by
>> TOMOYO's learning mode (i.e. garbage entries appears in the learned policy).
>> The other is that warning messages on pathnames which will be later denied by
>> DAC are generated by TOMOYO's enforcing mode.
>>
>> Thus, it will be preferable for TOMOYO to "do MAC checks after DAC checks"
>> rather than to "return DAC's error in priority to MAC's error while doing MAC
>> checks before DAC checks".
> 
> It sounds like the existing security_path* hooks are not adequate for
> your needs then, and that patch should not in fact be merged.  Yes?
Sorry for confusing you. security_path_*() hooks are adequate for TOMOYO 
functionality itself. But they are inadequate for performing DAC before MAC, 
which we eventually want to do. We've reached this "passing vfsmount's pathname" 
approach after proposing the previous patch. The new approach is a little 
divergence of the existing approach, which Serge has patiently advised us. 
It should be more suitable for DAC-before-MAC than the previous patch, we think.

>> To do so, "security_path_*() should be replaced by security_path_set(vfsmount)"
>> and "let security_inode_*() do MAC checks using the result of
>> security_path_set()" and "let security_path_clear() clear the result of
>> security_path_set() in case security_inode_*() was not called".
>>
>> So, I think storing the pathname of "struct vfsmount" in the form of "char *"
>> into private hash at security_path_set() and clearing the private hash at
>> security_path_clear() should be most preferable.
> 
> Then I guess you need to redo your patches along those lines and
> re-submit them.  Likely starting with just a patch adding the
> security_path_set/clear hooks, posted to lsm and fsdevel.
I'll post it soon.

If we pass the access decision result through stack memory, we don't need 
security_path_clear() as you mentioned. However, if we pass the vfsmount's 
pathname, security_path_clear() is still needed in order to free the pathname.

Regards,


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

* [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks.
  2008-12-03  8:49         ` Kentaro Takeda
@ 2008-12-03  8:56           ` Kentaro Takeda
  2008-12-03 14:13             ` Stephen Smalley
  0 siblings, 1 reply; 29+ messages in thread
From: Kentaro Takeda @ 2008-12-03  8:56 UTC (permalink / raw)
  To: sds, serue, linux-security-module, linux-fsdevel
  Cc: akpm, linux-kernel, haradats

Stephen, Serge,
Here is the patch for introducing new security_path_set()/clear() hooks.

This patch enables LSM module to remember vfsmount's pathname so that it can 
calculate absolute pathname in security_inode_*(). Since actual MAC can be 
performed after DAC, there will not be any noise in auditing and learning 
features. This patch currently assumes that the vfsmount's pathname is stored in 
hash table in LSM module. (Should I use stack memory?)

Since security_inode_*() are not always called after security_path_set(), 
security_path_clear() hook is needed to free the remembered pathname.

Andrew,
If lsm and fs guys accept this patch, please replace 
introduce-new-lsm-hooks-where-vfsmount-is-available.patch with this patch.


Otherwise, if we could call DAC functions, such as may_create(), in LSM module, 
security_path_clear() hook would not be needed. This approach is simple, but it 
might be objected because of layering. ;-)

Regards,

----- What is this patch for? -----

There are security_inode_*() LSM hooks for attribute-based MAC, but they are not
suitable for pathname-based MAC because they don't receive "struct vfsmount"
information.

----- How this patch was developed? -----

Two pathname-based MACs, AppArmor and TOMOYO Linux, are trying to merge
upstream. But because of "struct vfsmount" problem, they have been unable to
merge upstream.

Here are the list of approaches and the reasons of denial.

(1) Not using LSM
 http://lwn.net/Articles/277833/

 This approach was rejected because security modules should use LSM because the
 whole idea behind LSM was to have a single set of hooks for all security
 modules; if every module now adds its own set of hooks, that purpose will have
 been defeated and the kernel will turn into a big mess of security hooks.

(2) Retrieving "struct vfsmount" from "struct task_struct".
 http://lkml.org/lkml/2007/11/5/388

 Since "struct task_struct" contains list of "struct vfsmount",
 "struct vfsmount" which corresponds to "struct dentry" can be retrieved from
 the list unless "mount --bind" is used.

 This approach turned out to cause a critical problem that getting namespace_sem
 lock from security_inode_*() triggers AB-BA deadlock.

(3) Adding "struct vfsmount" parameter to VFS helper functions.
 http://lkml.org/lkml/2008/5/29/207

 This approach adds "struct vfsmount" to VFS helper functions (e.g. vfs_mkdir()
 and vfs_symlink()) and LSM hooks inside VFS helper functions. This approach is
 helpful for not only AppArmor and TOMOYO Linux 2.x but also SELinux and
 auditing purpose, for this approach allows existent LSM users to use pathnames
 in their access control and audit logs.

 This approach was rejected by Al Viro, the VFS maintainer, because he thinks
 individual filesystem should remain "struct vfsmount"-unaware and VFS helper
 functions should not receive "struct vfsmount".

 Al Viro also suggested to move existing security_inode_*() to out of VFS
 helper functions so that security_inode_*() can receive "struct vfsmount"
 without modifying VFS helper functions, but this suggestion was opposed by
 Stephen Smalley because changing the order of permission checks (i.e.
 MAC checks before DAC checks) is not acceptable.

(4) Passing "struct vfsmount" via "struct task_struct".
 http://lkml.org/lkml/2007/11/16/157

 Since we didn't understand the reason why accessing "struct vfsmount" from
 LSM hooks inside VFS helper functions is not acceptable, we thought the reason
 why VFS helper functions don't receive "struct vfsmount" is the amount of
 modifications needed to do so. Thus, we proposed to pass "struct vfsmount" via
 "struct task_struct" so that modifications remain minimal.

 This approach was rejected because this is an abuse of "struct task_struct".

(5) Remembering pathname of "struct vfsmount" via "struct task_struct".
 http://lkml.org/lkml/2008/8/19/16

 Since pathname of a "struct dentry" up to the mount point can be calculated
 without "struct vfsmount", absolute pathname of a "struct dentry" can be
 calculated if "struct task_struct" can remember absolute pathname of a
 "struct vfsmount" which corresponds to "struct dentry".
 As we now understand that Al Viro is opposing to access "struct vfsmount" from
 LSM hooks inside VFS helper functions, we gave up delivering "struct vfsmount"
 to LSM hooks inside VFS helper functions.
 Kernel 2.6.26 introduced read-only bind mount feature, and hooks for that
 feature (i.e. mnt_want_write() and mnt_drop_write()) were inserted around
 VFS helper functions call. Since mnt_want_write() receives "struct vfsmount"
 which corresponds to "struct dentry" that will be passed to subsequent VFS
 helper functions call, we associated pathname of "struct vfsmount" with
 "struct task_struct" instead of associating "struct vfsmount" itself.

 This approach was not explicitly rejected, but there seems to be performance
 problem.

(6) Introducing new LSM hooks.
 http://lkml.org/lkml/2008/9/24/48

 We understand that adding new LSM hooks which receive "struct vfsmount" outside
 VFS helper functions is the most straightforward approach. This approach has
 less impact to existing LSM module and no impact to VFS helper functions.

(7) Remembering pathname of "struct vfsmount" via new LSM hooks.
 (this patch)

 We proposed (6) so that we can implement MAC which can take an absolute
 pathname of a requested file into account. We embedded DAC's code copied from
 VFS helper functions into (6). But we received a comment that copying DAC's
 code is not a good thing. Thus, we once gave up doing DAC checks before MAC
 checks.
 
 But, we do want to do DAC checks before MAC checks so that we can avoid MAC's
 noisy error messages generated by access requests which will be rejected by
 DAC.
 
 There are two restrictions for now.
 
 One is that we should avoid accessing "struct vfsmount" inside VFS helper
 functions and security_inode_*() so that we can keep clear separation between
 vfsmount-aware layer and vfsmount-unaware layer. Thus, there is no chance to
 pass "struct vfsmount" parameter to VFS helper functions and
 security_inode_*().

 The other is that we should avoid copying DAC's code into security_path_*().
 The security_path_*() which was proposed in (6) allows us to implement MAC
 which can take an absolute pathname of a requested file into account, but
 keeps us away from doing DAC checks before MAC checks.
 
 We were using security_path_*(), but we still want to do DAC checks before
 MAC checks. Thus, we propose this patch which is a variant of (5) (which was
 not explicitly rejected). This patch introduces
 security_path_set(struct vfsmount *) and security_path_clear().
 We want to use these hooks as follows.
 
 (a) Let security_path_set() which are inserted before calling VFS helper
     functions remember the absolute pathname of "struct vfsmount" and
     store the result which are in the form of "char *" into private hash.
 
 (b) Let security_inode_*() do MAC using the pathname stored by
     security_path_set().
 
 (c) Let security_path_clear() which are inserted after calling VFS helper
     functions clear the pathname from private hash.
 
 This approach is similar to (5), but to avoid performance problem,
 this patch inserts into minimal locations that TOMOYO Linux needs.

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               |   43 +++++++++++++++++++++++++++++++++++++++++++
 fs/open.c                |    6 ++++++
 include/linux/security.h |   24 ++++++++++++++++++++++++
 net/unix/af_unix.c       |    5 +++++
 security/Kconfig         |   10 ++++++++++
 security/capability.c    |   17 +++++++++++++++++
 security/security.c      |   16 ++++++++++++++++
 7 files changed, 121 insertions(+)

--- linux-2.6.28-rc7-mm1.orig/fs/namei.c
+++ linux-2.6.28-rc7-mm1/fs/namei.c
@@ -1556,12 +1556,15 @@ int may_open(struct nameidata *nd, int a
 		 * Refuse to truncate files with mandatory locks held on them.
 		 */
 		error = locks_verify_locked(inode);
+		if (!error)
+			error = security_path_set(nd->path.mnt);
 		if (!error) {
 			DQUOT_INIT(inode);
 
 			error = do_truncate(dentry, 0,
 					    ATTR_MTIME|ATTR_CTIME|ATTR_OPEN,
 					    NULL);
+			security_path_clear();
 		}
 		put_write_access(inode);
 		if (error)
@@ -1586,7 +1589,12 @@ static int __open_namei_create(struct na
 
 	if (!IS_POSIXACL(dir->d_inode))
 		mode &= ~current->fs->umask;
+	error = security_path_set(nd->path.mnt);
+	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);
 	nd->path.dentry = path->dentry;
@@ -1999,6 +2007,9 @@ asmlinkage long sys_mknodat(int dfd, con
 	error = mnt_want_write(nd.path.mnt);
 	if (error)
 		goto out_dput;
+	error = security_path_set(nd.path.mnt);
+	if (error)
+		goto out_drop_write;
 	switch (mode & S_IFMT) {
 		case 0: case S_IFREG:
 			error = vfs_create(nd.path.dentry->d_inode,dentry,mode,&nd);
@@ -2011,6 +2022,8 @@ 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:
 	dput(dentry);
@@ -2070,7 +2083,12 @@ asmlinkage long sys_mkdirat(int dfd, con
 	error = mnt_want_write(nd.path.mnt);
 	if (error)
 		goto out_dput;
+	error = security_path_set(nd.path.mnt);
+	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:
 	dput(dentry);
@@ -2180,7 +2198,12 @@ static long do_rmdir(int dfd, const char
 	error = mnt_want_write(nd.path.mnt);
 	if (error)
 		goto exit3;
+	error = security_path_set(nd.path.mnt);
+	if (error)
+		goto exit4;
 	error = vfs_rmdir(nd.path.dentry->d_inode, dentry);
+	security_path_clear();
+exit4:
 	mnt_drop_write(nd.path.mnt);
 exit3:
 	dput(dentry);
@@ -2265,7 +2288,12 @@ static long do_unlinkat(int dfd, const c
 		error = mnt_want_write(nd.path.mnt);
 		if (error)
 			goto exit2;
+		error = security_path_set(nd.path.mnt);
+		if (error)
+			goto exit3;
 		error = vfs_unlink(nd.path.dentry->d_inode, dentry);
+		security_path_clear();
+exit3:
 		mnt_drop_write(nd.path.mnt);
 	exit2:
 		dput(dentry);
@@ -2346,7 +2374,12 @@ asmlinkage long sys_symlinkat(const char
 	error = mnt_want_write(nd.path.mnt);
 	if (error)
 		goto out_dput;
+	error = security_path_set(nd.path.mnt);
+	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:
 	dput(dentry);
@@ -2443,7 +2476,12 @@ asmlinkage long sys_linkat(int olddfd, c
 	error = mnt_want_write(nd.path.mnt);
 	if (error)
 		goto out_dput;
+	error = security_path_set(nd.path.mnt);
+	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:
 	dput(new_dentry);
@@ -2677,8 +2715,13 @@ asmlinkage long sys_renameat(int olddfd,
 	error = mnt_want_write(oldnd.path.mnt);
 	if (error)
 		goto exit5;
+	error = security_path_set(oldnd.path.mnt);
+	if (error)
+		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:
 	dput(new_dentry);
--- linux-2.6.28-rc7-mm1.orig/fs/open.c
+++ linux-2.6.28-rc7-mm1/fs/open.c
@@ -272,9 +272,12 @@ static long do_sys_truncate(const char _
 		goto put_write_and_out;
 
 	error = locks_verify_truncate(inode, NULL, length);
+	if (!error)
+		error = security_path_set(path.mnt);
 	if (!error) {
 		DQUOT_INIT(inode);
 		error = do_truncate(path.dentry, length, 0, NULL);
+		security_path_clear();
 	}
 
 put_write_and_out:
@@ -329,7 +332,10 @@ static long do_sys_ftruncate(unsigned in
 
 	error = locks_verify_truncate(inode, file, length);
 	if (!error)
+		error = security_path_set(file->f_path.mnt);
+	if (!error)
 		error = do_truncate(dentry, length, ATTR_MTIME|ATTR_CTIME, file);
+	security_path_clear();
 out_putf:
 	fput(file);
 out:
--- linux-2.6.28-rc7-mm1.orig/include/linux/security.h
+++ linux-2.6.28-rc7-mm1/include/linux/security.h
@@ -470,6 +470,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_set:
+ *	Calculate pathname of vfsmount for subsequent vfs operation.
+ *	@vfsmnt contains the vfsmount structure.
+ *	Return 0 on success, negative value otherwise.
+ * @path_clear:
+ *	Clear pathname of vfsmount calculated by @path_set.
  *
  * Security hooks for file operations
  *
@@ -1331,6 +1337,11 @@ struct security_operations {
 				   struct super_block *newsb);
 	int (*sb_parse_opts_str) (char *options, struct security_mnt_opts *opts);
 
+#ifdef CONFIG_SECURITY_PATH
+	int (*path_set) (struct vfsmount *vfsmnt);
+	void (*path_clear) (void);
+#endif
+
 	int (*inode_alloc_security) (struct inode *inode);
 	void (*inode_free_security) (struct inode *inode);
 	int (*inode_init_security) (struct inode *inode, struct inode *dir,
@@ -2705,6 +2716,19 @@ static inline void security_skb_classify
 
 #endif	/* CONFIG_SECURITY_NETWORK_XFRM */
 
+#ifdef CONFIG_SECURITY_PATH
+int security_path_set(struct vfsmount *vfsmnt);
+void security_path_clear(void);
+#else	/* CONFIG_SECURITY_PATH */
+static inline int security_path_set(struct vfsmount *vfsmnt)
+{
+	return 0;
+}
+static inline void security_path_clear(void)
+{
+}
+#endif	/* CONFIG_SECURITY_PATH */
+
 #ifdef CONFIG_KEYS
 #ifdef CONFIG_SECURITY
 
--- linux-2.6.28-rc7-mm1.orig/net/unix/af_unix.c
+++ linux-2.6.28-rc7-mm1/net/unix/af_unix.c
@@ -836,7 +836,12 @@ static int unix_bind(struct socket *sock
 		err = mnt_want_write(nd.path.mnt);
 		if (err)
 			goto out_mknod_dput;
+		err = security_path_set(nd.path.mnt);
+		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)
 			goto out_mknod_dput;
--- linux-2.6.28-rc7-mm1.orig/security/Kconfig
+++ linux-2.6.28-rc7-mm1/security/Kconfig
@@ -81,6 +81,16 @@ config SECURITY_NETWORK_XFRM
 	  IPSec.
 	  If you are unsure how to answer this question, answer N.
 
+config SECURITY_PATH
+	bool "Security hooks for pathname based access control"
+	depends on SECURITY
+	help
+	  This adds security_path_set() and security_path_clear()
+	  hooks for pathname based access control.
+	  If enabled, a security module can use these hooks to
+	  implement pathname based access controls.
+	  If you are unsure how to answer this question, answer N.
+
 config SECURITY_FILE_CAPABILITIES
 	bool "File POSIX Capabilities"
 	default n
--- linux-2.6.28-rc7-mm1.orig/security/capability.c
+++ linux-2.6.28-rc7-mm1/security/capability.c
@@ -263,6 +263,19 @@ static void cap_inode_getsecid(const str
 	*secid = 0;
 }
 
+#ifdef CONFIG_SECURITY_PATH
+
+static int cap_path_set(struct vfsmount *vfsmnt)
+{
+	return 0;
+}
+
+static void cap_path_clear(void)
+{
+}
+
+#endif
+
 static int cap_file_permission(struct file *file, int mask)
 {
 	return 0;
@@ -883,6 +896,10 @@ void security_fixup_ops(struct security_
 	set_to_cap_if_null(ops, inode_setsecurity);
 	set_to_cap_if_null(ops, inode_listsecurity);
 	set_to_cap_if_null(ops, inode_getsecid);
+#ifdef CONFIG_SECURITY_PATH
+	set_to_cap_if_null(ops, path_set);
+	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);
 	set_to_cap_if_null(ops, file_free_security);
--- linux-2.6.28-rc7-mm1.orig/security/security.c
+++ linux-2.6.28-rc7-mm1/security/security.c
@@ -355,6 +355,22 @@ int security_inode_init_security(struct 
 }
 EXPORT_SYMBOL(security_inode_init_security);
 
+#ifdef CONFIG_SECURITY_PATH
+
+int security_path_set(struct vfsmount *vfsmnt)
+{
+	return security_ops->path_set(vfsmnt);
+}
+EXPORT_SYMBOL(security_path_set);
+
+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)
 {
 	if (unlikely(IS_PRIVATE(dir)))


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

* Re: [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks.
  2008-12-03  8:56           ` [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks Kentaro Takeda
@ 2008-12-03 14:13             ` Stephen Smalley
  2008-12-04 12:00               ` Tetsuo Handa
  0 siblings, 1 reply; 29+ messages in thread
From: Stephen Smalley @ 2008-12-03 14:13 UTC (permalink / raw)
  To: Kentaro Takeda
  Cc: serue, linux-security-module, linux-fsdevel, akpm, linux-kernel,
	haradats, James Morris

On Wed, 2008-12-03 at 17:56 +0900, Kentaro Takeda wrote:
> Stephen, Serge,
> Here is the patch for introducing new security_path_set()/clear() hooks.
> 
> This patch enables LSM module to remember vfsmount's pathname so that it can 
> calculate absolute pathname in security_inode_*(). Since actual MAC can be 
> performed after DAC, there will not be any noise in auditing and learning 
> features. This patch currently assumes that the vfsmount's pathname is stored in 
> hash table in LSM module. (Should I use stack memory?)
> 
> Since security_inode_*() are not always called after security_path_set(), 
> security_path_clear() hook is needed to free the remembered pathname.

Your security_path_set()/security_path_clear() pairs look rather similar
to mnt_want_write()/mnt_drop_write() pairs.  What if you were to call
your hooks from those functions, and then you would only need to add
further hook calls in the case of read-only and execute/search checks?

-- 
Stephen Smalley
National Security Agency


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

* Re: [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks.
  2008-12-03 14:13             ` Stephen Smalley
@ 2008-12-04 12:00               ` Tetsuo Handa
  2008-12-04 18:20                 ` Serge E. Hallyn
  2008-12-05 21:53                 ` [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks Stephen Smalley
  0 siblings, 2 replies; 29+ messages in thread
From: Tetsuo Handa @ 2008-12-04 12:00 UTC (permalink / raw)
  To: sds, serue, jmorris
  Cc: linux-security-module, linux-fsdevel, akpm, linux-kernel,
	takedakn, haradats

Hello.

Stephen Smalley wrote:
> On Wed, 2008-12-03 at 17:56 +0900, Kentaro Takeda wrote:
> > Stephen, Serge,
> > Here is the patch for introducing new security_path_set()/clear() hooks.
> > 
> > This patch enables LSM module to remember vfsmount's pathname so that it can 
> > calculate absolute pathname in security_inode_*(). Since actual MAC can be 
> > performed after DAC, there will not be any noise in auditing and learning 
> > features. This patch currently assumes that the vfsmount's pathname is stored in 
> > hash table in LSM module. (Should I use stack memory?)
> > 
> > Since security_inode_*() are not always called after security_path_set(), 
> > security_path_clear() hook is needed to free the remembered pathname.
> 
> Your security_path_set()/security_path_clear() pairs look rather similar
> to mnt_want_write()/mnt_drop_write() pairs.  What if you were to call
> your hooks from those functions, and then you would only need to add
> further hook calls in the case of read-only and execute/search checks?

Right. Locations of inserting security_path_set()/security_path_clear() pairs
are subset of mnt_want_write()/mnt_drop_write() pairs. Thus, we can insert
security_path_set()/security_path_clear() pairs into
mnt_want_write()/mnt_drop_write() pairs, if we can tolerate performance
regression. According to our rough measurement, there is about 8 - 22% of
performance regression. But this approach needs minimum modification to the
existing kernel (only two hooks to be inserted).

The attached patch embeds security_path_set()/security_path_clear() into
mnt_want_write()/mnt_drop_write() and adds an example LSM module which
calculates vfsmount's pathname.
If LSM and FS people can accept this approach, we want to use it.

(----- When below patch is enabled -----)
# time dd status=noxfer if=/dev/zero of=/tmp/file bs=1 count=10485760
10485760+0 records in
10485760+0 records out

real    0m32.139s
user    0m2.303s
sys     0m29.756s
# time dd status=noxfer if=/dev/zero of=/tmp/file bs=512 count=20480
20480+0 records in
20480+0 records out

real    0m0.087s
user    0m0.002s
sys     0m0.085s
# time dd status=noxfer if=/dev/zero of=/tmp/file bs=4096 count=2560
2560+0 records in
2560+0 records out

real    0m0.028s
user    0m0.001s
sys     0m0.027s

(----- When below patch is disbled -----)
# time dd status=noxfer if=/dev/zero of=/tmp/file bs=1 count=10485760
10485760+0 records in
10485760+0 records out

real    0m26.776s
user    0m2.281s
sys     0m24.373s
# time dd status=noxfer if=/dev/zero of=/tmp/file bs=512 count=20480
20480+0 records in
20480+0 records out

real    0m0.077s
user    0m0.002s
sys     0m0.073s
# time dd status=noxfer if=/dev/zero of=/tmp/file bs=4096 count=2560
2560+0 records in
2560+0 records out

real    0m0.025s
user    0m0.001s
sys     0m0.024s


Regards.

--------------------
Subject: Embed security_path_set()/security_path_clear() into mnt_want_write()/mnt_drop_write().

This is a LSM version of http://lkml.org/lkml/2008/8/19/16 . 

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/namespace.c           |   11 +++++
 include/linux/security.h |   24 +++++++++++
 security/Kconfig         |   10 ++++
 security/Makefile        |    1 
 security/capability.c    |   17 +++++++
 security/mnt_path.c      |  100 +++++++++++++++++++++++++++++++++++++++++++++++
 security/security.c      |   14 ++++++
 7 files changed, 177 insertions(+)

--- linux-2.6.28-rc7-mm1.orig/fs/namespace.c
+++ linux-2.6.28-rc7-mm1/fs/namespace.c
@@ -254,6 +254,10 @@ int mnt_want_write(struct vfsmount *mnt)
 	int ret = 0;
 	struct mnt_writer *cpu_writer;
 
+#ifdef CONFIG_SECURITY_PATH
+	if (security_path_set(mnt) < 0)
+		return -ENOMEM;
+#endif
 	cpu_writer = &get_cpu_var(mnt_writers);
 	spin_lock(&cpu_writer->lock);
 	if (__mnt_is_readonly(mnt)) {
@@ -265,6 +269,10 @@ int mnt_want_write(struct vfsmount *mnt)
 out:
 	spin_unlock(&cpu_writer->lock);
 	put_cpu_var(mnt_writers);
+#ifdef CONFIG_SECURITY_PATH
+	if (ret)
+		security_path_clear();
+#endif
 	return ret;
 }
 EXPORT_SYMBOL_GPL(mnt_want_write);
@@ -362,6 +370,9 @@ void mnt_drop_write(struct vfsmount *mnt
 	 * we could theoretically wrap __mnt_writers.
 	 */
 	put_cpu_var(mnt_writers);
+#ifdef CONFIG_SECURITY_PATH
+	security_path_clear();
+#endif
 }
 EXPORT_SYMBOL_GPL(mnt_drop_write);
 
--- linux-2.6.28-rc7-mm1.orig/include/linux/security.h
+++ linux-2.6.28-rc7-mm1/include/linux/security.h
@@ -470,6 +470,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_set:
+ *	Calculate pathname of vfsmount for subsequent vfs operation.
+ *	@vfsmnt contains the vfsmount structure.
+ *	Return 0 on success, negative value otherwise.
+ * @path_clear:
+ *	Clear pathname of vfsmount calculated by @path_set.
  *
  * Security hooks for file operations
  *
@@ -1331,6 +1337,11 @@ struct security_operations {
 				   struct super_block *newsb);
 	int (*sb_parse_opts_str) (char *options, struct security_mnt_opts *opts);
 
+#ifdef CONFIG_SECURITY_PATH
+	int (*path_set) (struct vfsmount *vfsmnt);
+	void (*path_clear) (void);
+#endif
+
 	int (*inode_alloc_security) (struct inode *inode);
 	void (*inode_free_security) (struct inode *inode);
 	int (*inode_init_security) (struct inode *inode, struct inode *dir,
@@ -2705,6 +2716,19 @@ static inline void security_skb_classify
 
 #endif	/* CONFIG_SECURITY_NETWORK_XFRM */
 
+#ifdef CONFIG_SECURITY_PATH
+int security_path_set(struct vfsmount *vfsmnt);
+void security_path_clear(void);
+#else	/* CONFIG_SECURITY_PATH */
+static inline int security_path_set(struct vfsmount *vfsmnt)
+{
+	return 0;
+}
+static inline void security_path_clear(void)
+{
+}
+#endif	/* CONFIG_SECURITY_PATH */
+
 #ifdef CONFIG_KEYS
 #ifdef CONFIG_SECURITY
 
--- linux-2.6.28-rc7-mm1.orig/security/Kconfig
+++ linux-2.6.28-rc7-mm1/security/Kconfig
@@ -81,6 +81,16 @@ config SECURITY_NETWORK_XFRM
 	  IPSec.
 	  If you are unsure how to answer this question, answer N.
 
+config SECURITY_PATH
+	bool "Security hooks for pathname based access control"
+	depends on SECURITY
+	help
+	  This adds security_path_set() and security_path_clear()
+	  hooks for pathname based access control.
+	  If enabled, a security module can use these hooks to
+	  implement pathname based access controls.
+	  If you are unsure how to answer this question, answer N.
+
 config SECURITY_FILE_CAPABILITIES
 	bool "File POSIX Capabilities"
 	default n
--- linux-2.6.28-rc7-mm1.orig/security/Makefile
+++ linux-2.6.28-rc7-mm1/security/Makefile
@@ -17,3 +17,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_PATH)             += mnt_path.o
--- linux-2.6.28-rc7-mm1.orig/security/capability.c
+++ linux-2.6.28-rc7-mm1/security/capability.c
@@ -263,6 +263,19 @@ static void cap_inode_getsecid(const str
 	*secid = 0;
 }
 
+#ifdef CONFIG_SECURITY_PATH
+
+static int cap_path_set(struct vfsmount *vfsmnt)
+{
+	return 0;
+}
+
+static void cap_path_clear(void)
+{
+}
+
+#endif
+
 static int cap_file_permission(struct file *file, int mask)
 {
 	return 0;
@@ -883,6 +896,10 @@ void security_fixup_ops(struct security_
 	set_to_cap_if_null(ops, inode_setsecurity);
 	set_to_cap_if_null(ops, inode_listsecurity);
 	set_to_cap_if_null(ops, inode_getsecid);
+#ifdef CONFIG_SECURITY_PATH
+	set_to_cap_if_null(ops, path_set);
+	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);
 	set_to_cap_if_null(ops, file_free_security);
--- /dev/null
+++ linux-2.6.28-rc7-mm1/security/mnt_path.c
@@ -0,0 +1,100 @@
+/* mnt_path tracker */
+#include <linux/security.h>
+#include <linux/mount.h>
+
+/**
+ * mp_update_mnt_path - Update list of pathname of vfsmount.
+ *
+ * @mnt_path: Pointer to "const char *" or NULL.
+ *
+ * Returns @mnt_path on success, NULL otherwise if @mnt_path != NULL.
+ * Returns previously saved "const char *" and clears it if @mnt_path == NULL.
+ */
+static const char *mp_update_mnt_path(const char *mnt_path)
+{
+	struct mnt_path_entry {
+		struct list_head list;
+		struct task_struct *task; /* = current */
+		const char *mnt_path;
+	};
+	static LIST_HEAD(list);
+	static DEFINE_SPINLOCK(lock);
+	struct task_struct *task = current;
+	struct mnt_path_entry *entry;
+	if (!mnt_path) {
+		if (!list_empty(&list)) {
+			struct mnt_path_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) {
+				mnt_path = entry->mnt_path;
+				kfree(entry);
+			}
+		}
+		return mnt_path;
+	}
+	entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+	if (!entry)
+		return NULL;
+	entry->task = task;
+	entry->mnt_path = mnt_path;
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&lock);
+	list_add(&entry->list, &list);
+	spin_unlock(&lock);
+	/***** CRITICAL SECTION END *****/
+	return mnt_path;
+}
+
+static void mp_path_clear(void)
+{
+	kfree(mp_update_mnt_path(NULL));
+}
+
+static int mp_path_set(struct vfsmount *vfsmnt)
+{
+	char *sp;
+	struct path path = { vfsmnt, vfsmnt->mnt_root };
+	char *mnt_path = kmalloc(PATH_MAX, GFP_KERNEL);
+	mp_path_clear();
+	if (!mnt_path)
+		return -ENOMEM;
+	sp = d_path(&path, mnt_path, PATH_MAX - 1);
+	if (IS_ERR(sp)) {
+		kfree(mnt_path);
+		return -ENOMEM;
+	}
+	sp = kstrdup(sp, GFP_KERNEL);
+	kfree(mnt_path);
+	if (!sp)
+		return -ENOMEM;
+	return mp_update_mnt_path(sp) ? 0 : -ENOMEM;
+}
+
+static struct security_operations mp_security_ops = {
+	.name                = "mnt_path",
+	.path_set            = mp_path_set,
+	.path_clear          = mp_path_clear,
+};
+
+static int __init mp_init(void)
+{
+	if (!security_module_enable(&mp_security_ops))
+		return 0;
+	if (register_security(&mp_security_ops))
+		panic("Failure registering mnt_path tracker");
+	printk(KERN_INFO "mnt_path tracker enabled.\n");
+	return 0;
+}
+
+security_initcall(mp_init);
--- linux-2.6.28-rc7-mm1.orig/security/security.c
+++ linux-2.6.28-rc7-mm1/security/security.c
@@ -355,6 +355,20 @@ int security_inode_init_security(struct 
 }
 EXPORT_SYMBOL(security_inode_init_security);
 
+#ifdef CONFIG_SECURITY_PATH
+
+int security_path_set(struct vfsmount *vfsmnt)
+{
+	return security_ops->path_set(vfsmnt);
+}
+
+void security_path_clear(void)
+{
+	return security_ops->path_clear();
+}
+
+#endif
+
 int security_inode_create(struct inode *dir, struct dentry *dentry, int mode)
 {
 	if (unlikely(IS_PRIVATE(dir)))

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

* Re: [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks.
  2008-12-04 12:00               ` Tetsuo Handa
@ 2008-12-04 18:20                 ` Serge E. Hallyn
  2008-12-04 21:41                   ` [PATCH (mmotm-2008-12-02-17-08)] Introducesecurity_path_set/clear() hooks Tetsuo Handa
  2008-12-05 21:53                 ` [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks Stephen Smalley
  1 sibling, 1 reply; 29+ messages in thread
From: Serge E. Hallyn @ 2008-12-04 18:20 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: sds, jmorris, linux-security-module, linux-fsdevel, akpm,
	linux-kernel, takedakn, haradats

Quoting Tetsuo Handa (penguin-kernel@I-love.SAKURA.ne.jp):
> Hello.
> 
> Stephen Smalley wrote:
> > On Wed, 2008-12-03 at 17:56 +0900, Kentaro Takeda wrote:
> > > Stephen, Serge,
> > > Here is the patch for introducing new security_path_set()/clear() hooks.
> > > 
> > > This patch enables LSM module to remember vfsmount's pathname so that it can 
> > > calculate absolute pathname in security_inode_*(). Since actual MAC can be 
> > > performed after DAC, there will not be any noise in auditing and learning 
> > > features. This patch currently assumes that the vfsmount's pathname is stored in 
> > > hash table in LSM module. (Should I use stack memory?)
> > > 
> > > Since security_inode_*() are not always called after security_path_set(), 
> > > security_path_clear() hook is needed to free the remembered pathname.
> > 
> > Your security_path_set()/security_path_clear() pairs look rather similar
> > to mnt_want_write()/mnt_drop_write() pairs.  What if you were to call
> > your hooks from those functions, and then you would only need to add
> > further hook calls in the case of read-only and execute/search checks?
> 
> Right. Locations of inserting security_path_set()/security_path_clear() pairs
> are subset of mnt_want_write()/mnt_drop_write() pairs. Thus, we can insert
> security_path_set()/security_path_clear() pairs into
> mnt_want_write()/mnt_drop_write() pairs, if we can tolerate performance
> regression. According to our rough measurement, there is about 8 - 22% of
> performance regression.

... compared to what, exactly?

If having CONFIG_SECURITY_PATH=y but TOMOYO  disabled has this kind of
regression against just not having CONFIG_SECURITY_PATH, then no that is
not acceptable.

-serge

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

* Re: [PATCH (mmotm-2008-12-02-17-08)] Introducesecurity_path_set/clear() hooks.
  2008-12-04 18:20                 ` Serge E. Hallyn
@ 2008-12-04 21:41                   ` Tetsuo Handa
  0 siblings, 0 replies; 29+ messages in thread
From: Tetsuo Handa @ 2008-12-04 21:41 UTC (permalink / raw)
  To: serue
  Cc: sds, jmorris, linux-security-module, linux-fsdevel, akpm,
	linux-kernel, takedakn, haradats, penguin-kernel

Hello.

Serge E. Hallyn wrote:
> > Right. Locations of inserting security_path_set()/security_path_clear() pairs
> > are subset of mnt_want_write()/mnt_drop_write() pairs. Thus, we can insert
> > security_path_set()/security_path_clear() pairs into
> > mnt_want_write()/mnt_drop_write() pairs, if we can tolerate performance
> > regression. According to our rough measurement, there is about 8 - 22% of
> > performance regression.
> 
> ... compared to what, exactly?
> 
> If having CONFIG_SECURITY_PATH=y but TOMOYO  disabled has this kind of
> regression against just not having CONFIG_SECURITY_PATH, then no that is
> not acceptable.
> 
Comparison between a module using mnt_path.c and a module not using mnt_path.c .
If mp_update_mnt_path() is not called, there is no performance regression.
TOMOYO will need mp_update_mnt_path().

Regards.

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

* Re: [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks.
  2008-12-04 12:00               ` Tetsuo Handa
  2008-12-04 18:20                 ` Serge E. Hallyn
@ 2008-12-05 21:53                 ` Stephen Smalley
  2008-12-05 23:27                   ` Tetsuo Handa
  2008-12-06  6:16                   ` [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks Al Viro
  1 sibling, 2 replies; 29+ messages in thread
From: Stephen Smalley @ 2008-12-05 21:53 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: serue, jmorris, linux-security-module, linux-fsdevel, akpm,
	linux-kernel, takedakn, haradats

On Thu, 2008-12-04 at 21:00 +0900, Tetsuo Handa wrote:
> Hello.
> 
> Stephen Smalley wrote:
> > On Wed, 2008-12-03 at 17:56 +0900, Kentaro Takeda wrote:
> > > Stephen, Serge,
> > > Here is the patch for introducing new security_path_set()/clear() hooks.
> > > 
> > > This patch enables LSM module to remember vfsmount's pathname so that it can 
> > > calculate absolute pathname in security_inode_*(). Since actual MAC can be 
> > > performed after DAC, there will not be any noise in auditing and learning 
> > > features. This patch currently assumes that the vfsmount's pathname is stored in 
> > > hash table in LSM module. (Should I use stack memory?)
> > > 
> > > Since security_inode_*() are not always called after security_path_set(), 
> > > security_path_clear() hook is needed to free the remembered pathname.
> > 
> > Your security_path_set()/security_path_clear() pairs look rather similar
> > to mnt_want_write()/mnt_drop_write() pairs.  What if you were to call
> > your hooks from those functions, and then you would only need to add
> > further hook calls in the case of read-only and execute/search checks?
> 
> Right. Locations of inserting security_path_set()/security_path_clear() pairs
> are subset of mnt_want_write()/mnt_drop_write() pairs. Thus, we can insert
> security_path_set()/security_path_clear() pairs into
> mnt_want_write()/mnt_drop_write() pairs, if we can tolerate performance
> regression. According to our rough measurement, there is about 8 - 22% of
> performance regression. But this approach needs minimum modification to the
> existing kernel (only two hooks to be inserted).

I assume you also need separate hooks to cover the read-only open case?
As for your performance, your implementation of mp_* is clearly
non-optimal, so I'd expect there is plenty of room for improvement
there.

<snip>
> --- linux-2.6.28-rc7-mm1.orig/fs/namespace.c
> +++ linux-2.6.28-rc7-mm1/fs/namespace.c
> @@ -254,6 +254,10 @@ int mnt_want_write(struct vfsmount *mnt)
>  	int ret = 0;
>  	struct mnt_writer *cpu_writer;
>  
> +#ifdef CONFIG_SECURITY_PATH
> +	if (security_path_set(mnt) < 0)
> +		return -ENOMEM;
> +#endif

No #ifdef's within the functions, of course.  That gets handled by
security.h.

-- 
Stephen Smalley
National Security Agency


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

* Re: [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks.
  2008-12-05 21:53                 ` [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks Stephen Smalley
@ 2008-12-05 23:27                   ` Tetsuo Handa
  2008-12-06  5:25                     ` [RFC] Add "reason" parameter to mnt_want_write() Tetsuo Handa
  2008-12-06  6:16                   ` [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks Al Viro
  1 sibling, 1 reply; 29+ messages in thread
From: Tetsuo Handa @ 2008-12-05 23:27 UTC (permalink / raw)
  To: sds, viro, miklos
  Cc: serue, jmorris, linux-security-module, linux-fsdevel, akpm,
	linux-kernel, takedakn, haradats, penguin-kernel

Hello.

Stephen Smalley wrote:
> > Right. Locations of inserting security_path_set()/security_path_clear() pairs
> > are subset of mnt_want_write()/mnt_drop_write() pairs. Thus, we can insert
> > security_path_set()/security_path_clear() pairs into
> > mnt_want_write()/mnt_drop_write() pairs, if we can tolerate performance
> > regression. According to our rough measurement, there is about 8 - 22% of
> > performance regression. But this approach needs minimum modification to the
> > existing kernel (only two hooks to be inserted).
> 
> I assume you also need separate hooks to cover the read-only open case?

security_dentry_open() receives "struct file *", so I think we don't need
separate hooks for open(O_RDONLY).

> As for your performance, your implementation of mp_* is clearly
> non-optimal, so I'd expect there is plenty of room for improvement
> there.

Yes. Thus, I want to pass a caller identifier to mnt_want_write() so that
we can skip calculating vfsmount's pathname when it is not interested for
a LSM module (e.g. mnt_want_write() called for updating atime/ctime/mtime
checks).
May I add "int caller_id" to mnt_want_write()?

> No #ifdef's within the functions, of course.  That gets handled by
> security.h.
OK.

Regards.

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

* [RFC] Add "reason" parameter to mnt_want_write().
  2008-12-05 23:27                   ` Tetsuo Handa
@ 2008-12-06  5:25                     ` Tetsuo Handa
  2008-12-06  5:53                       ` Al Viro
  0 siblings, 1 reply; 29+ messages in thread
From: Tetsuo Handa @ 2008-12-06  5:25 UTC (permalink / raw)
  To: sds, viro, miklos
  Cc: serue, jmorris, linux-security-module, linux-fsdevel, akpm,
	linux-kernel, takedakn, haradats, penguin-kernel

We want to allow LSM modules to perform MAC which takes an absolute pathname of
a requested file into account. Since we can't pass "struct vfsmount" to VFS
helper functions, we are trying to somehow pass "struct vfsmount"'s pathnames
instead of "struct vfsmount" itself.

The mnt_want_write() and mnt_drop_write() hooks are inserted around VFS helper
functions call. Thus, I think we can insert security_path_set() into
mnt_want_write() and secuity_path_clear() into mnt_drop_write() rather than
scattering security_path_set() and security_path_clear() all around the places.

But, mnt_want_write() and mnt_drop_write() are used for not only VFS helper
functions call but also various places. Thus, honestly calculating vfsmount's
pathnames for touch_atime()/file_update_time() operations triggers measurable
performance regression.

We want to skip calculating vfsmount's pathnames when the mnt_want_write()
call is not interested for the LSM modules.
This patch adds "reason" parameter to mnt_want_write() so that LSM modules can
calculate vfsmount's pathnames only when needed. The "reason" parameter is one
of constants listed below.

  /* For subsequent VFS helper functions call.                     */
  MNT_WANT_FOR_VFS_REQUEST,    /* vfs_create()/vfs_mkdir() etc.    */
  /* For implicit write of inode's timestamps.                     */
  MNT_WANT_FOR_UPDATE_ACMTIME, /* touch_atime()/file_update_time() */
  /* For explicit write of inode's attributes and timestamps.      */
  MNT_WANT_FOR_UPDATE_ATTR,    /* chmod()/chown()/utimes() etc.    */
  /* For filesystem's ioctl.                                       */
  MNT_WANT_FOR_FS_IOCTL,       /* ext3_ioctl() etc.                */
  /* For opening a file for writing.                               */
  MNT_WANT_FOR_OPEN_WRITE,     /* __get_file_write_access()        */
  /* Needs to be granted since the caller doesn't check errors.    */
  MNT_WANT_ANYWAY              /* init_file()                      */

Please review.

Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
 fs/ext2/ioctl.c              |    6 +++---
 fs/ext3/ioctl.c              |   10 +++++-----
 fs/ext4/ioctl.c              |   10 +++++-----
 fs/fat/file.c                |    2 +-
 fs/file_table.c              |    2 +-
 fs/gfs2/ops_file.c           |    2 +-
 fs/hfsplus/ioctl.c           |    2 +-
 fs/inode.c                   |    4 ++--
 fs/jfs/ioctl.c               |    2 +-
 fs/namei.c                   |   18 +++++++++---------
 fs/namespace.c               |   15 ++++++++++++++-
 fs/ncpfs/ioctl.c             |    2 +-
 fs/nfsd/nfs4proc.c           |    3 ++-
 fs/nfsd/nfs4recover.c        |    6 +++---
 fs/nfsd/nfs4state.c          |    3 ++-
 fs/nfsd/vfs.c                |   21 ++++++++++++++-------
 fs/ocfs2/ioctl.c             |    3 ++-
 fs/open.c                    |   16 ++++++++--------
 fs/reiserfs/ioctl.c          |    5 +++--
 fs/ubifs/ioctl.c             |    2 +-
 fs/utimes.c                  |    2 +-
 fs/xattr.c                   |   12 ++++++------
 fs/xfs/linux-2.6/xfs_ioctl.c |    6 ++++--
 fs/xfs/linux-2.6/xfs_lrw.c   |    3 ++-
 include/linux/mount.h        |   17 ++++++++++++++++-
 ipc/mqueue.c                 |    4 ++--
 net/unix/af_unix.c           |    2 +-
 27 files changed, 111 insertions(+), 69 deletions(-)

--- linux-2.6.28-rc7-mm1.orig/fs/ext2/ioctl.c
+++ linux-2.6.28-rc7-mm1/fs/ext2/ioctl.c
@@ -36,7 +36,7 @@ long ext2_ioctl(struct file *filp, unsig
 	case EXT2_IOC_SETFLAGS: {
 		unsigned int oldflags;
 
-		ret = mnt_want_write(filp->f_path.mnt);
+		ret = mnt_want_write(filp->f_path.mnt, MNT_WANT_FOR_FS_IOCTL);
 		if (ret)
 			return ret;
 
@@ -93,7 +93,7 @@ setflags_out:
 	case EXT2_IOC_SETVERSION:
 		if (!is_owner_or_cap(inode))
 			return -EPERM;
-		ret = mnt_want_write(filp->f_path.mnt);
+		ret = mnt_want_write(filp->f_path.mnt, MNT_WANT_FOR_FS_IOCTL);
 		if (ret)
 			return ret;
 		if (get_user(inode->i_generation, (int __user *) arg)) {
@@ -123,7 +123,7 @@ setflags_out:
 		if (get_user(rsv_window_size, (int __user *)arg))
 			return -EFAULT;
 
-		ret = mnt_want_write(filp->f_path.mnt);
+		ret = mnt_want_write(filp->f_path.mnt, MNT_WANT_FOR_FS_IOCTL);
 		if (ret)
 			return ret;
 
--- linux-2.6.28-rc7-mm1.orig/fs/ext3/ioctl.c
+++ linux-2.6.28-rc7-mm1/fs/ext3/ioctl.c
@@ -39,7 +39,7 @@ int ext3_ioctl (struct inode * inode, st
 		unsigned int oldflags;
 		unsigned int jflag;
 
-		err = mnt_want_write(filp->f_path.mnt);
+		err = mnt_want_write(filp->f_path.mnt, MNT_WANT_FOR_FS_IOCTL);
 		if (err)
 			return err;
 
@@ -141,7 +141,7 @@ flags_out:
 
 		if (!is_owner_or_cap(inode))
 			return -EPERM;
-		err = mnt_want_write(filp->f_path.mnt);
+		err = mnt_want_write(filp->f_path.mnt, MNT_WANT_FOR_FS_IOCTL);
 		if (err)
 			return err;
 		if (get_user(generation, (int __user *) arg)) {
@@ -202,7 +202,7 @@ setversion_out:
 		if (!test_opt(inode->i_sb, RESERVATION) ||!S_ISREG(inode->i_mode))
 			return -ENOTTY;
 
-		err = mnt_want_write(filp->f_path.mnt);
+		err = mnt_want_write(filp->f_path.mnt, MNT_WANT_FOR_FS_IOCTL);
 		if (err)
 			return err;
 
@@ -244,7 +244,7 @@ setrsvsz_out:
 		if (!capable(CAP_SYS_RESOURCE))
 			return -EPERM;
 
-		err = mnt_want_write(filp->f_path.mnt);
+		err = mnt_want_write(filp->f_path.mnt, MNT_WANT_FOR_FS_IOCTL);
 		if (err)
 			return err;
 
@@ -270,7 +270,7 @@ group_extend_out:
 		if (!capable(CAP_SYS_RESOURCE))
 			return -EPERM;
 
-		err = mnt_want_write(filp->f_path.mnt);
+		err = mnt_want_write(filp->f_path.mnt, MNT_WANT_FOR_FS_IOCTL);
 		if (err)
 			return err;
 
--- linux-2.6.28-rc7-mm1.orig/fs/ext4/ioctl.c
+++ linux-2.6.28-rc7-mm1/fs/ext4/ioctl.c
@@ -44,7 +44,7 @@ long ext4_ioctl(struct file *filp, unsig
 		if (get_user(flags, (int __user *) arg))
 			return -EFAULT;
 
-		err = mnt_want_write(filp->f_path.mnt);
+		err = mnt_want_write(filp->f_path.mnt, MNT_WANT_FOR_FS_IOCTL);
 		if (err)
 			return err;
 
@@ -141,7 +141,7 @@ flags_out:
 		if (!is_owner_or_cap(inode))
 			return -EPERM;
 
-		err = mnt_want_write(filp->f_path.mnt);
+		err = mnt_want_write(filp->f_path.mnt, MNT_WANT_FOR_FS_IOCTL);
 		if (err)
 			return err;
 		if (get_user(generation, (int __user *) arg)) {
@@ -200,7 +200,7 @@ setversion_out:
 		if (get_user(n_blocks_count, (__u32 __user *)arg))
 			return -EFAULT;
 
-		err = mnt_want_write(filp->f_path.mnt);
+		err = mnt_want_write(filp->f_path.mnt, MNT_WANT_FOR_FS_IOCTL);
 		if (err)
 			return err;
 
@@ -226,7 +226,7 @@ setversion_out:
 				sizeof(input)))
 			return -EFAULT;
 
-		err = mnt_want_write(filp->f_path.mnt);
+		err = mnt_want_write(filp->f_path.mnt, MNT_WANT_FOR_FS_IOCTL);
 		if (err)
 			return err;
 
@@ -247,7 +247,7 @@ setversion_out:
 		if (!is_owner_or_cap(inode))
 			return -EACCES;
 
-		err = mnt_want_write(filp->f_path.mnt);
+		err = mnt_want_write(filp->f_path.mnt, MNT_WANT_FOR_FS_IOCTL);
 		if (err)
 			return err;
 		/*
--- linux-2.6.28-rc7-mm1.orig/fs/fat/file.c
+++ linux-2.6.28-rc7-mm1/fs/fat/file.c
@@ -47,7 +47,7 @@ int fat_generic_ioctl(struct inode *inod
 
 		mutex_lock(&inode->i_mutex);
 
-		err = mnt_want_write(filp->f_path.mnt);
+		err = mnt_want_write(filp->f_path.mnt, MNT_WANT_FOR_FS_IOCTL);
 		if (err)
 			goto up_no_drop_write;
 
--- linux-2.6.28-rc7-mm1.orig/fs/file_table.c
+++ linux-2.6.28-rc7-mm1/fs/file_table.c
@@ -210,7 +210,7 @@ int init_file(struct file *file, struct 
 	 */
 	if ((mode & FMODE_WRITE) && !special_file(dentry->d_inode->i_mode)) {
 		file_take_write(file);
-		error = mnt_want_write(mnt);
+		error = mnt_want_write(mnt, MNT_WANT_ANYWAY);
 		WARN_ON(error);
 	}
 	return error;
--- linux-2.6.28-rc7-mm1.orig/fs/gfs2/ops_file.c
+++ linux-2.6.28-rc7-mm1/fs/gfs2/ops_file.c
@@ -211,7 +211,7 @@ static int do_gfs2_set_flags(struct file
 	int error;
 	u32 new_flags, flags;
 
-	error = mnt_want_write(filp->f_path.mnt);
+	error = mnt_want_write(filp->f_path.mnt, MNT_WANT_FOR_FS_IOCTL);
 	if (error)
 		return error;
 
--- linux-2.6.28-rc7-mm1.orig/fs/hfsplus/ioctl.c
+++ linux-2.6.28-rc7-mm1/fs/hfsplus/ioctl.c
@@ -37,7 +37,7 @@ int hfsplus_ioctl(struct inode *inode, s
 		return put_user(flags, (int __user *)arg);
 	case HFSPLUS_IOC_EXT2_SETFLAGS: {
 		int err = 0;
-		err = mnt_want_write(filp->f_path.mnt);
+		err = mnt_want_write(filp->f_path.mnt, MNT_WANT_FOR_FS_IOCTL);
 		if (err)
 			return err;
 
--- linux-2.6.28-rc7-mm1.orig/fs/inode.c
+++ linux-2.6.28-rc7-mm1/fs/inode.c
@@ -1235,7 +1235,7 @@ void touch_atime(struct vfsmount *mnt, s
 	struct inode *inode = dentry->d_inode;
 	struct timespec now;
 
-	if (mnt_want_write(mnt))
+	if (mnt_want_write(mnt, MNT_WANT_FOR_UPDATE_ACMTIME))
 		return;
 	if (inode->i_flags & S_NOATIME)
 		goto out;
@@ -1291,7 +1291,7 @@ void file_update_time(struct file *file)
 	if (IS_NOCMTIME(inode))
 		return;
 
-	err = mnt_want_write(file->f_path.mnt);
+	err = mnt_want_write(file->f_path.mnt, MNT_WANT_FOR_UPDATE_ACMTIME);
 	if (err)
 		return;
 
--- linux-2.6.28-rc7-mm1.orig/fs/jfs/ioctl.c
+++ linux-2.6.28-rc7-mm1/fs/jfs/ioctl.c
@@ -68,7 +68,7 @@ long jfs_ioctl(struct file *filp, unsign
 		unsigned int oldflags;
 		int err;
 
-		err = mnt_want_write(filp->f_path.mnt);
+		err = mnt_want_write(filp->f_path.mnt, MNT_WANT_FOR_FS_IOCTL);
 		if (err)
 			return err;
 
--- linux-2.6.28-rc7-mm1.orig/fs/namei.c
+++ linux-2.6.28-rc7-mm1/fs/namei.c
@@ -1723,7 +1723,7 @@ do_last:
 		 * a permanent write count is taken through
 		 * the 'struct file' in nameidata_to_filp().
 		 */
-		error = mnt_want_write(nd.path.mnt);
+		error = mnt_want_write(nd.path.mnt, MNT_WANT_FOR_VFS_REQUEST);
 		if (error)
 			goto exit_mutex_unlock;
 		error = __open_namei_create(&nd, &path, flag, mode);
@@ -1775,7 +1775,7 @@ ok:
 	 */
 	will_write = open_will_write_to_fs(flag, nd.path.dentry->d_inode);
 	if (will_write) {
-		error = mnt_want_write(nd.path.mnt);
+		error = mnt_want_write(nd.path.mnt, MNT_WANT_FOR_VFS_REQUEST);
 		if (error)
 			goto exit;
 	}
@@ -1996,7 +1996,7 @@ asmlinkage long sys_mknodat(int dfd, con
 	error = may_mknod(mode);
 	if (error)
 		goto out_dput;
-	error = mnt_want_write(nd.path.mnt);
+	error = mnt_want_write(nd.path.mnt, MNT_WANT_FOR_VFS_REQUEST);
 	if (error)
 		goto out_dput;
 	switch (mode & S_IFMT) {
@@ -2067,7 +2067,7 @@ asmlinkage long sys_mkdirat(int dfd, con
 
 	if (!IS_POSIXACL(nd.path.dentry->d_inode))
 		mode &= ~current->fs->umask;
-	error = mnt_want_write(nd.path.mnt);
+	error = mnt_want_write(nd.path.mnt, MNT_WANT_FOR_VFS_REQUEST);
 	if (error)
 		goto out_dput;
 	error = vfs_mkdir(nd.path.dentry->d_inode, dentry, mode);
@@ -2177,7 +2177,7 @@ static long do_rmdir(int dfd, const char
 	error = PTR_ERR(dentry);
 	if (IS_ERR(dentry))
 		goto exit2;
-	error = mnt_want_write(nd.path.mnt);
+	error = mnt_want_write(nd.path.mnt, MNT_WANT_FOR_VFS_REQUEST);
 	if (error)
 		goto exit3;
 	error = vfs_rmdir(nd.path.dentry->d_inode, dentry);
@@ -2262,7 +2262,7 @@ static long do_unlinkat(int dfd, const c
 		inode = dentry->d_inode;
 		if (inode)
 			atomic_inc(&inode->i_count);
-		error = mnt_want_write(nd.path.mnt);
+		error = mnt_want_write(nd.path.mnt, MNT_WANT_FOR_VFS_REQUEST);
 		if (error)
 			goto exit2;
 		error = vfs_unlink(nd.path.dentry->d_inode, dentry);
@@ -2343,7 +2343,7 @@ asmlinkage long sys_symlinkat(const char
 	if (IS_ERR(dentry))
 		goto out_unlock;
 
-	error = mnt_want_write(nd.path.mnt);
+	error = mnt_want_write(nd.path.mnt, MNT_WANT_FOR_VFS_REQUEST);
 	if (error)
 		goto out_dput;
 	error = vfs_symlink(nd.path.dentry->d_inode, dentry, from);
@@ -2440,7 +2440,7 @@ asmlinkage long sys_linkat(int olddfd, c
 	error = PTR_ERR(new_dentry);
 	if (IS_ERR(new_dentry))
 		goto out_unlock;
-	error = mnt_want_write(nd.path.mnt);
+	error = mnt_want_write(nd.path.mnt, MNT_WANT_FOR_VFS_REQUEST);
 	if (error)
 		goto out_dput;
 	error = vfs_link(old_path.dentry, nd.path.dentry->d_inode, new_dentry);
@@ -2674,7 +2674,7 @@ asmlinkage long sys_renameat(int olddfd,
 	if (new_dentry == trap)
 		goto exit5;
 
-	error = mnt_want_write(oldnd.path.mnt);
+	error = mnt_want_write(oldnd.path.mnt, MNT_WANT_FOR_VFS_REQUEST);
 	if (error)
 		goto exit5;
 	error = vfs_rename(old_dir->d_inode, old_dentry,
--- linux-2.6.28-rc7-mm1.orig/fs/namespace.c
+++ linux-2.6.28-rc7-mm1/fs/namespace.c
@@ -242,6 +242,7 @@ static inline void use_cpu_writer_for_mo
 /**
  * mnt_want_write - get write access to a mount
  * @mnt: the mount on which to take a write
+ * @reason: reason for this call
  *
  * This tells the low-level filesystem that a write is
  * about to be performed to it, and makes sure that
@@ -249,11 +250,16 @@ static inline void use_cpu_writer_for_mo
  * the write operation is finished, mnt_drop_write()
  * must be called.  This is effectively a refcount.
  */
-int mnt_want_write(struct vfsmount *mnt)
+int mnt_want_write(struct vfsmount *mnt, const int reason)
 {
 	int ret = 0;
 	struct mnt_writer *cpu_writer;
 
+	/*
+	ret = security_path_set(mnt, reason);
+	if (ret)
+		return ret;
+	*/
 	cpu_writer = &get_cpu_var(mnt_writers);
 	spin_lock(&cpu_writer->lock);
 	if (__mnt_is_readonly(mnt)) {
@@ -265,6 +271,10 @@ int mnt_want_write(struct vfsmount *mnt)
 out:
 	spin_unlock(&cpu_writer->lock);
 	put_cpu_var(mnt_writers);
+	/*
+	if (ret)
+		security_path_clear();
+	*/
 	return ret;
 }
 EXPORT_SYMBOL_GPL(mnt_want_write);
@@ -362,6 +372,9 @@ void mnt_drop_write(struct vfsmount *mnt
 	 * we could theoretically wrap __mnt_writers.
 	 */
 	put_cpu_var(mnt_writers);
+	/*
+	security_path_clear();
+	*/
 }
 EXPORT_SYMBOL_GPL(mnt_drop_write);
 
--- linux-2.6.28-rc7-mm1.orig/fs/ncpfs/ioctl.c
+++ linux-2.6.28-rc7-mm1/fs/ncpfs/ioctl.c
@@ -861,7 +861,7 @@ int ncp_ioctl(struct inode *inode, struc
 		 * -EACCESS, so it seems consistent to keep
 		 *  that here.
 		 */
-		if (mnt_want_write(filp->f_path.mnt))
+		if (mnt_want_write(filp->f_path.mnt, MNT_WANT_FOR_FS_IOCTL))
 			return -EACCES;
 	}
 	ret = __ncp_ioctl(inode, filp, cmd, arg);
--- linux-2.6.28-rc7-mm1.orig/fs/nfsd/nfs4proc.c
+++ linux-2.6.28-rc7-mm1/fs/nfsd/nfs4proc.c
@@ -661,7 +661,8 @@ nfsd4_setattr(struct svc_rqst *rqstp, st
 			return status;
 		}
 	}
-	status = mnt_want_write(cstate->current_fh.fh_export->ex_path.mnt);
+	status = mnt_want_write(cstate->current_fh.fh_export->ex_path.mnt,
+				MNT_WANT_FOR_VFS_REQUEST);
 	if (status)
 		return status;
 	status = nfs_ok;
--- linux-2.6.28-rc7-mm1.orig/fs/nfsd/nfs4recover.c
+++ linux-2.6.28-rc7-mm1/fs/nfsd/nfs4recover.c
@@ -162,7 +162,7 @@ nfsd4_create_clid_dir(struct nfs4_client
 		dprintk("NFSD: nfsd4_create_clid_dir: DIRECTORY EXISTS\n");
 		goto out_put;
 	}
-	status = mnt_want_write(rec_dir.mnt);
+	status = mnt_want_write(rec_dir.mnt, MNT_WANT_FOR_VFS_REQUEST);
 	if (status)
 		goto out_put;
 	status = vfs_mkdir(rec_dir.dentry->d_inode, dentry, S_IRWXU);
@@ -326,7 +326,7 @@ nfsd4_remove_clid_dir(struct nfs4_client
 	if (!rec_dir_init || !clp->cl_firststate)
 		return;
 
-	status = mnt_want_write(rec_dir.mnt);
+	status = mnt_want_write(rec_dir.mnt, MNT_WANT_FOR_VFS_REQUEST);
 	if (status)
 		goto out;
 	clp->cl_firststate = 0;
@@ -369,7 +369,7 @@ nfsd4_recdir_purge_old(void) {
 
 	if (!rec_dir_init)
 		return;
-	status = mnt_want_write(rec_dir.mnt);
+	status = mnt_want_write(rec_dir.mnt, MNT_WANT_FOR_VFS_REQUEST);
 	if (status)
 		goto out;
 	status = nfsd4_list_rec_dir(rec_dir.dentry, purge_old);
--- linux-2.6.28-rc7-mm1.orig/fs/nfsd/nfs4state.c
+++ linux-2.6.28-rc7-mm1/fs/nfsd/nfs4state.c
@@ -1587,7 +1587,8 @@ nfs4_upgrade_open(struct svc_rqst *rqstp
 		int err = get_write_access(inode);
 		if (err)
 			return nfserrno(err);
-		err = mnt_want_write(cur_fh->fh_export->ex_path.mnt);
+		err = mnt_want_write(cur_fh->fh_export->ex_path.mnt,
+				     MNT_WANT_FOR_VFS_REQUEST);
 		if (err)
 			return nfserrno(err);
 		file_take_write(filp);
--- linux-2.6.28-rc7-mm1.orig/fs/nfsd/vfs.c
+++ linux-2.6.28-rc7-mm1/fs/nfsd/vfs.c
@@ -1261,7 +1261,8 @@ nfsd_create(struct svc_rqst *rqstp, stru
 		goto out;
 	}
 
-	host_err = mnt_want_write(fhp->fh_export->ex_path.mnt);
+	host_err = mnt_want_write(fhp->fh_export->ex_path.mnt,
+				  MNT_WANT_FOR_VFS_REQUEST);
 	if (host_err)
 		goto out_nfserr;
 
@@ -1374,7 +1375,8 @@ nfsd_create_v3(struct svc_rqst *rqstp, s
 		v_atime = verifier[1]&0x7fffffff;
 	}
 	
-	host_err = mnt_want_write(fhp->fh_export->ex_path.mnt);
+	host_err = mnt_want_write(fhp->fh_export->ex_path.mnt,
+				  MNT_WANT_FOR_VFS_REQUEST);
 	if (host_err)
 		goto out_nfserr;
 	if (dchild->d_inode) {
@@ -1538,7 +1540,8 @@ nfsd_symlink(struct svc_rqst *rqstp, str
 	if (IS_ERR(dnew))
 		goto out_nfserr;
 
-	host_err = mnt_want_write(fhp->fh_export->ex_path.mnt);
+	host_err = mnt_want_write(fhp->fh_export->ex_path.mnt,
+				  MNT_WANT_FOR_VFS_REQUEST);
 	if (host_err)
 		goto out_nfserr;
 
@@ -1614,7 +1617,8 @@ nfsd_link(struct svc_rqst *rqstp, struct
 	dold = tfhp->fh_dentry;
 	dest = dold->d_inode;
 
-	host_err = mnt_want_write(tfhp->fh_export->ex_path.mnt);
+	host_err = mnt_want_write(tfhp->fh_export->ex_path.mnt,
+				  MNT_WANT_FOR_VFS_REQUEST);
 	if (host_err) {
 		err = nfserrno(host_err);
 		goto out_dput;
@@ -1716,7 +1720,8 @@ nfsd_rename(struct svc_rqst *rqstp, stru
 	host_err = -EXDEV;
 	if (ffhp->fh_export->ex_path.mnt != tfhp->fh_export->ex_path.mnt)
 		goto out_dput_new;
-	host_err = mnt_want_write(ffhp->fh_export->ex_path.mnt);
+	host_err = mnt_want_write(ffhp->fh_export->ex_path.mnt,
+				  MNT_WANT_FOR_VFS_REQUEST);
 	if (host_err)
 		goto out_dput_new;
 
@@ -1787,7 +1792,8 @@ nfsd_unlink(struct svc_rqst *rqstp, stru
 	if (!type)
 		type = rdentry->d_inode->i_mode & S_IFMT;
 
-	host_err = mnt_want_write(fhp->fh_export->ex_path.mnt);
+	host_err = mnt_want_write(fhp->fh_export->ex_path.mnt,
+				  MNT_WANT_FOR_VFS_REQUEST);
 	if (host_err)
 		goto out_nfserr;
 
@@ -2188,7 +2194,8 @@ nfsd_set_posix_acl(struct svc_fh *fhp, i
 	} else
 		size = 0;
 
-	error = mnt_want_write(fhp->fh_export->ex_path.mnt);
+	error = mnt_want_write(fhp->fh_export->ex_path.mnt,
+			       MNT_WANT_FOR_VFS_REQUEST);
 	if (error)
 		goto getout;
 	if (size)
--- linux-2.6.28-rc7-mm1.orig/fs/ocfs2/ioctl.c
+++ linux-2.6.28-rc7-mm1/fs/ocfs2/ioctl.c
@@ -129,7 +129,8 @@ long ocfs2_ioctl(struct file *filp, unsi
 		if (get_user(flags, (int __user *) arg))
 			return -EFAULT;
 
-		status = mnt_want_write(filp->f_path.mnt);
+		status = mnt_want_write(filp->f_path.mnt,
+					MNT_WANT_FOR_FS_IOCTL);
 		if (status)
 			return status;
 		status = ocfs2_set_inode_attr(inode, flags,
--- linux-2.6.28-rc7-mm1.orig/fs/open.c
+++ linux-2.6.28-rc7-mm1/fs/open.c
@@ -247,7 +247,7 @@ static long do_sys_truncate(const char _
 	if (!S_ISREG(inode->i_mode))
 		goto dput_and_out;
 
-	error = mnt_want_write(path.mnt);
+	error = mnt_want_write(path.mnt, MNT_WANT_FOR_VFS_REQUEST);
 	if (error)
 		goto dput_and_out;
 
@@ -587,7 +587,7 @@ asmlinkage long sys_fchmod(unsigned int 
 
 	audit_inode(NULL, dentry);
 
-	err = mnt_want_write(file->f_path.mnt);
+	err = mnt_want_write(file->f_path.mnt, MNT_WANT_FOR_UPDATE_ATTR);
 	if (err)
 		goto out_putf;
 	mutex_lock(&inode->i_mutex);
@@ -617,7 +617,7 @@ asmlinkage long sys_fchmodat(int dfd, co
 		goto out;
 	inode = path.dentry->d_inode;
 
-	error = mnt_want_write(path.mnt);
+	error = mnt_want_write(path.mnt, MNT_WANT_FOR_UPDATE_ATTR);
 	if (error)
 		goto dput_and_out;
 	mutex_lock(&inode->i_mutex);
@@ -672,7 +672,7 @@ asmlinkage long sys_chown(const char __u
 	error = user_path(filename, &path);
 	if (error)
 		goto out;
-	error = mnt_want_write(path.mnt);
+	error = mnt_want_write(path.mnt, MNT_WANT_FOR_UPDATE_ATTR);
 	if (error)
 		goto out_release;
 	error = chown_common(path.dentry, user, group);
@@ -697,7 +697,7 @@ asmlinkage long sys_fchownat(int dfd, co
 	error = user_path_at(dfd, filename, follow, &path);
 	if (error)
 		goto out;
-	error = mnt_want_write(path.mnt);
+	error = mnt_want_write(path.mnt, MNT_WANT_FOR_UPDATE_ATTR);
 	if (error)
 		goto out_release;
 	error = chown_common(path.dentry, user, group);
@@ -716,7 +716,7 @@ asmlinkage long sys_lchown(const char __
 	error = user_lpath(filename, &path);
 	if (error)
 		goto out;
-	error = mnt_want_write(path.mnt);
+	error = mnt_want_write(path.mnt, MNT_WANT_FOR_UPDATE_ATTR);
 	if (error)
 		goto out_release;
 	error = chown_common(path.dentry, user, group);
@@ -738,7 +738,7 @@ asmlinkage long sys_fchown(unsigned int 
 	if (!file)
 		goto out;
 
-	error = mnt_want_write(file->f_path.mnt);
+	error = mnt_want_write(file->f_path.mnt, MNT_WANT_FOR_UPDATE_ATTR);
 	if (error)
 		goto out_fput;
 	dentry = file->f_path.dentry;
@@ -773,7 +773,7 @@ static inline int __get_file_write_acces
 		/*
 		 * Balanced in __fput()
 		 */
-		error = mnt_want_write(mnt);
+		error = mnt_want_write(mnt, MNT_WANT_FOR_OPEN_WRITE);
 		if (error)
 			put_write_access(inode);
 	}
--- linux-2.6.28-rc7-mm1.orig/fs/reiserfs/ioctl.c
+++ linux-2.6.28-rc7-mm1/fs/reiserfs/ioctl.c
@@ -48,7 +48,8 @@ int reiserfs_ioctl(struct inode *inode, 
 			if (!reiserfs_attrs(inode->i_sb))
 				return -ENOTTY;
 
-			err = mnt_want_write(filp->f_path.mnt);
+			err = mnt_want_write(filp->f_path.mnt,
+					     MNT_WANT_FOR_FS_IOCTL);
 			if (err)
 				return err;
 
@@ -97,7 +98,7 @@ setflags_out:
 	case REISERFS_IOC_SETVERSION:
 		if (!is_owner_or_cap(inode))
 			return -EPERM;
-		err = mnt_want_write(filp->f_path.mnt);
+		err = mnt_want_write(filp->f_path.mnt, MNT_WANT_FOR_FS_IOCTL);
 		if (err)
 			return err;
 		if (get_user(inode->i_generation, (int __user *)arg)) {
--- linux-2.6.28-rc7-mm1.orig/fs/ubifs/ioctl.c
+++ linux-2.6.28-rc7-mm1/fs/ubifs/ioctl.c
@@ -173,7 +173,7 @@ long ubifs_ioctl(struct file *file, unsi
 		 * Make sure the file-system is read-write and make sure it
 		 * will not become read-only while we are changing the flags.
 		 */
-		err = mnt_want_write(file->f_path.mnt);
+		err = mnt_want_write(file->f_path.mnt, MNT_WANT_FOR_FS_IOCTL);
 		if (err)
 			return err;
 		err = setflags(inode, flags);
--- linux-2.6.28-rc7-mm1.orig/fs/utimes.c
+++ linux-2.6.28-rc7-mm1/fs/utimes.c
@@ -54,7 +54,7 @@ static int utimes_common(struct path *pa
 	struct iattr newattrs;
 	struct inode *inode = path->dentry->d_inode;
 
-	error = mnt_want_write(path->mnt);
+	error = mnt_want_write(path->mnt, MNT_WANT_FOR_UPDATE_ATTR);
 	if (error)
 		goto out;
 
--- linux-2.6.28-rc7-mm1.orig/fs/xattr.c
+++ linux-2.6.28-rc7-mm1/fs/xattr.c
@@ -261,7 +261,7 @@ sys_setxattr(const char __user *pathname
 	error = user_path(pathname, &path);
 	if (error)
 		return error;
-	error = mnt_want_write(path.mnt);
+	error = mnt_want_write(path.mnt, MNT_WANT_FOR_UPDATE_ATTR);
 	if (!error) {
 		error = setxattr(path.dentry, name, value, size, flags);
 		mnt_drop_write(path.mnt);
@@ -280,7 +280,7 @@ sys_lsetxattr(const char __user *pathnam
 	error = user_lpath(pathname, &path);
 	if (error)
 		return error;
-	error = mnt_want_write(path.mnt);
+	error = mnt_want_write(path.mnt, MNT_WANT_FOR_UPDATE_ATTR);
 	if (!error) {
 		error = setxattr(path.dentry, name, value, size, flags);
 		mnt_drop_write(path.mnt);
@@ -302,7 +302,7 @@ sys_fsetxattr(int fd, const char __user 
 		return error;
 	dentry = f->f_path.dentry;
 	audit_inode(NULL, dentry);
-	error = mnt_want_write(f->f_path.mnt);
+	error = mnt_want_write(f->f_path.mnt, MNT_WANT_FOR_UPDATE_ATTR);
 	if (!error) {
 		error = setxattr(dentry, name, value, size, flags);
 		mnt_drop_write(f->f_path.mnt);
@@ -494,7 +494,7 @@ sys_removexattr(const char __user *pathn
 	error = user_path(pathname, &path);
 	if (error)
 		return error;
-	error = mnt_want_write(path.mnt);
+	error = mnt_want_write(path.mnt, MNT_WANT_FOR_UPDATE_ATTR);
 	if (!error) {
 		error = removexattr(path.dentry, name);
 		mnt_drop_write(path.mnt);
@@ -512,7 +512,7 @@ sys_lremovexattr(const char __user *path
 	error = user_lpath(pathname, &path);
 	if (error)
 		return error;
-	error = mnt_want_write(path.mnt);
+	error = mnt_want_write(path.mnt, MNT_WANT_FOR_UPDATE_ATTR);
 	if (!error) {
 		error = removexattr(path.dentry, name);
 		mnt_drop_write(path.mnt);
@@ -533,7 +533,7 @@ sys_fremovexattr(int fd, const char __us
 		return error;
 	dentry = f->f_path.dentry;
 	audit_inode(NULL, dentry);
-	error = mnt_want_write(f->f_path.mnt);
+	error = mnt_want_write(f->f_path.mnt, MNT_WANT_FOR_UPDATE_ATTR);
 	if (!error) {
 		error = removexattr(dentry, name);
 		mnt_drop_write(f->f_path.mnt);
--- linux-2.6.28-rc7-mm1.orig/fs/xfs/linux-2.6/xfs_ioctl.c
+++ linux-2.6.28-rc7-mm1/fs/xfs/linux-2.6/xfs_ioctl.c
@@ -629,7 +629,8 @@ xfs_attrmulti_by_handle(
 					&ops[i].am_length, ops[i].am_flags);
 			break;
 		case ATTR_OP_SET:
-			ops[i].am_error = mnt_want_write(parfilp->f_path.mnt);
+			ops[i].am_error = mnt_want_write(parfilp->f_path.mnt,
+							 MNT_WANT_FOR_FS_IOCTL);
 			if (ops[i].am_error)
 				break;
 			ops[i].am_error = xfs_attrmulti_attr_set(inode,
@@ -638,7 +639,8 @@ xfs_attrmulti_by_handle(
 			mnt_drop_write(parfilp->f_path.mnt);
 			break;
 		case ATTR_OP_REMOVE:
-			ops[i].am_error = mnt_want_write(parfilp->f_path.mnt);
+			ops[i].am_error = mnt_want_write(parfilp->f_path.mnt,
+							 MNT_WANT_FOR_FS_IOCTL);
 			if (ops[i].am_error)
 				break;
 			ops[i].am_error = xfs_attrmulti_attr_remove(inode,
--- linux-2.6.28-rc7-mm1.orig/fs/xfs/linux-2.6/xfs_lrw.c
+++ linux-2.6.28-rc7-mm1/fs/xfs/linux-2.6/xfs_lrw.c
@@ -673,7 +673,8 @@ start:
 	 * filesystems.  Throw it away if anyone asks us.
 	 */
 	if (likely(!(ioflags & IO_INVIS) &&
-		   !mnt_want_write(file->f_path.mnt))) {
+		   !mnt_want_write(file->f_path.mnt,
+				   MNT_WANT_FOR_UPDATE_ACMTIME))) {
 		xfs_ichgtime(xip, XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
 		mnt_drop_write(file->f_path.mnt);
 	}
--- linux-2.6.28-rc7-mm1.orig/include/linux/mount.h
+++ linux-2.6.28-rc7-mm1/include/linux/mount.h
@@ -78,7 +78,22 @@ static inline struct vfsmount *mntget(st
 	return mnt;
 }
 
-extern int mnt_want_write(struct vfsmount *mnt);
+enum mnt_want_reasons {
+	/* For subsequent VFS helper functions call.                     */
+	MNT_WANT_FOR_VFS_REQUEST,    /* vfs_create()/vfs_mkdir() etc.    */
+	/* For implicit write of inode's timestamps.                     */
+	MNT_WANT_FOR_UPDATE_ACMTIME, /* touch_atime()/file_update_time() */
+	/* For explicit write of inode's attributes and timestamps.      */
+	MNT_WANT_FOR_UPDATE_ATTR,    /* chmod()/chown()/utimes() etc.    */
+	/* For filesystem's ioctl.                                       */
+	MNT_WANT_FOR_FS_IOCTL,       /* ext3_ioctl() etc.                */
+	/* For opening a file for writing.                               */
+	MNT_WANT_FOR_OPEN_WRITE,     /* __get_file_write_access()        */
+	/* Needs to be granted since the caller doesn't check errors.    */
+	MNT_WANT_ANYWAY              /* init_file()                      */
+};
+
+extern int mnt_want_write(struct vfsmount *mnt, const int reason);
 extern void mnt_drop_write(struct vfsmount *mnt);
 extern void mntput_no_expire(struct vfsmount *mnt);
 extern void mnt_pin(struct vfsmount *mnt);
--- linux-2.6.28-rc7-mm1.orig/ipc/mqueue.c
+++ linux-2.6.28-rc7-mm1/ipc/mqueue.c
@@ -611,7 +611,7 @@ static struct file *do_create(struct den
 	}
 
 	mode &= ~current->fs->umask;
-	ret = mnt_want_write(mqueue_mnt);
+	ret = mnt_want_write(mqueue_mnt, MNT_WANT_FOR_VFS_REQUEST);
 	if (ret)
 		goto out;
 	ret = vfs_create(dir->d_inode, dentry, mode, NULL);
@@ -753,7 +753,7 @@ asmlinkage long sys_mq_unlink(const char
 	inode = dentry->d_inode;
 	if (inode)
 		atomic_inc(&inode->i_count);
-	err = mnt_want_write(mqueue_mnt);
+	err = mnt_want_write(mqueue_mnt, MNT_WANT_FOR_VFS_REQUEST);
 	if (err)
 		goto out_err;
 	err = vfs_unlink(dentry->d_parent->d_inode, dentry);
--- linux-2.6.28-rc7-mm1.orig/net/unix/af_unix.c
+++ linux-2.6.28-rc7-mm1/net/unix/af_unix.c
@@ -833,7 +833,7 @@ static int unix_bind(struct socket *sock
 		 */
 		mode = S_IFSOCK |
 		       (SOCK_INODE(sock)->i_mode & ~current->fs->umask);
-		err = mnt_want_write(nd.path.mnt);
+		err = mnt_want_write(nd.path.mnt, MNT_WANT_FOR_VFS_REQUEST);
 		if (err)
 			goto out_mknod_dput;
 		err = vfs_mknod(nd.path.dentry->d_inode, dentry, mode, 0);

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

* Re: [RFC] Add "reason" parameter to mnt_want_write().
  2008-12-06  5:25                     ` [RFC] Add "reason" parameter to mnt_want_write() Tetsuo Handa
@ 2008-12-06  5:53                       ` Al Viro
  0 siblings, 0 replies; 29+ messages in thread
From: Al Viro @ 2008-12-06  5:53 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: sds, miklos, serue, jmorris, linux-security-module, linux-fsdevel,
	akpm, linux-kernel, takedakn, haradats

On Sat, Dec 06, 2008 at 02:25:01PM +0900, Tetsuo Handa wrote:
> We want to allow LSM modules to perform MAC which takes an absolute pathname of
> a requested file into account. Since we can't pass "struct vfsmount" to VFS
> helper functions, we are trying to somehow pass "struct vfsmount"'s pathnames
> instead of "struct vfsmount" itself.
> 
> The mnt_want_write() and mnt_drop_write() hooks are inserted around VFS helper
> functions call. Thus, I think we can insert security_path_set() into
> mnt_want_write() and secuity_path_clear() into mnt_drop_write() rather than
> scattering security_path_set() and security_path_clear() all around the places.

No.  Use separate set of hooks AND PASS vfsmount DIRECTLY TO THEM.  Damnit,
people, just how many times does it have to be repeated?

Any version that pulls that class of tricks is no-go.  I don't _CARE_ whether
you hide vfsmount in task struct, do the same with string, send yourself a
datagram over magic socket or mail it to kludges-R-US.webtv.com, downloading
it back in LSM hook.

It's not a problem with implementation; it's a problem with the kludge
itself *and* with having the effect of vfs_mkdir() et.al. dependent on
anything except the arguments it's getting.

Adding global context of that kind is every bit as wrong as passing vfsmount
(or absolute pathname, or...) to vfs_mkdir() and its ilk.  It's worse,
actually, since it has an extra helping of ugliness on top of doing the
wrong thing.

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

* Re: [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks.
  2008-12-05 21:53                 ` [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks Stephen Smalley
  2008-12-05 23:27                   ` Tetsuo Handa
@ 2008-12-06  6:16                   ` Al Viro
  1 sibling, 0 replies; 29+ messages in thread
From: Al Viro @ 2008-12-06  6:16 UTC (permalink / raw)
  To: Stephen Smalley
  Cc: Tetsuo Handa, serue, jmorris, linux-security-module,
	linux-fsdevel, akpm, linux-kernel, takedakn, haradats

On Fri, Dec 05, 2008 at 04:53:18PM -0500, Stephen Smalley wrote:
> > Right. Locations of inserting security_path_set()/security_path_clear() pairs
> > are subset of mnt_want_write()/mnt_drop_write() pairs. Thus, we can insert
> > security_path_set()/security_path_clear() pairs into
> > mnt_want_write()/mnt_drop_write() pairs, if we can tolerate performance
> > regression. According to our rough measurement, there is about 8 - 22% of
> > performance regression. But this approach needs minimum modification to the
> > existing kernel (only two hooks to be inserted).
> 
> I assume you also need separate hooks to cover the read-only open case?
> As for your performance, your implementation of mp_* is clearly
> non-optimal, so I'd expect there is plenty of room for improvement
> there.

And just what will happen if you end up with foo_mkdir() calling something
that does e.g. pathname resolution in fs-controlled private namespace and
creates/removes some files there?

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

end of thread, other threads:[~2008-12-06  6:16 UTC | newest]

Thread overview: 29+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-11-20 11:25 [TOMOYO #13 (mmotm 2008-11-19-02-19) 00/11] TOMOYO Linux Tetsuo Handa
2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 01/11] Introduce security_path_clear() hook Tetsuo Handa
2008-12-01 20:00   ` Stephen Smalley
2008-12-02 10:39     ` Tetsuo Handa
2008-12-02 13:48       ` Stephen Smalley
2008-12-03  8:49         ` Kentaro Takeda
2008-12-03  8:56           ` [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks Kentaro Takeda
2008-12-03 14:13             ` Stephen Smalley
2008-12-04 12:00               ` Tetsuo Handa
2008-12-04 18:20                 ` Serge E. Hallyn
2008-12-04 21:41                   ` [PATCH (mmotm-2008-12-02-17-08)] Introducesecurity_path_set/clear() hooks Tetsuo Handa
2008-12-05 21:53                 ` [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks Stephen Smalley
2008-12-05 23:27                   ` Tetsuo Handa
2008-12-06  5:25                     ` [RFC] Add "reason" parameter to mnt_want_write() Tetsuo Handa
2008-12-06  5:53                       ` Al Viro
2008-12-06  6:16                   ` [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks Al Viro
2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 02/11] Add in_execve flag into task_struct Tetsuo Handa
2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 03/11] Singly linked list implementation Tetsuo Handa
2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 04/11] Introduce d_realpath() Tetsuo Handa
2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 05/11] Memory and pathname management functions Tetsuo Handa
2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 06/11] Common functions for TOMOYO Linux Tetsuo Handa
2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 07/11] File operation restriction part Tetsuo Handa
2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 08/11] Domain transition handler Tetsuo Handa
2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 09/11] LSM adapter functions Tetsuo Handa
2008-12-01 20:10   ` Stephen Smalley
2008-12-02 10:40     ` Tetsuo Handa
2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 10/11] Kconfig and Makefile Tetsuo Handa
2008-11-20 11:25 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 11/11] MAINTAINERS info Tetsuo Handa
2008-11-29 11:59 ` [TOMOYO #13 (mmotm 2008-11-19-02-19) 00/11] TOMOYO Linux Tetsuo Handa

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox