linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [TOMOYO #5 00/18] TOMOYO Linux - MAC based on process invocation history.
@ 2007-11-16 17:34 penguin-kernel
  2007-11-16 17:34 ` [TOMOYO #5 01/18] Add struct vfsmount to struct task_struct penguin-kernel
                   ` (17 more replies)
  0 siblings, 18 replies; 39+ messages in thread
From: penguin-kernel @ 2007-11-16 17:34 UTC (permalink / raw)
  To: akpm; +Cc: linux-kernel, linux-security-module

"TOMOYO Linux" is our work in the field of security enhancement for Linux.
You can try TOMOYO Linux 1.5.1 on Ubuntu 7.10's Live CD
http://tomoyo.sourceforge.jp/wiki-e/?TomoyoLive

Changes from previous posting.

 * Made patches against latest -mm tree.

   This time, we made patches for -mm tree.
   Documents about installing and experiencing TOMOYO Linux
   are available at http://tomoyo.sourceforge.jp/en/lkml-5/ .

 * Avoid namespace_sem deadlock.

   To avoid the possibility of AB-BA deadlock
   (see http://lkml.org/lkml/2007/11/5/388 for detail),
   we made patches not to access namespace_sem from LSM.
   Instead, we made some wrapper functions to pass "struct vfsmount"
   to LSM functions in a way of associating "struct vfsmount"
   with "struct task_struct" suggested at
   http://www.mail-archive.com/linux-security-module@vger.kernel.org/msg01712.html .

   We would like to merge either AppArmor's "Pass struct vfsmount to ..." patches or
   our patches marked as [01/18], [02/18], [03/18] into mainline kernel
   so that AppArmor and TOMOYO Linux can safely access "struct vfsmount" from LSM.

 * Avoid rcu_read_lock() by inserting mb() when appending to list.

   I heard from an embedded system developer that holding RCU's read lock
   for long time affects response time since it disables preemption.
   Since list elements are append-only and nobody needs to refer ->prev element,
   we made patches not to call rcu_read_lock() by replacing smp_wmb() with mb().

 * Don't send access logs to auditing system.

   TOMOYO Linux generates two types of logs.
   One is access logs in the form of policy file.
   The other is other messages like warning/info.
   We were sending both logs to auditing system.
   But some users complain about the flooding of access logs on the console.
   Thus, we decided to stop sending access logs to auditing system
   and removed AUDIT_TMY_GRANTED and AUDIT_TMY_REJECTED from include/linux/audit.h .
   Now, we are sending access logs to /sys/kernel/security/tomoyo/ interface.
   Logs other than access logs are sent to auditing system or printk() depending on kernel config.

 * Added capabilities support.

   TOMOYO Linux 2.1 now supports capabilities supported by TOMOYO Linux 1.5.1 .
   But some of them doesn't work due to LSM limitation (i.e. missing hooks).

Patches consist of four types.

 * [TOMOYO 01-03/18]: Essential modifications against -mm kernel.
 * [TOMOYO 04-16/18]: LSM implementation of TOMOYO Linux.
 * [TOMOYO 17/18]:    Makefile and Kconfig.
 * [TOMOYO 18/18]:    Optional modifications against -mm kernel.

--
  Tetsuo Handa
 

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

* [TOMOYO #5 01/18] Add struct vfsmount to struct task_struct.
  2007-11-16 17:34 [TOMOYO #5 00/18] TOMOYO Linux - MAC based on process invocation history penguin-kernel
@ 2007-11-16 17:34 ` penguin-kernel
  2007-11-16 17:34 ` [TOMOYO #5 02/18] Add wrapper functions for VFS helper functions penguin-kernel
                   ` (16 subsequent siblings)
  17 siblings, 0 replies; 39+ messages in thread
From: penguin-kernel @ 2007-11-16 17:34 UTC (permalink / raw)
  To: akpm; +Cc: linux-kernel, linux-security-module, Tetsuo Handa

[-- Attachment #1: add-struct-vfsmount-to-struct-task_struct.patch --]
[-- Type: text/plain, Size: 1085 bytes --]

This patch allows VFS wrapper functions associate "struct vfsmount"
with "struct task_struct" so that LSM hooks can calculate
pathname of given "struct dentry".

Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
 include/linux/init_task.h |    1 +
 include/linux/sched.h     |    2 ++
 2 files changed, 3 insertions(+)

--- linux-2.6-mm.orig/include/linux/init_task.h	2007-11-14 15:12:03.000000000 +0900
+++ linux-2.6-mm/include/linux/init_task.h	2007-11-14 15:15:33.000000000 +0900
@@ -173,6 +173,7 @@ extern struct group_info init_groups;
 	.dirties = INIT_PROP_LOCAL_SINGLE(dirties),			\
 	INIT_TRACE_IRQFLAGS						\
 	INIT_LOCKDEP							\
+	.last_vfsmount  = NULL,                                         \
 }
 
 
--- linux-2.6-mm.orig/include/linux/sched.h	2007-11-14 15:14:35.000000000 +0900
+++ linux-2.6-mm/include/linux/sched.h	2007-11-14 15:15:33.000000000 +0900
@@ -1191,6 +1191,8 @@ struct task_struct {
 	int make_it_fail;
 #endif
 	struct prop_local_single dirties;
+	/* vfsmount info for LSM hooks. */
+	struct vfsmount *last_vfsmount;
 };
 
 /*

-- 

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

* [TOMOYO #5 02/18] Add wrapper functions for VFS helper functions.
  2007-11-16 17:34 [TOMOYO #5 00/18] TOMOYO Linux - MAC based on process invocation history penguin-kernel
  2007-11-16 17:34 ` [TOMOYO #5 01/18] Add struct vfsmount to struct task_struct penguin-kernel
@ 2007-11-16 17:34 ` penguin-kernel
  2007-11-16 17:47   ` Trond Myklebust
  2007-11-16 17:34 ` [TOMOYO #5 03/18] Replace VFS with wrapper functions penguin-kernel
                   ` (15 subsequent siblings)
  17 siblings, 1 reply; 39+ messages in thread
From: penguin-kernel @ 2007-11-16 17:34 UTC (permalink / raw)
  To: akpm; +Cc: linux-kernel, linux-security-module, Tetsuo Handa

[-- Attachment #1: add-wrapper-functions-for-vfs-helper-functions.patch --]
[-- Type: text/plain, Size: 5298 bytes --]

This patch allows LSM hooks refer previously associated "struct vfsmount" parameter
so that they can calculate pathname of given "struct dentry".

Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
 include/linux/fs.h |  138 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 138 insertions(+)

--- linux-2.6-mm.orig/include/linux/fs.h	2007-11-14 15:14:52.000000000 +0900
+++ linux-2.6-mm/include/linux/fs.h	2007-11-14 15:20:32.000000000 +0900
@@ -1086,6 +1086,116 @@ extern int vfs_rmdir(struct inode *, str
 extern int vfs_unlink(struct inode *, struct dentry *);
 extern int vfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *);
 
+#include <linux/mount.h>
+#include <linux/sched.h>
+
+static inline int vfs_create2(struct inode *dir, struct dentry *dentry,
+			      int mode, struct nameidata *nd)
+{
+	int ret;
+	struct vfsmount *mnt = nd ? nd->path.mnt : NULL;
+	struct task_struct *task = current;
+	struct vfsmount *prev_mnt = task->last_vfsmount;
+	task->last_vfsmount = mntget(mnt);
+	ret = vfs_create(dir, dentry, mode, nd);
+	task->last_vfsmount = prev_mnt;
+	mntput(mnt);
+	return ret;
+}
+
+static inline int vfs_mkdir2(struct inode *dir, struct dentry *dentry,
+			     struct vfsmount *mnt, int mode)
+{
+	int ret;
+	struct task_struct *task = current;
+	struct vfsmount *prev_mnt = task->last_vfsmount;
+	task->last_vfsmount = mntget(mnt);
+	ret = vfs_mkdir(dir, dentry, mode);
+	task->last_vfsmount = prev_mnt;
+	mntput(mnt);
+	return ret;
+}
+
+static inline int vfs_mknod2(struct inode *dir, struct dentry *dentry,
+			     struct vfsmount *mnt, int mode, dev_t dev)
+{
+	int ret;
+	struct task_struct *task = current;
+	struct vfsmount *prev_mnt = task->last_vfsmount;
+	task->last_vfsmount = mntget(mnt);
+	ret = vfs_mknod(dir, dentry, mode, dev);
+	task->last_vfsmount = prev_mnt;
+	mntput(mnt);
+	return ret;
+}
+
+static inline int vfs_symlink2(struct inode *dir, struct dentry *dentry,
+			       struct vfsmount *mnt, const char *oldname,
+			       int mode)
+{
+	int ret;
+	struct task_struct *task = current;
+	struct vfsmount *prev_mnt = task->last_vfsmount;
+	task->last_vfsmount = mntget(mnt);
+	ret = vfs_symlink(dir, dentry, oldname, mode);
+	task->last_vfsmount = prev_mnt;
+	mntput(mnt);
+	return ret;
+}
+
+static inline int vfs_link2(struct dentry *old_dentry, struct inode *dir,
+			    struct dentry *new_dentry, struct vfsmount *mnt)
+{
+	int ret;
+	struct task_struct *task = current;
+	struct vfsmount *prev_mnt = task->last_vfsmount;
+	task->last_vfsmount = mntget(mnt);
+	ret = vfs_link(old_dentry, dir, new_dentry);
+	task->last_vfsmount = prev_mnt;
+	mntput(mnt);
+	return ret;
+}
+
+static inline int vfs_rmdir2(struct inode *dir, struct dentry *dentry,
+			     struct vfsmount *mnt)
+{
+	int ret;
+	struct task_struct *task = current;
+	struct vfsmount *prev_mnt = task->last_vfsmount;
+	task->last_vfsmount = mntget(mnt);
+	ret = vfs_rmdir(dir, dentry);
+	task->last_vfsmount = prev_mnt;
+	mntput(mnt);
+	return ret;
+}
+
+static inline int vfs_unlink2(struct inode *dir, struct dentry *dentry,
+			     struct vfsmount *mnt)
+{
+	int ret;
+	struct task_struct *task = current;
+	struct vfsmount *prev_mnt = task->last_vfsmount;
+	task->last_vfsmount = mntget(mnt);
+	ret = vfs_unlink(dir, dentry);
+	task->last_vfsmount = prev_mnt;
+	mntput(mnt);
+	return ret;
+}
+
+static inline int vfs_rename2(struct inode *old_dir, struct dentry *old_dentry,
+			      struct inode *new_dir, struct dentry *new_dentry,
+			      struct vfsmount *mnt)
+{
+	int ret;
+	struct task_struct *task = current;
+	struct vfsmount *prev_mnt = task->last_vfsmount;
+	task->last_vfsmount = mntget(mnt);
+	ret = vfs_rename(old_dir, old_dentry, new_dir, new_dentry);
+	task->last_vfsmount = prev_mnt;
+	mntput(mnt);
+	return ret;
+}
+
 /*
  * VFS dentry helper functions.
  */
@@ -1548,6 +1658,21 @@ static inline int break_lease(struct ino
 
 extern int do_truncate(struct dentry *, loff_t start, unsigned int time_attrs,
 		       struct file *filp);
+
+static inline int do_truncate2(struct dentry *dentry, struct vfsmount *mnt,
+			       loff_t length, unsigned int time_attrs,
+			       struct file *filp)
+{
+	int ret;
+	struct task_struct *task = current;
+	struct vfsmount *prev_mnt = task->last_vfsmount;
+	task->last_vfsmount = mntget(mnt);
+	ret = do_truncate(dentry, length, time_attrs, filp);
+	task->last_vfsmount = prev_mnt;
+	mntput(mnt);
+	return ret;
+}
+
 extern long do_sys_open(int dfd, const char __user *filename, int flags,
 			int mode);
 extern struct file * dentry_open(struct dentry *, struct vfsmount *, int);
@@ -1708,6 +1833,19 @@ extern int permission(struct inode *, in
 extern int generic_permission(struct inode *, int,
 		int (*check_acl)(struct inode *, int));
 
+static inline int notify_change2(struct dentry *dentry, struct vfsmount *mnt,
+				 struct iattr *attr)
+{
+	int ret;
+	struct task_struct *task = current;
+	struct vfsmount *prev_mnt = task->last_vfsmount;
+	task->last_vfsmount = mntget(mnt);
+	ret = notify_change(dentry, attr);
+	task->last_vfsmount = prev_mnt;
+	mntput(mnt);
+	return ret;
+}
+
 extern int get_write_access(struct inode *);
 extern int deny_write_access(struct file *);
 static inline void put_write_access(struct inode * inode)

-- 

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

* [TOMOYO #5 03/18] Replace VFS with wrapper functions.
  2007-11-16 17:34 [TOMOYO #5 00/18] TOMOYO Linux - MAC based on process invocation history penguin-kernel
  2007-11-16 17:34 ` [TOMOYO #5 01/18] Add struct vfsmount to struct task_struct penguin-kernel
  2007-11-16 17:34 ` [TOMOYO #5 02/18] Add wrapper functions for VFS helper functions penguin-kernel
@ 2007-11-16 17:34 ` penguin-kernel
  2007-11-16 17:34 ` [TOMOYO #5 04/18] Data structures and prototype defitions penguin-kernel
                   ` (14 subsequent siblings)
  17 siblings, 0 replies; 39+ messages in thread
From: penguin-kernel @ 2007-11-16 17:34 UTC (permalink / raw)
  To: akpm; +Cc: linux-kernel, linux-security-module, Tetsuo Handa

[-- Attachment #1: replace-vfs-with-wrapper-functions.patch --]
[-- Type: text/plain, Size: 7979 bytes --]

This patch replaces VFS helper function calls caused by
userland process's request with VFS wrapper functions call.
This patch doesn't modify individual filesystems in fs/*/ directory.

Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
 fs/namei.c         |   34 +++++++++++++++++++---------------
 fs/open.c          |   23 +++++++++++++----------
 net/unix/af_unix.c |    3 ++-
 3 files changed, 34 insertions(+), 26 deletions(-)

--- linux-2.6-mm.orig/fs/open.c	2007-11-14 15:14:48.000000000 +0900
+++ linux-2.6-mm/fs/open.c	2007-11-14 15:15:38.000000000 +0900
@@ -271,7 +271,8 @@ static long do_sys_truncate(const char _
 	error = locks_verify_truncate(inode, NULL, length);
 	if (!error) {
 		DQUOT_INIT(inode);
-		error = do_truncate(nd.path.dentry, length, 0, NULL);
+		error = do_truncate2(nd.path.dentry, nd.path.mnt, length, 0,
+				     NULL);
 	}
 
 put_write_and_out:
@@ -326,7 +327,8 @@ static long do_sys_ftruncate(unsigned in
 
 	error = locks_verify_truncate(inode, file, length);
 	if (!error)
-		error = do_truncate(dentry, length, ATTR_MTIME|ATTR_CTIME, file);
+		error = do_truncate2(dentry, file->f_path.mnt, length,
+				     ATTR_MTIME|ATTR_CTIME, file);
 out_putf:
 	fput(file);
 out:
@@ -589,7 +591,7 @@ asmlinkage long sys_fchmod(unsigned int 
 		mode = inode->i_mode;
 	newattrs.ia_mode = (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO);
 	newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
-	err = notify_change(dentry, &newattrs);
+	err = notify_change2(dentry, file->f_path.mnt, &newattrs);
 	mutex_unlock(&inode->i_mutex);
 
 out_drop_write:
@@ -626,7 +628,7 @@ asmlinkage long sys_fchmodat(int dfd, co
 		mode = inode->i_mode;
 	newattrs.ia_mode = (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO);
 	newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
-	error = notify_change(nd.path.dentry, &newattrs);
+	error = notify_change2(nd.path.dentry, nd.path.mnt, &newattrs);
 	mutex_unlock(&inode->i_mutex);
 
 out_drop_write:
@@ -642,7 +644,8 @@ asmlinkage long sys_chmod(const char __u
 	return sys_fchmodat(AT_FDCWD, filename, mode);
 }
 
-static int chown_common(struct dentry * dentry, uid_t user, gid_t group)
+static int chown_common(struct dentry *dentry, struct vfsmount *mnt,
+			uid_t user, gid_t group)
 {
 	struct inode * inode;
 	int error;
@@ -669,7 +672,7 @@ static int chown_common(struct dentry * 
 		newattrs.ia_valid |=
 			ATTR_KILL_SUID | ATTR_KILL_SGID | ATTR_KILL_PRIV;
 	mutex_lock(&inode->i_mutex);
-	error = notify_change(dentry, &newattrs);
+	error = notify_change2(dentry, mnt, &newattrs);
 	mutex_unlock(&inode->i_mutex);
 out:
 	return error;
@@ -686,7 +689,7 @@ asmlinkage long sys_chown(const char __u
 	error = mnt_want_write(nd.path.mnt);
 	if (error)
 		goto out_release;
-	error = chown_common(nd.path.dentry, user, group);
+	error = chown_common(nd.path.dentry, nd.path.mnt, user, group);
 	mnt_drop_write(nd.path.mnt);
 out_release:
 	path_put(&nd.path);
@@ -711,7 +714,7 @@ asmlinkage long sys_fchownat(int dfd, co
 	error = mnt_want_write(nd.path.mnt);
 	if (error)
 		goto out_release;
-	error = chown_common(nd.path.dentry, user, group);
+	error = chown_common(nd.path.dentry, nd.path.mnt, user, group);
 	mnt_drop_write(nd.path.mnt);
 out_release:
 	path_put(&nd.path);
@@ -730,7 +733,7 @@ asmlinkage long sys_lchown(const char __
 	error = mnt_want_write(nd.path.mnt);
 	if (error)
 		goto out_release;
-	error = chown_common(nd.path.dentry, user, group);
+	error = chown_common(nd.path.dentry, nd.path.mnt, user, group);
 	mnt_drop_write(nd.path.mnt);
 out_release:
 	path_put(&nd.path);
@@ -754,7 +757,7 @@ asmlinkage long sys_fchown(unsigned int 
 		goto out_fput;
 	dentry = file->f_path.dentry;
 	audit_inode(NULL, dentry);
-	error = chown_common(dentry, user, group);
+	error = chown_common(dentry, file->f_vfsmnt, user, group);
 	mnt_drop_write(file->f_vfsmnt);
 out_fput:
 	fput(file);
--- linux-2.6-mm.orig/fs/namei.c	2007-11-14 15:14:48.000000000 +0900
+++ linux-2.6-mm/fs/namei.c	2007-11-14 15:22:32.000000000 +0900
@@ -1663,7 +1663,7 @@ int may_open(struct nameidata *nd, int a
 		if (!error) {
 			DQUOT_INIT(inode);
 
-			error = do_truncate(dentry, 0,
+			error = do_truncate2(dentry, nd->path.mnt, 0,
 					    ATTR_MTIME|ATTR_CTIME|ATTR_OPEN,
 					    NULL);
 		}
@@ -1690,7 +1690,7 @@ static int __open_namei_create(struct na
 
 	if (!IS_POSIXACL(dir->d_inode))
 		mode &= ~current->fs->umask;
-	error = vfs_create(dir->d_inode, path->dentry, mode, nd);
+	error = vfs_create2(dir->d_inode, path->dentry, mode, nd);
 	mutex_unlock(&dir->d_inode->i_mutex);
 	dput(nd->path.dentry);
 	nd->path.dentry = path->dentry;
@@ -2076,16 +2076,17 @@ asmlinkage long sys_mknodat(int dfd, con
 		goto out_dput;
 	switch (mode & S_IFMT) {
 		case 0: case S_IFREG:
-			error = vfs_create(nd.path.dentry->d_inode, dentry,
-						mode, &nd);
+			error = vfs_create2(nd.path.dentry->d_inode, dentry,
+					    mode, &nd);
 			break;
 		case S_IFCHR: case S_IFBLK:
-			error = vfs_mknod(nd.path.dentry->d_inode, dentry, mode,
-					new_decode_dev(dev));
+			error = vfs_mknod2(nd.path.dentry->d_inode, dentry,
+					   nd.path.mnt, mode,
+					   new_decode_dev(dev));
 			break;
 		case S_IFIFO: case S_IFSOCK:
-			error = vfs_mknod(nd.path.dentry->d_inode, dentry,
-						mode, 0);
+			error = vfs_mknod2(nd.path.dentry->d_inode, dentry,
+					   nd.path.mnt, mode, 0);
 			break;
 	}
 	mnt_drop_write(nd.path.mnt);
@@ -2152,7 +2153,7 @@ asmlinkage long sys_mkdirat(int dfd, con
 	error = mnt_want_write(nd.path.mnt);
 	if (error)
 		goto out_dput;
-	error = vfs_mkdir(nd.path.dentry->d_inode, dentry, mode);
+	error = vfs_mkdir2(nd.path.dentry->d_inode, dentry, nd.path.mnt, mode);
 	mnt_drop_write(nd.path.mnt);
 out_dput:
 	dput(dentry);
@@ -2264,7 +2265,7 @@ static long do_rmdir(int dfd, const char
 	error = mnt_want_write(nd.path.mnt);
 	if (error)
 		goto exit3;
-	error = vfs_rmdir(nd.path.dentry->d_inode, dentry);
+	error = vfs_rmdir2(nd.path.dentry->d_inode, dentry, nd.path.mnt);
 	mnt_drop_write(nd.path.mnt);
 exit3:
 	dput(dentry);
@@ -2349,7 +2350,8 @@ static long do_unlinkat(int dfd, const c
 		error = mnt_want_write(nd.path.mnt);
 		if (error)
 			goto exit2;
-		error = vfs_unlink(nd.path.dentry->d_inode, dentry);
+		error = vfs_unlink2(nd.path.dentry->d_inode, dentry,
+				    nd.path.mnt);
 		mnt_drop_write(nd.path.mnt);
 	exit2:
 		dput(dentry);
@@ -2434,7 +2436,8 @@ asmlinkage long sys_symlinkat(const char
 	error = mnt_want_write(nd.path.mnt);
 	if (error)
 		goto out_dput;
-	error = vfs_symlink(nd.path.dentry->d_inode, dentry, from, S_IALLUGO);
+	error = vfs_symlink2(nd.path.dentry->d_inode, dentry, nd.path.mnt, from,
+			     S_IALLUGO);
 	mnt_drop_write(nd.path.mnt);
 out_dput:
 	dput(dentry);
@@ -2534,7 +2537,8 @@ asmlinkage long sys_linkat(int olddfd, c
 	error = mnt_want_write(nd.path.mnt);
 	if (error)
 		goto out_dput;
-	error = vfs_link(old_nd.path.dentry, nd.path.dentry->d_inode, new_dentry);
+	error = vfs_link2(old_nd.path.dentry, nd.path.dentry->d_inode,
+			  new_dentry, nd.path.mnt);
 	mnt_drop_write(nd.path.mnt);
 out_dput:
 	dput(new_dentry);
@@ -2765,8 +2769,8 @@ static int do_rename(int olddfd, const c
 	error = mnt_want_write(oldnd.path.mnt);
 	if (error)
 		goto exit5;
-	error = vfs_rename(old_dir->d_inode, old_dentry,
-				   new_dir->d_inode, new_dentry);
+	error = vfs_rename2(old_dir->d_inode, old_dentry,
+			    new_dir->d_inode, new_dentry, newnd.path.mnt);
 	mnt_drop_write(oldnd.path.mnt);
 exit5:
 	dput(new_dentry);
--- linux-2.6-mm.orig/net/unix/af_unix.c	2007-11-14 15:14:48.000000000 +0900
+++ linux-2.6-mm/net/unix/af_unix.c	2007-11-14 15:15:38.000000000 +0900
@@ -842,7 +842,8 @@ static int unix_bind(struct socket *sock
 		err = mnt_want_write(nd.path.mnt);
 		if (err)
 			goto out_mknod_dput;
-		err = vfs_mknod(nd.path.dentry->d_inode, dentry, mode, 0);
+		err = vfs_mknod2(nd.path.dentry->d_inode, dentry, nd.path.mnt,
+				 mode, 0);
 		mnt_drop_write(nd.path.mnt);
 		if (err)
 			goto out_mknod_dput;

-- 

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

* [TOMOYO #5 04/18] Data structures and prototype defitions.
  2007-11-16 17:34 [TOMOYO #5 00/18] TOMOYO Linux - MAC based on process invocation history penguin-kernel
                   ` (2 preceding siblings ...)
  2007-11-16 17:34 ` [TOMOYO #5 03/18] Replace VFS with wrapper functions penguin-kernel
@ 2007-11-16 17:34 ` penguin-kernel
  2007-11-16 17:34 ` [TOMOYO #5 05/18] Memory and pathname management functions penguin-kernel
                   ` (13 subsequent siblings)
  17 siblings, 0 replies; 39+ messages in thread
From: penguin-kernel @ 2007-11-16 17:34 UTC (permalink / raw)
  To: akpm; +Cc: linux-kernel, linux-security-module, Kentaro Takeda, Tetsuo Handa

[-- Attachment #1: tomoyo-headers.diff --]
[-- Type: text/plain, Size: 26523 bytes --]

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
 security/tomoyo/include/realpath.h |   45 ++
 security/tomoyo/include/tomoyo.h   |  671 +++++++++++++++++++++++++++++++++++++
 2 files changed, 716 insertions(+)

--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6-mm/security/tomoyo/include/realpath.h	2007-11-14 15:15:41.000000000 +0900
@@ -0,0 +1,45 @@
+/*
+ * security/tomoyo/include/realpath.h
+ *
+ * Get the canonicalized absolute pathnames.
+ * The basis for TOMOYO.
+ */
+
+#ifndef _TMY_REALPATH_H
+#define _TMY_REALPATH_H
+
+#include "tomoyo.h"
+
+struct path_info;
+
+/* Returns realpath(3) of the given pathname but ignores chroot'ed root. */
+int tmy_realpath_dentry2(struct dentry *dentry,
+			 struct vfsmount *mnt,
+			 char *newname,
+			 int newname_len);
+
+/* Returns realpath(3) of the given pathname but ignores chroot'ed root. */
+/* These functions use tmy_alloc(), so caller must tmy_free() */
+/* if these functions didn't return NULL. */
+char *tmy_realpath(const char *pathname);
+char *tmy_realpath_nofollow(const char *pathname);
+char *tmy_realpath_dentry(struct dentry *dentry, struct vfsmount *mnt);
+
+/* Allocate memory for structures. */
+/* The RAM is chunked, so NEVER try to kfree() the returned pointer. */
+void *tmy_alloc_element(const unsigned int size);
+
+/* Get used RAM size for tmy_alloc_elements(). */
+unsigned int tmy_get_memory_used_for_elements(void);
+
+/* Keep the given name on the RAM. */
+/* The RAM is shared, so NEVER try to modify or kfree() the returned name. */
+const struct path_info *tmy_save_name(const char *name);
+
+/* Get used RAM size for tmy_save_name(). */
+unsigned int tmy_get_memory_used_for_save_name(void);
+
+unsigned int tmy_get_memory_used_for_dynamic(void);
+char *sysctlpath_from_table(struct ctl_table *table);
+
+#endif
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6-mm/security/tomoyo/include/tomoyo.h	2007-11-14 15:15:41.000000000 +0900
@@ -0,0 +1,671 @@
+/*
+ * security/tomoyo/include/tomoyo.h
+ *
+ * Header for TOMOYO Linux.
+ */
+
+#ifndef _TOMOYO_H
+#define _TOMOYO_H
+
+#define TOMOYO_VERSION_CODE "2.1.0"
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+
+#include <asm/ioctls.h>
+#include <linux/audit.h>
+#include <linux/binfmts.h>
+#include <linux/delay.h>
+#include <linux/file.h>
+#include <linux/highmem.h>
+#include <linux/init.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+#include <linux/net.h>
+#include <linux/poll.h>
+#include <linux/proc_fs.h>
+#include <linux/security.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/smp_lock.h>
+#include <linux/string.h>
+#include <linux/sysctl.h>
+#include <linux/utime.h>
+#include <linux/version.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <stdarg.h>
+#include <linux/uaccess.h>
+#include <linux/hardirq.h>
+#include <linux/mount.h>
+#include <linux/mnt_namespace.h>
+
+/**
+ * list_for_each_cookie - iterate over a list with cookie.
+ * @pos:        the &struct list_head to use as a loop cursor.
+ * @cookie:     the &struct list_head to use as a cookie.
+ * @head:       the head for your list.
+ *
+ * Same with list_for_each except that this primitive uses cookie
+ * so that we can continue iteration.
+ */
+#define list_for_each_cookie(pos, cookie, head) \
+	for ((cookie) || ((cookie) = (head)), pos = (cookie)->next; \
+	     prefetch(pos->next), pos != (head) || ((cookie) = NULL); \
+	     (cookie) = pos, pos = pos->next)
+
+/**
+ * list_add_tail_mb - add a new entry with memory barrier.
+ * @new: new entry to be added.
+ * @head: list head to add it before.
+ *
+ * Same with list_add_tail_rcu() except that this primitive uses mb()
+ * so that we can traverse forwards using list_for_each() and
+ * list_for_each_cookie().
+ */
+static inline void list_add_tail_mb(struct list_head *new,
+				    struct list_head *head)
+{
+	struct list_head *prev = head->prev;
+	struct list_head *next = head;
+	new->next = next;
+	new->prev = prev;
+	mb(); /* Avoid out-of-order execution. */
+	next->prev = new;
+	prev->next = new;
+}
+
+extern struct seq_operations mounts_op;
+extern struct mutex domain_acl_lock;
+extern bool sbin_init_started;
+
+struct tmy_security {
+	struct domain_info *domain;
+	struct domain_info *prev_domain;
+	u32 flags;
+};
+
+#define TMY_SECURITY ((struct tmy_security *) current->security)
+
+struct path_info {
+	const char *name;
+	u32 hash;        /* = full_name_hash(name, strlen(name)) */
+	u16 total_len;   /* = strlen(name)                       */
+	u16 const_len;   /* = tmy_const_part_length(name)        */
+	bool is_dir;       /* = tmy_strendswith(name, "/")         */
+	bool is_patterned; /* = PathContainsPattern(name)          */
+	u16 depth;       /* = tmy_path_depth(name)               */
+};
+
+#define TMY_MAX_PATHNAME_LEN 4000
+
+struct path_group_member {
+	struct list_head list;
+	const struct path_info *member_name;
+	bool is_deleted;
+};
+
+struct path_group_entry {
+	struct list_head list;
+	const struct path_info *group_name;
+	struct list_head path_group_member_list;
+};
+
+
+struct mini_stat {
+	uid_t uid;
+	gid_t gid;
+	ino_t ino;
+};
+struct dentry;
+struct vfsmount;
+struct obj_info {
+	bool validate_done;
+	bool path1_valid;
+	bool path1_parent_valid;
+	bool path2_parent_valid;
+	struct dentry *path1_dentry;
+	struct vfsmount *path1_vfsmnt;
+	struct dentry *path2_dentry;
+	struct vfsmount *path2_vfsmnt;
+	struct mini_stat path1_stat;
+	/* I don't handle path2_stat for rename operation. */
+	struct mini_stat path1_parent_stat;
+	struct mini_stat path2_parent_stat;
+};
+struct condition_list;
+
+struct linux_binprm;
+struct pt_regs;
+
+/*
+ *  TOMOYO uses the following structures.
+ *  Memory allocated for these structures are never kfree()ed.
+ *  Since no locks are used for reading,
+ *  assignment must be performed atomically.
+ */
+
+/************************  The structure for domains.  ************************/
+
+struct acl_info {
+	struct list_head list;
+	const struct condition_list *cond;
+	u8 type;
+	bool is_deleted;
+} __attribute__((__packed__));
+
+struct domain_info {
+	struct list_head list;
+	struct list_head acl_info_list;
+	const struct path_info *domainname; /* Never NULL.   */
+	u8 profile;
+	u8 is_deleted; /* 0: active, 1:deleted, 255:deleted-permanently */
+	bool quota_warned;
+};
+
+#define TMY_MAX_PROFILES 256
+
+struct file_acl {
+	/* type = TMY_TYPE_FILE_ACL */
+	struct acl_info head;
+	u8 perm;
+	bool u_is_group;
+	union {
+		const struct path_info *filename;
+		const struct path_group_entry *group;
+	} u;
+};
+
+struct argv0_acl {
+	/* type = TMY_TYPE_ARGV0_ACL */
+	struct acl_info head;
+	const struct path_info *filename; /* Pointer to single pathname. */
+	const struct path_info *argv0;    /* strrchr(argv[0], '/') + 1   */
+};
+
+struct single_acl {
+	/* type = TMY_TYPE_* */
+	struct acl_info head;
+	bool u_is_group;
+	union {
+		const struct path_info *filename;
+		const struct path_group_entry *group;
+	} u;
+};
+
+struct double_acl {
+	/* type = TMY_TYPE_RENAME_ACL or TMY_TYPE_LINK_ACL, */
+	struct acl_info head;
+	bool u1_is_group;
+	bool u2_is_group;
+	union {
+		const struct path_info *filename1;
+		const struct path_group_entry *group1;
+	} u1;
+	union {
+		const struct path_info *filename2;
+		const struct path_group_entry *group2;
+	} u2;
+};
+
+struct address_group_member {
+	struct list_head list;
+	union {
+		u32 ipv4;    /* Host byte order    */
+		u16 ipv6[8]; /* Network byte order */
+	} min, max;
+	bool is_deleted;
+	bool is_ipv6;
+};
+
+struct address_group_entry {
+	struct list_head list;
+	const struct path_info *group_name;
+	struct list_head address_group_member_list;
+};
+
+#define TMY_TYPE_ADDRESS_GROUP 0
+#define TMY_TYPE_IPv4          1
+#define TMY_TYPE_IPv6          2
+
+struct net_acl {
+	/* type = TYPE_IP_NETWORK_ACL */
+	struct acl_info head;
+	u8 operation_type;
+	u8 record_type;
+	union {
+		struct {
+			u32 min;
+			u32 max;
+		} ipv4;
+		struct {
+			u16 min[8];
+			u16 max[8];
+		} ipv6;
+		const struct address_group_entry *group;
+	} u;
+	u16 min_port;           /* Start of port number range. */
+	u16 max_port;           /* End of port number range.   */
+};
+
+struct signal_acl {
+	/* type = TYPE_SIGNAL_ACL */
+	struct acl_info head;
+	u16 sig;
+	/* Pointer to destination pattern.            */
+	const struct path_info *domainname;
+};
+
+struct capability_acl {
+	/* type = TYPE_CAPABILITY_ACL */
+	struct acl_info head;
+	u16 capability;
+};
+
+/*************************  Keywords for ACLs.  *************************/
+
+#define TMY_AGGREGATOR               	"aggregator "
+#define TMY_AGGREGATOR_LEN           	(sizeof(TMY_AGGREGATOR) - 1)
+#define TMY_ALIAS                    	"alias "
+#define TMY_ALIAS_LEN                	(sizeof(TMY_ALIAS) - 1)
+#define TMY_ALLOW_READ               	"allow_read "
+#define TMY_ALLOW_READ_LEN           	(sizeof(TMY_ALLOW_READ) - 1)
+#define TMY_DELETE                   	"delete "
+#define TMY_DELETE_LEN               	(sizeof(TMY_DELETE) - 1)
+#define TMY_DENY_REWRITE             	"deny_rewrite "
+#define TMY_DENY_REWRITE_LEN         	(sizeof(TMY_DENY_REWRITE) - 1)
+#define TMY_FILE_PATTERN             	"file_pattern "
+#define TMY_FILE_PATTERN_LEN         	(sizeof(TMY_FILE_PATTERN) - 1)
+#define TMY_INITIALIZE_DOMAIN        	"initialize_domain "
+#define TMY_INITIALIZE_DOMAIN_LEN    	(sizeof(TMY_INITIALIZE_DOMAIN) - 1)
+#define TMY_KEEP_DOMAIN              	"keep_domain "
+#define TMY_KEEP_DOMAIN_LEN          	(sizeof(TMY_KEEP_DOMAIN) - 1)
+#define TMY_NO_INITIALIZE_DOMAIN     	"no_initialize_domain "
+#define TMY_NO_INITIALIZE_DOMAIN_LEN 	(sizeof(TMY_NO_INITIALIZE_DOMAIN) - 1)
+#define TMY_NO_KEEP_DOMAIN           	"no_keep_domain "
+#define TMY_NO_KEEP_DOMAIN_LEN       	(sizeof(TMY_NO_KEEP_DOMAIN) - 1)
+#define TMY_PATH_GROUP               	"path_group "
+#define TMY_PATH_GROUP_LEN           	(sizeof(TMY_PATH_GROUP) - 1)
+#define TMY_SELECT                   	"select "
+#define TMY_SELECT_LEN               	(sizeof(TMY_SELECT) - 1)
+#define TMY_UNDELETE                 	"undelete "
+#define TMY_UNDELETE_LEN             	(sizeof(TMY_UNDELETE) - 1)
+
+#define TMY_ALLOW_MOUNT                 "allow_mount "
+#define TMY_ALLOW_MOUNT_LEN             (sizeof(TMY_ALLOW_MOUNT) - 1)
+#define TMY_DENY_UNMOUNT                "deny_unmount "
+#define TMY_DENY_UNMOUNT_LEN            (sizeof(TMY_DENY_UNMOUNT) - 1)
+#define TMY_ALLOW_PIVOT_ROOT            "allow_pivot_root "
+#define TMY_ALLOW_PIVOT_ROOT_LEN        (sizeof(TMY_ALLOW_PIVOT_ROOT) - 1)
+
+#define TMY_USE_PROFILE "use_profile "
+
+#define TMY_ROOT_NAME 		"<kernel>"
+#define TMY_ROOT_NAME_LEN 	(sizeof(TMY_ROOT_NAME) - 1)
+
+#define TMY_ALLOW_ARGV0         "allow_argv0 "
+#define TMY_ALLOW_ARGV0_LEN     (sizeof(TMY_ALLOW_ARGV0) - 1)
+
+#define TMY_ADDRESS_GROUP       "address_group "
+#define TMY_ADDRESS_GROUP_LEN   (sizeof(TMY_ADDRESS_GROUP) - 1)
+#define TMY_ALLOW_NETWORK       "allow_network "
+#define TMY_ALLOW_NETWORK_LEN   (sizeof(TMY_ALLOW_NETWORK) - 1)
+
+#define TMY_ALLOW_SIGNAL        "allow_signal "
+#define TMY_ALLOW_SIGNAL_LEN    (sizeof(TMY_ALLOW_SIGNAL) - 1)
+
+#define TMY_ALLOW_CAPABILITY    "allow_capability "
+#define TMY_ALLOW_CAPABILITY_LEN  (sizeof(TMY_ALLOW_CAPABILITY) - 1)
+#define TMY_MAC_FOR_CAPABILITY       "MAC_FOR_CAPABILITY::"
+#define TMY_MAC_FOR_CAPABILITY_LEN   (sizeof(TMY_MAC_FOR_CAPABILITY) - 1)
+
+/********************  Index numbers for Access Controls.  ********************/
+
+#define TMY_COMMENT              0
+#define TMY_MAC_FOR_FILE         1
+#define TMY_MAC_FOR_ARGV0        2
+#define TMY_MAC_FOR_NETWORK      3
+#define TMY_MAC_FOR_SIGNAL       4
+#define TMY_DENY_CONCEAL_MOUNT   5
+#define TMY_RESTRICT_MOUNT       6
+#define TMY_RESTRICT_UMOUNT      7
+#define TMY_RESTRICT_PIVOT_ROOT  8
+#define TMY_MAX_ACCEPT_ENTRY     9
+#define TMY_MAX_GRANT_LOG       10
+#define TMY_MAX_REJECT_LOG      11
+#define TMY_VERBOSE             12
+#define TMY_ALLOW_ENFORCE_GRACE 13
+#define TMY_MAX_CONTROL_INDEX   14
+
+#define TMY_NETWORK_ACL_UDP_BIND    0
+#define TMY_NETWORK_ACL_UDP_CONNECT 1
+#define TMY_NETWORK_ACL_TCP_BIND    2
+#define TMY_NETWORK_ACL_TCP_LISTEN  3
+#define TMY_NETWORK_ACL_TCP_CONNECT 4
+#define TMY_NETWORK_ACL_TCP_ACCEPT  5
+#define TMY_NETWORK_ACL_RAW_BIND    6
+#define TMY_NETWORK_ACL_RAW_CONNECT 7
+
+/*************  Index numbers for Capability Controls.  **********/
+
+/* socket(PF_INET or PF_INET6, SOCK_STREAM, *)                   */
+#define TMY_INET_STREAM_SOCKET_CREATE         0
+/* listen() for PF_INET or PF_INET6, SOCK_STREAM                 */
+#define TMY_INET_STREAM_SOCKET_LISTEN         1
+/* connect() for PF_INET or PF_INET6, SOCK_STREAM                */
+#define TMY_INET_STREAM_SOCKET_CONNECT        2
+/* socket(PF_INET or PF_INET6, SOCK_DGRAM, *)                    */
+#define TMY_USE_INET_DGRAM_SOCKET             3
+/* socket(PF_INET or PF_INET6, SOCK_RAW, *)                      */
+#define TMY_USE_INET_RAW_SOCKET               4
+/* socket(PF_ROUTE, *, *)                                        */
+#define TMY_USE_ROUTE_SOCKET                  5
+/* socket(PF_PACKET, *, *)                                       */
+#define TMY_USE_PACKET_SOCKET                 6
+/* sys_mount()                                                   */
+#define TMY_SYS_MOUNT                         7
+/* sys_umount()                                                  */
+#define TMY_SYS_UMOUNT                        8
+/* sys_reboot()                                                  */
+#define TMY_SYS_REBOOT                        9
+/* sys_chroot()                                                  */
+#define TMY_SYS_CHROOT                       10
+/* sys_kill(), sys_tkill(), sys_tgkill()                         */
+#define TMY_SYS_KILL                         11
+/* sys_vhangup()                                                 */
+#define TMY_SYS_VHANGUP                      12
+/* do_settimeofday(), sys_adjtimex()                             */
+#define TMY_SYS_SETTIME                      13
+/* sys_nice(), sys_setpriority()                                 */
+#define TMY_SYS_NICE                         14
+/* sys_sethostname(), sys_setdomainname()                        */
+#define TMY_SYS_SETHOSTNAME                  15
+/* sys_create_module(), sys_init_module(), sys_delete_module()   */
+#define TMY_USE_KERNEL_MODULE                16
+/* sys_mknod(S_IFIFO)                                            */
+#define TMY_CREATE_FIFO                      17
+/* sys_mknod(S_IFBLK)                                            */
+#define TMY_CREATE_BLOCK_DEV                 18
+/* sys_mknod(S_IFCHR)                                            */
+#define TMY_CREATE_CHAR_DEV                  19
+/* sys_mknod(S_IFSOCK)                                           */
+#define TMY_CREATE_UNIX_SOCKET               20
+/* sys_link()                                                    */
+#define TMY_SYS_LINK                         21
+/* sys_symlink()                                                 */
+#define TMY_SYS_SYMLINK                      22
+/* sys_rename()                                                  */
+#define TMY_SYS_RENAME                       23
+/* sys_unlink()                                                  */
+#define TMY_SYS_UNLINK                       24
+/* sys_chmod(), sys_fchmod()                                     */
+#define TMY_SYS_CHMOD                        25
+/* sys_chown(), sys_fchown(), sys_lchown()                       */
+#define TMY_SYS_CHOWN                        26
+/* sys_ioctl(), compat_sys_ioctl()                               */
+#define TMY_SYS_IOCTL                        27
+/* sys_kexec_load()                                              */
+#define TMY_SYS_KEXEC_LOAD                   28
+/* sys_pivot_root()                                              */
+#define TMY_SYS_PIVOT_ROOT                   29
+#define TMY_MAX_CAPABILITY_INDEX             30
+
+/********************  Index numbers for updates counter.  ********************/
+
+#define TMY_UPDATE_DOMAINPOLICY    0
+#define TMY_UPDATE_SYSTEMPOLICY    1
+#define TMY_UPDATE_EXCEPTIONPOLICY 2
+#define TMY_UPDATE_PROFILE         3
+#define TMY_UPDATE_QUERY           4
+#define TMY_UPDATE_MANAGER         5
+#define TMY_UPDATE_GRANT_LOG       6
+#define TMY_UPDATE_REJECT_LOG      7
+#define TMY_MAX_UPDATES_COUNTER    8
+
+/***************  Indexes for /sys/kernel/security interfaces.  ***************/
+
+#define TMY_DOMAINPOLICY     0
+#define TMY_SYSTEMPOLICY     1
+#define TMY_EXCEPTIONPOLICY  2
+#define TMY_DOMAIN_STATUS    3
+#define TMY_PROCESS_STATUS   4
+#define TMY_MEMINFO          5
+#define TMY_SELFDOMAIN       6
+#define TMY_PROFILE          7
+#define TMY_QUERY            8
+#define TMY_MANAGER          9
+#define TMY_UPDATESCOUNTER  10
+#define TMY_GRANT_LOG       11
+#define TMY_REJECT_LOG      12
+#define TMY_VERSION         13
+
+/***************  The structure for /sys/kernel/security interfaces.  *********/
+
+struct io_buffer {
+	int (*read) (struct io_buffer *);
+	struct mutex read_mutex;      /* Lock for reading.                    */
+	int (*write) (struct io_buffer *);
+	struct mutex write_mutex;     /* Lock for updating.                   */
+	int (*poll) (struct file *file, poll_table *wait);
+	struct list_head *read_var1;  /* The position currently reading from. */
+	struct list_head *read_var2;  /* Extra variables for reading.         */
+	struct domain_info *write_var1; /* The position currently writing to. */
+	int read_step;                /* The step for reading.                */
+	char *read_buf;               /* Buffer for reading.                  */
+	bool read_eof;                /* EOF flag for reading.                */
+	int read_avail;               /* Bytes available for reading.         */
+	int readbuf_size;             /* Size of read buffer.                 */
+	char *write_buf;              /* Buffer for writing.                  */
+	int write_avail;              /* Bytes available for writing.         */
+	int writebuf_size;            /* Size of write buffer.                */
+};
+
+/*************************  PROTOTYPES  *************************/
+
+char *tmy_find_condition_part(char *data);
+const struct condition_list *tmy_assign_condition(const char *condition);
+int tmy_check_condition(const struct condition_list *ptr,
+			struct obj_info *obj);
+int tmy_dump_condition(struct io_buffer *head,
+		       const struct condition_list *ptr);
+const char *tmy_get_exe(void);
+const char *tmy_getmsg(bool is_enforce);
+const char *tmy_lastname(const struct domain_info *domain);
+const char *tmy_acltype2keyword(const unsigned int acl_type);
+
+int tmy_mount_perm(char *dev_name,
+		   char *dir_name,
+		   char *type,
+		   unsigned long flags);
+int tmy_conceal_mount(struct nameidata *nd);
+int tmy_umount_perm(struct vfsmount *mnt);
+int tmy_add_mount_policy(char *data, const bool is_delete);
+int tmy_read_mount_policy(struct io_buffer *head);
+int tmy_add_no_umount_policy(char *data, const bool is_delete);
+int tmy_read_no_umount_policy(struct io_buffer *head);
+int tmy_pivot_root_perm(struct nameidata *old_nd,
+			struct nameidata *new_nd);
+int tmy_add_pivot_root_policy(char *data, const bool is_delete);
+int tmy_read_pivot_root_policy(struct io_buffer *head);
+
+int tmy_add_aggregator_policy(char *data, const bool is_delete);
+int tmy_add_address_group_policy(char *data, const bool is_delete);
+int tmy_add_alias_policy(char *data, const bool is_delete);
+int tmy_add_argv0_policy(char *data,
+			 struct domain_info *domain,
+			 const struct condition_list *cond,
+			 const bool is_delete);
+int tmy_add_acl(struct domain_info *domain, struct acl_info *acl);
+int tmy_add_capability_policy(char *data,
+			      struct domain_info *domain,
+			      const struct condition_list *cond,
+			      const bool is_delete);
+int tmy_add_domain_initializer_policy(char *data,
+				      const bool is_not,
+				      const bool is_delete);
+int tmy_add_domain_keeper_policy(char *data,
+				 const bool is_not,
+				 const bool is_delete);
+int tmy_file_perm(const char *filename0, const u8 perm, const char *operation);
+int tmy_add_file_policy(char *data,
+			struct domain_info *domain,
+			const struct condition_list *cond,
+			const bool is_delete);
+int tmy_add_globally_readable_policy(char *data, const bool is_delete);
+int tmy_add_group_policy(char *data, const bool is_delete);
+int tmy_add_network_policy(char *data,
+			   struct domain_info *domain,
+			   const struct condition_list *cond,
+			   const bool is_delete);
+int tmy_add_no_rewrite_policy(char *pattern, const bool is_delete);
+int tmy_add_pattern_policy(char *data, const bool is_delete);
+int tmy_supervisor(const char *fmt, ...)
+	__attribute__((format(printf, 1, 2)));
+#ifdef CONFIG_SECURITY_TOMOYO_USE_AUDITD
+int tmy_audit(const char *fmt, ...)
+	__attribute__((format(printf, 1, 2)));
+#else
+#define tmy_audit printk
+#endif
+
+int tmy_del_acl(struct acl_info *ptr);
+int tmy_delete_domain(char *data);
+bool tmy_is_correct_domain(const unsigned char *domainname,
+			  const char *function);
+bool tmy_correct_path(const char *filename,
+		     const int start_type,
+		     const int pattern_type,
+		     const int end_type,
+		     const char *function);
+bool tmy_is_domain_def(const unsigned char *buffer);
+bool tmy_path_match(const struct path_info *pathname0,
+		   const struct path_info *pattern0);
+int tmy_read_aggregator_policy(struct io_buffer *head);
+int tmy_read_alias_policy(struct io_buffer *head);
+int tmy_read_domain_initializer_policy(struct io_buffer *head);
+int tmy_read_domain_keeper_policy(struct io_buffer *head);
+int tmy_read_globally_readable_policy(struct io_buffer *head);
+int tmy_read_path_group_policy(struct io_buffer *head);
+int tmy_read_no_rewrite_policy(struct io_buffer *head);
+int tmy_read_pattern_policy(struct io_buffer *head);
+int tmy_read_address_group_policy(struct io_buffer *head);
+int tmy_argv0_perm(const struct path_info *filename, const char *argv0);
+int tmy_capable(const unsigned int capability);
+int tmy_network_listen_acl(const bool is_ipv6,
+			   const u8 *address,
+			   const u16 port);
+int tmy_network_connect_acl(const bool is_ipv6,
+			    const int sock_type,
+			    const u8 *address,
+			    const u16 port);
+int tmy_network_bind_acl(const bool is_ipv6,
+			 const int sock_type,
+			 const u8 *address,
+			 const u16 port);
+int tmy_network_sendmsg_acl(const bool is_ipv6,
+			    const int sock_type,
+			    const u8 *address,
+			    const u16 port);
+int tmy_network_accept_acl(const bool is_ipv6,
+			   const u8 *address,
+			   const u16 port);
+int tmy_network_recvmsg_acl(const bool is_ipv6,
+			    const int sock_type,
+			    const u8 *address,
+			    const u16 port);
+
+int tmy_signal_acl(int sig, int pid);
+int tmy_add_signal_policy(char *data,
+			  struct domain_info *domain,
+			  const struct condition_list *cond,
+			  const bool is_delete);
+
+char *tmy_init_audit_log(int *len, const u8 profile, const unsigned int mode);
+int tmy_write_audit_log(char *log, const bool is_granted);
+int tmy_acltype2paths(const unsigned int acl_type);
+int tmy_io_printf(struct io_buffer *head, const char *fmt, ...)
+	__attribute__((format(printf, 2, 3)));
+struct domain_info *tmy_find_domain(const char *domainname);
+struct domain_info *tmy_new_domain(const char *domainname, const u8 profile);
+struct domain_info *tmy_undelete_domain(const char *domainname0);
+bool tmy_quota(void);
+unsigned int tmy_flags(const unsigned int index);
+bool tmy_audit_grant(void);
+bool tmy_audit_reject(void);
+void tmy_update_counter(const unsigned char index);
+void *tmy_alloc(const size_t size);
+void tmy_free(const void *p);
+void tmy_fill_path_info(struct path_info *ptr);
+const char *tmy_capability2keyword(const unsigned int capability);
+int tmy_read_capability_profile(struct io_buffer *head);
+int tmy_set_capability_profile(const char *data, unsigned int value,
+			       const unsigned int profile);
+
+int tmy_read_grant_log(struct io_buffer *head);
+int tmy_poll_grant_log(struct file *file, poll_table *wait);
+int tmy_read_reject_log(struct io_buffer *head);
+int tmy_poll_reject_log(struct file *file, poll_table *wait);
+
+static inline bool tmy_pathcmp(const struct path_info *a,
+			       const struct path_info *b)
+{
+	return a->hash != b->hash || strcmp(a->name, b->name);
+}
+
+extern struct domain_info KERNEL_DOMAIN;
+extern struct list_head domain_list;
+void tmy_load_policy(const char *filename);
+int tmy_find_next_domain(struct linux_binprm *,
+			 struct domain_info **);
+
+struct path_info;
+
+int tmy_exec_perm(const struct path_info *filename, struct file *filp);
+/* Check whether the given dentry is allowed to read/write/execute. */
+int tmy_open_perm(struct dentry *dentry, struct vfsmount *mnt, const int flag);
+/* Check whether the given dentry is allowed to write. */
+int tmy_single_write_perm(const unsigned int operation,
+			  struct dentry *dentry,
+			  struct vfsmount *mnt);
+int tmy_double_write_perm(const unsigned int operation,
+			  struct dentry *dentry1,
+			  struct vfsmount *mnt1,
+			  struct dentry *dentry2,
+			  struct vfsmount *mnt2);
+int tmy_rewrite_perm(struct file *filp);
+
+struct inode;
+
+/********************  Index numbers for Access Controls.  ********************/
+
+#define TMY_TYPE_CREATE_ACL       0
+#define TMY_TYPE_UNLINK_ACL       1
+#define TMY_TYPE_MKDIR_ACL        2
+#define TMY_TYPE_RMDIR_ACL        3
+#define TMY_TYPE_MKFIFO_ACL       4
+#define TMY_TYPE_MKSOCK_ACL       5
+#define TMY_TYPE_MKBLOCK_ACL      6
+#define TMY_TYPE_MKCHAR_ACL       7
+#define TMY_TYPE_TRUNCATE_ACL     8
+#define TMY_TYPE_SYMLINK_ACL      9
+#define TMY_TYPE_LINK_ACL        10
+#define TMY_TYPE_RENAME_ACL      11
+#define TMY_TYPE_REWRITE_ACL     12
+
+#define TMY_TYPE_FILE_ACL    	100
+#define TMY_TYPE_ARGV0_ACL      101
+#define TMY_TYPE_IP_NETWORK_ACL 103
+#define TMY_TYPE_SIGNAL_ACL     104
+#define TMY_TYPE_CAPABILITY_ACL 105
+
+#define TMY_CHECK_READ_FOR_OPEN_EXEC 1
+
+char *tmy_print_ipv6(char *buffer, const int buffer_len, const u16 *ip);
+const char *tmy_network2keyword(const unsigned int operation);
+
+/* to check "if task.parent.pid" condition. */
+extern asmlinkage long sys_getppid(void);
+
+#endif

-- 

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

* [TOMOYO #5 05/18] Memory and pathname management functions.
  2007-11-16 17:34 [TOMOYO #5 00/18] TOMOYO Linux - MAC based on process invocation history penguin-kernel
                   ` (3 preceding siblings ...)
  2007-11-16 17:34 ` [TOMOYO #5 04/18] Data structures and prototype defitions penguin-kernel
@ 2007-11-16 17:34 ` penguin-kernel
  2007-11-16 17:34 ` [TOMOYO #5 06/18] Utility functions and policy manipulation interface penguin-kernel
                   ` (12 subsequent siblings)
  17 siblings, 0 replies; 39+ messages in thread
From: penguin-kernel @ 2007-11-16 17:34 UTC (permalink / raw)
  To: akpm; +Cc: linux-kernel, linux-security-module, Kentaro Takeda, Tetsuo Handa

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

Basic functions to get canonicalized absolute pathnames
for TOMOYO Linux. Even the requested pathname is symlink()ed
or chroot()ed, TOMOYO Linux uses the original pathname.

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

--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6-mm/security/tomoyo/realpath.c	2007-11-14 15:58:58.000000000 +0900
@@ -0,0 +1,658 @@
+/*
+ * security/tomoyo/realpath.c
+ *
+ * Get the canonicalized absolute pathnames.
+ * The basis for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+/***** realpath handler *****/
+
+static int tmy_print_ascii(const char *sp, const char *cp,
+			   int *buflen0, char **end0)
+{
+	int buflen = *buflen0;
+	char *end = *end0;
+
+	while (sp <= cp) {
+		unsigned char c;
+
+		c = *(unsigned char *) cp;
+		if (c == '\\') {
+			buflen -= 2;
+			if (buflen < 0)
+				goto out;
+			*--end = '\\';
+			*--end = '\\';
+		} else if (c > ' ' && c < 127) {
+			if (--buflen < 0)
+				goto out;
+			*--end = (char) c;
+		} else {
+			buflen -= 4;
+			if (buflen < 0)
+				goto out;
+			*--end = (c & 7) + '0';
+			*--end = ((c >> 3) & 7) + '0';
+			*--end = (c >> 6) + '0';
+			*--end = '\\';
+		}
+		cp--;
+	}
+
+	*buflen0 = buflen;
+	*end0 = end;
+
+	return 0;
+out: ;
+	return -ENOMEM;
+}
+
+/**
+ * tmy_get_absolute_path - return the realpath of a dentry.
+ * @dentry: pointer to "struct dentry".
+ * @vfsmnt: pointer to "struct vfsmount" to which the @dentry belongs.
+ * @buffer: size of buffer to save the result.
+ * @buflen: size of @buffer .
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * Caller holds the dcache_lock.
+ * Based on __d_path() in fs/dcache.c
+ *
+ * Unlike d_path(), this function traverses upto the root directory of
+ * process's namespace.
+ *
+ * If @dentry is a directory, trailing '/' is appended.
+ * Characters other than ' ' < c < 127 are converted to \ooo style octal string.
+ * Character \ is converted to \\ string.
+ */
+static int tmy_get_absolute_path(struct dentry *dentry,
+				 struct vfsmount *vfsmnt,
+				 char *buffer,
+				 int buflen)
+{
+	char *start = buffer;
+	char *end = buffer + buflen;
+	bool is_dir = (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode));
+	const char *sp;
+	const char *cp;
+
+	if (buflen < 256)
+		goto out;
+
+	*--end = '\0';
+	buflen--;
+
+	while (1) {
+		struct dentry *parent;
+
+		if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
+			/* Global root? */
+			spin_lock(&vfsmount_lock);
+			if (vfsmnt->mnt_parent == vfsmnt) {
+				spin_unlock(&vfsmount_lock);
+				break;
+			}
+			dentry = vfsmnt->mnt_mountpoint;
+			vfsmnt = vfsmnt->mnt_parent;
+			spin_unlock(&vfsmount_lock);
+			continue;
+		}
+
+		if (is_dir) {
+			is_dir = 0;
+			*--end = '/';
+			buflen--;
+		}
+
+		parent = dentry->d_parent;
+		sp = dentry->d_name.name;
+		cp = sp + dentry->d_name.len - 1;
+
+		/* Exception: Use /proc/self/ rather than */
+		/* /proc/\$/ for current process. */
+		if (IS_ROOT(parent) &&
+		    *sp > '0' && *sp <= '9' && parent->d_sb &&
+		    parent->d_sb->s_magic == PROC_SUPER_MAGIC) {
+
+			char *ep;
+			const pid_t pid = (pid_t) simple_strtoul(sp, &ep, 10);
+
+			if (!*ep && pid == current->tgid) {
+				sp = "self";
+				cp = sp + 3;
+			}
+
+		}
+
+		if (tmy_print_ascii(sp, cp, &buflen, &end))
+			goto out;
+
+		if (--buflen < 0)
+			goto out;
+		*--end = '/';
+
+		dentry = parent;
+	}
+	if (*end == '/') {
+		buflen++;
+		end++;
+	}
+
+	sp = dentry->d_name.name;
+	cp = sp + dentry->d_name.len - 1;
+
+	if (tmy_print_ascii(sp, cp, &buflen, &end))
+		goto out;
+
+	/* Move the pathname to the top of the buffer. */
+	memmove(start, end, strlen(end) + 1);
+	return 0;
+out: ;
+	return -ENOMEM;
+}
+
+/**
+ * tmy_realpath_dentry2 - return the realpath of a dentry.
+ * @dentry:      pointer to "struct dentry".
+ * @mnt:         pointer to "struct vfsmount" to which the @dentry belongs.
+ * @newname:     buffer to save the result.
+ * @newname_len: size of @newname .
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_realpath_dentry2(struct dentry *dentry,
+			 struct vfsmount *mnt,
+			 char *newname,
+			 int newname_len)
+{
+	int error;
+	struct dentry *d_dentry;
+	struct vfsmount *d_mnt;
+
+	if (!dentry || !mnt || !newname || newname_len <= 0)
+		return -EINVAL;
+
+	d_dentry = dget(dentry);
+	d_mnt = mntget(mnt);
+
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&dcache_lock);
+	error = tmy_get_absolute_path(d_dentry, d_mnt, newname, newname_len);
+	spin_unlock(&dcache_lock);
+	/***** CRITICAL SECTION END *****/
+
+	dput(d_dentry);
+	mntput(d_mnt);
+	return error;
+}
+
+/**
+ * tmy_realpath_dentry - return the realpath of a dentry.
+ * @dentry: pointer to "struct dentry".
+ * @mnt:    pointer to "struct vfsmount" to which the @dentry belongs.
+ *
+ * Returns realpath(3) of the @dentry on success.
+ * Returns NULL on failure.
+ *
+ * This function uses tmy_alloc(), so caller must call tmy_free()
+ * if this function didn't return NULL.
+ */
+char *tmy_realpath_dentry(struct dentry *dentry, struct vfsmount *mnt)
+{
+	char *buf = tmy_alloc(TMY_MAX_PATHNAME_LEN);
+
+	if (buf &&
+	    tmy_realpath_dentry2(dentry, mnt, buf,
+				 TMY_MAX_PATHNAME_LEN - 1) == 0)
+		return buf;
+
+	tmy_free(buf);
+	return NULL;
+}
+
+/**
+ * tmy_realpath - return the realpath of a pathname.
+ * @pathname: pathname to report.
+ *
+ * Returns realpath(3) of the @pathname on success.
+ * Returns NULL on failure.
+ *
+ * This function uses tmy_alloc(), so caller must call tmy_free()
+ * if this function didn't return NULL.
+ */
+char *tmy_realpath(const char *pathname)
+{
+	struct nameidata nd;
+
+	if (pathname && path_lookup(pathname, LOOKUP_FOLLOW, &nd) == 0) {
+		char *buf = tmy_realpath_dentry(nd.path.dentry, nd.path.mnt);
+
+		path_put(&nd.path);
+		return buf;
+	}
+
+	return NULL;
+}
+
+/**
+ * tmy_realpath_nofollow - return the realpath of a pathname.
+ * @pathname: pathname to report.
+ *
+ * Returns realpath(3) of the @pathname on success.
+ * Returns NULL on failure.
+ *
+ * Unlike tmy_realpath(), this function doesn't follow if @pathname is
+ * a symbolic link.
+ *
+ * This function uses tmy_alloc(), so caller must call tmy_free()
+ * if this function didn't return NULL.
+ */
+char *tmy_realpath_nofollow(const char *pathname)
+{
+	struct nameidata nd;
+
+	if (pathname && path_lookup(pathname, 0, &nd) == 0) {
+		char *buf = tmy_realpath_dentry(nd.path.dentry, nd.path.mnt);
+
+		path_put(&nd.path);
+		return buf;
+	}
+
+	return NULL;
+}
+
+/* tmy_get_absolute_path() for "struct ctl_table". */
+static int tmy_sysctl_path(struct ctl_table *table, char *buffer, int buflen)
+{
+	char *end = buffer + buflen;
+
+	if (buflen < 256)
+		goto out;
+
+	*--end = '\0';
+	buflen--;
+
+	buflen -= 9; /* for "/proc/sys" prefix */
+
+	while (table) {
+		char buf[32];
+		const char *sp = table->procname;
+		const char *cp;
+
+		if (!sp) {
+			memset(buf, 0, sizeof(buf));
+			snprintf(buf, sizeof(buf) - 1, "=%d=", table->ctl_name);
+			sp = buf;
+		}
+		cp = strchr(sp, '\0') - 1;
+
+		if (tmy_print_ascii(sp, cp, &buflen, &end))
+			goto out;
+
+		if (--buflen < 0)
+			goto out;
+
+		*--end = '/';
+		table = table->parent;
+	}
+
+	/* Move the pathname to the top of the buffer. */
+	memmove(buffer, "/proc/sys", 9);
+	memmove(buffer + 9, end, strlen(end) + 1);
+	return 0;
+out: ;
+	return -ENOMEM;
+}
+
+/**
+ * sysctlpath_from_table - return the realpath of a ctl_table.
+ * @table: pointer to "struct ctl_table".
+ *
+ * Returns realpath(3) of the @table on success.
+ * Returns NULL on failure.
+ *
+ * This function uses tmy_alloc(), so caller must call tmy_free()
+ * if this function didn't return NULL.
+ */
+char *sysctlpath_from_table(struct ctl_table *table)
+{
+	char *buf = tmy_alloc(TMY_MAX_PATHNAME_LEN);
+
+	if (buf && tmy_sysctl_path(table, buf, TMY_MAX_PATHNAME_LEN - 1) == 0)
+		return buf;
+
+	tmy_free(buf);
+	return NULL;
+}
+
+/***** Private memory allocator. *****/
+
+/*
+ * Round up an integer so that the returned pointers are appropriately aligned.
+ * FIXME: Are there more requirements that is needed for assigning value
+ * atomically?
+ */
+static inline unsigned int tmy_roundup(const unsigned int size)
+{
+	if (sizeof(void *) >= sizeof(long))
+		return ((size + sizeof(void *) - 1) / sizeof(void *))
+			* sizeof(void *);
+
+	return ((size + sizeof(long) - 1) / sizeof(long)) * sizeof(long);
+}
+
+static unsigned int allocated_memory_for_elements;
+
+/**
+ * tmy_get_memory_used_for_elements - get memory usage for private data.
+ *
+ * Returns size of memory allocated for keeping individual ACL elements.
+ */
+unsigned int tmy_get_memory_used_for_elements(void)
+{
+	return allocated_memory_for_elements;
+}
+
+/**
+ * tmy_alloc_element - allocate memory for structures.
+ * @size: requested size in bytes.
+ *
+ * Returns '\0'-initialized memory region on success.
+ * Returns NULL on failure.
+ *
+ * This function allocates memory for keeping ACL entries.
+ * The RAM is chunked, so NEVER try to kfree() the returned pointer.
+ */
+void *tmy_alloc_element(const unsigned int size)
+{
+	static DEFINE_MUTEX(mutex);
+	static char *buf;
+	static unsigned int buf_used_len = PAGE_SIZE;
+	char *ptr = NULL;
+	const unsigned int word_aligned_size = tmy_roundup(size);
+
+	if (word_aligned_size > PAGE_SIZE)
+		return NULL;
+
+	mutex_lock(&mutex);
+
+	if (buf_used_len + word_aligned_size > PAGE_SIZE) {
+		ptr = kzalloc(PAGE_SIZE, GFP_KERNEL);
+
+		if (!ptr) {
+			printk(KERN_INFO "ERROR: "
+			       "Out of memory for tmy_alloc_element().\n");
+			if (!sbin_init_started)
+				panic("MAC Initialization failed.\n");
+		} else {
+			buf = ptr;
+			allocated_memory_for_elements += PAGE_SIZE;
+			buf_used_len = word_aligned_size;
+			ptr = buf;
+		}
+
+	} else if (word_aligned_size) {
+		unsigned int i;
+
+		ptr = buf + buf_used_len;
+		buf_used_len += word_aligned_size;
+
+		for (i = 0; i < word_aligned_size; i++) {
+			if (ptr[i]) { /* This must not happen! */
+				printk(KERN_ERR
+				       "WARNING: Reserved memory was tainted! "
+				       "The system might go wrong.\n");
+				ptr[i] = '\0';
+			}
+		}
+
+	}
+
+	mutex_unlock(&mutex);
+	return ptr;
+}
+
+/***** Shared memory allocator. *****/
+
+static unsigned int allocated_memory_for_savename;
+
+/**
+ * tmy_get_memory_used_for_save_name - get memory usage for shared data.
+ *
+ * Returns size of memory allocated for keeping string tokens.
+ */
+unsigned int tmy_get_memory_used_for_save_name(void)
+{
+	return allocated_memory_for_savename;
+}
+
+#define MAX_HASH 256
+
+/* List of tokens. */
+struct name_entry {
+	struct list_head list;
+	struct path_info entry;
+};
+
+/* List of free memory. */
+struct free_memory_block {
+	struct list_head list;
+	char *ptr; /* Pointer to a free area. */
+	int len;   /* Length of the area.     */
+};
+
+/**
+ * tmy_save_name - keep the given token on the RAM.
+ * @name: the string token to remember.
+ *
+ * Returns pointer to memory region on success.
+ * Returns NULL on failure.
+ *
+ * This function allocates memory for keeping any string data.
+ * The RAM is shared, so NEVER try to modify or kfree() the returned name.
+ */
+const struct path_info *tmy_save_name(const char *name)
+{
+	static bool first_call = 1;
+	static DEFINE_MUTEX(mutex);
+	static LIST_HEAD(fmb_list);
+	struct free_memory_block *fmb;
+	static struct list_head name_list[MAX_HASH]; /* The list of names. */
+	struct name_entry *ptr;
+	unsigned int hash;
+	int len;
+	bool found = 0;
+	if (!name)
+		return NULL;
+	len = strlen(name) + 1;
+	if (len > TMY_MAX_PATHNAME_LEN) {
+		printk(KERN_INFO "ERROR: Name too long for tmy_save_name().\n");
+		return NULL;
+	}
+	hash = full_name_hash((const unsigned char *) name, len - 1);
+	/* List access in this function is protected by mutex. */
+	mutex_lock(&mutex);
+	if (first_call) {
+		int i;
+		first_call = 0;
+		for (i = 0; i < MAX_HASH; i++)
+			INIT_LIST_HEAD(&name_list[i]);
+		if (TMY_MAX_PATHNAME_LEN > PAGE_SIZE)
+			panic("Bad size.");
+	}
+	list_for_each_entry(ptr, &name_list[hash % MAX_HASH], list) {
+		if (hash == ptr->entry.hash &&
+		    strcmp(name, ptr->entry.name) == 0)
+			goto out;
+	}
+	list_for_each_entry(fmb, &fmb_list, list) {
+		if (len <= fmb->len) {
+			found = 1;
+			break;
+		}
+	}
+	if (!found) {
+		char *cp;
+		cp = kzalloc(PAGE_SIZE, GFP_KERNEL);
+		if (!cp)
+			goto out2;
+		fmb = kzalloc(sizeof(*fmb), GFP_KERNEL);
+		if (!fmb) {
+			kfree(cp);
+out2: ;
+			printk(KERN_INFO
+			       "ERROR: Out of memory for tmy_save_name().\n");
+			if (!sbin_init_started)
+				panic("MAC Initialization failed.\n");
+			ptr = NULL;
+			goto out;
+		}
+		allocated_memory_for_savename += PAGE_SIZE;
+		fmb->ptr = cp;
+		fmb->len = PAGE_SIZE;
+		list_add_tail(&fmb->list, &fmb_list);
+	}
+	ptr = tmy_alloc_element(sizeof(*ptr));
+	if (!ptr)
+		goto out;
+	ptr->entry.name = fmb->ptr;
+	memmove(fmb->ptr, name, len);
+	tmy_fill_path_info(&ptr->entry);
+	fmb->ptr += len;
+	fmb->len -= len;
+	list_add_tail(&ptr->list, &name_list[hash % MAX_HASH]);
+	if (fmb->len == 0) {
+		list_del(&fmb->list); /* Protected by mutex. */
+		kfree(fmb);
+	}
+out: ;
+	mutex_unlock(&mutex);
+	return ptr ? &ptr->entry : NULL;
+}
+
+/***** Dynamic memory allocator. *****/
+
+/* List of temporary memory blocks. */
+struct cache_entry {
+	struct list_head list;
+	void *ptr;
+	int size;
+};
+
+static struct kmem_cache *tmy_cachep;
+
+/**
+ * tmy_realpath_init - initialize memory allocator.
+ */
+static int __init tmy_realpath_init(void)
+{
+	tmy_cachep = kmem_cache_create("tomoyo_cache",
+				       sizeof(struct cache_entry),
+				       0, SLAB_PANIC, NULL);
+	return 0;
+}
+postcore_initcall(tmy_realpath_init);
+
+static LIST_HEAD(cache_list);
+static DEFINE_SPINLOCK(cache_list_lock);
+static unsigned int dynamic_memory_size;
+
+/**
+ * tmy_get_memory_used_for_dynamic - get memory usage for temporary purpose.
+ *
+ * Returns size of memory allocated for keeping temporary purpose.
+ */
+unsigned int tmy_get_memory_used_for_dynamic(void)
+{
+	return dynamic_memory_size;
+}
+
+/**
+ * tmy_alloc - allocate memory for temporary purpose.
+ * @size: requested size in bytes.
+ *
+ * Returns '\0'-initialized memory region on success.
+ * Returns NULL on failure.
+ *
+ * This function allocates memory for keeping ACL entries.
+ * The caller has to call tmy_free() the returned pointer
+ * when memory is no longer needed.
+ */
+void *tmy_alloc(const size_t size)
+{
+	void *ret = kzalloc(size, GFP_KERNEL);
+	struct cache_entry *new_entry;
+
+	if (!ret) {
+		printk(KERN_INFO "ERROR: "
+		       "kmalloc(): Out of memory for tmy_alloc().\n");
+		return ret;
+	}
+
+	new_entry = kmem_cache_alloc(tmy_cachep, GFP_KERNEL);
+
+	if (!new_entry) {
+		printk(KERN_INFO "ERROR: "
+		       "kmem_cache_alloc(): Out of memory for tmy_alloc().\n");
+		kfree(ret);
+		ret = NULL;
+	} else {
+		INIT_LIST_HEAD(&new_entry->list);
+		new_entry->ptr = ret;
+		new_entry->size = ksize(ret);
+
+		/***** CRITICAL SECTION START *****/
+		spin_lock(&cache_list_lock);
+		list_add_tail(&new_entry->list, &cache_list);
+		dynamic_memory_size += new_entry->size;
+		spin_unlock(&cache_list_lock);
+		/***** CRITICAL SECTION END *****/
+	}
+
+	return ret;
+}
+
+/**
+ * tmy_free - free memory allocated by tmy_alloc().
+ * @p: pointer to memory block allocated by tmy_alloc().
+ *
+ * If caller calls this function for multiple times (i.e. double-free() bug) or
+ * calls this function with invalid pointer, warning message will appear.
+ * If caller forgets to call this function,
+ * tmy_get_memory_used_for_dynamic() will indicate memory leaks.
+ */
+void tmy_free(const void *p)
+{
+	struct list_head *v;
+	struct cache_entry *entry = NULL;
+	if (!p)
+		return;
+
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&cache_list_lock);
+	list_for_each_prev(v, &cache_list) {
+		entry = list_entry(v, struct cache_entry, list);
+		if (entry->ptr != p) {
+			entry = NULL;
+			continue;
+		}
+		list_del(&entry->list);
+		dynamic_memory_size -= entry->size;
+		break;
+	}
+	spin_unlock(&cache_list_lock);
+	/***** CRITICAL SECTION END *****/
+
+	if (entry) {
+		kfree(p);
+		kmem_cache_free(tmy_cachep, entry);
+	} else
+		printk(KERN_INFO "BUG: tmy_free() with invalid pointer.\n");
+
+}

-- 

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

* [TOMOYO #5 06/18] Utility functions and policy manipulation interface.
  2007-11-16 17:34 [TOMOYO #5 00/18] TOMOYO Linux - MAC based on process invocation history penguin-kernel
                   ` (4 preceding siblings ...)
  2007-11-16 17:34 ` [TOMOYO #5 05/18] Memory and pathname management functions penguin-kernel
@ 2007-11-16 17:34 ` penguin-kernel
  2007-11-16 17:34 ` [TOMOYO #5 07/18] Domain transition functions penguin-kernel
                   ` (11 subsequent siblings)
  17 siblings, 0 replies; 39+ messages in thread
From: penguin-kernel @ 2007-11-16 17:34 UTC (permalink / raw)
  To: akpm; +Cc: linux-kernel, linux-security-module, Kentaro Takeda, Tetsuo Handa

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

Common functions for TOMOYO Linux.

TOMOYO Linux uses /sys/kernel/security/tomoyo interface for configuration.

/sys/kernel/security/tomoyo/domain_policy is the domain-based access policy.
Access control list for files, networks, argv[0], capabilities and signal is
stored in domain_policy.

/sys/kernel/security/tomoyo/system_policy is the system-wide access policy.
Access control list for mount, umount and pivot_root is
stored in system_policy.

/sys/kernel/security/tomoyo/exception_policy is the other settings such as
globally readable files, domain transition configurations
or pre-defined patterned pathnames.

/sys/kernel/security/tomoyo/profile has some profiles, which configure
the access control level of TOMOYO Linux. A profile is assigned to a domain.

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

--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6-mm/security/tomoyo/common.c	2007-11-14 15:57:51.000000000 +0900
@@ -0,0 +1,2391 @@
+/*
+ * security/tomoyo/common.c
+ *
+ * Common functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+#define MAX_ACCEPT_ENTRY 2048
+
+static int tmy_read_control(struct file *file,
+			    char __user *buffer,
+			    const int buffer_len);
+
+/*************************  VARIABLES  *************************/
+
+/* /sbin/init started? */
+bool sbin_init_started;
+
+static struct {
+	const char *keyword;
+	unsigned int current_value;
+	const unsigned int max_value;
+} tmy_control[TMY_MAX_CONTROL_INDEX] = {
+	[TMY_COMMENT]             = { "COMMENT",             0, 0 },
+	[TMY_MAC_FOR_FILE]        = { "MAC_FOR_FILE",        0, 3 },
+	[TMY_MAC_FOR_ARGV0]       = { "MAC_FOR_ARGV0",       0, 3 },
+	[TMY_MAC_FOR_NETWORK]     = { "MAC_FOR_NETWORK",     0, 3 },
+	[TMY_MAC_FOR_SIGNAL]      = { "MAC_FOR_SIGNAL",      0, 3 },
+	[TMY_DENY_CONCEAL_MOUNT]  = { "DENY_CONCEAL_MOUNT",  0, 3 },
+	[TMY_RESTRICT_MOUNT]      = { "RESTRICT_MOUNT",      0, 3 },
+	[TMY_RESTRICT_UMOUNT]     = { "RESTRICT_UNMOUNT",    0, 3 },
+	[TMY_RESTRICT_PIVOT_ROOT] = { "RESTRICT_PIVOT_ROOT", 0, 3 },
+	[TMY_MAX_ACCEPT_ENTRY]    =
+	{ "MAX_ACCEPT_ENTRY", MAX_ACCEPT_ENTRY, INT_MAX },
+	[TMY_MAX_GRANT_LOG]       = { "MAX_GRANT_LOG",       1024, INT_MAX },
+	[TMY_MAX_REJECT_LOG]      = { "MAX_REJECT_LOG",      1024, INT_MAX },
+	[TMY_VERBOSE]             = { "TOMOYO_VERBOSE",      1, 1 },
+	[TMY_ALLOW_ENFORCE_GRACE] = { "ALLOW_ENFORCE_GRACE", 0, 1 },
+};
+
+struct profile {
+	unsigned int value[TMY_MAX_CONTROL_INDEX];
+	const struct path_info *comment;
+};
+
+static struct profile *profile_ptr[TMY_MAX_PROFILES];
+
+/*************************  UTILITY FUNCTIONS  *************************/
+
+/* Is the current process running as root? */
+static int tmy_is_root(void)
+{
+	return !current->uid && !current->euid;
+}
+
+/**
+ * tmy_normalize_line - make a line tidy.
+ * @buffer: the line to make tidy.
+ *
+ * All tokens (such as pathnames) used in TOMOYO Linux contains
+ * only ASCII printable (i.e. 0x21-0x7E) range characters.
+ * This allows policy files and auditing logs split monotonically
+ * using space (i.e. ' ') and new line (i.e. '\n') characters.
+ *
+ * Remove leading and trailing non ASCII printable chracters and
+ * replace one or more non ASCII printable chracters with single space.
+ */
+static void tmy_normalize_line(unsigned char *buffer)
+{
+	unsigned char *sp = buffer;
+	unsigned char *dp = buffer;
+	int first = 1;
+
+	while (*sp && (*sp <= 0x20 || *sp >= 0x7F))
+		sp++;
+
+	while (*sp) {
+		if (!first)
+			*dp++ = ' ';
+		first = 0;
+		while (*sp > 0x20 && *sp < 0x7F)
+			*dp++ = *sp++;
+		while (*sp && (*sp <= 0x20 || *sp >= 0x7F))
+			sp++;
+	}
+
+	*dp = '\0';
+}
+
+/* Is @c the first letter of "\ooo" expression? */
+static int tmy_char_is_0to3(const unsigned char c)
+{
+	return c >= '0' && c <= '3';
+}
+
+/* Is @c the second or third letter of "\ooo" expression? */
+static int tmy_char_is_0to7(const unsigned char c)
+{
+	return c >= '0' && c <= '7';
+}
+
+/* Is @c a decimal letter? */
+static int tmy_char_is_0to9(const unsigned char c)
+{
+	return c >= '0' && c <= '9';
+}
+
+/* Is @c a hexadecimal letter? */
+static int tmy_char_is_hex(const unsigned char c)
+{
+	return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')
+		|| (c >= 'a' && c <= 'f');
+}
+
+/* Is @c an alphabet letter? */
+static int tmy_char_is_alpha(const unsigned char c)
+{
+	return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
+}
+
+/* Convert \ooo style expression to a byte. */
+static unsigned char tmy_str2chr(const unsigned char c,
+				 const unsigned char d,
+				 const unsigned char e)
+{
+	return (((unsigned char) (c - '0')) << 6) +
+		(((unsigned char) (d - '0')) << 3) +
+		(((unsigned char) (e - '0')));
+}
+
+/* Does the @src starts with @find? */
+static int tmy_strstarts(char **src, const char *find)
+{
+	const int len = strlen(find);
+	char *tmp = *src;
+
+	if (strncmp(tmp, find, len) == 0) {
+		tmp += len;
+		*src = tmp;
+		return 1;
+	}
+
+	return 0;
+}
+
+/**
+ * tmy_correct_path - validate a pathname.
+ * @filename:     the pathname to check
+ * @start_type:   1 if the pathname must start with '/'
+ *                -1 if the pathname must not start with '/'
+ *                0 if it does not matter
+ * @pattern_type: 1 if the pathname must contain patterns
+ *                -1 if the pathname must not contain patterns
+ *                0 if it does not matter
+ * @end_type:     1 if the pathname must end with '/'
+ *                -1 if the pathname must not end with '/'
+ *                0 if it does not matter
+ * @function:     string to display if the @filename is invalid
+ *
+ * Returns true if the pathname is valid.
+ * Returns false otherwise.
+ *
+ * Check whether the given pathname follows the naming rules.
+ */
+bool tmy_correct_path(const char *filename,
+		     const int start_type,
+		     const int pattern_type,
+		     const int end_type,
+		     const char *function)
+{
+	int contains_pattern = 0;
+	char c;
+	char d;
+	char e;
+	const char *original_filename = filename;
+
+	if (!filename)
+		goto out;
+
+	c = *filename;
+	if (start_type == 1) { /* Must start with '/' */
+		if (c != '/')
+			goto out;
+	} else if (start_type == -1) { /* Must not start with '/' */
+		if (c == '/')
+			goto out;
+	}
+
+	if (c)
+		c = *(strchr(filename, '\0') - 1);
+	if (end_type == 1) { /* Must end with '/' */
+		if (c != '/')
+			goto out;
+	} else if (end_type == -1) { /* Must not end with '/' */
+		if (c == '/')
+			goto out;
+	}
+
+	while ((c = *filename++) != '\0') {
+		if (c == '\\') {
+			unsigned char f;
+			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)
+					goto out; /* Must not contain pattern */
+				contains_pattern = 1;
+				continue;
+			case '0':   /* "\ooo" */
+			case '1':
+			case '2':
+			case '3':
+				d = *filename++;
+				if (!tmy_char_is_0to7(d))
+					goto out;
+				e = *filename++;
+				if (!tmy_char_is_0to7(e))
+					goto out;
+				f = tmy_str2chr(c, d, e);
+				if (f && (f <= 0x20 || f >= 0x7F))
+					/* valid \ooo expression */
+					continue;
+			}
+			goto out;
+		} else if (c <= 0x20 || c >= 0x7F)
+			goto out;
+	}
+
+	if (pattern_type == 1) { /* Must contain pattern */
+		if (!contains_pattern)
+			goto out;
+	}
+
+	return 1;
+
+out: ;
+	printk(KERN_DEBUG "%s: Invalid pathname '%s'\n",
+	       function, original_filename);
+	return 0;
+}
+
+/**
+ * tmy_is_correct_domain - validate a domainname.
+ * @domainname: the domainname to check.
+ * @function:   string to display if the @domainname is invalid.
+ *
+ * Returns true if the domainname is valid.
+ * Returns false otherwise.
+ *
+ * Check whether the given domainname follows the naming rules.
+ */
+bool tmy_is_correct_domain(const unsigned char *domainname,
+			  const char *function)
+{
+	unsigned char c;
+	unsigned char d;
+	unsigned char e;
+	const char *org_domainname = domainname;
+
+	if (!domainname || !tmy_strstarts((char **) &domainname, TMY_ROOT_NAME))
+		goto out;
+
+	if (!*domainname)
+		return 1;
+
+	do {
+		/* 0x20 is a domainname separator. */
+		if (*domainname++ != ' ')
+			goto out;
+		/* Must starts with '/'. */
+		if (*domainname++ != '/')
+			goto out;
+		while ((c = *domainname) != '\0' && c != ' ') {
+			domainname++;
+			if (c == '\\') {
+				unsigned char f;
+				switch ((c = *domainname++)) {
+				case '\\':  /* "\\" */
+					continue;
+				case '0':   /* "\ooo" */
+				case '1':
+				case '2':
+				case '3':
+					d = *domainname++;
+					if (!tmy_char_is_0to7(d))
+						goto out;
+					e = *domainname++;
+					if (!tmy_char_is_0to7(e))
+						goto out;
+					f = tmy_str2chr(c, d, e);
+					if (f && (f <= 0x20 || f >= 0x7F))
+						continue;
+				}
+				goto out;
+			} else if (c < 0x20 || c >= 0x7F)
+				/* 0x20 is a domainname separator. */
+				goto out;
+		}
+	} while (*domainname);
+
+	return 1;
+
+out: ;
+	printk(KERN_DEBUG "%s: Invalid domainname '%s'\n",
+	       function, org_domainname);
+	return 0;
+}
+
+/**
+ * tmy_path_depth - evaluate the number of '/' characters in a token.
+ * @pathname: the token to evaluate.
+ *
+ * Each '/' character but the trailing '/' scores 2.
+ * The trailing '/' scores 1.
+ *
+ * If @pathname ends with '/', the return value is an odd integer.
+ * If @pathname does not end with '/', the return value is an even integer.
+ */
+static int tmy_path_depth(const char *pathname)
+{
+	int i = 0;
+
+	if (pathname) {
+		char *ep = strchr(pathname, '\0');
+
+		if (pathname < ep--) {
+			if (*ep != '/')
+				i++;
+			while (pathname <= ep)
+				if (*ep-- == '/')
+					i += 2;
+		}
+	}
+
+	return i;
+}
+
+/**
+ * tmy_const_part_length - calculate the constant part in a token.
+ * @filename: the token to calculate.
+ *
+ * Returns leading length of @filename that can be compared using strncmp().
+ */
+static int tmy_const_part_length(const char *filename)
+{
+	int len = 0;
+
+	if (filename) {
+		char c;
+
+		while ((c = *filename++) != '\0') {
+			if (c != '\\') {
+				len++;
+				continue;
+			}
+			switch (c = *filename++) {
+			case '\\':  /* "\\" */
+				len += 2;
+				continue;
+			case '0':   /* "\ooo" */
+			case '1':
+			case '2':
+			case '3':
+				if (!tmy_char_is_0to7(*filename++))
+					break;
+				if (!tmy_char_is_0to7(*filename++))
+					break;
+				len += 4;
+				continue;
+			}
+			break;
+		}
+	}
+
+	return len;
+}
+
+/**
+ * tmy_fill_path_info - fill "struct path_info" entry.
+ * @ptr: pointer to "struct path_info".
+ *
+ * Caller stores a token in "struct path_info"->name .
+ * This function will fill rest of "struct path_info" members.
+ */
+void tmy_fill_path_info(struct path_info *ptr)
+{
+	const char *name = ptr->name;
+	const int len = strlen(name);
+	ptr->total_len = len;
+	ptr->const_len = tmy_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 = tmy_path_depth(name);
+}
+
+/**
+ * tmy_file_match2 - internal function for tmy_path_match().
+ * @filename:     start address of the token.
+ * @filename_end: end address of the token.
+ * @pattern:      start address of the pattern.
+ * @pattern_end:  end address of the pattern.
+ *
+ * Handle all patterns other than '\-' pattern.
+ * Returns true if matches.
+ * Returns false otherwise.
+ *
+ * @filename and @pattern do not contain '/'.
+ * @filename and @pattern are not '\0'-terminated.
+ * @pattern does not contain '\-'.
+ */
+static bool tmy_file_match2(const char *filename,
+			   const char *filename_end,
+			   const char *pattern,
+			   const char *pattern_end)
+{
+	while (filename < filename_end && pattern < pattern_end) {
+		char c;
+		int i;
+		int j;
+
+		if (*pattern != '\\') {
+			if (*filename++ != *pattern++)
+				goto out;
+			continue;
+		}
+
+		c = *filename;
+		pattern++;
+
+		switch (*pattern) {
+		case '?':
+			if (c == '/')
+				goto out;
+			if (c != '\\')
+				break;
+			/* filename[0] != '\0' */
+			c = filename[1];
+			if (c == '\\')
+				filename++;
+			else if (tmy_char_is_0to3(c) &&
+				 /* filename[1] != '\0' */
+				 tmy_char_is_0to7(filename[2]) &&
+				 /* filename[2] != '\0' */
+				 tmy_char_is_0to7(filename[3]))
+				/* filename[3] != '\0' */
+				filename += 3;
+				/*
+				 * Why not "filename += 4;" here
+				 * because it processed 4 (i.e. "\ooo") bytes?
+				 * - Because "filename++;" is done
+				 * after "break;".
+				 */
+			else
+				goto out;
+			break;
+		case '\\':
+			if (c != '\\')
+				goto out;
+			/* filename[0] != '\0' */
+			if (*++filename != '\\')
+				goto out;
+			break;
+		case '+':
+			if (!tmy_char_is_0to9(c))
+				goto out;
+			break;
+		case 'x':
+			if (!tmy_char_is_hex(c))
+				goto out;
+			break;
+		case 'a':
+			if (!tmy_char_is_alpha(c))
+				goto out;
+			break;
+		case '0':
+		case '1':
+		case '2':
+		case '3':
+			if (c != '\\')
+				goto out;
+			/* filename[0] != '\0' */
+			c = filename[1];
+			if (!tmy_char_is_0to3(c) || c != *pattern)
+				goto out;
+			/* filename[1] != '\0' */
+			c = filename[2];
+			/* pattern[0] != '\0' */
+			if (!tmy_char_is_0to7(c) || c != pattern[1])
+				goto out;
+			/* filename[2] != '\0' */
+			c = filename[3];
+			/* pattern[1] != '\0' */
+			if (!tmy_char_is_0to7(c) || c != pattern[2])
+				goto out;
+			/* filename[3] != '\0' */
+			filename += 3;
+			/* pattern[2] != '\0' */
+			pattern += 2;
+			break;
+		case '*':
+		case '@':
+			for (i = 0; i <= filename_end - filename; i++) {
+				if (tmy_file_match2(filename + i,
+						    filename_end,
+						    pattern + 1,
+						    pattern_end))
+					return 1;
+				c = filename[i];
+				if (c == '.' && *pattern == '@')
+					break;
+				if (c != '\\')
+					continue;
+				/* filename[i] != '\0' */
+				c = filename[i + 1];
+				if (c == '\\')
+					/* filename[i + 1] != '\0' */
+					i++;
+				else if (tmy_char_is_0to3(c) &&
+					 /* filename[i + 1] != '\0' */
+					 tmy_char_is_0to7(filename[i + 2]) &&
+					 /* filename[i + 2] != '\0' */
+					 tmy_char_is_0to7(filename[i + 3]))
+					 /* filename[i + 3] != '\0' */
+					i += 3;
+				else
+					break; /* Bad pattern. */
+			}
+			goto out;
+		default:
+			j = 0;
+			/*
+			 * If j == 0, for() loop does nothing.
+			 * So I can use while() without checking first letter.
+			 */
+			c = *pattern;
+			if (c == '$')
+				while (tmy_char_is_0to9(filename[j]))
+					j++;
+			else if (c == 'X')
+				while (tmy_char_is_hex(filename[j]))
+					j++;
+			else if (c == 'A')
+				while (tmy_char_is_alpha(filename[j]))
+					j++;
+			for (i = 1; i <= j; i++)
+				if (tmy_file_match2(filename + i,
+						    filename_end,
+						    pattern + 1,
+						    pattern_end))
+					return 1;
+			goto out; /* Not matched or bad pattern. */
+		}
+		filename++; /* filename[0] != '\0' */
+		pattern++; /* pattern[0] != '\0' */
+	}
+
+	/* Skip trailing "\*" and "\@" patterns. */
+	while (*pattern == '\\' &&
+	       (*(pattern + 1) == '*' ||
+		*(pattern + 1) == '@'))
+		pattern += 2;
+
+	/* If both are at ending position, they are equals. */
+	return (filename == filename_end && pattern == pattern_end);
+out: ;
+	return 0;
+}
+
+/**
+ * tmy_file_match - internal function for tmy_path_match().
+ * @filename:     start address of the token.
+ * @filename_end: end address of the token.
+ * @pattern:      start address of the pattern.
+ * @pattern_end:  end address of the pattern.
+ *
+ * Handle patterns containing '\-' pattern.
+ * Returns true if matches.
+ * Returns false otherwise.
+ *
+ * @filename and @pattern do not contain '/'.
+ * @filename and @pattern are not '\0'-terminated.
+ */
+static bool tmy_file_match(const char *filename,
+			  const char *filename_end,
+			  const char *pattern,
+			  const char *pattern_end)
+{
+	const char *pattern_start = pattern;
+	bool first = 1;
+	bool result;
+
+	while (pattern < pattern_end - 1) {
+		/* Split at "\-" pattern. */
+		if (*pattern++ != '\\' || *pattern++ != '-')
+			continue;
+		result = tmy_file_match2(filename,
+					 filename_end,
+					 pattern_start,
+					 pattern - 2);
+		/* If before "\-" pattern, invert the result. */
+		if (first)
+			result = !result;
+		/*
+		 * If not matched before first "\-" pattern, return 0.
+		 * If matched after first "\-" pattern, return 0.
+		 */
+		if (result)
+			return 0;
+		first = 0;
+		pattern_start = pattern;
+	}
+
+	result = tmy_file_match2(filename,
+				 filename_end, pattern_start, pattern_end);
+	/* If after first "\-" pattern, invert the result. */
+	return first ? result : !result;
+}
+
+/**
+ * tmy_path_match - do a pattern matching.
+ * @pathname0: pointer to a token to compare.
+ * @pattern0:  pointer to a pattern to compare.
+ *
+ * Returns true if @pathname0 matches to @pattern0 .
+ * Returns false otherwise.
+ *
+ *
+ *  Check whether the given token matches to the given pattern.
+ *
+ *  The following patterns are available.
+ *    \\     \ itself.
+ *    \ooo   Octal representation of a byte.
+ *    \*     More than or equals to 0 character other than '/'.
+ *    \@     More than or equals to 0 character other than '/' or '.'.
+ *    \?     1 byte character other than '/'.
+ *    \$     More than or equals to 1 decimal digit.
+ *    \+     1 decimal digit.
+ *    \X     More than or equals to 1 hexadecimal digit.
+ *    \x     1 hexadecimal digit.
+ *    \A     More than or equals to 1 alphabet character.
+ *    \a     1 alphabet character.
+ *    \-     Subtraction operator.
+ */
+bool tmy_path_match(const struct path_info *pathname0,
+		   const struct path_info *pattern0)
+{
+	const char *pathname = pathname0->name;
+	const char *pattern = pattern0->name;
+	const int len = pattern0->const_len;
+
+	/* If it doesn't contains patterns, I can use tmy_pathcmp() */
+	if (!pattern0->is_patterned)
+		return !tmy_pathcmp(pathname0, pattern0);
+	/* Check the depth of directory. */
+	if (pathname0->depth != pattern0->depth)
+		return 0;
+	/* Use strncmp() for constant part. */
+	if (strncmp(pathname, pattern, len))
+		return 0;
+
+	pathname += len;
+	pattern += len;
+
+	/* Split at '/' character, and compare. */
+	while (*pathname && *pattern) {
+		const char *pathname_delimiter = strchr(pathname, '/');
+		const char *pattern_delimiter = strchr(pattern, '/');
+
+		if (!pathname_delimiter)
+			pathname_delimiter = strchr(pathname, '\0');
+		if (!pattern_delimiter)
+			pattern_delimiter = strchr(pattern, '\0');
+		if (!tmy_file_match(pathname,
+				    pathname_delimiter,
+				    pattern,
+				    pattern_delimiter))
+			return 0;
+
+		pathname = *pathname_delimiter ?
+			pathname_delimiter + 1 :
+			pathname_delimiter;
+		pattern = *pattern_delimiter ?
+			pattern_delimiter + 1 :
+			pattern_delimiter;
+	}
+
+	/* Skip trailing "\*" and "\@" patterns. */
+	while (*pattern == '\\' &&
+	       (*(pattern + 1) == '*' ||
+		*(pattern + 1) == '@'))
+		pattern += 2;
+
+	/* If both are at '\0' position, they are equals. */
+	return (!*pathname && !*pattern);
+}
+
+/**
+ * tmy_io_printf - transactional printf() for "struct io_buffer".
+ * @head: pointer to "struct io_buffer".
+ * @fmt:  format strings for printf().
+ *
+ * Returns zero on success.
+ * Returns nonzero otherwise.
+ *
+ * Transactional printf() to "struct io_buffer" structure.
+ * snprintf() will truncate, but tmy_io_printf() won't.
+ * This is needed because dumping partially truncated tokens
+ * is not acceptable for TOMOYO Linux.
+ */
+int tmy_io_printf(struct 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 -ENOMEM;
+
+	va_start(args, fmt);
+	len = vsnprintf(head->read_buf + pos, size, fmt, args);
+	va_end(args);
+
+	if (pos + len >= head->readbuf_size)
+		return -ENOMEM;
+
+	head->read_avail += len;
+
+	return 0;
+}
+
+/**
+ * tmy_get_exe - get realpath of current process.
+ *
+ * Returns realpath(3) of current process on success.
+ * Returns NULL on failure.
+ *
+ * This function uses tmy_alloc(), so caller must tmy_free()
+ * if this function didn't return NULL.
+ */
+const char *tmy_get_exe(void)
+{
+	struct vm_area_struct *vma;
+
+	if (!current->mm)
+		return NULL;
+	for (vma = current->mm->mmap; vma; vma = vma->vm_next)
+		if ((vma->vm_flags & VM_EXECUTABLE) && vma->vm_file)
+			return tmy_realpath_dentry(vma->vm_file->f_dentry,
+						   vma->vm_file->f_vfsmnt);
+
+	return NULL;
+}
+
+/**
+ * tmy_lastname - get last part of domainname.
+ *
+ * Returns last part of domainname.
+ */
+const char *tmy_lastname(const struct domain_info *domain)
+{
+	const char *cp0 = domain->domainname->name;
+	const char *cp1 = strrchr(cp0, ' ');
+	if (cp1)
+		return cp1 + 1;
+	return cp0;
+}
+
+/**
+ * tmy_get_msg - get message from mode.
+ * @is_enforce: enforcing flag.
+ *
+ * Returns "ERROR" if enforcing, "WARNING" otherwise.
+ */
+const char *tmy_getmsg(bool is_enforce)
+{
+	return is_enforce ? "ERROR" : "WARNING";
+}
+
+/*************************  DOMAIN POLICY HANDLER  *************************/
+
+/**
+ * tmy_flags - get flags of given access control.
+ * @index: index to retrieve flags.
+ *
+ * Returns current value of given access control.
+ */
+unsigned int tmy_flags(const unsigned int index)
+{
+	const u8 profile =
+		TMY_SECURITY->domain->profile;
+
+	/* All operations might sleep. See tmy_supervisor(). */
+	might_sleep();
+	if (in_interrupt())
+		return 0;
+	return sbin_init_started && index < TMY_MAX_CONTROL_INDEX
+		&& profile_ptr[profile] ?
+		profile_ptr[profile]->value[index] :
+		0;
+}
+
+/**
+ * tmy_quota - check quota.
+ *
+ * Returns true if not quota has not exceeded.
+ * Returns false otherwise.
+ *
+ * This is a safeguard for "learning mode", for appending entries
+ * without limit dulls the system response and consumes much memory.
+ */
+bool tmy_quota(void)
+{
+	unsigned int count = 0;
+	struct acl_info *ptr;
+	struct domain_info * const domain = TMY_SECURITY->domain;
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (!ptr->is_deleted)
+			count++;
+	}
+	/* If there are so many entries, don't append any more. */
+	if (count < tmy_flags(TMY_MAX_ACCEPT_ENTRY))
+		return 1;
+	if (!domain->quota_warned) {
+		domain->quota_warned = 1;
+		printk(KERN_INFO
+		       "TOMOYO-WARNING: Domain '%s' has so many ACLs "
+		       "to hold. Stopped learning mode.\n",
+		       domain->domainname->name);
+	}
+	return 0;
+}
+
+/* Create a new profile. */
+static struct profile *tmy_find_new_profile(const unsigned int profile)
+{
+	static DEFINE_MUTEX(profile_lock);
+	struct profile *ptr = NULL;
+
+	mutex_lock(&profile_lock);
+
+	ptr = profile_ptr[profile];
+	if (profile < TMY_MAX_PROFILES && ptr == NULL) {
+		ptr = tmy_alloc_element(sizeof(*ptr));
+		if (ptr != NULL) {
+			int i;
+			for (i = 0; i < TMY_MAX_CONTROL_INDEX; i++)
+				ptr->value[i] = tmy_control[i].current_value;
+			mb(); /* Instead of using spinlock. */
+			profile_ptr[profile] = ptr;
+		}
+	}
+
+	mutex_unlock(&profile_lock);
+
+	return ptr;
+}
+
+/* Loading policy done? */
+static int profile_loaded;
+
+/* Update profile values. */
+static int tmy_set_profile(struct io_buffer *head)
+{
+	char *data = head->write_buf;
+	unsigned int i;
+	unsigned int value;
+	char *cp;
+	struct profile *profile;
+
+	if (!tmy_is_root())
+		return -EPERM;
+
+	/* If profile index is not given, assume 0. */
+	i = simple_strtoul(data, &cp, 10);
+	if (data != cp) {
+		if (*cp != '-')
+			return -EINVAL;
+		data = cp + 1;
+	}
+
+	profile = tmy_find_new_profile(i);
+	if (!profile)
+		return -EINVAL;
+	cp = strchr(data, '=');
+	if (!cp)
+		return -EINVAL;
+
+	*cp = '\0';
+	profile_loaded = 1;
+	tmy_update_counter(TMY_UPDATE_PROFILE);
+	if (strcmp(data, tmy_control[TMY_COMMENT].keyword) == 0) {
+		profile->comment = tmy_save_name(cp + 1);
+		return 0;
+	}
+
+	if (sscanf(cp + 1, "%u", &value) != 1)
+		return -EINVAL;
+
+	if (tmy_strstarts(&data, TMY_MAC_FOR_CAPABILITY))
+		return tmy_set_capability_profile(data, value, i);
+
+	for (i = 0; i < TMY_MAX_CONTROL_INDEX; i++) {
+		if (strcmp(data, tmy_control[i].keyword))
+			continue;
+		if (value > tmy_control[i].max_value)
+			value = tmy_control[i].max_value;
+		profile->value[i] = value;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+/* Read profile values. */
+static int tmy_read_profile(struct io_buffer *head)
+{
+	int step;
+
+	if (head->read_eof)
+		return 0;
+	if (!tmy_is_root())
+		return -EPERM;
+
+	if (head->read_var1)
+		goto capability;
+
+	for (step = head->read_step;
+	     step < TMY_MAX_PROFILES * TMY_MAX_CONTROL_INDEX;
+	     step++) {
+		const int i = step / TMY_MAX_CONTROL_INDEX;
+		const int j = step % TMY_MAX_CONTROL_INDEX;
+		const struct profile *profile = profile_ptr[i];
+
+		head->read_step = step;
+		if (!profile)
+			continue;
+		if (j != TMY_COMMENT)
+			goto non_comment;
+		if (tmy_io_printf(head, "%u-%s=%s\n",
+				  i, tmy_control[TMY_COMMENT].keyword,
+				  profile->comment ?
+				  profile->comment->name : ""))
+			break;
+		goto comment_ok;
+non_comment: ;
+		if (tmy_io_printf(head, "%u-%s=%u\n",
+				  i, tmy_control[j].keyword,
+				  profile->value[j]))
+			break;
+comment_ok: ;
+	}
+
+	if (step == TMY_MAX_PROFILES * TMY_MAX_CONTROL_INDEX) {
+		head->read_var1 = (void *) "";
+		head->read_step = 0;
+		goto capability;
+	}
+	return 0;
+capability: ;
+	if (tmy_read_capability_profile(head) == 0)
+		head->read_eof = 1;
+	return 0;
+}
+
+/*************************  POLICY MANAGER HANDLER  *************************/
+
+struct policy_manager_entry {
+	struct list_head list;
+	const struct path_info *manager;
+	bool is_domain;
+	bool is_deleted;
+};
+
+static LIST_HEAD(policy_manager_list);
+
+/* Update manager list. */
+static int tmy_add_manager_entry(const char *manager, const bool is_delete)
+{
+	struct policy_manager_entry *new_entry;
+	struct policy_manager_entry *ptr;
+	static DEFINE_MUTEX(mutex);
+	const struct path_info *saved_manager;
+	int error = -ENOMEM;
+
+	bool is_domain = 0;
+	if (!tmy_is_root())
+		return -EPERM;
+	if (tmy_is_domain_def(manager)) {
+		if (!tmy_is_correct_domain(manager, __FUNCTION__))
+			return -EINVAL;
+		is_domain = 1;
+	} else {
+		if (!tmy_correct_path(manager, 1, -1, -1, __FUNCTION__))
+			return -EINVAL;
+	}
+
+	saved_manager = tmy_save_name(manager);
+	if (saved_manager == NULL)
+		return -ENOMEM;
+
+	mutex_lock(&mutex);
+
+	list_for_each_entry(ptr, &policy_manager_list, list) {
+		if (ptr->manager == saved_manager) {
+			ptr->is_deleted = is_delete;
+			error = 0;
+			goto out;
+		}
+	}
+
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+
+	new_entry = tmy_alloc_element(sizeof(*new_entry));
+	if (new_entry == NULL)
+		goto out;
+	new_entry->manager = saved_manager;
+	new_entry->is_domain = is_domain;
+	list_add_tail_mb(&new_entry->list, &policy_manager_list);
+	error = 0;
+out: ;
+
+	mutex_unlock(&mutex);
+
+	if (!error)
+		tmy_update_counter(TMY_UPDATE_MANAGER);
+	return error;
+}
+
+/* Update manager list. */
+static int tmy_add_manager_policy(struct io_buffer *head)
+{
+	char *data = head->write_buf;
+	bool is_delete = 0;
+
+	if (!tmy_is_root())
+		return -EPERM;
+	if (tmy_strstarts(&data, TMY_DELETE))
+		is_delete = 1;
+	return tmy_add_manager_entry(data, is_delete);
+}
+
+/* Read manager list.*/
+static int tmy_read_manager_policy(struct io_buffer *head)
+{
+	struct list_head *pos;
+	if (head->read_eof)
+		return 0;
+	if (!tmy_is_root())
+		return -EPERM;
+	list_for_each_cookie(pos, head->read_var2, &policy_manager_list) {
+		struct policy_manager_entry *ptr;
+		ptr = list_entry(pos, struct policy_manager_entry, list);
+		if (!ptr->is_deleted &&
+		    tmy_io_printf(head, "%s\n", ptr->manager->name))
+			break;
+	}
+	if (!head->read_var2)
+		head->read_eof = 1;
+	return 0;
+}
+
+/**
+ * tmy_is_policy_manager - check whether modifying policy is permitted.
+ *
+ * Returns nonzero if modifying policy is permitted.
+ * Returns zero otherwise.
+ *
+ * Only programs or domains registers as manager are permitted to modify
+ * policy via /sys/kernel/security/tomoyo/ interface.
+ * This function checks whether the current process is a policy manager.
+ * But policy manager is not the only processes that can modify policy;
+ * other process are permitted to add policy
+ * if their domains are assigned a profile for learning mode.
+ */
+static int tmy_is_policy_manager(void)
+{
+	struct policy_manager_entry *ptr;
+	const char *exe;
+	const struct path_info *domainname =
+		TMY_SECURITY->domain->domainname;
+	bool found = 0;
+
+	/* Everyone can modify policy before /sbin/init starts. */
+	if (!sbin_init_started)
+		return 1;
+
+	list_for_each_entry(ptr, &policy_manager_list, list) {
+		if (!ptr->is_deleted &&
+		    ptr->is_domain &&
+		    !tmy_pathcmp(domainname, ptr->manager))
+			return 1;
+	}
+
+	exe = tmy_get_exe();
+	if (!exe)
+		return 0;
+
+	list_for_each_entry(ptr, &policy_manager_list, list) {
+		if (!ptr->is_deleted &&
+		    !ptr->is_domain &&
+		    !strcmp(exe, ptr->manager->name)) {
+			found = 1;
+			break;
+		}
+	}
+
+	if (!found) { /* Reduce error messages. */
+		static pid_t last_pid;
+		const pid_t pid = current->pid;
+		if (last_pid != pid) {
+			printk(KERN_INFO
+			       "%s is not permitted to update policies.\n",
+			       exe);
+			last_pid = pid;
+		}
+	}
+
+	tmy_free(exe);
+	return found;
+}
+
+/*************************  DOMAIN POLICY HANDLER  *************************/
+
+/* Update domain policy. */
+static int tmy_add_domain_policy(struct io_buffer *head)
+{
+	char *data = head->write_buf;
+	char *cp;
+	const struct condition_list *cond = NULL;
+	struct domain_info *domain = head->write_var1;
+	bool is_delete = 0;
+	bool is_select = 0;
+	bool is_undelete = 0;
+	unsigned int profile;
+
+	if (!tmy_is_root())
+		return -EPERM;
+
+	if (tmy_strstarts(&data, TMY_DELETE))
+		is_delete = 1;
+	else if (tmy_strstarts(&data, TMY_SELECT))
+		is_select = 1;
+	else if (tmy_strstarts(&data, TMY_UNDELETE))
+		is_undelete = 1;
+
+	tmy_update_counter(TMY_UPDATE_DOMAINPOLICY);
+
+	if (tmy_is_domain_def(data)) {
+		if (is_delete) {
+			tmy_delete_domain(data);
+			domain = NULL;
+		} else if (is_select)
+			domain = tmy_find_domain(data);
+		else if (is_undelete)
+			domain = tmy_undelete_domain(data);
+		else
+			domain = tmy_new_domain(data, 0);
+		head->write_var1 = domain;
+		return 0;
+	}
+
+	if (!domain)
+		return -EINVAL;
+
+	if (sscanf(data, TMY_USE_PROFILE "%u", &profile) == 1 &&
+	    profile < TMY_MAX_PROFILES) {
+		if (profile_ptr[profile] || !sbin_init_started)
+			domain->profile = (u8) profile;
+		return 0;
+	}
+	cp = tmy_find_condition_part(data);
+	if (cp) {
+		cond = tmy_assign_condition(cp);
+		if (!cond)
+			return -EINVAL;
+	}
+	if (tmy_strstarts(&data, TMY_ALLOW_NETWORK))
+		return tmy_add_network_policy(data, domain, cond, is_delete);
+	else if (tmy_strstarts(&data, TMY_ALLOW_ARGV0))
+		return tmy_add_argv0_policy(data, domain, cond, is_delete);
+	else if (tmy_strstarts(&data, TMY_ALLOW_SIGNAL))
+		return tmy_add_signal_policy(data, domain, cond, is_delete);
+	else if (tmy_strstarts(&data, TMY_ALLOW_CAPABILITY))
+		return tmy_add_capability_policy(data, domain, cond, is_delete);
+	else
+		return tmy_add_file_policy(data, domain, cond, is_delete);
+
+	return -EINVAL;
+}
+
+/* Dump file's open ACL. */
+static inline int print_file_rwx_acl(struct io_buffer *head,
+				     struct acl_info *ptr)
+{
+	struct file_acl *ptr2 = (struct file_acl *) ptr;
+	const unsigned char b = ptr2->u_is_group;
+
+	if (tmy_io_printf(head, "%d %s%s",
+			  ptr2->perm, b ? "@" : "",
+			  b ? ptr2->u.group->group_name->name :
+			  ptr2->u.filename->name))
+		return -ENOMEM;
+	return 0;
+}
+
+/* Dump argv[0]'s ACL. */
+static inline int print_argv0_acl(struct io_buffer *head,
+				  struct acl_info *ptr)
+{
+	struct argv0_acl *ptr2 = (struct argv0_acl *) ptr;
+
+	if (tmy_io_printf(head, TMY_ALLOW_ARGV0 "%s %s",
+			  ptr2->filename->name, ptr2->argv0->name))
+		return -ENOMEM;
+	return 0;
+}
+
+/* Dump network's ACL. */
+static inline int print_network_acl(struct io_buffer *head,
+				    struct acl_info *ptr)
+{
+	struct net_acl *ptr2 = (struct net_acl *) ptr;
+	u8 record_type = ptr2->record_type;
+	u32 min_address;
+	u32 max_address;
+	char buf[64];
+	const u16 *min_address_ptr;
+	const u16 *max_address_ptr;
+	u16 min_port;
+	u16 max_port;
+
+	if (tmy_io_printf(head, TMY_ALLOW_NETWORK "%s ",
+			  tmy_network2keyword(ptr2->operation_type)))
+		goto out;
+	if (record_type != TMY_TYPE_ADDRESS_GROUP)
+		goto non_address_group;
+
+	if (tmy_io_printf(head, "@%s", ptr2->u.group->group_name->name))
+		goto out;
+	goto print_port;
+
+non_address_group: ;
+	if (record_type != TMY_TYPE_IPv4)
+		goto ipv6_address;
+
+	min_address = ptr2->u.ipv4.min;
+	max_address = ptr2->u.ipv4.max;
+	if (tmy_io_printf(head, NIPQUAD_FMT, HIPQUAD(min_address)))
+		goto out;
+	if (min_address != max_address &&
+	    tmy_io_printf(head, "-" NIPQUAD_FMT, HIPQUAD(max_address)))
+		goto out;
+	goto print_port;
+
+ipv6_address: ;
+	min_address_ptr = ptr2->u.ipv6.min;
+	max_address_ptr = ptr2->u.ipv6.max;
+	tmy_print_ipv6(buf, sizeof(buf), min_address_ptr);
+	if (tmy_io_printf(head, "%s", buf))
+		goto out;
+	if (memcmp(min_address_ptr, max_address_ptr, 16)) {
+		tmy_print_ipv6(buf, sizeof(buf), max_address_ptr);
+		if (tmy_io_printf(head, "-%s", buf))
+			goto out;
+	}
+
+print_port: ;
+	min_port = ptr2->min_port;
+	max_port = ptr2->max_port;
+	if (tmy_io_printf(head, " %u", min_port))
+		goto out;
+	if (min_port != max_port && tmy_io_printf(head, "-%u", max_port))
+		goto out;
+	return 0;
+out: ;
+	return -ENOMEM;
+}
+
+/* Dump signal's ACL. */
+static inline int print_signal_acl(struct io_buffer *head,
+				   struct acl_info *ptr)
+{
+	struct signal_acl *ptr2 = (struct signal_acl *) ptr;
+
+	if (tmy_io_printf(head, TMY_ALLOW_SIGNAL "%u %s",
+			  ptr2->sig, ptr2->domainname->name))
+		return -ENOMEM;
+	return 0;
+}
+
+/* Dump capability's ACL. */
+static inline int print_capability_acl(struct io_buffer *head,
+				       struct acl_info *ptr)
+{
+	struct capability_acl *ptr2 = (struct capability_acl *) ptr;
+
+	if (tmy_io_printf(head, TMY_ALLOW_CAPABILITY "%s",
+			  tmy_capability2keyword(ptr2->capability)))
+		return -ENOMEM;
+	return 0;
+}
+
+/* Dump file's non-open ACL. */
+static inline int print_file_other_acl(struct io_buffer *head,
+				       struct acl_info *ptr)
+{
+	const u8 acl_type = ptr->type;
+	const char *keyword = tmy_acltype2keyword(acl_type);
+
+	if (!keyword)
+		return 0; /* This must not happen. */
+
+	if (tmy_acltype2paths(acl_type) == 2) {
+		struct double_acl *ptr2 = (struct double_acl *) ptr;
+		const bool b1 = ptr2->u1_is_group;
+		const bool b2 = ptr2->u2_is_group;
+
+		if (tmy_io_printf(head,
+				  "allow_%s %s%s %s%s",
+				  keyword,
+				  b1 ? "@" : "",
+				  b1 ? ptr2->u1.group1->group_name->name :
+				  ptr2->u1.filename1->name,
+				  b2 ? "@" : "",
+				  b2 ? ptr2->u2.group2->group_name->name :
+				  ptr2->u2.filename2->name))
+			return -ENOMEM;
+	} else {
+		struct single_acl *ptr2 = (struct single_acl *) ptr;
+		const bool b = ptr2->u_is_group;
+
+		if (tmy_io_printf(head,
+				  "allow_%s %s%s",
+				  keyword,
+				  b ? "@" : "",
+				  b ? ptr2->u.group->group_name->name :
+				  ptr2->u.filename->name))
+			return -ENOMEM;
+	}
+	return 0;
+}
+
+/* Read domain policy. */
+static int tmy_read_domain_policy(struct io_buffer *head)
+{
+	struct list_head *dpos;
+	struct list_head *apos;
+
+	if (head->read_eof)
+		return 0;
+
+	if (head->read_step == 0) {
+		if (!tmy_is_root())
+			return -EPERM;
+		head->read_step = 1;
+	}
+	list_for_each_cookie(dpos, head->read_var1, &domain_list) {
+		struct domain_info *domain;
+		domain = list_entry(dpos, struct domain_info, list);
+		if (head->read_step != 1)
+			goto acl_loop;
+		if (domain->is_deleted)
+			continue;
+		if (tmy_io_printf(head,
+				  "%s\n" TMY_USE_PROFILE "%u\n%s\n",
+				  domain->domainname->name,
+				  domain->profile,
+				  domain->quota_warned ?
+				  "quota_exceeded\n" : ""))
+			return 0;
+		head->read_step = 2;
+acl_loop: ;
+		if (head->read_step == 3)
+			goto tail_mark;
+		list_for_each_cookie(apos, head->read_var2,
+				     &domain->acl_info_list) {
+			struct acl_info *ptr;
+			int pos;
+			u8 acl_type;
+			ptr = list_entry(apos, struct acl_info, list);
+			if (ptr->is_deleted)
+				continue;
+			pos = head->read_avail;
+			acl_type = ptr->type;
+			if (acl_type == TMY_TYPE_FILE_ACL) {
+				if (print_file_rwx_acl(head, ptr))
+					goto print_acl_rollback;
+			} else if (acl_type == TMY_TYPE_ARGV0_ACL) {
+				if (print_argv0_acl(head, ptr))
+					goto print_acl_rollback;
+			} else if (acl_type == TMY_TYPE_IP_NETWORK_ACL) {
+				if (print_network_acl(head, ptr))
+					goto print_acl_rollback;
+			} else if (acl_type == TMY_TYPE_SIGNAL_ACL) {
+				if (print_signal_acl(head, ptr))
+					goto print_acl_rollback;
+			} else if (acl_type == TMY_TYPE_CAPABILITY_ACL) {
+				if (print_capability_acl(head, ptr))
+					goto print_acl_rollback;
+			} else
+				if (print_file_other_acl(head, ptr))
+					goto print_acl_rollback;
+			if (tmy_dump_condition(head, ptr->cond)) {
+print_acl_rollback: ;
+				head->read_avail = pos;
+				return 0;
+			}
+		}
+		head->read_step = 3;
+tail_mark: ;
+		if (tmy_io_printf(head, "\n"))
+			return 0;
+		head->read_step = 1;
+	}
+	head->read_eof = 1;
+	return 0;
+}
+
+/* Read domainname and its profile values. */
+static int tmy_read_domain_profile(struct io_buffer *head)
+{
+	struct list_head *pos;
+	if (head->read_eof)
+		return 0;
+	if (!tmy_is_root())
+		return -EPERM;
+	list_for_each_cookie(pos, head->read_var1, &domain_list) {
+		struct domain_info *domain;
+		domain = list_entry(pos, struct domain_info, list);
+		if (domain->is_deleted)
+			continue;
+		if (tmy_io_printf(head, "%u %s\n",
+				  domain->profile, domain->domainname->name))
+			return 0;
+	}
+	head->read_eof = 1;
+	return 0;
+}
+
+/* Set PID to report. Non manager process is permitted to call this function. */
+static int tmy_write_pid(struct io_buffer *head)
+{
+	head->read_step = (int) simple_strtoul(head->write_buf, NULL, 10);
+	head->read_eof = 0;
+	return 0;
+}
+
+/* Read domainname and its profile values. */
+static int tmy_read_pid(struct io_buffer *head)
+{
+	const int pid = head->read_step;
+	struct task_struct *p;
+	struct domain_info *domain = NULL;
+
+	/* If PID is not set via tmy_write_pid(), do nothing. */
+	if (head->read_avail || head->read_eof)
+		return 0;
+
+	/***** CRITICAL SECTION START *****/
+	read_lock(&tasklist_lock);
+	p = find_task_by_pid(pid);
+	if (p) {
+		/* "struct task_struct"->security is not NULL. */
+		domain = ((struct tmy_security *) p->security)->domain;
+	}
+	read_unlock(&tasklist_lock);
+	/***** CRITICAL SECTION END *****/
+
+	if (domain)
+		tmy_io_printf(head, "%d %u %s",
+			      pid, domain->profile, domain->domainname->name);
+	head->read_eof = 1;
+
+	return 0;
+}
+
+/* Update profile values for domains. */
+static int tmy_update_domain_profile(struct io_buffer *head)
+{
+	char *data = head->write_buf;
+	char *cp = strchr(data, ' ');
+	struct domain_info *domain;
+	unsigned int profile;
+
+	if (!tmy_is_root())
+		return -EPERM;
+	if (!cp)
+		return -EINVAL;
+
+	*cp = '\0';
+	domain = tmy_find_domain(cp + 1);
+	profile = simple_strtoul(data, NULL, 10);
+	if (domain && profile < TMY_MAX_PROFILES &&
+	    (profile_ptr[profile] || !sbin_init_started))
+		domain->profile = (u8) profile;
+	tmy_update_counter(TMY_UPDATE_DOMAINPOLICY);
+
+	return 0;
+}
+
+/*************************  SYSTEM POLICY HANDLER  *************************/
+
+/* Update system policy. */
+static int tmy_add_system_policy(struct io_buffer *head)
+{
+	char *data = head->write_buf;
+	bool is_delete = 0;
+
+	if (!tmy_is_root())
+		return -EPERM;
+
+	tmy_update_counter(TMY_UPDATE_SYSTEMPOLICY);
+
+	if (tmy_strstarts(&data, TMY_DELETE))
+		is_delete = 1;
+	if (tmy_strstarts(&data, TMY_ALLOW_MOUNT))
+		return tmy_add_mount_policy(data, is_delete);
+	if (tmy_strstarts(&data, TMY_DENY_UNMOUNT))
+		return tmy_add_no_umount_policy(data, is_delete);
+	if (tmy_strstarts(&data, TMY_ALLOW_PIVOT_ROOT))
+		return tmy_add_pivot_root_policy(data, is_delete);
+
+	return -EINVAL;
+}
+
+/* Read system policy. */
+static int tmy_read_system_policy(struct io_buffer *head)
+{
+	if (head->read_eof)
+		return 0;
+	switch (head->read_step) {
+	case 0:
+		if (!tmy_is_root())
+			return -EPERM;
+		head->read_step = 1;
+	case 1:
+		if (tmy_read_mount_policy(head))
+			break;
+		head->read_step = 2;
+	case 2:
+		if (tmy_read_no_umount_policy(head))
+			break;
+		head->read_step = 3;
+	case 3:
+		if (tmy_read_pivot_root_policy(head))
+			break;
+		head->read_eof = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*************************  EXCEPTION POLICY HANDLER  *************************/
+/* Update exception policy. */
+static int tmy_add_exception_policy(struct io_buffer *head)
+{
+	char *data = head->write_buf;
+	bool is_delete = 0;
+
+	if (!tmy_is_root())
+		return -EPERM;
+
+	tmy_update_counter(TMY_UPDATE_EXCEPTIONPOLICY);
+
+	if (tmy_strstarts(&data, TMY_DELETE))
+		is_delete = 1;
+	if (tmy_strstarts(&data, TMY_KEEP_DOMAIN))
+		return tmy_add_domain_keeper_policy(data, 0, is_delete);
+	if (tmy_strstarts(&data, TMY_NO_KEEP_DOMAIN))
+		return tmy_add_domain_keeper_policy(data, 1, is_delete);
+	if (tmy_strstarts(&data, TMY_INITIALIZE_DOMAIN))
+		return tmy_add_domain_initializer_policy(data, 0, is_delete);
+	if (tmy_strstarts(&data, TMY_NO_INITIALIZE_DOMAIN))
+		return tmy_add_domain_initializer_policy(data, 1, is_delete);
+	if (tmy_strstarts(&data, TMY_ALIAS))
+		return tmy_add_alias_policy(data, is_delete);
+	if (tmy_strstarts(&data, TMY_AGGREGATOR))
+		return tmy_add_aggregator_policy(data, is_delete);
+	if (tmy_strstarts(&data, TMY_ALLOW_READ))
+		return tmy_add_globally_readable_policy(data, is_delete);
+	if (tmy_strstarts(&data, TMY_FILE_PATTERN))
+		return tmy_add_pattern_policy(data, is_delete);
+	if (tmy_strstarts(&data, TMY_PATH_GROUP))
+		return tmy_add_group_policy(data, is_delete);
+	if (tmy_strstarts(&data, TMY_DENY_REWRITE))
+		return tmy_add_no_rewrite_policy(data, is_delete);
+	if (tmy_strstarts(&data, TMY_ADDRESS_GROUP))
+		return tmy_add_address_group_policy(data, is_delete);
+
+	return -EINVAL;
+}
+
+/* Read exception policy. */
+static int tmy_read_exception_policy(struct io_buffer *head)
+{
+	if (head->read_eof)
+		return 0;
+	switch (head->read_step) {
+	case 0:
+		if (!tmy_is_root())
+			return -EPERM;
+		head->read_step = 1;
+	case 1:
+		if (tmy_read_domain_keeper_policy(head))
+			break;
+		head->read_step = 2;
+	case 2:
+		if (tmy_read_globally_readable_policy(head))
+			break;
+		head->read_step = 3;
+	case 3:
+		if (tmy_read_domain_initializer_policy(head))
+			break;
+		head->read_step = 4;
+	case 4:
+		if (tmy_read_alias_policy(head))
+			break;
+		head->read_step = 5;
+	case 5:
+		if (tmy_read_aggregator_policy(head))
+			break;
+		head->read_step = 6;
+	case 6:
+		if (tmy_read_pattern_policy(head))
+			break;
+		head->read_step = 7;
+	case 7:
+		if (tmy_read_no_rewrite_policy(head))
+			break;
+		head->read_step = 8;
+	case 8:
+		if (tmy_read_path_group_policy(head))
+			break;
+		head->read_step = 9;
+	case 9:
+		if (tmy_read_address_group_policy(head))
+			break;
+		head->read_eof = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*************************  POLICY LOADER  *************************/
+
+/**
+ * tmy_load_policy - load policy and activate access control.
+ * @filename: program to be executed.
+ *
+ * This function calls /sbin/tomoyo-init using call_usermodehelper()
+ * to load policy
+ * if "execution of /sbin/init is requested" and "/sbin/tomoyo-init exists".
+ */
+void tmy_load_policy(const char *filename)
+{
+	if (sbin_init_started)
+		return;
+	if (strcmp(filename, "/sbin/init") != 0)
+		return;
+
+	/*
+	 * Don't activate MAC if the path '/sbin/tomoyo-init' doesn't exist.
+	 * If initrd.img includes /sbin/init but real-root-dev has not
+	 * mounted on / yet, activating MAC will block the system
+	 * since policies are not loaded yet.
+	 * So let do_execve() call this function everytime.
+	 */
+	if (!profile_loaded) {
+		const char *tmy_loader = "/sbin/tomoyo-init";
+		struct nameidata nd;
+		char *argv[2];
+		char *envp[3];
+
+		if (path_lookup(tmy_loader, LOOKUP_FOLLOW, &nd)) {
+			printk("TOMOYO: Not activating Mandatory Access Control"
+			       " now since %s doesn't exist.\n", tmy_loader);
+			return;
+		}
+		path_put(&nd.path);
+		argv[0] = (char *) tmy_loader;
+		argv[1] = NULL;
+		envp[0] = "HOME=/";
+		envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
+		envp[2] = NULL;
+		call_usermodehelper(argv[0], argv, envp, 1);
+	}
+
+	printk(KERN_INFO "TOMOYO: %s   2007/11/11\n", TOMOYO_VERSION_CODE);
+
+	if (!profile_loaded)
+		panic("TOMOYO: No profiles loaded.\n");
+
+	printk(KERN_INFO "TOMOYO: Mandatory Access Control activated.\n");
+	sbin_init_started = 1;
+
+	{ /* Check all profiles currently assigned to domains are defined. */
+		struct domain_info *domain;
+		list_for_each_entry(domain, &domain_list, list) {
+			const u8 profile = domain->profile;
+			if (profile_ptr[profile])
+				continue;
+			panic("TOMOYO: Profile %u (used by '%s') "
+			      "not defined.\n",
+			      profile,
+			      domain->domainname->name);
+		}
+	}
+}
+
+
+/*************************  MAC Decision Delayer  *************************/
+
+static DECLARE_WAIT_QUEUE_HEAD(query_wait);
+
+static DEFINE_SPINLOCK(query_lock);
+
+struct query_entry {
+	struct list_head list;
+	char *query;
+	int query_len;
+	unsigned int serial;
+	int timer;
+	int answer;
+};
+
+static LIST_HEAD(query_list);
+static atomic_t queryd_watcher = ATOMIC_INIT(0);
+
+/**
+ * tmy_supervisor - ask for supervisors decision.
+ * @fmt: format strings for printf().
+ *
+ * Returns zero if administrator allowed.
+ * Returns nonzero otherwise.
+ *
+ * This is one of TOMOYO Linux's feature.
+ * Normally, access requests that violates policy is rejected immediately.
+ * But this behavior is inconvenient when developing policy.
+ * TOMOYO Linux allows administrators handle access requests that violated
+ * policy in enforce mode to adjust policy.
+ *
+ * This function blocks a process who is requesting access that violated policy
+ * and tell it via /sys/kernel/security/tomoyo/query and wait for supervisor's
+ * decision.
+ */
+int tmy_supervisor(const char *fmt, ...)
+{
+	va_list args;
+	int error = -EPERM;
+	int pos;
+	int len;
+	static unsigned int serial;
+	struct query_entry *query_entry;
+
+	if (!tmy_flags(TMY_ALLOW_ENFORCE_GRACE))
+		return -EPERM;
+	if (!atomic_read(&queryd_watcher))
+		return -EPERM;
+
+	va_start(args, fmt);
+	len = vsnprintf((char *) &pos, sizeof(pos) - 1, fmt, args) + 32;
+	va_end(args);
+
+	query_entry = tmy_alloc(sizeof(*query_entry));
+	if (!query_entry)
+		goto out;
+	query_entry->query = tmy_alloc(len);
+	if (!query_entry->query)
+		goto out;
+
+	INIT_LIST_HEAD(&query_entry->list);
+
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&query_lock);
+	query_entry->serial = serial++;
+	spin_unlock(&query_lock);
+	/***** CRITICAL SECTION END *****/
+
+	pos = snprintf(query_entry->query, len - 1,
+		       "Q%u\n", query_entry->serial);
+	va_start(args, fmt);
+	vsnprintf(query_entry->query + pos, len - 1 - pos, fmt, args);
+	query_entry->query_len = strlen(query_entry->query) + 1;
+	va_end(args);
+
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&query_lock);
+	list_add_tail(&query_entry->list, &query_list);
+	spin_unlock(&query_lock);
+	/***** CRITICAL SECTION END *****/
+
+	tmy_update_counter(TMY_UPDATE_QUERY);
+
+	/* Give 10 seconds for supervisor's opinion. */
+	for (query_entry->timer = 0;
+	     atomic_read(&queryd_watcher) &&
+		     tmy_flags(TMY_ALLOW_ENFORCE_GRACE) &&
+		     query_entry->timer < 100;
+	     query_entry->timer++) {
+		wake_up(&query_wait);
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(HZ / 10);
+		if (query_entry->answer)
+			break;
+	}
+
+	tmy_update_counter(TMY_UPDATE_QUERY);
+
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&query_lock);
+	list_del(&query_entry->list);
+	spin_unlock(&query_lock);
+	/***** CRITICAL SECTION END *****/
+
+	switch (query_entry->answer) {
+	case 1:
+		/* Granted by administrator. */
+		error = 0;
+		break;
+	case 0:
+		/* Timed out. */
+		break;
+	default:
+		/* Rejected by administrator. */
+		break;
+	}
+
+out: ;
+	if (query_entry)
+		tmy_free(query_entry->query);
+	tmy_free(query_entry);
+	return error;
+}
+
+/* Check for pending access requests. */
+static int tmy_poll_query(struct file *file, poll_table *wait)
+{
+	int found;
+
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&query_lock);
+	found = !list_empty(&query_list);
+	spin_unlock(&query_lock);
+	/***** CRITICAL SECTION END *****/
+
+	if (found)
+		return POLLIN | POLLRDNORM;
+	poll_wait(file, &query_wait, wait);
+
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&query_lock);
+	found = !list_empty(&query_list);
+	spin_unlock(&query_lock);
+	/***** CRITICAL SECTION END *****/
+
+	if (found)
+		return POLLIN | POLLRDNORM;
+	return 0;
+}
+
+/* Read pending access requests. */
+static int tmy_read_query(struct io_buffer *head)
+{
+	struct list_head *tmp;
+	int pos = 0;
+	int len = 0;
+	char *buf;
+
+	if (head->read_avail)
+		return 0;
+	if (head->read_buf) {
+		tmy_free(head->read_buf);
+		head->read_buf = NULL;
+		head->readbuf_size = 0;
+	}
+
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&query_lock);
+	list_for_each(tmp, &query_list) {
+		struct query_entry *ptr
+			= list_entry(tmp, struct query_entry, list);
+		if (pos++ == head->read_step) {
+			len = ptr->query_len;
+			break;
+		}
+	}
+	spin_unlock(&query_lock);
+	/***** CRITICAL SECTION END *****/
+
+	if (!len) {
+		head->read_step = 0;
+		return 0;
+	}
+	buf = tmy_alloc(len);
+	if (buf) {
+		pos = 0;
+
+		/***** CRITICAL SECTION START *****/
+		spin_lock(&query_lock);
+		list_for_each(tmp, &query_list) {
+			struct query_entry *ptr
+				= list_entry(tmp, struct query_entry, list);
+			if (pos++ == head->read_step) {
+				/* Some query can be skiipped because
+				 * query_list can change, but I don't care.
+				 */
+				if (len == ptr->query_len)
+					memmove(buf, ptr->query, len);
+				break;
+			}
+		}
+		spin_unlock(&query_lock);
+		/***** CRITICAL SECTION END *****/
+
+		if (buf[0]) {
+			head->readbuf_size = len;
+			head->read_avail = len;
+			head->read_buf = buf;
+			head->read_step++;
+		} else
+			tmy_free(buf);
+	}
+
+	return 0;
+}
+
+/* Reply to pending access requests. */
+static int tmy_write_answer(struct io_buffer *head)
+{
+	char *data = head->write_buf;
+	struct list_head *tmp;
+	unsigned int serial;
+	unsigned int answer;
+
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&query_lock);
+	list_for_each(tmp, &query_list) {
+		struct query_entry *ptr
+			= list_entry(tmp, struct query_entry, list);
+		ptr->timer = 0;
+	}
+	spin_unlock(&query_lock);
+	/***** CRITICAL SECTION END *****/
+
+	if (sscanf(data, "A%u=%u", &serial, &answer) != 2)
+		return -EINVAL;
+
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&query_lock);
+	list_for_each(tmp, &query_list) {
+		struct query_entry *ptr
+			= list_entry(tmp, struct query_entry, list);
+		if (ptr->serial != serial)
+			continue;
+		if (!ptr->answer)
+			ptr->answer = answer;
+		break;
+	}
+	spin_unlock(&query_lock);
+	/***** CRITICAL SECTION END *****/
+
+	return 0;
+}
+
+/******************  /sys/kernel/security INTERFACE HANDLER  ******************/
+
+/* Policy updates counter. */
+static unsigned int updates_counter[TMY_MAX_UPDATES_COUNTER];
+static DEFINE_SPINLOCK(updates_counter_lock);
+
+/**
+ * tmy_update_counter - notify userland that policy is changed.
+ * @index: index to update counter.
+ *
+ * This is for userland process who is monitoring policy changes.
+ */
+void tmy_update_counter(const unsigned char index)
+{
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&updates_counter_lock);
+	if (index < TMY_MAX_UPDATES_COUNTER)
+		updates_counter[index]++;
+	spin_unlock(&updates_counter_lock);
+	/***** CRITICAL SECTION END *****/
+}
+
+/* Read policy update counter. */
+static int tmy_read_updates_counter(struct io_buffer *head)
+{
+	unsigned int counter[TMY_MAX_UPDATES_COUNTER];
+	if (head->read_eof)
+		return 0;
+
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&updates_counter_lock);
+	memmove(counter, updates_counter, sizeof(updates_counter));
+	memset(updates_counter, 0, sizeof(updates_counter));
+	spin_unlock(&updates_counter_lock);
+	/***** CRITICAL SECTION END *****/
+
+	tmy_io_printf(head,
+		      "/sys/kernel/security/tomoyo/system_policy:    %10u\n"
+		      "/sys/kernel/security/tomoyo/domain_policy:    %10u\n"
+		      "/sys/kernel/security/tomoyo/exception_policy: %10u\n"
+		      "/sys/kernel/security/tomoyo/profile:          %10u\n"
+		      "/sys/kernel/security/tomoyo/query:            %10u\n"
+		      "/sys/kernel/security/tomoyo/manager:          %10u\n"
+		      "/sys/kernel/security/tomoyo/grant_log:        %10u\n"
+		      "/sys/kernel/security/tomoyo/reject_log:       %10u\n",
+		      counter[TMY_UPDATE_SYSTEMPOLICY],
+		      counter[TMY_UPDATE_DOMAINPOLICY],
+		      counter[TMY_UPDATE_EXCEPTIONPOLICY],
+		      counter[TMY_UPDATE_PROFILE],
+		      counter[TMY_UPDATE_QUERY],
+		      counter[TMY_UPDATE_MANAGER],
+		      counter[TMY_UPDATE_GRANT_LOG],
+		      counter[TMY_UPDATE_REJECT_LOG]);
+
+	head->read_eof = 1;
+
+	return 0;
+}
+
+/* Read how much memory is used. */
+static int tmy_read_memory_counter(struct io_buffer *head)
+{
+	int shared;
+	int private;
+	int dynamic;
+
+	if (head->read_eof)
+		return 0;
+	shared = tmy_get_memory_used_for_save_name();
+	private = tmy_get_memory_used_for_elements();
+	dynamic = tmy_get_memory_used_for_dynamic();
+	if (tmy_io_printf(head,
+			  "Shared:  %10u\n"
+			  "Private: %10u\n"
+			  "Dynamic: %10u\n"
+			  "Total:   %10u\n",
+			  shared,
+			  private,
+			  dynamic,
+			  shared + private + dynamic) == 0)
+		head->read_eof = 1;
+	return 0;
+}
+
+/* Read TOMOYO Linux's version. */
+static int tmy_read_version(struct io_buffer *head)
+{
+	if (!head->read_eof) {
+		tmy_io_printf(head, TOMOYO_VERSION_CODE "\n");
+		head->read_eof = 1;
+	}
+	return 0;
+}
+
+/* Read current process's domainname. */
+static int tmy_read_self_domain(struct io_buffer *head)
+{
+	if (!head->read_eof) {
+		tmy_io_printf(head,
+			      "%s",
+			      TMY_SECURITY->domain->domainname->name);
+		head->read_eof = 1;
+	}
+
+	return 0;
+}
+
+/* This is /sys/kernel/security/tomoyo/ interface. */
+static int tmy_open_control(const int type, struct file *file)
+{
+	struct io_buffer *head = tmy_alloc(sizeof(*head));
+
+	if (!head)
+		return -ENOMEM;
+	mutex_init(&head->read_mutex);
+	mutex_init(&head->write_mutex);
+
+	switch (type) {
+	case TMY_DOMAINPOLICY:
+		head->write = tmy_add_domain_policy;
+		head->read = tmy_read_domain_policy;
+		break;
+	case TMY_SYSTEMPOLICY:
+		head->write = tmy_add_system_policy;
+		head->read = tmy_read_system_policy;
+		break;
+	case TMY_EXCEPTIONPOLICY:
+		head->write = tmy_add_exception_policy;
+		head->read = tmy_read_exception_policy;
+		break;
+	case TMY_DOMAIN_STATUS:
+		head->write = tmy_update_domain_profile;
+		head->read = tmy_read_domain_profile;
+		break;
+	case TMY_PROCESS_STATUS:
+		head->write = tmy_write_pid;
+		head->read = tmy_read_pid;
+		break;
+	case TMY_SELFDOMAIN:
+		head->read = tmy_read_self_domain;
+		break;
+	case TMY_MEMINFO:
+		head->read = tmy_read_memory_counter;
+		head->readbuf_size = 128;
+		break;
+	case TMY_PROFILE:
+		head->write = tmy_set_profile;
+		head->read = tmy_read_profile;
+		break;
+	case TMY_QUERY:
+		head->poll = tmy_poll_query;
+		head->write = tmy_write_answer;
+		head->read = tmy_read_query;
+		break;
+	case TMY_MANAGER:
+		head->write = tmy_add_manager_policy;
+		head->read = tmy_read_manager_policy;
+		break;
+	case TMY_UPDATESCOUNTER:
+		head->read = tmy_read_updates_counter;
+		break;
+	case TMY_VERSION:
+		head->read = tmy_read_version;
+		break;
+	case TMY_GRANT_LOG:
+		head->read = tmy_read_grant_log;
+		head->poll = tmy_poll_grant_log;
+		break;
+	case TMY_REJECT_LOG:
+		head->read = tmy_read_reject_log;
+		head->poll = tmy_poll_reject_log;
+		break;
+	}
+
+	if (type != TMY_QUERY) {
+		if (!head->readbuf_size)
+			head->readbuf_size = PAGE_SIZE * 2;
+		head->read_buf = tmy_alloc(head->readbuf_size);
+		if (!head->read_buf) {
+			tmy_free(head);
+			return -ENOMEM;
+		}
+	}
+
+	if (head->write) {
+		head->writebuf_size = PAGE_SIZE * 2;
+		head->write_buf = tmy_alloc(head->writebuf_size);
+		if (!head->write_buf) {
+			tmy_free(head->read_buf);
+			tmy_free(head);
+			return -ENOMEM;
+		}
+	}
+
+	file->private_data = head;
+
+	if (type == TMY_SELFDOMAIN)
+		tmy_read_control(file, NULL, 0);
+	else if (head->write == tmy_write_answer)
+		atomic_inc(&queryd_watcher);
+
+	return 0;
+}
+
+/* Copy read data to userland buffer. */
+static int tmy_copy_to_user(struct io_buffer *head, char __user *buffer,
+			    int buffer_len)
+{
+	int len = head->read_avail;
+	char *cp = head->read_buf;
+
+	if (len > buffer_len)
+		len = buffer_len;
+	if (len) {
+		if (copy_to_user(buffer, cp, len))
+			return -EFAULT;
+		head->read_avail -= len;
+		memmove(cp, cp + len, head->read_avail);
+	}
+
+	return len;
+}
+
+/* Check for pending requests. */
+static int tmy_poll_control(struct file *file, poll_table *wait)
+{
+	struct io_buffer *head = (struct io_buffer *) file->private_data;
+	if (!head->poll)
+		return -ENOSYS;
+	return head->poll(file, wait);
+}
+
+/* Read policy. */
+static int tmy_read_control(struct file *file, char __user *buffer,
+			    const int buffer_len)
+{
+	int len = 0;
+	struct io_buffer *head = (struct io_buffer *) file->private_data;
+
+	if (!head->read)
+		return -ENOSYS;
+	if (!access_ok(VERIFY_WRITE, buffer, buffer_len))
+		return -EFAULT;
+	if (mutex_lock_interruptible(&head->read_mutex))
+		return -EINTR;
+	len = head->read(head);
+	if (len >= 0)
+		len = tmy_copy_to_user(head, buffer, buffer_len);
+	mutex_unlock(&head->read_mutex);
+
+	return len;
+}
+
+/* Update policy. */
+static int tmy_write_control(struct file *file, const char __user *buffer,
+			     const int buffer_len)
+{
+	struct io_buffer *head = (struct io_buffer *) 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;
+	if (!tmy_is_root())
+		return -EPERM;
+	if (head->write != tmy_write_pid && !tmy_is_policy_manager())
+		/* Forbid updating policies for non manager programs. */
+		return -EPERM;
+
+	if (mutex_lock_interruptible(&head->write_mutex))
+		return -EINTR;
+	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;
+		tmy_normalize_line(cp0);
+		head->write(head);
+	}
+	mutex_unlock(&head->write_mutex);
+
+	return error;
+}
+
+/* Close /sys/kernel/security/tomoyo/ interface. */
+static int tmy_close_control(struct file *file)
+{
+	struct io_buffer *head = file->private_data;
+
+	if (head->write == tmy_write_answer)
+		atomic_dec(&queryd_watcher);
+
+	tmy_free(head->read_buf);
+	head->read_buf = NULL;
+	tmy_free(head->write_buf);
+	head->write_buf = NULL;
+	tmy_free(head);
+	head = NULL;
+	file->private_data = NULL;
+	return 0;
+}
+
+/* open() operation for /sys/kernel/security/tomoyo/ interface. */
+static int tmy_open(struct inode *inode, struct file *file)
+{
+	return tmy_open_control(((u8 *) file->f_dentry->d_inode->i_private)
+				- ((u8 *) NULL), file);
+}
+
+/* close() operation for /sys/kernel/security/tomoyo/ interface. */
+static int tmy_release(struct inode *inode, struct file *file)
+{
+	return tmy_close_control(file);
+}
+
+/* poll() operation for /sys/kernel/security/tomoyo/ interface. */
+static unsigned int tmy_poll(struct file *file, poll_table *wait)
+{
+	return tmy_poll_control(file, wait);
+}
+
+/* read() operation for /sys/kernel/security/tomoyo/ interface. */
+static ssize_t tmy_read(struct file *file, char __user *buf,
+			size_t count, loff_t *ppos)
+{
+	return tmy_read_control(file, buf, count);
+}
+
+/* write() operation for /sys/kernel/security/tomoyo/ interface. */
+static ssize_t tmy_write(struct file *file, const char __user *buf,
+			 size_t count, loff_t *ppos)
+{
+	return tmy_write_control(file, buf, count);
+}
+
+static struct file_operations tmy_operations = {
+	.open 	 = tmy_open,
+	.release = tmy_release,
+	.poll 	 = tmy_poll,
+	.read 	 = tmy_read,
+	.write 	 = tmy_write
+};
+
+/* Associate /sys/kernel/security/tomoyo/ interface with key. */
+static void __init tmy_create_entry(const char *name,
+			     const mode_t mode,
+			     struct dentry *parent,
+			     const int key)
+{
+	securityfs_create_file(name, mode, parent, ((u8 *) NULL) + key,
+			       &tmy_operations);
+}
+
+/**
+ * tmy_interface_init - initialize /sys/kernel/security/tomoyo/ interface.
+ */
+static int __init tmy_interface_init(void)
+{
+	struct dentry *tmy_dir;
+	tmy_dir = securityfs_create_dir("tomoyo", NULL);
+	tmy_create_entry("query",            0600, tmy_dir,
+			 TMY_QUERY);
+	tmy_create_entry("domain_policy",    0600, tmy_dir,
+			 TMY_DOMAINPOLICY);
+	tmy_create_entry("system_policy",    0600, tmy_dir,
+			 TMY_SYSTEMPOLICY);
+	tmy_create_entry("exception_policy", 0600, tmy_dir,
+			 TMY_EXCEPTIONPOLICY);
+	tmy_create_entry(".domain_status",   0600, tmy_dir,
+			 TMY_DOMAIN_STATUS);
+	tmy_create_entry(".process_status",  0400, tmy_dir,
+			 TMY_PROCESS_STATUS);
+	tmy_create_entry("self_domain",      0400, tmy_dir,
+			 TMY_SELFDOMAIN);
+	tmy_create_entry("meminfo",          0400, tmy_dir,
+			 TMY_MEMINFO);
+	tmy_create_entry("profile",          0600, tmy_dir,
+			 TMY_PROFILE);
+	tmy_create_entry("manager",          0600, tmy_dir,
+			 TMY_MANAGER);
+	tmy_create_entry(".updates_counter", 0400, tmy_dir,
+			 TMY_UPDATESCOUNTER);
+	tmy_create_entry("version",          0400, tmy_dir,
+			 TMY_VERSION);
+	tmy_create_entry("grant_log",        0400, tmy_dir,
+			 TMY_GRANT_LOG);
+	tmy_create_entry("reject_log",       0400, tmy_dir,
+			 TMY_REJECT_LOG);
+	return 0;
+}
+
+postcore_initcall(tmy_interface_init);

-- 

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

* [TOMOYO #5 07/18] Domain transition functions.
  2007-11-16 17:34 [TOMOYO #5 00/18] TOMOYO Linux - MAC based on process invocation history penguin-kernel
                   ` (5 preceding siblings ...)
  2007-11-16 17:34 ` [TOMOYO #5 06/18] Utility functions and policy manipulation interface penguin-kernel
@ 2007-11-16 17:34 ` penguin-kernel
  2007-11-16 17:34 ` [TOMOYO #5 08/18] Auditing interface penguin-kernel
                   ` (10 subsequent siblings)
  17 siblings, 0 replies; 39+ messages in thread
From: penguin-kernel @ 2007-11-16 17:34 UTC (permalink / raw)
  To: akpm; +Cc: linux-kernel, linux-security-module, Kentaro Takeda, Tetsuo Handa

[-- Attachment #1: tomoyo-domain.diff --]
[-- Type: text/plain, Size: 30867 bytes --]

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

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

--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6-mm/security/tomoyo/domain.c	2007-11-14 15:15:44.000000000 +0900
@@ -0,0 +1,1168 @@
+/*
+ * security/tomoyo/domain.c
+ *
+ * Domain transition functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+/*************************  VARIABLES  *************************/
+
+/* The initial domain. */
+struct domain_info KERNEL_DOMAIN;
+
+/* Lock for appending domain's ACL. */
+DEFINE_MUTEX(domain_acl_lock);
+
+/* Domain creation lock. */
+static DEFINE_MUTEX(new_domain_assign_lock);
+
+/***** The structure for program files to force domain reconstruction. *****/
+
+struct domain_initializer_entry {
+	struct list_head list;
+	const struct path_info *domainname;    /* This may be NULL */
+	const struct path_info *program;
+	bool is_deleted;
+	bool is_not;
+	bool is_last_name;
+};
+
+/***** The structure for domains to not to transit domains. *****/
+
+struct domain_keeper_entry {
+	struct list_head list;
+	const struct path_info *domainname;
+	const struct path_info *program;       /* This may be NULL */
+	bool is_deleted;
+	bool is_not;
+	bool is_last_name;
+};
+
+/***** The structure for program files that should be aggregated. *****/
+
+struct aggregator_entry {
+	struct list_head list;
+	const struct path_info *original_name;
+	const struct path_info *aggregated_name;
+	bool is_deleted;
+};
+
+/***** The structure for program files that should be aliased. *****/
+
+struct alias_entry {
+	struct list_head list;
+	const struct path_info *original_name;
+	const struct path_info *aliased_name;
+	bool is_deleted;
+};
+
+/*************************  UTILITY FUNCTIONS  *************************/
+
+/**
+ * tmy_is_domain_def - check if the line is likely a domain definition.
+ * @buffer: the line to check.
+ *
+ * Returns true if @buffer is likely a domain definition.
+ * Returns false otherwise.
+ *
+ * For complete validation check, use tmy_is_correct_domain().
+ */
+bool tmy_is_domain_def(const unsigned char *buffer)
+{
+	return strncmp(buffer, TMY_ROOT_NAME, TMY_ROOT_NAME_LEN) == 0;
+}
+
+/**
+ * tmy_add_acl - add an entry to a domain.
+ * @domain: pointer to "struct domain_info".
+ * @acl: pointer to "struct acl_info" to add.
+ *
+ * Returns zero.
+ */
+int tmy_add_acl(struct domain_info *domain,
+		struct acl_info *acl)
+{
+	list_add_tail_mb(&acl->list, &domain->acl_info_list);
+	tmy_update_counter(TMY_UPDATE_DOMAINPOLICY);
+	return 0;
+}
+
+/**
+ * tmy_del_acl - remove an entry from a domain
+ * @ptr: pointer to "struct acl_info" to remove.
+ *
+ * Returns zero.
+ *
+ * TOMOYO Linux doesn't free memory used by policy because policies are not
+ * so frequently changed after entring into enforcing mode.
+ * This makes the code free of read-lock.
+ * The caller uses "down(&domain_acl_lock);" as write-lock.
+ */
+int tmy_del_acl(struct acl_info *ptr)
+{
+	ptr->is_deleted = 1;
+	tmy_update_counter(TMY_UPDATE_DOMAINPOLICY);
+	return 0;
+}
+
+/************************  DOMAIN INITIALIZER HANDLER  ************************/
+
+static LIST_HEAD(domain_initializer_list);
+
+/* Update domain initializer list. */
+static int tmy_add_domain_initializer_entry(const char *domainname,
+					    const char *program,
+					    const bool is_not,
+					    const bool is_delete)
+{
+	struct domain_initializer_entry *new_entry;
+	struct domain_initializer_entry *ptr;
+	static DEFINE_MUTEX(mutex);
+	const struct path_info *saved_program;
+	const struct path_info *saved_domainname = NULL;
+	int error = -ENOMEM;
+	bool is_last_name = 0;
+
+	if (!tmy_correct_path(program, 1, -1, -1, __FUNCTION__))
+		return -EINVAL; /* No patterns allowed. */
+
+	if (domainname) {
+		if (!tmy_is_domain_def(domainname) &&
+		    tmy_correct_path(domainname, 1, -1, -1, __FUNCTION__))
+			is_last_name = 1;
+
+		else if (!tmy_is_correct_domain(domainname, __FUNCTION__))
+			return -EINVAL;
+
+		saved_domainname = tmy_save_name(domainname);
+		if (!saved_domainname)
+			return -ENOMEM;
+	}
+
+	saved_program = tmy_save_name(program);
+	if (!saved_program)
+		return -ENOMEM;
+
+	mutex_lock(&mutex);
+
+	list_for_each_entry(ptr, &domain_initializer_list, list) {
+		if (ptr->is_not == is_not &&
+		    ptr->domainname == saved_domainname &&
+		    ptr->program == saved_program) {
+			ptr->is_deleted = is_delete;
+			error = 0;
+			goto out;
+		}
+	}
+
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+
+	new_entry = tmy_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+
+	new_entry->domainname = saved_domainname;
+	new_entry->program = saved_program;
+	new_entry->is_not = is_not;
+	new_entry->is_last_name = is_last_name;
+	list_add_tail_mb(&new_entry->list, &domain_initializer_list);
+	error = 0;
+out: ;
+
+	mutex_unlock(&mutex);
+	return error;
+}
+
+/**
+ * tmy_read_domain_initializer_policy - read domain initializer policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_domain_initializer_policy(struct io_buffer *head)
+{
+	struct list_head *pos;
+	list_for_each_cookie(pos, head->read_var2, &domain_initializer_list) {
+		struct domain_initializer_entry *ptr;
+		ptr = list_entry(pos, struct domain_initializer_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (ptr->domainname) {
+			if (tmy_io_printf(head, "%s" TMY_INITIALIZE_DOMAIN
+					  "%s from %s\n",
+					  ptr->is_not ? "no_" : "",
+					  ptr->program->name,
+					  ptr->domainname->name))
+				return -ENOMEM;
+		} else {
+			if (tmy_io_printf(head, "%s" TMY_INITIALIZE_DOMAIN
+					  "%s\n",
+					  ptr->is_not ? "no_" : "",
+					  ptr->program->name))
+				return -ENOMEM;
+		}
+	}
+	return 0;
+}
+
+/**
+ * tmy_add_domain_initializer_policy - add domain initializer policy
+ * @data:      a line to parse.
+ * @is_not:    is this overriding?
+ * @is_delete: is this remove request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * This function adds or removes a domain initializer entry.
+ */
+int tmy_add_domain_initializer_policy(char *data,
+				      const bool is_not,
+				      const bool is_delete)
+{
+	char *cp = strstr(data, " from ");
+
+	if (cp) {
+		*cp = '\0';
+		return tmy_add_domain_initializer_entry(cp + 6,	data, is_not,
+							is_delete);
+	}
+
+	return tmy_add_domain_initializer_entry(NULL, data, is_not, is_delete);
+}
+
+/* Should I transit to a domain under "<kernel>" domain? */
+static int tmy_is_domain_initializer(const struct path_info *domainname,
+				     const struct path_info *program,
+				     const struct path_info *last_name)
+{
+	struct domain_initializer_entry *ptr;
+	int flag = 0;
+	list_for_each_entry(ptr,  &domain_initializer_list, list) {
+		if (ptr->is_deleted)
+			continue;
+		if (ptr->domainname) {
+			if (!ptr->is_last_name) {
+				if (ptr->domainname != domainname)
+					continue;
+			} else {
+				if (tmy_pathcmp(ptr->domainname, last_name))
+					continue;
+			}
+		}
+		if (tmy_pathcmp(ptr->program, program))
+			continue;
+		if (ptr->is_not)
+			return 0;
+		flag = 1;
+	}
+	return flag;
+}
+
+/*************************  DOMAIN KEEPER HANDLER  *************************/
+
+static LIST_HEAD(domain_keeper_list);
+
+/* Update domain keeper list. */
+static int tmy_add_domain_keeper_entry(const char *domainname,
+				       const char *program,
+				       const bool is_not,
+				       const bool is_delete)
+{
+	struct domain_keeper_entry *new_entry;
+	struct domain_keeper_entry *ptr;
+	const struct path_info *saved_domainname;
+	const struct path_info *saved_program = NULL;
+	static DEFINE_MUTEX(mutex);
+	int error = -ENOMEM;
+	bool is_last_name = 0;
+
+	if (!tmy_is_domain_def(domainname) &&
+	    tmy_correct_path(domainname, 1, -1, -1, __FUNCTION__))
+		is_last_name = 1;
+
+	else if (!tmy_is_correct_domain(domainname, __FUNCTION__))
+		return -EINVAL;
+
+	if (program) {
+		if (!tmy_correct_path(program, 1, -1, -1, __FUNCTION__))
+			return -EINVAL;
+
+		saved_program = tmy_save_name(program);
+		if (!saved_program)
+			return -ENOMEM;
+	}
+
+	saved_domainname = tmy_save_name(domainname);
+	if (!saved_domainname)
+		return -ENOMEM;
+
+	mutex_lock(&mutex);
+	list_for_each_entry(ptr, &domain_keeper_list, list) {
+		if (ptr->is_not == is_not &&
+		    ptr->domainname == saved_domainname &&
+		    ptr->program == saved_program) {
+			ptr->is_deleted = is_delete;
+			error = 0;
+			goto out;
+		}
+	}
+
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+
+	new_entry = tmy_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+
+	new_entry->domainname = saved_domainname;
+	new_entry->program = saved_program;
+	new_entry->is_not = is_not;
+	new_entry->is_last_name = is_last_name;
+	list_add_tail_mb(&new_entry->list, &domain_keeper_list);
+	error = 0;
+
+out: ;
+
+	mutex_unlock(&mutex);
+	return error;
+}
+
+/**
+ * tmy_add_domain_keeper_policy - add domain keeper policy.
+ * @data:      a line to parse.
+ * @is_not:    is this overriding?
+ * @is_delete: is this remove request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * This function adds or removes a domain keeper entry.
+ *
+ */
+int tmy_add_domain_keeper_policy(char *data,
+				 const bool is_not,
+				 const bool is_delete)
+{
+	char *cp = strstr(data, " from ");
+
+	if (cp) {
+		*cp = '\0';
+		return tmy_add_domain_keeper_entry(cp + 6, data,
+						   is_not, is_delete);
+	}
+
+	return tmy_add_domain_keeper_entry(data, NULL, is_not, is_delete);
+}
+
+/**
+ * tmy_read_domain_keeper_policy - read domain keeper policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_domain_keeper_policy(struct io_buffer *head)
+{
+	struct list_head *pos;
+	list_for_each_cookie(pos, head->read_var2, &domain_keeper_list) {
+		struct domain_keeper_entry *ptr;
+		ptr = list_entry(pos, struct domain_keeper_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (ptr->program) {
+			if (tmy_io_printf(head,
+					  "%s" TMY_KEEP_DOMAIN
+					  "%s from %s\n",
+					  ptr->is_not ? "no_" : "",
+					  ptr->program->name,
+					  ptr->domainname->name))
+				return -ENOMEM;
+		} else {
+			if (tmy_io_printf(head,
+					  "%s" TMY_KEEP_DOMAIN
+					  "%s\n",
+					  ptr->is_not ? "no_" : "",
+					  ptr->domainname->name))
+				return -ENOMEM;
+		}
+	}
+	return 0;
+}
+
+/* Should I remain in current domain? */
+static int tmy_is_domain_keeper(const struct path_info *domainname,
+				const struct path_info *program,
+				const struct path_info *last_name)
+{
+	struct domain_keeper_entry *ptr;
+	int flag = 0;
+	list_for_each_entry(ptr, &domain_keeper_list, list) {
+		if (ptr->is_deleted)
+			continue;
+		if (!ptr->is_last_name) {
+			if (ptr->domainname != domainname)
+				continue;
+		} else {
+			if (tmy_pathcmp(ptr->domainname, last_name))
+				continue;
+		}
+		if (ptr->program && tmy_pathcmp(ptr->program, program))
+			continue;
+		if (ptr->is_not)
+			return 0;
+		flag = 1;
+	}
+	return flag;
+}
+
+/*********************  SYMBOLIC LINKED PROGRAM HANDLER  *********************/
+
+static LIST_HEAD(alias_list);
+
+/* Update alias list. */
+static int tmy_add_alias_entry(const char *original_name,
+			       const char *aliased_name,
+			       const bool is_delete)
+{
+	struct alias_entry *new_entry;
+	struct alias_entry *ptr;
+	static DEFINE_MUTEX(mutex);
+	const struct path_info *saved_original_name;
+	const struct path_info *saved_aliased_name;
+	int error = -ENOMEM;
+
+	if (!tmy_correct_path(original_name, 1, -1, -1, __FUNCTION__) ||
+	    !tmy_correct_path(aliased_name, 1, -1, -1, __FUNCTION__))
+		return -EINVAL; /* No patterns allowed. */
+
+	saved_original_name = tmy_save_name(original_name);
+	saved_aliased_name = tmy_save_name(aliased_name);
+	if (!saved_original_name || !saved_aliased_name)
+		return -ENOMEM;
+
+	mutex_lock(&mutex);
+
+	list_for_each_entry(ptr, &alias_list, list) {
+		if (ptr->original_name == saved_original_name &&
+		    ptr->aliased_name == saved_aliased_name) {
+			ptr->is_deleted = is_delete;
+			error = 0;
+			goto out;
+		}
+	}
+
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+
+	new_entry = tmy_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+
+	new_entry->original_name = saved_original_name;
+	new_entry->aliased_name = saved_aliased_name;
+	list_add_tail_mb(&new_entry->list, &alias_list);
+	error = 0;
+out: ;
+	mutex_unlock(&mutex);
+	return error;
+}
+
+/**
+ * tmy_read_alias_policy - read alias policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_alias_policy(struct io_buffer *head)
+{
+	struct list_head *pos;
+	list_for_each_cookie(pos, head->read_var2, &alias_list) {
+		struct alias_entry *ptr;
+		ptr = list_entry(pos, struct alias_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (tmy_io_printf(head,
+				  TMY_ALIAS "%s %s\n",
+				  ptr->original_name->name,
+				  ptr->aliased_name->name))
+			return -ENOMEM;
+	}
+	return 0;
+}
+
+/**
+ * tmy_add_alias_policy - add alias policy.
+ * @data:      a line to parse.
+ * @is_delete: is this remove request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * This function adds or removes an alias entry.
+ */
+int tmy_add_alias_policy(char *data, const bool is_delete)
+{
+	char *cp = strchr(data, ' ');
+
+	if (!cp)
+		return -EINVAL;
+	*cp++ = '\0';
+
+	return tmy_add_alias_entry(data, cp, is_delete);
+}
+
+/************************  DOMAIN AGGREGATOR HANDLER  ************************/
+
+static LIST_HEAD(aggregator_list);
+
+/* Update aggregator list. */
+static int tmy_add_aggregator_entry(const char *original_name,
+				    const char *aggregated_name,
+				    const bool is_delete)
+{
+	struct aggregator_entry *new_entry;
+	struct aggregator_entry *ptr;
+	static DEFINE_MUTEX(mutex);
+	const struct path_info *saved_original_name;
+	const struct path_info *saved_aggregated_name;
+	int error = -ENOMEM;
+
+	if (!tmy_correct_path(original_name, 1, 0, -1, __FUNCTION__) ||
+	    !tmy_correct_path(aggregated_name, 1, -1, -1, __FUNCTION__))
+		return -EINVAL;
+
+	saved_original_name = tmy_save_name(original_name);
+	saved_aggregated_name = tmy_save_name(aggregated_name);
+	if (!saved_original_name || !saved_aggregated_name)
+		return -ENOMEM;
+
+	mutex_lock(&mutex);
+
+	list_for_each_entry(ptr, &aggregator_list, list) {
+		if (ptr->original_name == saved_original_name &&
+		    ptr->aggregated_name == saved_aggregated_name) {
+			ptr->is_deleted = is_delete;
+			error = 0;
+			goto out;
+		}
+	}
+
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+
+	new_entry = tmy_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+
+	new_entry->original_name = saved_original_name;
+	new_entry->aggregated_name = saved_aggregated_name;
+	list_add_tail_mb(&new_entry->list, &aggregator_list);
+	error = 0;
+out: ;
+
+	mutex_unlock(&mutex);
+
+	return error;
+}
+
+/**
+ * tmy_read_aggregator_policy - read aggregator policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_aggregator_policy(struct io_buffer *head)
+{
+	struct list_head *pos;
+	list_for_each_cookie(pos, head->read_var2, &aggregator_list) {
+		struct aggregator_entry *ptr;
+		ptr = list_entry(pos, struct aggregator_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (tmy_io_printf(head,
+				  TMY_AGGREGATOR "%s %s\n",
+				  ptr->original_name->name,
+				  ptr->aggregated_name->name))
+			return -ENOMEM;
+	}
+	return 0;
+}
+
+/**
+ * tmy_add_aggregator_policy - add aggregator policy.
+ * @data:      a line to parse.
+ * @is_delete: is this remove request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * This function adds or removes an aggregator entry.
+ */
+int tmy_add_aggregator_policy(char *data, const bool is_delete)
+{
+	char *cp = strchr(data, ' ');
+
+	if (!cp)
+		return -EINVAL;
+	*cp++ = '\0';
+
+	return tmy_add_aggregator_entry(data, cp, is_delete);
+}
+
+/*************************  DOMAIN DELETION HANDLER  *************************/
+
+/**
+ * tmy_delete_domain - delete a domain.
+ * @domainname0: domainname to delete.
+ *
+ * Returns zero.
+ *
+ * This function deletes domains.
+ * The behavior of deleting domain is like deleting files on Linux's
+ * filesystem. A process transits to different domain upon do_execve(),
+ * and the process can refer the deleted domains after the domain is deleted,
+ * like a process opens a file and the process can read()/write() the deleted
+ * file after the file is deleted.
+ * This avoids processes from crashing due to referring non-existent domains.
+ * Administrator manually terminates processes thet are referring deleted
+ * domains after deleting domains.
+ * Also, undeleting domains is supported. See tmy_undelete_domain().
+ */
+int tmy_delete_domain(char *domainname0)
+{
+	struct domain_info *domain;
+	struct path_info domainname;
+
+	domainname.name = domainname0;
+	tmy_fill_path_info(&domainname);
+
+	mutex_lock(&new_domain_assign_lock);
+	/* Is there an active domain? */ /* Never delete KERNEL_DOMAIN */
+	list_for_each_entry(domain, &domain_list, list) {
+		struct domain_info *domain2;
+		if (domain == &KERNEL_DOMAIN ||
+		    domain->is_deleted ||
+		    tmy_pathcmp(domain->domainname, &domainname))
+			continue;
+		/* Mark already deleted domains as non undeletable. */
+		list_for_each_entry(domain2, &domain_list, list) {
+			if (!domain2->is_deleted ||
+			    tmy_pathcmp(domain2->domainname, &domainname))
+				continue;
+			domain2->is_deleted = 255;
+		}
+		/* Delete and mark active domain as undeletable. */
+		domain->is_deleted = 1;
+		break;
+	}
+	mutex_unlock(&new_domain_assign_lock);
+	return 0;
+}
+
+/**
+ * tmy_undelete_domain - undelete a domain.
+ * @domainname0: domainname to undelete.
+ *
+ * Returns pointer to undeleted "struct domain_info" on success.
+ * Returns NULL on failure.
+ *
+ * This function undeletes domains.
+ * Not only the domain previously deleted by tmy_delete_domain()
+ * but also all domains deleted by tmy_delete_domain() are undeletable.
+ * If there is no deleted domain named @domainname0 or
+ * a not-yet-deleted domain named @domainname0 exists, undelete fails.
+ * Otherwise, previously deleted domain named @domainname0 is undeleted.
+ */
+struct domain_info *tmy_undelete_domain(const char *domainname0)
+{
+	struct domain_info *domain;
+	struct domain_info *candidate_domain = NULL;
+	struct path_info domainname;
+
+	domainname.name = domainname0;
+	tmy_fill_path_info(&domainname);
+
+	mutex_lock(&new_domain_assign_lock);
+
+	list_for_each_entry(domain, &domain_list, list) {
+		if (tmy_pathcmp(&domainname, domain->domainname))
+			continue;
+
+		if (!domain->is_deleted) {
+			/* This domain is active. I can't undelete. */
+			candidate_domain = NULL;
+			break;
+		}
+
+		/* Is this domain undeletable? */
+		if (domain->is_deleted == 1)
+			candidate_domain = domain;
+	}
+	if (candidate_domain)
+		candidate_domain->is_deleted = 0;
+
+	mutex_unlock(&new_domain_assign_lock);
+
+	return candidate_domain;
+}
+
+/************************  DOMAIN TRANSITION HANDLER  ************************/
+
+/**
+ * tmy_find_domain - find a domain with given domainname.
+ * @domainname0: the domainname to find.
+ *
+ * Returns pointer to "struct domain_info" on success.
+ * Returns NULL on failure.
+ *
+ * This function does not create a new domain
+ * if a domain named @domainname0 does not exist.
+ */
+struct domain_info *tmy_find_domain(const char *domainname0)
+{
+	struct domain_info *domain;
+	static int first = 1;
+	struct path_info domainname;
+
+	domainname.name = domainname0;
+	tmy_fill_path_info(&domainname);
+
+	if (first) {
+		KERNEL_DOMAIN.domainname = tmy_save_name(TMY_ROOT_NAME);
+		first = 0;
+	}
+
+	list_for_each_entry(domain, &domain_list, list) {
+		if (!domain->is_deleted &&
+		    !tmy_pathcmp(&domainname, domain->domainname))
+			return domain;
+	}
+
+	return NULL;
+}
+
+/**
+ * tmy_new_domain - find or assign a domain with given domainname.
+ * @domainname: the domainname to find.
+ * @profile:    profile number to assign if newly created.
+ *
+ * Returns pointer to "struct domain_info" on success.
+ * Returns NULL on failure.
+ *
+ * This function creates a new domain if a domain named @domainname0
+ * does not exist.
+ */
+struct domain_info *tmy_new_domain(const char *domainname,
+				   const u8 profile)
+{
+	struct domain_info *domain = NULL;
+	const struct path_info *saved_domainname;
+
+	mutex_lock(&new_domain_assign_lock);
+
+	domain = tmy_find_domain(domainname);
+	if (domain)
+		goto out;
+
+	if (!tmy_is_correct_domain(domainname, __FUNCTION__))
+		goto out;
+
+	saved_domainname = tmy_save_name(domainname);
+	if (!saved_domainname)
+		goto out;
+
+	/* Can I reuse memory of deleted domain? */
+	list_for_each_entry(domain, &domain_list, list) {
+		struct task_struct *p;
+		struct acl_info *ptr;
+		int flag;
+
+		if (!domain->is_deleted ||
+		    domain->domainname != saved_domainname)
+			continue;
+		flag = 0;
+
+		/***** CRITICAL SECTION START *****/
+		read_lock(&tasklist_lock);
+		for_each_process(p) {
+			/* "struct task_struct"->security is not NULL. */
+			if (((struct tmy_security *) p->security)->domain
+			    == domain) {
+				flag = 1;
+				break;
+			}
+		}
+		read_unlock(&tasklist_lock);
+		/***** CRITICAL SECTION END *****/
+
+		/* Somebody is still referring this deleted domain. */
+		if (flag)
+			continue;
+
+		/* OK. Let's reuse memory for this deleted domain. */
+
+		/* Delete all entries in this deleted domain. */
+		list_for_each_entry(ptr, &domain->acl_info_list, list)
+			ptr->is_deleted = 1;
+
+		domain->profile = profile;
+		domain->quota_warned = 0;
+
+		mb(); /* Avoid out-of-order execution. */
+		/* Undelete this deleted domain. */
+		domain->is_deleted = 0;
+		goto out;
+	}
+
+	/* No memory reusable. Create using new memory. */
+	domain = tmy_alloc_element(sizeof(*domain));
+	if (domain) {
+		INIT_LIST_HEAD(&domain->acl_info_list);
+		domain->domainname = saved_domainname;
+		domain->profile = profile;
+		list_add_tail_mb(&domain->list, &domain_list);
+	}
+out: ;
+	mutex_unlock(&new_domain_assign_lock);
+
+	return domain;
+}
+
+/* Convert non ASCII printable characters to ASCII printable characters. */
+static int tmy_escape(char *dest, const char *src, int dest_len)
+{
+	while (*src) {
+		const unsigned char c = *(const unsigned char *) src;
+
+		if (c == '\\') {
+			dest_len -= 2;
+			if (dest_len <= 0)
+				goto out;
+			*dest++ = '\\';
+			*dest++ = '\\';
+		} else if (c > ' ' && c < 127) {
+			if (--dest_len <= 0)
+				goto out;
+			*dest++ = c;
+		} else {
+			dest_len -= 4;
+			if (dest_len <= 0)
+				goto out;
+			*dest++ = '\\';
+			*dest++ = (c >> 6) + '0';
+			*dest++ = ((c >> 3) & 7) + '0';
+			*dest++ = (c & 7) + '0';
+		}
+		src++;
+	}
+
+	if (--dest_len <= 0)
+		goto out;
+	*dest = '\0';
+
+	return 0;
+out: ;
+	return -ENOMEM;
+}
+
+/* Get argv[0] of "struct linux_binprm". */
+static char *tmy_get_argv0(struct linux_binprm *bprm)
+{
+	char *arg_ptr;
+	int arg_len = 0;
+	unsigned long pos = bprm->p;
+	int i = pos / PAGE_SIZE;
+	int offset = pos % PAGE_SIZE;
+
+	if (bprm->argc <= 0)
+		return NULL;
+
+	arg_ptr = tmy_alloc(PAGE_SIZE);
+
+	if (!arg_ptr)
+		goto out;
+
+	while (1) {
+		struct page *page;
+		const char *kaddr;
+		char *tmp_arg;
+
+#ifdef CONFIG_MMU
+		if (get_user_pages(current, bprm->mm, pos,
+				   1, 0, 1, &page, NULL) <= 0)
+			goto out;
+#else
+		page = bprm->page[i];
+#endif
+		kaddr = kmap(page);
+		if (!kaddr) { /* Mapping failed. */
+#ifdef CONFIG_MMU
+			put_page(page);
+#endif
+			goto out;
+		}
+
+		memmove(arg_ptr + arg_len, kaddr + offset, PAGE_SIZE - offset);
+		kunmap(page);
+
+#ifdef CONFIG_MMU
+		put_page(page);
+		pos += PAGE_SIZE - offset;
+#endif
+
+		arg_len += PAGE_SIZE - offset;
+
+		if (memchr(arg_ptr, '\0', arg_len))
+			break;
+
+		tmp_arg = tmy_alloc(arg_len + PAGE_SIZE);
+		if (!tmp_arg)
+			goto out;
+
+		memmove(tmp_arg, arg_ptr, arg_len);
+		tmy_free(arg_ptr);
+		arg_ptr = tmp_arg;
+		i++;
+		offset = 0;
+	}
+	return arg_ptr;
+out: ;
+	tmy_free(arg_ptr);
+
+	return NULL;
+}
+
+/**
+ * tmy_find_next_domain - find a domain to transit to if do_execve() succeeds.
+ * @bprm:        pointer to "struct linux_binprm".
+ * @next_domain: pointer to pointer to "struct domain_info".
+ *
+ * Returns zero if success. @next_domain receives new domain to transit to.
+ * Returns nonzero on failure.
+ *
+ * This function handles TOMOYO Linux's domain transition.
+ * New domains are automatically created unless the domain the caller process
+ * belongs to is assigned a profile for enforcing mode.
+ */
+int tmy_find_next_domain(struct linux_binprm *bprm,
+			 struct domain_info **next_domain)
+{
+	/* This function assumes that the size of buffer returned */
+	/* by tmy_realpath() = TMY_MAX_PATHNAME_LEN. */
+	struct domain_info *old_domain = TMY_SECURITY->domain;
+	struct domain_info *domain = NULL;
+	const char *old_domain_name = old_domain->domainname->name;
+	const char *original_name = bprm->filename;
+	struct file *filp = bprm->file;
+	char *new_domain_name = NULL;
+	char *real_program_name = NULL;
+	char *symlink_program_name = NULL;
+	const bool is_enforce = (tmy_flags(TMY_MAC_FOR_FILE) == 3);
+	int retval;
+	struct path_info r;
+	struct path_info s;
+	struct path_info l;
+
+	/*
+	 * Built-in initializers.
+	 * This is needed because policies are not loaded
+	 * until starting /sbin/init .
+	 */
+	static int first = 1;
+	if (first) {
+		tmy_add_domain_initializer_entry(NULL, "/sbin/hotplug", 0, 0);
+		tmy_add_domain_initializer_entry(NULL, "/sbin/modprobe", 0, 0);
+		tmy_add_domain_initializer_entry(NULL, "/sbin/udevd", 0, 0);
+		first = 0;
+	}
+
+	/* Get realpath of program. */
+	/* I hope tmy_realpath() won't fail with -ENOMEM. */
+	retval = -ENOENT;
+	real_program_name = tmy_realpath(original_name);
+
+	if (!real_program_name)
+		goto out;
+
+	/* Get realpath of symbolic link. */
+	symlink_program_name = tmy_realpath_nofollow(original_name);
+	if (!symlink_program_name)
+		goto out;
+
+	r.name = real_program_name;
+	tmy_fill_path_info(&r);
+	s.name = symlink_program_name;
+	tmy_fill_path_info(&s);
+	l.name = strrchr(old_domain_name, ' ');
+
+	if (l.name)
+		l.name++;
+	else
+		l.name = old_domain_name;
+	tmy_fill_path_info(&l);
+
+	/* Check 'alias' directive. */
+	if (tmy_pathcmp(&r, &s)) {
+		struct alias_entry *ptr;
+
+		/* Is this program allowed to be called via symbolic links? */
+		list_for_each_entry(ptr, &alias_list, list) {
+			if (ptr->is_deleted ||
+			    tmy_pathcmp(&r, ptr->original_name) ||
+			    tmy_pathcmp(&s, ptr->aliased_name))
+				continue;
+			memset(real_program_name, 0, TMY_MAX_PATHNAME_LEN);
+			strncpy(real_program_name,
+				ptr->aliased_name->name,
+				TMY_MAX_PATHNAME_LEN - 1);
+			tmy_fill_path_info(&r);
+			break;
+		}
+	}
+
+	/* Compare basename of real_program_name and argv[0] */
+	if (bprm->argc > 0 && tmy_flags(TMY_MAC_FOR_ARGV0)) {
+
+		char *org_argv0 = tmy_get_argv0(bprm);
+
+		retval = -ENOMEM;
+		if (org_argv0) {
+
+			const int len = strlen(org_argv0);
+			char *printable_argv0 = tmy_alloc(len * 4 + 8);
+
+			if (printable_argv0 &&
+			    !tmy_escape(printable_argv0, org_argv0,
+					len * 4 + 8)) {
+				const char *base_argv0;
+				const char *base_filename;
+
+				base_argv0 = strrchr(printable_argv0, '/');
+				if (!base_argv0)
+					base_argv0 = printable_argv0;
+				else
+					base_argv0++;
+
+				base_filename = strrchr(real_program_name, '/');
+				if (!base_filename)
+					base_filename = real_program_name;
+				else
+					base_filename++;
+
+				if (strcmp(base_argv0, base_filename))
+					retval = tmy_argv0_perm(&r, base_argv0);
+				else
+					retval = 0;
+			}
+
+			tmy_free(printable_argv0);
+			tmy_free(org_argv0);
+		}
+
+		if (retval)
+			goto out;
+
+	}
+
+
+	/* Check 'aggregator' directive. */
+	{
+		struct aggregator_entry *ptr;
+
+		/* Is this program allowed to be aggregated? */
+		list_for_each_entry(ptr, &aggregator_list, list) {
+			if (ptr->is_deleted ||
+			    !tmy_path_match(&r, ptr->original_name))
+				continue;
+			memset(real_program_name, 0, TMY_MAX_PATHNAME_LEN);
+			strncpy(real_program_name,
+				ptr->aggregated_name->name,
+				TMY_MAX_PATHNAME_LEN - 1);
+			tmy_fill_path_info(&r);
+			break;
+		}
+	}
+
+	/* Check execute permission. */
+	retval = tmy_exec_perm(&r, filp);
+	if (retval < 0)
+		goto out;
+
+	/* Allocate memory for calcurating domain name. */
+	retval = -ENOMEM;
+	new_domain_name = tmy_alloc(TMY_MAX_PATHNAME_LEN + 16);
+	if (!new_domain_name)
+		goto out;
+
+	if (tmy_is_domain_initializer(old_domain->domainname, &r, &l))
+		/* Transit to the child of KERNEL_DOMAIN domain. */
+		snprintf(new_domain_name, TMY_MAX_PATHNAME_LEN + 1,
+			 TMY_ROOT_NAME " " "%s", real_program_name);
+	else if (old_domain == &KERNEL_DOMAIN && !sbin_init_started)
+		/*
+		 * Needn't to transit from kernel domain
+		 * before starting /sbin/init .
+		 * But transit from kernel domain if executing initializers,
+		 * for they might start before /sbin/init .
+		 */
+		domain = old_domain;
+	else if (tmy_is_domain_keeper(old_domain->domainname, &r, &l))
+		/* Keep current domain. */
+		domain = old_domain;
+	else
+		/* Normal domain transition. */
+		snprintf(new_domain_name,
+			 TMY_MAX_PATHNAME_LEN + 1,
+			 "%s %s",
+			 old_domain_name,
+			 real_program_name);
+
+	if (domain || strlen(new_domain_name) >= TMY_MAX_PATHNAME_LEN)
+		goto ok;
+
+	if (is_enforce) {
+		domain = tmy_find_domain(new_domain_name);
+		if (!domain &&
+		    tmy_supervisor("#Need to create domain\n%s\n",
+				   new_domain_name) == 0) {
+			const u8 profile = TMY_SECURITY->domain->profile;
+			domain = tmy_new_domain(new_domain_name, profile);
+		}
+	} else {
+		const u8 profile = TMY_SECURITY->domain->profile;
+		domain = tmy_new_domain(new_domain_name, profile);
+	}
+
+ok: ;
+
+	if (!domain) {
+		printk(KERN_INFO "TOMOYO-ERROR: Domain '%s' not defined.\n",
+		       new_domain_name);
+		if (is_enforce)
+			retval = -EPERM;
+	} else
+		retval = 0;
+out: ;
+
+	tmy_free(new_domain_name);
+	tmy_free(real_program_name);
+	tmy_free(symlink_program_name);
+	*next_domain = domain ? domain : old_domain;
+
+	return retval;
+}

-- 

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

* [TOMOYO #5 08/18] Auditing interface.
  2007-11-16 17:34 [TOMOYO #5 00/18] TOMOYO Linux - MAC based on process invocation history penguin-kernel
                   ` (6 preceding siblings ...)
  2007-11-16 17:34 ` [TOMOYO #5 07/18] Domain transition functions penguin-kernel
@ 2007-11-16 17:34 ` penguin-kernel
  2007-11-16 17:34 ` [TOMOYO #5 09/18] File access control functions penguin-kernel
                   ` (9 subsequent siblings)
  17 siblings, 0 replies; 39+ messages in thread
From: penguin-kernel @ 2007-11-16 17:34 UTC (permalink / raw)
  To: akpm; +Cc: linux-kernel, linux-security-module, Kentaro Takeda, Tetsuo Handa

[-- Attachment #1: tomoyo-audit.diff --]
[-- Type: text/plain, Size: 6562 bytes --]

TOMOYO Linux uses /sys/kernel/security/tomoyo/ interface
for reporting access logs in domain policy format.
One is 'grant_log', used for auditing accesses which are
granted in the TOMOYO Linux policy.
The other is 'reject_log', used for auditing accesses which
are not granted in the TOMOYO Linux policy.
The userland daemon /usr/lib/ccs/ccs-auditd will save these logs.

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

--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6-mm/security/tomoyo/audit.c	2007-11-14 15:15:44.000000000 +0900
@@ -0,0 +1,238 @@
+/*
+ * security/tomoyo/audit.c
+ *
+ * Audit functions for TOMOYO Linux
+ */
+
+#include "tomoyo.h"
+
+#ifdef CONFIG_SECURITY_TOMOYO_USE_AUDITD
+/**
+ * tmy_audit - write audit log.
+ * @fmt:  format strings for printf().
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * Write audit log.
+ */
+int tmy_audit(const char *fmt, ...)
+{
+	struct audit_buffer *ab;
+	int len;
+	va_list args;
+	char *buf;
+	char *cp;
+	ab = audit_log_start(current->audit_context, GFP_KERNEL, AUDIT_KERNEL);
+	if (!ab)
+		return -ENOMEM;
+	buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!buf)
+		goto out;
+	va_start(args, fmt);
+	len = vsnprintf(buf, PAGE_SIZE - 1, fmt, args);
+	va_end(args);
+	if (len > PAGE_SIZE - 1) {
+		kfree(buf);
+		buf = kzalloc(len + 16, GFP_KERNEL);
+		if (!buf)
+			goto out;
+		va_start(args, fmt);
+		vsnprintf(buf, len + 15, fmt, args);
+		va_end(args);
+	}
+	cp = strchr(buf, '\0') - 1;
+	if (cp >= buf && *cp == '\n')
+		*cp = '\0';
+	audit_log_format(ab, "TOMOYO: %s", buf);
+	kfree(buf);
+out: ;
+	audit_log_end(ab);
+	return buf ? 0 : -ENOMEM;
+}
+#endif
+
+static DECLARE_WAIT_QUEUE_HEAD(grant_log_wait);
+static DECLARE_WAIT_QUEUE_HEAD(reject_log_wait);
+
+static DEFINE_SPINLOCK(audit_log_lock);
+
+struct log_entry {
+	struct list_head list;
+	char *log;
+};
+
+static LIST_HEAD(grant_log);
+static LIST_HEAD(reject_log);
+
+static int grant_log_count;
+static int reject_log_count;
+
+/**
+ * tmy_audit_grant - get flags of auditing grant logs.
+ *
+ * Returns current value of auditing grant log flags.
+ */
+bool tmy_audit_grant(void)
+{
+	return grant_log_count < tmy_flags(TMY_MAX_GRANT_LOG);
+}
+
+/**
+ * tmy_audit_reject - get flags of auditing reject logs.
+ *
+ * Returns current value of auditing reject log flags.
+ */
+bool tmy_audit_reject(void)
+{
+	return reject_log_count < tmy_flags(TMY_MAX_REJECT_LOG);
+}
+
+/**
+ * tmy_init_audit_log - allocate and initialize audit buffer.
+ * @len: pointer to length of requested size.
+ * @profile: profile number for this log.
+ * @mode: profile value for this log.
+ *
+ * Returns pointer to audit buffer on success. @len received allocated size.
+ * Returns NULL on failure.
+ *
+ * @len must not be a NULL.
+ */
+char *tmy_init_audit_log(int *len, const u8 profile, const unsigned int mode)
+{
+	char *buf;
+	struct timeval tv;
+	struct task_struct *task = current;
+	const char *domainname = TMY_SECURITY->domain->domainname->name;
+	do_gettimeofday(&tv);
+	*len += strlen(domainname) + 256;
+	buf = tmy_alloc(*len);
+	if (!buf)
+		return NULL;
+	snprintf(buf, (*len) - 1, "#timestamp=%lu profile=%u mode=%u "
+		 "pid=%d uid=%d gid=%d euid=%d egid=%d "
+		 "suid=%d sgid=%d fsuid=%d fsgid=%d \n%s\n",
+		 tv.tv_sec, profile, mode,
+		 task->pid, task->uid, task->gid, task->euid, task->egid,
+		 task->suid, task->sgid, task->fsuid, task->fsgid, domainname);
+	return buf;
+}
+
+/**
+ * tmy_write_audit_log - write audit log.
+ * @buf:        pointer to access log contents.
+ * @is_granted: is the access request granted?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * Write audit log.
+ * Caller must allocate @buf with tmy_init_audit_log().
+ */
+int tmy_write_audit_log(char *buf, const bool is_granted)
+{
+	struct log_entry *new_entry;
+	new_entry = tmy_alloc(sizeof(*new_entry));
+	if (!new_entry) {
+		tmy_free(buf);
+		return -ENOMEM;
+	}
+	INIT_LIST_HEAD(&new_entry->list);
+	new_entry->log = buf;
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&audit_log_lock);
+	if (is_granted) {
+		list_add_tail(&new_entry->list, &grant_log);
+		grant_log_count++;
+		buf = NULL;
+		tmy_update_counter(TMY_UPDATE_GRANT_LOG);
+	} else {
+		list_add_tail(&new_entry->list, &reject_log);
+		reject_log_count++;
+		buf = NULL;
+		tmy_update_counter(TMY_UPDATE_REJECT_LOG);
+	}
+	spin_unlock(&audit_log_lock);
+	/***** CRITICAL SECTION END *****/
+	if (is_granted)
+		wake_up(&grant_log_wait);
+	else
+		wake_up(&reject_log_wait);
+	return 0;
+}
+
+int tmy_read_grant_log(struct io_buffer *head)
+{
+	struct log_entry *ptr = NULL;
+	if (head->read_avail) return 0;
+	if (head->read_buf) {
+		tmy_free(head->read_buf);
+		head->read_buf = NULL;
+		head->readbuf_size = 0;
+	}
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&audit_log_lock);
+	if (!list_empty(&grant_log)) {
+		ptr = list_entry(grant_log.next, struct log_entry, list);
+		list_del(&ptr->list);
+		grant_log_count--;
+	}
+	spin_unlock(&audit_log_lock);
+	/***** CRITICAL SECTION END *****/
+	if (ptr) {
+		head->read_buf = ptr->log;
+		head->read_avail = strlen(ptr->log) + 1;
+		head->readbuf_size = head->read_avail;
+		tmy_free(ptr);
+	}
+	return 0;
+}
+
+int tmy_poll_grant_log(struct file *file, poll_table *wait)
+{
+	if (grant_log_count)
+		return POLLIN | POLLRDNORM;
+	poll_wait(file, &grant_log_wait, wait);
+	if (grant_log_count)
+		return POLLIN | POLLRDNORM;
+	return 0;
+}
+
+int tmy_read_reject_log(struct io_buffer *head)
+{
+	struct log_entry *ptr = NULL;
+	if (head->read_avail)
+		return 0;
+	if (head->read_buf) {
+		tmy_free(head->read_buf);
+		head->read_buf = NULL;
+		head->readbuf_size = 0;
+	}
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&audit_log_lock);
+	if (!list_empty(&reject_log)) {
+		ptr = list_entry(reject_log.next, struct log_entry, list);
+		list_del(&ptr->list);
+		reject_log_count--;
+	}
+	spin_unlock(&audit_log_lock);
+	/***** CRITICAL SECTION END *****/
+	if (ptr) {
+		head->read_buf = ptr->log;
+		head->read_avail = strlen(ptr->log) + 1;
+		head->readbuf_size = head->read_avail;
+		tmy_free(ptr);
+	}
+	return 0;
+}
+
+int tmy_poll_reject_log(struct file *file, poll_table *wait)
+{
+	if (reject_log_count)
+		return POLLIN | POLLRDNORM;
+	poll_wait(file, &reject_log_wait, wait);
+	if (reject_log_count)
+		return POLLIN | POLLRDNORM;
+	return 0;
+}

-- 

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

* [TOMOYO #5 09/18] File access control functions.
  2007-11-16 17:34 [TOMOYO #5 00/18] TOMOYO Linux - MAC based on process invocation history penguin-kernel
                   ` (7 preceding siblings ...)
  2007-11-16 17:34 ` [TOMOYO #5 08/18] Auditing interface penguin-kernel
@ 2007-11-16 17:34 ` penguin-kernel
  2007-11-16 17:34 ` [TOMOYO #5 10/18] argv0 check functions penguin-kernel
                   ` (8 subsequent siblings)
  17 siblings, 0 replies; 39+ messages in thread
From: penguin-kernel @ 2007-11-16 17:34 UTC (permalink / raw)
  To: akpm; +Cc: linux-kernel, linux-security-module, Kentaro Takeda, Tetsuo Handa

[-- Attachment #1: tomoyo-file.diff --]
[-- Type: text/plain, Size: 36968 bytes --]

TOMOYO Linux checks permission in
open/creat/unlink/truncate/ftruncate/mknod/mkdir/
rmdir/symlink/link/rename/uselib/sysctl .

Each permission can be automatically accumulated into
the policy of each domain using 'learning mode'.

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

--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6-mm/security/tomoyo/file.c	2007-11-14 15:15:44.000000000 +0900
@@ -0,0 +1,1471 @@
+/*
+ * security/tomoyo/file.c
+ *
+ * File access control functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+#define ACC_MODE(x) ("\000\004\002\006"[(x)&O_ACCMODE])
+
+/*************************  VARIABLES  *************************/
+
+/***** The structure for globally readable files. *****/
+
+struct globally_readable_file_entry {
+	struct list_head list;
+	const struct path_info *filename;
+	bool is_deleted;
+};
+
+/***** The structure for filename patterns. *****/
+
+struct pattern_entry {
+	struct list_head list;
+	const struct path_info *pattern;
+	bool is_deleted;
+};
+
+/***** The structure for non-rewritable-by-default file patterns. *****/
+
+struct no_rewrite_entry {
+	struct list_head list;
+	const struct path_info *pattern;
+	bool is_deleted;
+};
+
+/***** The structure for detailed write operations. *****/
+
+static struct {
+	const char *keyword;
+	const int paths;
+} acl_type_array[] = {
+	{ "create",   1 }, /* TMY_TYPE_CREATE_ACL */
+	{ "unlink",   1 }, /* TMY_TYPE_UNLINK_ACL */
+	{ "mkdir",    1 }, /* TMY_TYPE_MKDIR_ACL */
+	{ "rmdir",    1 }, /* TMY_TYPE_RMDIR_ACL */
+	{ "mkfifo",   1 }, /* TMY_TYPE_MKFIFO_ACL */
+	{ "mksock",   1 }, /* TMY_TYPE_MKSOCK_ACL */
+	{ "mkblock",  1 }, /* TMY_TYPE_MKBLOCK_ACL */
+	{ "mkchar",   1 }, /* TMY_TYPE_MKCHAR_ACL */
+	{ "truncate", 1 }, /* TMY_TYPE_TRUNCATE_ACL */
+	{ "symlink",  1 }, /* TMY_TYPE_SYMLINK_ACL */
+	{ "link",     2 }, /* TMY_TYPE_LINK_ACL */
+	{ "rename",   2 }, /* TMY_TYPE_RENAME_ACL */
+	{ "rewrite",  1 }, /* TMY_TYPE_REWRITE_ACL */
+	{ NULL, 0 }
+};
+
+/*************************  UTILITY FUNCTIONS  *************************/
+
+/**
+ * tmy_acltype2keyword - get keyword from access control index.
+ * @acl_type: index number.
+ *
+ * Returns keyword that corresponds with @acl_type .
+ */
+const char *tmy_acltype2keyword(const unsigned int acl_type)
+{
+	return (acl_type < ARRAY_SIZE(acl_type_array))
+		? acl_type_array[acl_type].keyword : NULL;
+}
+
+/**
+ * tmy_acltype2paths - get number of arguments from access control index.
+ * @acl_type: index number.
+ *
+ * Returns number of arguments that corresponds with @acl_type .
+ */
+int tmy_acltype2paths(const unsigned int acl_type)
+{
+	return (acl_type < ARRAY_SIZE(acl_type_array))
+		? acl_type_array[acl_type].paths : 0;
+}
+
+static int tmy_strendswith(const char *name, const char *tail)
+{
+	int len;
+
+	if (!name || !tail)
+		return 0;
+
+	len = strlen(name) - strlen(tail);
+	return len >= 0 && strcmp(name + len, tail) == 0;
+}
+
+static struct path_info *tmy_get_path(struct dentry *dentry,
+				      struct vfsmount *mnt)
+{
+	/* sizeof(struct path_info_with_data) <= PAGE_SIZE */
+	struct path_info_with_data {
+		/* Keep this first, this pointer is passed to tmy_free(). */
+		struct path_info head;
+		char bariier1[16];
+		char body[TMY_MAX_PATHNAME_LEN];
+		char barrier2[16];
+	} *buf = tmy_alloc(sizeof(*buf));
+
+	if (buf) {
+		int error = tmy_realpath_dentry2(dentry,
+						 mnt,
+						 buf->body,
+						 sizeof(buf->body) - 1);
+
+		if (error == 0) {
+			buf->head.name = buf->body;
+			tmy_fill_path_info(&buf->head);
+			return &buf->head;
+		}
+
+		tmy_free(buf);
+		buf = NULL;
+		printk(KERN_INFO "tmy_realpath_dentry = %d\n", error);
+	}
+
+	return NULL;
+}
+
+/*************************  PROTOTYPES  *************************/
+
+static int tmy_add_double_write_acl(const u8 type,
+				    const char *filename1,
+				    const char *filename2,
+				    struct domain_info * const domain,
+				    const struct condition_list *cond,
+				    const bool is_delete);
+static int tmy_add_single_write_acl(const u8 type,
+				    const char *filename,
+				    struct domain_info * const domain,
+				    const struct condition_list *cond,
+				    const bool is_delete);
+
+/*************************  AUDIT FUNCTIONS  *************************/
+
+static int tmy_audit_file_log(const struct path_info *filename,
+			      const u8 perm,
+			      const bool is_granted,
+			      const u8 profile,
+			      const unsigned int mode)
+{
+	char *buf;
+	int len;
+
+	if (is_granted) {
+		if (!tmy_audit_grant())
+			return 0;
+	} else {
+		if (!tmy_audit_reject())
+			return 0;
+	}
+
+	len = filename->total_len + 8;
+	buf = tmy_init_audit_log(&len, profile, mode);
+
+	if (!buf)
+		return -ENOMEM;
+
+	snprintf(buf + strlen(buf),
+		 len - strlen(buf) - 1,
+		 "%d %s\n",
+		 perm,
+		 filename->name);
+
+	return tmy_write_audit_log(buf, is_granted);
+}
+
+static int tmy_audit_write_log(const char *operation,
+			       const struct path_info *filename1,
+			       const struct path_info *filename2,
+			       const bool is_granted,
+			       const u8 profile,
+			       const unsigned int mode)
+{
+	char *buf;
+	int len;
+
+	if (is_granted) {
+		if (!tmy_audit_grant())
+			return 0;
+	} else {
+		if (!tmy_audit_reject())
+			return 0;
+	}
+
+	len = strlen(operation) +
+		filename1->total_len +
+		(filename2 ? filename2->total_len : 0)
+		+ 16;
+
+	buf = tmy_init_audit_log(&len, profile, mode);
+	if (!buf)
+		return -ENOMEM;
+
+	snprintf(buf + strlen(buf), len - strlen(buf) - 1,
+		 "allow_%s %s %s\n",
+		 operation, filename1->name, filename2 ? filename2->name : "");
+
+	return tmy_write_audit_log(buf, is_granted);
+}
+
+/**********************  GLOBALLY READABLE FILE HANDLER  **********************/
+
+static LIST_HEAD(globally_readable_list);
+
+static int tmy_add_globally_readable_entry(const char *filename,
+					   const bool is_delete)
+{
+	struct globally_readable_file_entry *new_entry;
+	struct globally_readable_file_entry *ptr;
+	static DEFINE_MUTEX(mutex);
+	const struct path_info *saved;
+	int error = -ENOMEM;
+
+	if (!tmy_correct_path(filename, 1, -1, -1, __FUNCTION__))
+		return -EINVAL; /* No patterns allowed. */
+	saved = tmy_save_name(filename);
+	if (!saved)
+		return -ENOMEM;
+
+	mutex_lock(&mutex);
+
+	list_for_each_entry(ptr, &globally_readable_list, list) {
+		if (ptr->filename == saved) {
+			ptr->is_deleted = is_delete;
+			error = 0;
+			goto out;
+		}
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+
+	new_entry = tmy_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+
+	new_entry->filename = saved;
+	list_add_tail_mb(&new_entry->list, &globally_readable_list);
+	error = 0;
+
+out: ;
+	mutex_unlock(&mutex);
+
+	return error;
+}
+
+static int tmy_globally_readable(const struct path_info *filename)
+{
+	struct globally_readable_file_entry *ptr;
+
+	list_for_each_entry(ptr, &globally_readable_list, list) {
+		if (!ptr->is_deleted &&
+		    !tmy_pathcmp(filename, ptr->filename))
+			return 1;
+	}
+
+	return 0;
+}
+
+/**
+ * tmy_add_globally_readable_policy - add or delete globally readable policy.
+ * @filename:  pointer to filename to add ore remove.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_globally_readable_policy(char *filename, const bool is_delete)
+{
+	return tmy_add_globally_readable_entry(filename, is_delete);
+}
+
+/**
+ * tmy_read_globally_readable_policy - read globally readable policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_globally_readable_policy(struct io_buffer *head)
+{
+	struct list_head *pos;
+	list_for_each_cookie(pos, head->read_var2, &globally_readable_list) {
+		struct globally_readable_file_entry *ptr;
+		ptr = list_entry(pos, struct globally_readable_file_entry,
+				 list);
+		if (ptr->is_deleted)
+			continue;
+		if (tmy_io_printf(head, TMY_ALLOW_READ "%s\n",
+				  ptr->filename->name))
+			return -ENOMEM;
+	}
+	return 0;
+}
+
+/*************************  FILE GROUP HANDLER  *************************/
+
+static LIST_HEAD(path_group_list);
+
+static int tmy_add_group_entry(const char *group_name,
+			       const char *member_name,
+			       const bool is_delete)
+{
+	static DEFINE_MUTEX(mutex);
+	struct path_group_entry *new_group;
+	struct path_group_entry *group;
+	struct path_group_member *new_member;
+	struct path_group_member *member;
+	const struct path_info *saved_group;
+	const struct path_info *saved_member;
+	int error = -ENOMEM;
+	bool found = 0;
+
+	if (!tmy_correct_path(group_name, 0, 0, 0, __FUNCTION__) ||
+	    !group_name[0] ||
+	    !tmy_correct_path(member_name, 0, 0, 0, __FUNCTION__) ||
+	    !member_name[0])
+		return -EINVAL;
+
+	saved_group = tmy_save_name(group_name);
+	saved_member = tmy_save_name(member_name);
+
+	if (!saved_group || !saved_member)
+		return -ENOMEM;
+
+	mutex_lock(&mutex);
+	list_for_each_entry(group, &path_group_list, list) {
+		if (saved_group != group->group_name)
+			continue;
+		list_for_each_entry(member, &group->path_group_member_list,
+				    list) {
+			if (member->member_name == saved_member) {
+				member->is_deleted = is_delete;
+				error = 0;
+				goto out;
+			}
+		}
+		found = 1;
+		break;
+	}
+
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+
+	if (!found) {
+		new_group = tmy_alloc_element(sizeof(*new_group));
+		if (!new_group)
+			goto out;
+		INIT_LIST_HEAD(&new_group->path_group_member_list);
+		new_group->group_name = saved_group;
+		list_add_tail_mb(&new_group->list, &path_group_list);
+		group = new_group;
+	}
+
+	new_member = tmy_alloc_element(sizeof(*new_member));
+	if (!new_member)
+		goto out;
+	new_member->member_name = saved_member;
+	list_add_tail_mb(&new_member->list, &group->path_group_member_list);
+	error = 0;
+out: ;
+	mutex_unlock(&mutex);
+
+	return error;
+}
+
+/**
+ * tmy_add_group_policy - add or delete path group policy.
+ * @data:      a line to parse.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_group_policy(char *data, const bool is_delete)
+{
+	char *cp = strchr(data, ' ');
+
+	if (!cp)
+		return -EINVAL;
+	*cp++ = '\0';
+	return tmy_add_group_entry(data, cp, is_delete);
+}
+
+static struct path_group_entry *tmy_new_path_group(const char *group_name)
+{
+	int i;
+	struct path_group_entry *group;
+
+	for (i = 0; i <= 1; i++) {
+		list_for_each_entry(group, &path_group_list, list) {
+			if (strcmp(group_name, group->group_name->name) == 0)
+				return group;
+		}
+
+		if (i == 0) {
+			/*
+			 * Add a dummy entry to create new path group
+			 * and delete that entry.
+			 */
+			tmy_add_group_entry(group_name, "/", 0);
+			tmy_add_group_entry(group_name, "/", 1);
+		}
+	}
+
+	return NULL;
+}
+
+static int tmy_path_match_group(const struct path_info *pathname,
+				const struct path_group_entry *group,
+				const int may_use_pattern)
+{
+	struct path_group_member *member;
+	list_for_each_entry(member, &group->path_group_member_list, list) {
+		if (member->is_deleted)
+			continue;
+		if (!member->member_name->is_patterned) {
+			if (!tmy_pathcmp(pathname, member->member_name))
+				return 1;
+		} else if (may_use_pattern) {
+			if (tmy_path_match(pathname, member->member_name))
+				return 1;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * tmy_read_path_group_policy - read path group policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_path_group_policy(struct io_buffer *head)
+{
+	struct list_head *gpos;
+	struct list_head *mpos;
+	list_for_each_cookie(gpos, head->read_var1, &path_group_list) {
+		struct path_group_entry *group;
+		group = list_entry(gpos, struct path_group_entry, list);
+		list_for_each_cookie(mpos, head->read_var2,
+				     &group->path_group_member_list) {
+			struct path_group_member *member;
+			member = list_entry(mpos, struct path_group_member,
+					    list);
+			if (member->is_deleted)
+				continue;
+			if (tmy_io_printf(head,
+					  TMY_PATH_GROUP "%s %s\n",
+					  group->group_name->name,
+					  member->member_name->name))
+				return -ENOMEM;
+		}
+	}
+	return 0;
+}
+
+/*************************  FILE PATTERN HANDLER  *************************/
+
+static LIST_HEAD(pattern_list);
+
+static int tmy_add_pattern_entry(const char *pattern, const bool is_delete)
+{
+	struct pattern_entry *new_entry;
+	struct pattern_entry *ptr;
+	static DEFINE_MUTEX(mutex);
+	const struct path_info *saved;
+	int error = -ENOMEM;
+
+	if (!tmy_correct_path(pattern, 0, 1, 0, __FUNCTION__))
+		return -EINVAL;
+
+	saved = tmy_save_name(pattern);
+	if (!saved)
+		return -ENOMEM;
+
+	mutex_lock(&mutex);
+
+	list_for_each_entry(ptr, &pattern_list, list) {
+		if (saved == ptr->pattern) {
+			ptr->is_deleted = is_delete;
+			error = 0;
+			goto out;
+		}
+	}
+
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+
+	new_entry = tmy_alloc_element(sizeof(*new_entry));
+
+	if (!new_entry)
+		goto out;
+	new_entry->pattern = saved;
+	list_add_tail_mb(&new_entry->list, &pattern_list);
+	error = 0;
+out: ;
+	mutex_unlock(&mutex);
+	return error;
+}
+
+static const struct path_info *tmy_get_pattern(const struct path_info *filename)
+{
+	struct pattern_entry *ptr;
+	const struct path_info *pattern = NULL;
+
+	list_for_each_entry(ptr, &pattern_list, list) {
+		if (ptr->is_deleted)
+			continue;
+		if (!tmy_path_match(filename, ptr->pattern))
+			continue;
+		pattern = ptr->pattern;
+		if (!tmy_strendswith(pattern->name, "/\\*"))
+			break;
+	}
+
+	if (pattern)
+		filename = pattern;
+
+	return filename;
+}
+
+/**
+ * tmy_add_pattern_policy - add or delete file pattern policy.
+ * @pattern:   pointer to file pattern entry.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_pattern_policy(char *pattern, const bool is_delete)
+{
+	return tmy_add_pattern_entry(pattern, is_delete);
+}
+
+/**
+ * tmy_read_pattern_policy - read file pattern policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_pattern_policy(struct io_buffer *head)
+{
+	struct list_head *pos;
+	list_for_each_cookie(pos, head->read_var2, &pattern_list) {
+		struct pattern_entry *ptr;
+		ptr = list_entry(pos, struct pattern_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (tmy_io_printf(head, TMY_FILE_PATTERN "%s\n",
+				  ptr->pattern->name))
+			return -ENOMEM;
+	}
+	return 0;
+}
+
+/***********************  NON REWRITABLE FILE HANDLER  ***********************/
+
+static LIST_HEAD(no_rewrite_list);
+
+static int tmy_add_no_rewrite_entry(const char *pattern, const bool is_delete)
+{
+	struct no_rewrite_entry *new_entry;
+	struct no_rewrite_entry *ptr;
+	static DEFINE_MUTEX(mutex);
+	const struct path_info *saved;
+	int error = -ENOMEM;
+
+	if (!tmy_correct_path(pattern, 0, 0, 0, __FUNCTION__))
+		return -EINVAL;
+	saved = tmy_save_name(pattern);
+	if (!saved)
+		return -ENOMEM;
+
+	mutex_lock(&mutex);
+	list_for_each_entry(ptr, &no_rewrite_list, list) {
+		if (ptr->pattern == saved) {
+			ptr->is_deleted = is_delete;
+			error = 0;
+			goto out;
+		}
+	}
+
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+
+	new_entry = tmy_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+
+	new_entry->pattern = saved;
+	list_add_tail_mb(&new_entry->list, &no_rewrite_list);
+	error = 0;
+out: ;
+	mutex_unlock(&mutex);
+
+	return error;
+}
+
+static int tmy_is_no_rewrite_file(const struct path_info *filename)
+{
+	struct no_rewrite_entry *ptr;
+
+	list_for_each_entry(ptr, &no_rewrite_list, list) {
+		if (ptr->is_deleted)
+			continue;
+		if (!tmy_path_match(filename, ptr->pattern))
+			continue;
+		return 1;
+	}
+
+	return 0;
+}
+
+/**
+ * tmy_add_no_rewrite_policy - add or delete no-rewrite policy.
+ * @pattern:   pointer to no-rewrite entry.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_no_rewrite_policy(char *pattern, const bool is_delete)
+{
+	return tmy_add_no_rewrite_entry(pattern, is_delete);
+}
+
+/**
+ * tmy_read_no_rewrite_policy - read no-rewrite policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_no_rewrite_policy(struct io_buffer *head)
+{
+	struct list_head *pos;
+	list_for_each_cookie(pos, head->read_var2, &no_rewrite_list) {
+		struct no_rewrite_entry *ptr;
+		ptr = list_entry(pos, struct no_rewrite_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (tmy_io_printf(head, TMY_DENY_REWRITE "%s\n",
+				  ptr->pattern->name))
+			return -ENOMEM;
+	}
+	return 0;
+}
+
+/*************************  FILE ACL HANDLER  *************************/
+
+static int tmy_add_file_acl(const char *filename,
+			    u8 perm,
+			    struct domain_info * const domain,
+			    const struct condition_list *cond,
+			    const bool is_delete)
+{
+	const struct path_info *saved;
+	struct acl_info *ptr;
+	struct file_acl *acl;
+	int error = -ENOMEM;
+	bool is_group = 0;
+
+	if (!domain)
+		return -EINVAL;
+	if (perm > 7 || !perm) {
+		printk(KERN_DEBUG "%s: Invalid permission '%d %s'\n",
+		       __FUNCTION__, perm, filename);
+		return -EINVAL;
+	}
+	if (!tmy_correct_path(filename, 0, 0, 0, __FUNCTION__))
+		return -EINVAL;
+
+	if (filename[0] == '@') {
+		/* This cast is OK because I don't dereference. */
+		saved = (struct path_info *) tmy_new_path_group(filename + 1);
+		if (!saved)
+			return -ENOMEM;
+		is_group = 1;
+	} else {
+
+		if (tmy_strendswith(filename, "/"))
+			/*
+			 * Valid permissions for directory are
+			 * only 'allow_mkdir' and 'allow_rmdir'.
+			 */
+			return 0;
+
+		saved = tmy_save_name(filename);
+		if (!saved)
+			return -ENOMEM;
+
+		if (!is_delete && perm == 4 &&
+		    tmy_globally_readable(saved))
+			return 0;
+
+	}
+
+	mutex_lock(&domain_acl_lock);
+
+	if (is_delete)
+		goto remove;
+
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		acl = (struct file_acl *) ptr;
+		if ((ptr->type == TMY_TYPE_FILE_ACL) &&
+		    ptr->cond == cond &&
+		    (acl->u.filename == saved)) {
+			if (ptr->is_deleted) {
+				acl->perm = 0;
+				mb(); /* Avoid out-of-order execution. */
+				ptr->is_deleted = 0;
+			}
+			/* Found. Just 'OR' the permission bits. */
+			acl->perm |= perm;
+			error = 0;
+			tmy_update_counter(TMY_UPDATE_DOMAINPOLICY);
+			goto ok;
+		}
+	}
+	/* Not found. Append it to the tail. */
+	acl = tmy_alloc_element(sizeof(*acl));
+	if (!acl)
+		goto ok;
+
+	acl->head.type = TMY_TYPE_FILE_ACL;
+	acl->head.cond = cond;
+	acl->perm = perm;
+	acl->u_is_group = is_group;
+	acl->u.filename = saved;
+	error = tmy_add_acl(domain, (struct acl_info *) acl);
+	goto ok;
+remove: ;
+	error = -ENOENT;
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		acl = (struct file_acl *) ptr;
+		if (ptr->type != TMY_TYPE_FILE_ACL ||
+		    ptr->cond != cond ||
+		    ptr->is_deleted ||
+		    acl->perm != perm ||
+		    acl->u.filename != saved)
+			continue;
+		error = tmy_del_acl(ptr);
+		break;
+	}
+ok: ;
+	mutex_unlock(&domain_acl_lock);
+	return error;
+}
+
+static int tmy_file_acl(const struct path_info *filename, const u8 perm,
+			struct obj_info *obj)
+{
+	const struct domain_info *domain = TMY_SECURITY->domain;
+	struct acl_info *ptr;
+	const int may_use_pat = ((perm & 1) == 0);
+
+	if (!tmy_flags(TMY_MAC_FOR_FILE))
+		return 0;
+	if (!filename->is_dir) {
+		if (perm == 4 && tmy_globally_readable(filename))
+			return 0;
+	}
+
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		struct file_acl *acl = (struct file_acl *) ptr;
+
+		if (ptr->type != TMY_TYPE_FILE_ACL ||
+		    ptr->is_deleted ||
+		    (acl->perm & perm) != perm ||
+		    tmy_check_condition(ptr->cond, obj))
+			continue;
+
+		if (acl->u_is_group) {
+			if (tmy_path_match_group(filename,
+						 acl->u.group,
+						 may_use_pat))
+				return 0;
+		} else {
+			if ((may_use_pat || !acl->u.filename->is_patterned) &&
+			    tmy_path_match(filename, acl->u.filename))
+				return 0;
+		}
+	}
+
+	return -EPERM;
+}
+
+static int tmy_file_perm2(const struct path_info *filename,
+			  const u8 perm,
+			  struct obj_info *obj,
+			  const char *operation)
+{
+	int error = 0;
+	struct domain_info * const domain = TMY_SECURITY->domain;
+	const u8 profile = domain->profile;
+	const unsigned int mode = tmy_flags(TMY_MAC_FOR_FILE);
+	const bool is_enforce = (mode == 3);
+
+	if (!filename)
+		return 0;
+
+	error = tmy_file_acl(filename, perm, obj);
+
+	tmy_audit_file_log(filename, perm, !error, profile, mode);
+
+	if (!error)
+		return error;
+
+	if (tmy_flags(TMY_VERBOSE))
+		tmy_audit("TOMOYO-%s: Access %d(%s) to %s denied for %s\n",
+			  tmy_getmsg(is_enforce), perm, operation,
+			  filename->name, tmy_lastname(domain));
+
+	if (is_enforce)
+		error =	tmy_supervisor("%s\n%d %s\n",
+				       domain->domainname->name,
+				       perm, filename->name);
+
+	else if (mode == 1 && tmy_quota()) {
+		/* Don't use patterns if execution bit is on. */
+		const struct path_info *patterned =
+			((perm & 1) == 0) ?
+			tmy_get_pattern(filename) : filename;
+		tmy_add_file_acl(patterned->name, perm, domain, NULL, 0);
+	}
+
+	if (!is_enforce)
+		error = 0;
+
+	return error;
+}
+
+/**
+ * tmy_file_perm - check permission for sysctl(2) operation.
+ * @filename0: pointer to filename returned by sysctlpath_from_table().
+ * @perm:      mode (read = 4, write = 2, read-write = 6).
+ * @operation: pointer to error message.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_file_perm(const char *filename0, const u8 perm, const char *operation)
+{
+	struct path_info filename;
+
+	if (!tmy_flags(TMY_MAC_FOR_FILE))
+		return 0;
+
+	filename.name = filename0;
+	tmy_fill_path_info(&filename);
+
+	return tmy_file_perm2(&filename, perm, NULL, operation);
+}
+
+/**
+ * tmy_add_file_policy - add or delete file policy.
+ * @data:      a line to parse.
+ * @domain:    pointer to "struct domain_info".
+ * @cond:      pointer to "struct condition_list". May be NULL.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_file_policy(char *data,
+			struct domain_info *domain,
+			const struct condition_list *cond,
+			const bool is_delete)
+{
+	char *filename = strchr(data, ' ');
+	unsigned int perm;
+	u8 type;
+
+	if (!filename)
+		return -EINVAL;
+	*filename++ = '\0';
+
+	if (sscanf(data, "%u", &perm) == 1)
+		return tmy_add_file_acl(filename, (u8) perm, domain, cond,
+					is_delete);
+
+	if (strncmp(data, "allow_", 6))
+		goto out;
+
+	data += 6;
+
+	for (type = 0; acl_type_array[type].keyword; type++) {
+		if (strcmp(data, acl_type_array[type].keyword))
+			continue;
+
+		if (acl_type_array[type].paths == 2) {
+			char *filename2 = strchr(filename, ' ');
+
+			if (!filename2)
+				break;
+			*filename2++ = '\0';
+			return tmy_add_double_write_acl(type, filename,
+							filename2,
+							domain, cond,
+							is_delete);
+		} else
+			return tmy_add_single_write_acl(type, filename,
+							domain, cond,
+							is_delete);
+
+		break;
+	}
+out: ;
+	return -EINVAL;
+}
+
+static int tmy_add_single_write_acl(const u8 type,
+				    const char *filename,
+				    struct domain_info * const domain,
+				    const struct condition_list *cond,
+				    const bool is_delete)
+{
+	const struct path_info *saved;
+	struct acl_info *ptr;
+	struct single_acl *acl;
+	int error = -ENOMEM;
+	bool is_group = 0;
+
+	if (!domain)
+		return -EINVAL;
+	if (!tmy_correct_path(filename, 0, 0, 0, __FUNCTION__))
+		return -EINVAL;
+
+	if (filename[0] == '@') {
+		/* This cast is OK because I don't dereference. */
+		saved = (struct path_info *) tmy_new_path_group(filename + 1);
+		if (!saved)
+			return -ENOMEM;
+		is_group = 1;
+	} else {
+		saved = tmy_save_name(filename);
+		if (!saved)
+			return -ENOMEM;
+	}
+
+	mutex_lock(&domain_acl_lock);
+	if (is_delete)
+		goto remove;
+
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		acl = (struct single_acl *) ptr;
+		if (ptr->type == type && ptr->cond == cond) {
+			if (acl->u.filename == saved) {
+				ptr->is_deleted = 0;
+				/* Found. Nothing to do. */
+				error = 0;
+				goto ok;
+			}
+		}
+	}
+	/* Not found. Append it to the tail. */
+	acl = tmy_alloc_element(sizeof(*acl));
+	if (!acl)
+		goto ok;
+
+	acl->head.type = type;
+	acl->head.cond = cond;
+	acl->u_is_group = is_group;
+	acl->u.filename = saved;
+	error = tmy_add_acl(domain, (struct acl_info *) acl);
+	goto ok;
+remove: ;
+	error = -ENOENT;
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		acl = (struct single_acl *) ptr;
+
+		if (ptr->type != type || ptr->is_deleted ||
+		    ptr->cond != cond || acl->u.filename != saved)
+			continue;
+
+		error = tmy_del_acl(ptr);
+		break;
+	}
+ok: ;
+	mutex_unlock(&domain_acl_lock);
+
+	return error;
+}
+
+static int tmy_add_double_write_acl(const u8 type,
+				    const char *filename1,
+				    const char *filename2,
+				    struct domain_info * const domain,
+				    const struct condition_list *cond,
+				    const bool is_delete)
+{
+	const struct path_info *saved1;
+	const struct path_info *saved2;
+	struct acl_info *ptr;
+	struct double_acl *acl;
+	int error = -ENOMEM;
+	bool is_group1 = 0;
+	bool is_group2 = 0;
+
+	if (!domain)
+		return -EINVAL;
+	if (!tmy_correct_path(filename1, 0, 0, 0, __FUNCTION__) ||
+	    !tmy_correct_path(filename2, 0, 0, 0, __FUNCTION__))
+		return -EINVAL;
+
+	if (filename1[0] == '@') {
+		/* This cast is OK because I don't dereference. */
+		saved1 = (struct path_info *) tmy_new_path_group(filename1 + 1);
+		if (!saved1)
+			return -ENOMEM;
+		is_group1 = 1;
+	} else {
+		saved1 = tmy_save_name(filename1);
+		if (!saved1)
+			return -ENOMEM;
+	}
+
+	if (filename2[0] == '@') {
+		/* This cast is OK because I don't dereference. */
+		saved2 = (struct path_info *) tmy_new_path_group(filename2 + 1);
+		if (!saved2)
+			return -ENOMEM;
+		is_group2 = 1;
+	} else {
+		saved2 = tmy_save_name(filename2);
+		if (!saved2)
+			return -ENOMEM;
+	}
+
+	mutex_lock(&domain_acl_lock);
+
+	if (is_delete)
+		goto remove;
+
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		acl = (struct double_acl *) ptr;
+		if (ptr->type == type && ptr->cond == cond) {
+			if (acl->u1.filename1 == saved1 &&
+			    acl->u2.filename2 == saved2) {
+				ptr->is_deleted = 0;
+				/* Found. Nothing to do. */
+				error = 0;
+				goto ok;
+			}
+		}
+	}
+	/* Not found. Append it to the tail. */
+	acl = tmy_alloc_element(sizeof(*acl));
+	if (!acl)
+		goto ok;
+
+	acl->head.type = type;
+	acl->head.cond = cond;
+	acl->u1_is_group = is_group1;
+	acl->u2_is_group = is_group2;
+	acl->u1.filename1 = saved1;
+	acl->u2.filename2 = saved2;
+	error = tmy_add_acl(domain,
+			    (struct acl_info *) acl);
+	goto ok;
+remove: ;
+	error = -ENOENT;
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		acl = (struct double_acl *) ptr;
+		if (ptr->type != type || ptr->is_deleted ||
+		    ptr->cond != cond ||
+		    acl->u1.filename1 != saved1 ||
+		    acl->u2.filename2 != saved2)
+			continue;
+		error = tmy_del_acl(ptr);
+		break;
+	}
+ ok: ;
+	mutex_unlock(&domain_acl_lock);
+	return error;
+}
+
+static int tmy_single_write_acl(const u8 type,
+				const struct path_info *filename,
+				struct obj_info *obj)
+{
+	const struct domain_info *domain = TMY_SECURITY->domain;
+	struct acl_info *ptr;
+
+	if (!tmy_flags(TMY_MAC_FOR_FILE))
+		return 0;
+
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		struct single_acl *acl = (struct single_acl *) ptr;
+
+		if (ptr->type != type || ptr->is_deleted ||
+		    tmy_check_condition(ptr->cond, obj))
+			continue;
+
+		if (acl->u_is_group) {
+			if (!tmy_path_match_group(filename, acl->u.group, 1))
+				continue;
+		} else {
+			if (!tmy_path_match(filename, acl->u.filename))
+				continue;
+		}
+		return 0;
+	}
+
+	return -EPERM;
+}
+
+static int tmy_double_write_acl(const u8 type,
+				const struct path_info *filename1,
+				const struct path_info *filename2,
+				struct obj_info *obj)
+{
+	const struct domain_info *domain = TMY_SECURITY->domain;
+	struct acl_info *ptr;
+
+	if (!tmy_flags(TMY_MAC_FOR_FILE))
+		return 0;
+
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		struct double_acl *acl = (struct double_acl *) ptr;
+
+		if (ptr->type != type || ptr->is_deleted ||
+		    tmy_check_condition(ptr->cond, obj))
+			continue;
+
+		if (acl->u1_is_group) {
+			if (!tmy_path_match_group(filename1,
+						  acl->u1.group1, 1))
+				continue;
+		} else {
+			if (!tmy_path_match(filename1, acl->u1.filename1))
+				continue;
+		}
+
+		if (acl->u2_is_group) {
+			if (!tmy_path_match_group(filename2,
+						  acl->u2.group2, 1))
+				continue;
+		} else {
+			if (!tmy_path_match(filename2, acl->u2.filename2))
+				continue;
+		}
+
+		return 0;
+	}
+
+	return -EPERM;
+}
+
+static int tmy_single_write_perm2(const unsigned int operation,
+				  const struct path_info *filename,
+				  struct obj_info *obj)
+{
+	int error;
+	struct domain_info * const domain = TMY_SECURITY->domain;
+	const u8 profile = domain->profile;
+	const unsigned int mode = tmy_flags(TMY_MAC_FOR_FILE);
+	const bool is_enforce = (mode == 3);
+
+	if (!mode)
+		return 0;
+
+	error = tmy_single_write_acl(operation, filename, obj);
+
+	tmy_audit_write_log(tmy_acltype2keyword(operation),
+			    filename, NULL, !error, profile, mode);
+
+	if (!error)
+		goto ok;
+
+	if (tmy_flags(TMY_VERBOSE))
+		tmy_audit("TOMOYO-%s: Access '%s %s' denied for %s\n",
+			  tmy_getmsg(is_enforce),
+			  tmy_acltype2keyword(operation), filename->name,
+			  tmy_lastname(domain));
+
+	if (is_enforce)
+		error = tmy_supervisor("%s\nallow_%s %s\n",
+				       domain->domainname->name,
+				       tmy_acltype2keyword(operation),
+				       filename->name);
+
+	else if (mode == 1 && tmy_quota())
+		tmy_add_single_write_acl(operation,
+					 tmy_get_pattern(filename)->name,
+					 domain, NULL, 0);
+
+	if (!is_enforce)
+		error = 0;
+
+ok: ;
+	if (!error && operation == TMY_TYPE_TRUNCATE_ACL &&
+	    tmy_is_no_rewrite_file(filename))
+		error = tmy_single_write_perm2(TMY_TYPE_REWRITE_ACL,
+					       filename, obj);
+
+	return error;
+}
+
+/**
+ * tmy_exec_perm - check permission for execve(2) operation.
+ * @filename: pointer to filename to execute.
+ * @filp:     pointer to "struct file".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_exec_perm(const struct path_info *filename, struct file *filp)
+{
+	struct obj_info obj;
+	if (!tmy_flags(TMY_MAC_FOR_FILE))
+		return 0;
+	memset(&obj, 0, sizeof(obj));
+	obj.path1_dentry = filp->f_dentry;
+	obj.path1_vfsmnt = filp->f_vfsmnt;
+	return tmy_file_perm2(filename, 1, &obj, "do_execve");
+}
+
+/**
+ * tmy_open_perm - check permission for open(2) operation.
+ * @dentry: pointer to "struct dentry".
+ * @mnt:    pointer to "struct vfsmount".
+ * @flag:   open flags.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_open_perm(struct dentry *dentry,
+		  struct vfsmount *mnt,
+		  const int flag)
+{
+	struct obj_info obj;
+	const int acc_mode = ACC_MODE(flag);
+	int error = -ENOMEM;
+	struct path_info *buf;
+	const unsigned int mode = tmy_flags(TMY_MAC_FOR_FILE);
+	const bool is_enforce = (mode == 3);
+
+	if (!mode)
+		return 0;
+	if (acc_mode == 0)
+		return 0;
+	if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode))
+		/* I don't check directories here             */
+		/* because mkdir() and rmdir() don't call me. */
+		return 0;
+
+	buf = tmy_get_path(dentry, mnt);
+
+	if (!buf)
+		goto out;
+
+	memset(&obj, 0, sizeof(obj));
+	obj.path1_dentry = dentry;
+	obj.path1_vfsmnt = mnt;
+
+	error = 0;
+	if ((acc_mode & MAY_WRITE) &&
+	    ((flag & O_TRUNC) || !(flag & O_APPEND)) &&
+	    tmy_is_no_rewrite_file(buf))
+		error = tmy_single_write_perm2(TMY_TYPE_REWRITE_ACL,
+					       buf, &obj);
+
+	if (error == 0)
+		error = tmy_file_perm2(buf, acc_mode, &obj, "open");
+
+	if (error == 0 && (flag & O_TRUNC))
+		error = tmy_single_write_perm2(TMY_TYPE_TRUNCATE_ACL,
+					       buf, &obj);
+
+	tmy_free(buf);
+
+out: ;
+	if (!is_enforce)
+		error = 0;
+	return error;
+}
+
+/**
+ * tmy_single_write_perm - check permission for create(2) etc. operation.
+ * @operation: operation index number.
+ * @dentry:    pointer to "struct dentry".
+ * @mnt:       pointer to "struct vfsmount".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_single_write_perm(const unsigned int operation,
+			  struct dentry *dentry,
+			  struct vfsmount *mnt)
+{
+	struct obj_info obj;
+	int error = -ENOMEM;
+	struct path_info *buf;
+	const unsigned int mode = tmy_flags(TMY_MAC_FOR_FILE);
+	const bool is_enforce = (mode == 3);
+
+	if (!mode)
+		return 0;
+
+	buf = tmy_get_path(dentry, mnt);
+
+	if (!buf)
+		goto out;
+
+	memset(&obj, 0, sizeof(obj));
+	obj.path1_dentry = dentry;
+	obj.path1_vfsmnt = mnt;
+
+	switch (operation) {
+	case TMY_TYPE_MKDIR_ACL:
+	case TMY_TYPE_RMDIR_ACL:
+		if (!buf->is_dir) {
+			strcat((char *) buf->name, "/");
+			tmy_fill_path_info(buf);
+		}
+	}
+	error = tmy_single_write_perm2(operation, buf, &obj);
+	tmy_free(buf);
+
+out: ;
+	if (!is_enforce)
+		error = 0;
+
+	return error;
+}
+
+/**
+ * tmy_rewrite_perm - check permission for truncate/overwrite operation.
+ * @filp: pointer to "struct file".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_rewrite_perm(struct file *filp)
+{
+	int error = -ENOMEM;
+	const unsigned int mode = tmy_flags(TMY_MAC_FOR_FILE);
+	const bool is_enforce = (mode == 3);
+	struct path_info *buf;
+
+	if (!mode)
+		return 0;
+
+	buf = tmy_get_path(filp->f_dentry, filp->f_vfsmnt);
+	if (!buf)
+		goto out;
+
+	if (tmy_is_no_rewrite_file(buf)) {
+		struct obj_info obj;
+		memset(&obj, 0, sizeof(obj));
+		obj.path1_dentry = filp->f_dentry;
+		obj.path1_vfsmnt = filp->f_vfsmnt;
+		error = tmy_single_write_perm2(TMY_TYPE_REWRITE_ACL,
+					       buf, &obj);
+	} else
+		error = 0;
+
+	tmy_free(buf);
+
+out: ;
+	if (!is_enforce)
+		error = 0;
+	return error;
+}
+
+/**
+ * tmy_double_write_perm - check permission for link(2)/rename(2) operation.
+ * @operation: operation index number.
+ * @dentry1:   pointer to "struct dentry".
+ * @mnt1:      pointer to "struct vfsmount".
+ * @dentry2:   pointer to "struct dentry".
+ * @mnt2:      pointer to "struct vfsmount".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_double_write_perm(const unsigned int operation,
+			  struct dentry *dentry1,
+			  struct vfsmount *mnt1,
+			  struct dentry *dentry2,
+			  struct vfsmount *mnt2)
+{
+	struct obj_info obj;
+	int error = -ENOMEM;
+	struct path_info *buf1;
+	struct path_info *buf2;
+	struct domain_info * const domain = TMY_SECURITY->domain;
+	const u8 profile = domain->profile;
+	const unsigned int mode = tmy_flags(TMY_MAC_FOR_FILE);
+	const bool is_enforce = (mode == 3);
+
+	if (!mode)
+		return 0;
+	buf1 = tmy_get_path(dentry1, mnt1);
+	buf2 = tmy_get_path(dentry2, mnt2);
+
+	if (!buf1 || !buf2)
+		goto out;
+
+	memset(&obj, 0, sizeof(obj));
+	obj.path1_dentry = dentry1;
+	obj.path1_vfsmnt = mnt1;
+	obj.path2_dentry = dentry2;
+	obj.path2_vfsmnt = mnt2;
+	if (operation == TMY_TYPE_RENAME_ACL) {
+		/* TMY_TYPE_LINK_ACL can't reach here for directory. */
+		if (dentry1->d_inode && S_ISDIR(dentry1->d_inode->i_mode)) {
+			if (!buf1->is_dir) {
+				strcat((char *) buf1->name, "/");
+				tmy_fill_path_info(buf1);
+			}
+			if (!buf2->is_dir) {
+				strcat((char *) buf2->name, "/");
+				tmy_fill_path_info(buf2);
+			}
+		}
+	}
+	error = tmy_double_write_acl(operation, buf1, buf2, &obj);
+
+	tmy_audit_write_log(tmy_acltype2keyword(operation),
+			    buf1, buf2, !error, profile, mode);
+
+	if (!error)
+		goto out;
+
+	if (tmy_flags(TMY_VERBOSE))
+		tmy_audit("TOMOYO-%s: Access '%s %s %s' denied for %s\n",
+			  tmy_getmsg(is_enforce),
+			  tmy_acltype2keyword(operation),
+			  buf1->name, buf2->name, tmy_lastname(domain));
+
+	if (is_enforce)
+		error = tmy_supervisor("%s\nallow_%s %s %s\n",
+				       domain->domainname->name,
+				       tmy_acltype2keyword(operation),
+				       buf1->name, buf2->name);
+	else if (mode == 1 && tmy_quota())
+		tmy_add_double_write_acl(operation,
+					 tmy_get_pattern(buf1)->name,
+					 tmy_get_pattern(buf2)->name,
+					 domain, NULL, 0);
+
+out: ;
+	tmy_free(buf1);
+	tmy_free(buf2);
+	if (!is_enforce)
+		error = 0;
+	return error;
+}

-- 

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

* [TOMOYO #5 10/18] argv0 check functions.
  2007-11-16 17:34 [TOMOYO #5 00/18] TOMOYO Linux - MAC based on process invocation history penguin-kernel
                   ` (8 preceding siblings ...)
  2007-11-16 17:34 ` [TOMOYO #5 09/18] File access control functions penguin-kernel
@ 2007-11-16 17:34 ` penguin-kernel
  2007-11-16 17:34 ` [TOMOYO #5 11/18] Network access control functions penguin-kernel
                   ` (7 subsequent siblings)
  17 siblings, 0 replies; 39+ messages in thread
From: penguin-kernel @ 2007-11-16 17:34 UTC (permalink / raw)
  To: akpm; +Cc: linux-kernel, linux-security-module, Kentaro Takeda, Tetsuo Handa

[-- Attachment #1: tomoyo-exec.diff --]
[-- Type: text/plain, Size: 5917 bytes --]

If the executed program name and argv[0] is different,
TOMOYO Linux checks permission.

Each permission can be automatically accumulated into
the policy of each domain using 'learning mode'.

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

--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6-mm/security/tomoyo/exec.c	2007-11-14 15:15:44.000000000 +0900
@@ -0,0 +1,218 @@
+/*
+ * security/tomoyo/exec.c
+ *
+ * Argv0 access control functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+/*************************  AUDIT FUNCTIONS  *************************/
+
+static int tmy_audit_argv0_log(const struct path_info *filename,
+			       const char *argv0,
+			       const bool is_granted,
+			       const u8 profile,
+			       const unsigned int mode)
+{
+	char *buf;
+	int len;
+
+	if (is_granted) {
+		if (!tmy_audit_grant())
+			return 0;
+	} else {
+		if (!tmy_audit_reject())
+			return 0;
+	}
+
+	len = filename->total_len + strlen(argv0) + 8;
+	buf = tmy_init_audit_log(&len, profile, mode);
+
+	if (!buf)
+		return -ENOMEM;
+
+	snprintf(buf + strlen(buf),
+		 len - strlen(buf) - 1,
+		 TMY_ALLOW_ARGV0 "%s %s",
+		 filename->name,
+		 argv0);
+
+	return tmy_write_audit_log(buf, is_granted);
+}
+
+/*************************  ARGV0 MISMATCH HANDLER  *************************/
+
+static int tmy_add_argv0_entry(const char *filename,
+			       const char *argv0,
+			       struct domain_info *domain,
+			       const struct condition_list *cond,
+			       const bool is_delete)
+{
+	struct acl_info *ptr;
+	struct argv0_acl *acl;
+	const struct path_info *saved_filename;
+	const struct path_info *saved_argv0;
+	int error = -ENOMEM;
+
+	if (!tmy_correct_path(filename, 1, 0, -1, __FUNCTION__) ||
+	    !tmy_correct_path(argv0, -1, 0, -1, __FUNCTION__) ||
+	    strchr(argv0, '/'))
+		return -EINVAL;
+
+	saved_filename = tmy_save_name(filename);
+	saved_argv0 = tmy_save_name(argv0);
+	if (!saved_filename || !saved_argv0)
+		return -ENOMEM;
+
+	mutex_lock(&domain_acl_lock);
+
+	if (is_delete)
+		goto remove;
+
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		acl = (struct argv0_acl *) ptr;
+		if (ptr->type == TMY_TYPE_ARGV0_ACL && ptr->cond == cond &&
+		    acl->filename == saved_filename &&
+		    acl->argv0 == saved_argv0) {
+			ptr->is_deleted = 0;
+			/* Found. Nothing to do. */
+			error = 0;
+			goto ok;
+		}
+	}
+
+	/* Not found. Append it to the tail. */
+	acl = tmy_alloc_element(sizeof(*acl));
+	if (!acl)
+		goto ok;
+
+	acl->head.type = TMY_TYPE_ARGV0_ACL;
+	acl->head.cond = cond;
+	acl->filename = saved_filename;
+	acl->argv0 = saved_argv0;
+	error = tmy_add_acl(domain,
+			    (struct acl_info *) acl);
+	goto ok;
+remove: ;
+	error = -ENOENT;
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		acl = (struct argv0_acl *) ptr;
+		if (ptr->type != TMY_TYPE_ARGV0_ACL ||
+		    ptr->cond != cond || ptr->is_deleted ||
+		    acl->filename != saved_filename ||
+		    acl->argv0 != saved_argv0)
+			continue;
+
+		error = tmy_del_acl(ptr);
+		break;
+	}
+ok: ;
+	mutex_unlock(&domain_acl_lock);
+
+	return error;
+}
+
+static int tmy_argv0_acl(const struct path_info *filename,
+			 const char *argv0_)
+{
+	const struct domain_info *domain = TMY_SECURITY->domain;
+	int error = -EPERM;
+	struct acl_info *ptr;
+	struct path_info argv0;
+
+	if (!tmy_flags(TMY_MAC_FOR_ARGV0))
+		return 0;
+
+	argv0.name = argv0_;
+	tmy_fill_path_info(&argv0);
+
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		struct argv0_acl *acl = (struct argv0_acl *) ptr;
+
+		if (ptr->type == TMY_TYPE_ARGV0_ACL &&
+		    ptr->is_deleted == 0 &&
+		    tmy_check_condition(ptr->cond, NULL) == 0 &&
+		    tmy_path_match(filename, acl->filename) &&
+		    tmy_path_match(&argv0, acl->argv0)) {
+			error = 0;
+			break;
+		}
+	}
+
+	return error;
+}
+
+/**
+ * tmy_argv0_perm - check for argv[0] permission.
+ * @filename: pointer to filename.
+ * @argv0:    pointer to basename of argv[0].
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_argv0_perm(const struct path_info *filename, const char *argv0)
+{
+	int error = 0;
+	const u8 profile = TMY_SECURITY->domain->profile;
+	const unsigned int mode = tmy_flags(TMY_MAC_FOR_ARGV0);
+	const bool is_enforce = (mode == 3);
+
+	if (!mode)
+		return 0;
+	if (!filename || !argv0 || !*argv0)
+		return 0;
+
+	error = tmy_argv0_acl(filename, argv0);
+
+	tmy_audit_argv0_log(filename, argv0, !error, profile, mode);
+
+	if (error) {
+		struct domain_info * const domain = TMY_SECURITY->domain;
+
+		if (tmy_flags(TMY_VERBOSE))
+			tmy_audit("TOMOYO-%s: Run %s as %s denied for %s\n",
+				  tmy_getmsg(is_enforce), filename->name, argv0,
+				  tmy_lastname(domain));
+
+		if (is_enforce)
+			error = tmy_supervisor("%s\n" TMY_ALLOW_ARGV0 "%s %s\n",
+					       domain->domainname->name,
+					       filename->name, argv0);
+
+		else if (mode == 1 && tmy_quota())
+			tmy_add_argv0_entry(filename->name, argv0, domain,
+					    NULL, 0);
+
+		if (!is_enforce)
+			error = 0;
+	}
+
+	return error;
+}
+
+/**
+ * tmy_add_argv0_policy - add or delete argv[0] policy.
+ * @data:      a line to parse.
+ * @domain:    pointer to "struct domain_info".
+ * @cond:      pointer to "struct condition_list". May be NULL.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_argv0_policy(char *data,
+			 struct domain_info *domain,
+			 const struct condition_list *cond,
+			 const bool is_delete)
+{
+	char *argv0 = strchr(data, ' ');
+
+	if (!argv0)
+		return -EINVAL;
+
+	*argv0++ = '\0';
+
+	return tmy_add_argv0_entry(data, argv0, domain, cond,
+				   is_delete);
+}

-- 

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

* [TOMOYO #5 11/18] Network access control functions.
  2007-11-16 17:34 [TOMOYO #5 00/18] TOMOYO Linux - MAC based on process invocation history penguin-kernel
                   ` (9 preceding siblings ...)
  2007-11-16 17:34 ` [TOMOYO #5 10/18] argv0 check functions penguin-kernel
@ 2007-11-16 17:34 ` penguin-kernel
  2007-11-16 17:57   ` YOSHIFUJI Hideaki / 吉藤英明
  2007-11-16 17:34 ` [TOMOYO #5 12/18] Namespace manipulation " penguin-kernel
                   ` (6 subsequent siblings)
  17 siblings, 1 reply; 39+ messages in thread
From: penguin-kernel @ 2007-11-16 17:34 UTC (permalink / raw)
  To: akpm; +Cc: linux-kernel, linux-security-module, Kentaro Takeda, Tetsuo Handa

[-- Attachment #1: tomoyo-net.diff --]
[-- Type: text/plain, Size: 24297 bytes --]

TOMOYO Linux checks permission by the following four parameters.
  * protocol type (TCP, UDP, RAW)
  * access type (bind, listen, connect, accept)
  * IP address (Both IPv4 and IPv6 are available)
  * port number
In order to check 'TCP accept' and 'UDP connect',
LSM expansion patch ([TOMOYO 18/18]) is needed.

Each permission can be automatically accumulated into
the policy of each domain using 'learning mode'.

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

--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6-mm/security/tomoyo/net.c	2007-11-14 15:15:44.000000000 +0900
@@ -0,0 +1,952 @@
+/*
+ * security/tomoyo/net.c
+ *
+ * Network access control functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+/*************************  AUDIT FUNCTIONS  *************************/
+
+static int tmy_audit_network_log(const bool is_ipv6,
+				 const char *operation,
+				 const u32 *address,
+				 const u16 port,
+				 const bool is_granted,
+				 const u8 profile,
+				 const unsigned int mode)
+{
+	char *buf;
+	int len = 256;
+
+	if (is_granted) {
+		if (!tmy_audit_grant())
+			return 0;
+	} else {
+		if (!tmy_audit_reject())
+			return 0;
+	}
+
+	buf = tmy_init_audit_log(&len, profile, mode);
+	if (!buf)
+		return -ENOMEM;
+
+	snprintf(buf + strlen(buf), len - strlen(buf) - 1,
+		 TMY_ALLOW_NETWORK "%s ", operation);
+
+	if (is_ipv6)
+		tmy_print_ipv6(buf + strlen(buf), len - strlen(buf),
+			       (const u16 *) address);
+	else {
+		u32 ip = *address;
+		snprintf(buf + strlen(buf), len - strlen(buf) - 1,
+			 NIPQUAD_FMT, NIPQUAD(ip));
+	}
+
+	snprintf(buf + strlen(buf), len - strlen(buf) - 1, " %u\n", port);
+
+	return tmy_write_audit_log(buf, is_granted);
+}
+
+/*************************  ADDRESS GROUP HANDLER  *************************/
+
+/* List of address group. */
+static LIST_HEAD(address_group_list);
+
+static int tmy_add_address_group_entry(const char *group_name,
+				    const bool is_ipv6,
+				    const u16 *min_address,
+				    const u16 *max_address,
+				    const bool is_delete)
+{
+	static DEFINE_MUTEX(mutex);
+	struct address_group_entry *new_group;
+	struct address_group_entry *group;
+	struct address_group_member *new_member;
+	struct address_group_member *member;
+	const struct path_info *saved_group_name;
+	int error = -ENOMEM;
+	bool found = 0;
+
+	if (!tmy_correct_path(group_name, 0, 0, 0, __FUNCTION__) ||
+	    !group_name[0])
+		return -EINVAL;
+
+	saved_group_name = tmy_save_name(group_name);
+	if (!saved_group_name)
+		return -ENOMEM;
+
+	mutex_lock(&mutex);
+
+	list_for_each_entry(group, &address_group_list, list) {
+		if (saved_group_name != group->group_name)
+			continue;
+		list_for_each_entry(member, &group->address_group_member_list,
+				    list) {
+			if (member->is_ipv6 != is_ipv6)
+				continue;
+			if (is_ipv6) {
+				if (memcmp(member->min.ipv6, min_address, 16) ||
+				    memcmp(member->max.ipv6, max_address, 16))
+					continue;
+			} else {
+				if (member->min.ipv4 != *(u32 *) min_address ||
+				    member->max.ipv4 != *(u32 *) max_address)
+					continue;
+			}
+			member->is_deleted = is_delete;
+			error = 0;
+			goto out;
+		}
+		found = 1;
+		break;
+	}
+
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+
+	if (!found) {
+		new_group = tmy_alloc_element(sizeof(*new_group));
+		if (!new_group)
+			goto out;
+		INIT_LIST_HEAD(&new_group->address_group_member_list);
+		new_group->group_name = saved_group_name;
+		list_add_tail_mb(&new_group->list, &address_group_list);
+		group = new_group;
+	}
+
+	new_member = tmy_alloc_element(sizeof(*new_member));
+	if (!new_member)
+		goto out;
+
+	new_member->is_ipv6 = is_ipv6;
+
+	if (is_ipv6) {
+		memmove(new_member->min.ipv6, min_address, 16);
+		memmove(new_member->max.ipv6, max_address, 16);
+	} else {
+		new_member->min.ipv4 = *(u32 *) min_address;
+		new_member->max.ipv4 = *(u32 *) max_address;
+	}
+
+	list_add_tail_mb(&new_member->list, &group->address_group_member_list);
+	error = 0;
+out: ;
+	mutex_unlock(&mutex);
+
+	return error;
+}
+
+/**
+ * tmy_add_address_group_policy - add or delete address group policy.
+ * @data: a line to parse.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_address_group_policy(char *data, const bool is_delete)
+{
+	int count;
+	bool is_ipv6;
+	u16 min_address[8];
+	u16 max_address[8];
+	unsigned int min[8];
+	unsigned int max[8];
+	char *cp = strchr(data, ' ');
+
+	if (!cp)
+		return -EINVAL;
+
+	*cp++ = '\0';
+	count = sscanf(cp,
+		       NIP6_FMT "-" NIP6_FMT,
+		       &min[0], &min[1], &min[2], &min[3],
+		       &min[4], &min[5], &min[6], &min[7],
+		       &max[0], &max[1], &max[2], &max[3],
+		       &max[4], &max[5], &max[6], &max[7]);
+
+	if (count == 8 || count == 16) {
+
+		int i;
+
+		for (i = 0; i < 8; i++) {
+			min_address[i] = htons((u16) min[i]);
+			max_address[i] = htons((u16) max[i]);
+		}
+		if (count == 8)
+			memmove(max_address, min_address, sizeof(min_address));
+		is_ipv6 = 1;
+
+		goto ok;
+
+	}
+
+	count = sscanf(cp,
+		       NIPQUAD_FMT "-" NIPQUAD_FMT,
+		       &min[0], &min[1],
+		       &min[2], &min[3],
+		       &max[0], &max[1],
+		       &max[2], &max[3]);
+
+	if (count == 4 || count == 8) {
+		u32 ip = ((((u8) min[0]) << 24) +
+			  (((u8) min[1]) << 16) +
+			  (((u8) min[2]) << 8) +
+			  (u8) min[3]);
+
+		*(u32 *) min_address = ip;
+
+		if (count == 8)
+			ip = ((((u8) max[0]) << 24) +
+			      (((u8) max[1]) << 16) +
+			      (((u8) max[2]) << 8) +
+			      (u8) max[3]);
+
+		*(u32 *) max_address = ip;
+		is_ipv6 = 0;
+
+		goto ok;
+
+	}
+
+	return -EINVAL;
+
+ok: ;
+	return tmy_add_address_group_entry(data, is_ipv6, min_address,
+					   max_address, is_delete);
+}
+
+static struct address_group_entry *tmy_new_address_group(const char *name)
+{
+	int i;
+	struct address_group_entry *group;
+
+	for (i = 0; i <= 1; i++) {
+		list_for_each_entry(group, &address_group_list, list) {
+			if (strcmp(name, group->group_name->name) == 0)
+				return group;
+		}
+
+		if (i == 0) {
+			/*
+			 * Add a dummy entry to create new address group
+			 * and delete that entry.
+			 */
+			const u16 dum[2] = { 0, 0 };
+			tmy_add_address_group_entry(name, 0, dum, dum, 0);
+			tmy_add_address_group_entry(name, 0, dum, dum, 1);
+		}
+	}
+
+	return NULL;
+}
+
+static int tmy_address_match_group(const bool is_ipv6,
+				   const u32 *address,
+				   const struct address_group_entry *group)
+{
+	struct address_group_member *member;
+	const u32 ip = ntohl(*address);
+
+	list_for_each_entry(member, &group->address_group_member_list, list) {
+		if (member->is_deleted)
+			continue;
+
+		if (member->is_ipv6) {
+
+			if (is_ipv6 &&
+			    memcmp(member->min.ipv6, address, 16) <= 0 &&
+			    memcmp(address, member->max.ipv6, 16) <= 0)
+				return 1;
+
+		} else {
+
+			if (!is_ipv6 &&
+			    member->min.ipv4 <= ip &&
+			    ip <= member->max.ipv4)
+				return 1;
+
+		}
+	}
+
+	return 0;
+}
+
+static int tmy_read_address_group(struct io_buffer *head,
+			       struct address_group_entry *group,
+			       struct address_group_member *member)
+{
+	char buf[128];
+	if (!member)
+		return 0;
+
+	if (member->is_ipv6) {
+
+		const u16 *min_addr = member->min.ipv6;
+		const u16 *max_addr = member->max.ipv6;
+
+		tmy_print_ipv6(buf, sizeof(buf), min_addr);
+
+		if (memcmp(min_addr, max_addr, 16)) {
+			char *cp = strchr(buf, '\0');
+			int len = sizeof(buf) - strlen(buf);
+
+			*cp++ = '-';
+			tmy_print_ipv6(cp, len, max_addr);
+		}
+
+	} else {
+
+		const u32 min_addr = member->min.ipv4;
+		const u32 max_addr = member->max.ipv4;
+
+		memset(buf, 0, sizeof(buf));
+		snprintf(buf, sizeof(buf) - 1,
+			 NIPQUAD_FMT, HIPQUAD(min_addr));
+
+		if (min_addr != max_addr) {
+			const int len = strlen(buf);
+
+			snprintf(buf + len, sizeof(buf) - 1 - len,
+				 "-" NIPQUAD_FMT, HIPQUAD(max_addr));
+		}
+
+	}
+
+	return tmy_io_printf(head, TMY_ADDRESS_GROUP "%s %s\n",
+			     group->group_name->name, buf);
+}
+
+/**
+ * tmy_read_address_group_policy - read address group policy
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_address_group_policy(struct io_buffer *head)
+{
+	struct list_head *gpos;
+	struct list_head *mpos;
+	list_for_each_cookie(gpos, head->read_var1, &address_group_list) {
+		struct address_group_entry *group;
+		group = list_entry(gpos, struct address_group_entry, list);
+		list_for_each_cookie(mpos, head->read_var2,
+				     &group->address_group_member_list) {
+			struct address_group_member *member;
+			member = list_entry(mpos, struct address_group_member,
+					    list);
+			if (member->is_deleted)
+				continue;
+			if (tmy_read_address_group(head, group, member))
+				return -ENOMEM;
+		}
+	}
+	return 0;
+}
+
+/***********************  NETWORK NETWORK ACL HANDLER  ***********************/
+
+/**
+ * tmy_print_ipv6 - print ipv6 address
+ * @buffer:     pointer to buffer to save the result.
+ * @buffer_len: sizeof @buffer .
+ * @ip:         pointer to an IPv6 address in network byte order.
+ *
+ * Returns @buffer .
+ */
+char *tmy_print_ipv6(char *buffer, const int buffer_len, const u16 *ip)
+{
+	memset(buffer, 0, buffer_len);
+	snprintf(buffer, buffer_len - 1, NIP6_FMT,
+		 ntohs(ip[0]), ntohs(ip[1]), ntohs(ip[2]), ntohs(ip[3]),
+		 ntohs(ip[4]), ntohs(ip[5]), ntohs(ip[6]), ntohs(ip[7]));
+	return buffer;
+}
+
+/**
+ * tmy_network2keyword - get keyword from access control index.
+ * @operation: index number.
+ *
+ * Returns keyword that corresponds with @operation .
+ */
+const char *tmy_network2keyword(const unsigned int operation)
+{
+	const char *keyword = "unknown";
+	switch (operation) {
+	case TMY_NETWORK_ACL_UDP_BIND:
+		keyword = "UDP bind";
+		break;
+	case TMY_NETWORK_ACL_UDP_CONNECT:
+		keyword = "UDP connect";
+		break;
+	case TMY_NETWORK_ACL_TCP_BIND:
+		keyword = "TCP bind";
+		break;
+	case TMY_NETWORK_ACL_TCP_LISTEN:
+		keyword = "TCP listen";
+		break;
+	case TMY_NETWORK_ACL_TCP_CONNECT:
+		keyword = "TCP connect";
+		break;
+	case TMY_NETWORK_ACL_TCP_ACCEPT:
+		keyword = "TCP accept";
+		break;
+	case TMY_NETWORK_ACL_RAW_BIND:
+		keyword = "RAW bind";
+		break;
+	case TMY_NETWORK_ACL_RAW_CONNECT:
+		keyword = "RAW connect";
+		break;
+	}
+	return keyword;
+}
+
+/* Compare IPv4/IPv6 address. */
+static int tmy_cmp_network_entry(const u8 record_type,
+				 struct net_acl *acl,
+				 const struct address_group_entry *group,
+				 const u32 min_ip,
+				 const u32 max_ip,
+				 const u32 *min_address,
+				 const u32 *max_address)
+{
+	int found = 0;
+
+	switch (record_type) {
+
+	case TMY_TYPE_ADDRESS_GROUP:
+		if (acl->u.group == group)
+			found = 1;
+		break;
+
+	case TMY_TYPE_IPv4:
+		if (acl->u.ipv4.min == min_ip &&
+		    max_ip == acl->u.ipv4.max)
+			found = 1;
+		break;
+
+	case TMY_TYPE_IPv6:
+		if (memcmp(acl->u.ipv6.min, min_address, 16) == 0 &&
+		    memcmp(max_address, acl->u.ipv6.max, 16) == 0)
+			found = 1;
+		break;
+
+	}
+
+	return found;
+}
+
+static int tmy_add_network_entry(const u8 operation,
+				 const u8 record_type,
+				 const struct address_group_entry *group,
+				 const u32 *min_address,
+				 const u32 *max_address,
+				 const u16 min_port,
+				 const u16 max_port,
+				 struct domain_info *domain,
+				 const struct condition_list *cond,
+				 const bool is_delete)
+{
+	struct acl_info *ptr;
+	struct net_acl *acl;
+	int error = -ENOMEM;
+	/* using host byte order to allow u32 comparison than memcmp().*/
+	const u32 min_ip = ntohl(*min_address);
+	const u32 max_ip = ntohl(*max_address);
+
+	if (!domain)
+		return -EINVAL;
+
+	mutex_lock(&domain_acl_lock);
+
+	if (is_delete)
+		goto remove;
+
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		acl = (struct net_acl *) ptr;
+		if (ptr->type == TMY_TYPE_IP_NETWORK_ACL &&
+		    ptr->cond == cond &&
+		    acl->operation_type == operation &&
+		    acl->record_type == record_type &&
+		    acl->min_port == min_port &&
+		    max_port == acl->max_port &&
+		    tmy_cmp_network_entry(record_type, acl,
+					  group, min_ip, max_ip,
+					  min_address,
+					  max_address)) {
+			ptr->is_deleted = 0;
+			error = 0;
+			goto ok;
+		}
+	}
+	/* Not found. Append it to the tail. */
+	acl = tmy_alloc_element(sizeof(*acl));
+	if (!acl)
+		goto ok;
+
+	acl->head.type = TMY_TYPE_IP_NETWORK_ACL;
+	acl->head.cond = cond;
+	acl->operation_type = operation;
+	acl->record_type = record_type;
+
+	if (record_type == TMY_TYPE_ADDRESS_GROUP)
+		acl->u.group = group;
+	else if (record_type == TMY_TYPE_IPv4) {
+		acl->u.ipv4.min = min_ip;
+		acl->u.ipv4.max = max_ip;
+	} else {
+		memmove(acl->u.ipv6.min, min_address, 16);
+		memmove(acl->u.ipv6.max, max_address, 16);
+	}
+
+	acl->min_port = min_port;
+	acl->max_port = max_port;
+	error = tmy_add_acl(domain,
+			    (struct acl_info *) acl);
+	goto ok;
+remove: ;
+	error = -ENOENT;
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		acl = (struct net_acl *) ptr;
+		if (ptr->type != TMY_TYPE_IP_NETWORK_ACL ||
+		    ptr->cond != cond ||
+		    ptr->is_deleted ||
+		    acl->operation_type != operation ||
+		    acl->record_type != record_type ||
+		    acl->min_port != min_port ||
+		    acl->max_port != max_port ||
+		    !tmy_cmp_network_entry(record_type, acl,
+					   group, min_ip, max_ip,
+					   min_address,
+					   max_address))
+			continue;
+		error = tmy_del_acl(ptr);
+		break;
+	}
+ok: ;
+	mutex_unlock(&domain_acl_lock);
+
+	return error;
+}
+
+/* Check network permission. */
+static int tmy_network_entry(const bool is_ipv6,
+			     const int operation,
+			     const u32 *address,
+			     const u16 port)
+{
+	struct domain_info * const domain = TMY_SECURITY->domain;
+	const u8 profile = domain->profile;
+	const unsigned int mode = tmy_flags(TMY_MAC_FOR_NETWORK);
+	struct acl_info *ptr;
+	const char *keyword = tmy_network2keyword(operation);
+	const bool is_enforce = (mode == 3);
+	/* using host byte order to allow u32 comparison than memcmp().*/
+	const u32 ip = ntohl(*address);
+	bool found = 0;
+
+	if (!mode)
+		return 0;
+
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		struct net_acl *acl = (struct net_acl *) ptr;
+		if (ptr->type != TMY_TYPE_IP_NETWORK_ACL ||
+		    ptr->is_deleted ||
+		    acl->operation_type != operation ||
+		    port < acl->min_port ||
+		    acl->max_port < port ||
+		    tmy_check_condition(ptr->cond, NULL))
+			continue;
+
+		if (acl->record_type == TMY_TYPE_ADDRESS_GROUP) {
+			if (tmy_address_match_group(is_ipv6, address,
+			    acl->u.group)) {
+				found = 1;
+				break;
+			}
+		} else if (acl->record_type == TMY_TYPE_IPv4) {
+			if (!is_ipv6 &&
+			    (acl->u.ipv4.min <= ip && ip <= acl->u.ipv4.max)) {
+				found = 1;
+				break;
+			}
+		} else {
+			if (is_ipv6 &&
+			    memcmp(acl->u.ipv6.min, address, 16) <= 0 &&
+			    memcmp(address, acl->u.ipv6.max, 16) <= 0) {
+				found = 1;
+				break;
+			}
+		}
+	}
+
+	tmy_audit_network_log(is_ipv6, keyword, address,
+			      port, found, profile, mode);
+
+	if (found)
+		return 0;
+
+	if (tmy_flags(TMY_VERBOSE)) {
+		if (is_ipv6) {
+			char buf[64];
+			tmy_print_ipv6(buf, sizeof(buf), (const u16 *) address);
+			tmy_audit("TOMOYO-%s: %s to %s %u denied for %s\n",
+				  tmy_getmsg(is_enforce), keyword, buf, port,
+				  tmy_lastname(domain));
+		} else {
+			tmy_audit("TOMOYO-%s: %s to %u.%u.%u.%u %u denied for "
+				  "%s\n", tmy_getmsg(is_enforce), keyword,
+				  HIPQUAD(ip), port, tmy_lastname(domain));
+		}
+	}
+
+	if (is_enforce) {
+
+		if (is_ipv6) {
+
+			char buf[64];
+
+			tmy_print_ipv6(buf, sizeof(buf), (const u16 *) address);
+			return tmy_supervisor("%s\n" TMY_ALLOW_NETWORK
+					      "%s %s %u\n",
+					      domain->domainname->name, keyword,
+					      buf, port);
+
+		}
+
+		return tmy_supervisor("%s\n" TMY_ALLOW_NETWORK
+				      "%s " NIPQUAD_FMT " %u\n",
+				      domain->domainname->name, keyword,
+				      HIPQUAD(ip), port);
+
+	}
+
+	if (mode == 1 && tmy_quota())
+		tmy_add_network_entry(operation,
+				      is_ipv6 ? TMY_TYPE_IPv6 : TMY_TYPE_IPv4,
+				      NULL, address, address,
+				      port, port, domain, NULL, 0);
+
+	return 0;
+}
+
+/**
+ * tmy_add_signal_policy - add or delete signal policy.
+ * @data:      a line to parse.
+ * @domain:    pointer to "struct domain_info".
+ * @cond:      pointer to "struct condition_list". May be NULL.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_network_policy(char *data,
+			   struct domain_info *domain,
+			   const struct condition_list *cond,
+			   const bool is_delete)
+{
+	u8 sock_type;
+	u8 operation;
+	u8 record_type;
+	u16 min_address[8];
+	u16 max_address[8];
+	unsigned int min[8];
+	unsigned int max[8];
+	struct address_group_entry *group = NULL;
+	u16 min_port;
+	u16 max_port;
+	int count;
+	char *cp1 = NULL;
+	char *cp2 = NULL;
+
+	cp1 = strchr(data, ' ');
+	if (!cp1)
+		goto out;
+	cp1++;
+
+	if (strncmp(data, "TCP ", 4) == 0)
+		sock_type = SOCK_STREAM;
+	else if (strncmp(data, "UDP ", 4) == 0)
+		sock_type = SOCK_DGRAM;
+	else if (strncmp(data, "RAW ", 4) == 0)
+		sock_type = SOCK_RAW;
+	else
+		goto out;
+
+	cp2 = strchr(cp1, ' ');
+	if (!cp2)
+		goto out;
+	cp2++;
+
+	if (strncmp(cp1, "bind ", 5) == 0) {
+		switch (sock_type) {
+		case SOCK_STREAM:
+			operation = TMY_NETWORK_ACL_TCP_BIND;
+			break;
+		case SOCK_DGRAM:
+			operation = TMY_NETWORK_ACL_UDP_BIND;
+			break;
+		default:
+			operation = TMY_NETWORK_ACL_RAW_BIND;
+			break;
+		}
+	} else if (strncmp(cp1, "connect ", 8) == 0) {
+		switch (sock_type) {
+		case SOCK_STREAM:
+			operation = TMY_NETWORK_ACL_TCP_CONNECT;
+			break;
+		case SOCK_DGRAM:
+			operation = TMY_NETWORK_ACL_UDP_CONNECT;
+			break;
+		default:
+			operation = TMY_NETWORK_ACL_RAW_CONNECT;
+			break;
+		}
+	} else if (sock_type == SOCK_STREAM &&
+		   strncmp(cp1, "listen ", 7) == 0)
+		operation = TMY_NETWORK_ACL_TCP_LISTEN;
+
+	else if (sock_type == SOCK_STREAM &&
+		 strncmp(cp1, "accept ", 7) == 0)
+		operation = TMY_NETWORK_ACL_TCP_ACCEPT;
+
+	else
+		goto out;
+
+	cp1 = strchr(cp2, ' ');
+	if (!cp1)
+		goto out;
+	*cp1++ = '\0';
+
+	count = sscanf(cp2,
+		       NIP6_FMT "-" NIP6_FMT,
+		       &min[0], &min[1], &min[2], &min[3],
+		       &min[4], &min[5], &min[6], &min[7],
+		       &max[0], &max[1], &max[2], &max[3],
+		       &max[4], &max[5], &max[6], &max[7]);
+
+	if (count == 8 || count == 16) {
+
+		int i;
+
+		for (i = 0; i < 8; i++) {
+			min_address[i] = htons((u16) min[i]);
+			max_address[i] = htons((u16) max[i]);
+		}
+
+		if (count == 8)
+			memmove(max_address, min_address, sizeof(min_address));
+		record_type = TMY_TYPE_IPv6;
+
+		goto ok;
+
+	}
+
+	count = sscanf(cp2,
+		       NIPQUAD_FMT "-" NIPQUAD_FMT,
+		       &min[0], &min[1], &min[2], &min[3],
+		       &max[0], &max[1], &max[2], &max[3]);
+
+	if (count == 4 || count == 8) {
+
+		u32 ip = htonl((((u8) min[0]) << 24) +
+			       (((u8) min[1]) << 16) +
+			       (((u8) min[2]) << 8) +
+			       (u8) min[3]);
+		*(u32 *) min_address = ip;
+
+		if (count == 8)
+			ip = htonl((((u8) max[0]) << 24) +
+				   (((u8) max[1]) << 16) +
+				   (((u8) max[2]) << 8) +
+				   (u8) max[3]);
+		*(u32 *) max_address = ip;
+		record_type = TMY_TYPE_IPv4;
+
+		goto ok;
+
+	}
+
+	if (*cp2 == '@') {
+
+		group = tmy_new_address_group(cp2 + 1);
+		if (!group)
+			return -ENOMEM;
+		record_type = TMY_TYPE_ADDRESS_GROUP;
+
+		goto ok;
+	}
+
+	goto out;
+
+ok: ;
+	if (strchr(cp1, ' '))
+		goto out;
+
+	count = sscanf(cp1, "%hu-%hu", &min_port, &max_port);
+	if (count != 1 && count != 2)
+		goto out;
+
+	if (count == 1)
+		max_port = min_port;
+
+	return tmy_add_network_entry(operation, record_type, group,
+				     (u32 *) min_address,
+				     (u32 *) max_address,
+				     min_port, max_port, domain,
+				     cond, is_delete);
+
+out: ;
+	return -EINVAL;
+}
+
+/**
+ * tmy_network_listen_acl - check permission for listen(2) operation.
+ * @is_ipv6: is @address an IPv6 address?
+ * @address: pointer to IPv4/IPv6 address in network byte order.
+ * @port:    TCP or UDP's port number.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_network_listen_acl(const bool is_ipv6,
+			   const u8 *address,
+			   const u16 port)
+{
+	return tmy_network_entry(is_ipv6,
+				 TMY_NETWORK_ACL_TCP_LISTEN,
+				 (const u32 *) address,
+				 ntohs(port));
+}
+
+/**
+ * tmy_network_connect_acl - check permission for connect(2) operation.
+ * @is_ipv6:   is @address an IPv6 address?
+ * @sock_type: socket type (TCP, UDP or IP).
+ * @address:   pointer to IPv4/IPv6 address in network byte order.
+ * @port:      TCP or UDP's port number or IP's protocol number.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_network_connect_acl(const bool is_ipv6,
+			    const int sock_type,
+			    const u8 *address,
+			    const u16 port)
+{
+	int type;
+
+	switch (sock_type) {
+	case SOCK_STREAM:
+		type = TMY_NETWORK_ACL_TCP_CONNECT;
+		break;
+	case SOCK_DGRAM:
+		type = TMY_NETWORK_ACL_UDP_CONNECT;
+		break;
+	default:
+		type = TMY_NETWORK_ACL_RAW_CONNECT;
+		break;
+	}
+
+	return tmy_network_entry(is_ipv6, type,
+				 (const u32 *) address, ntohs(port));
+}
+
+/**
+ * tmy_network_bind_acl - check permission for bind(2) operation.
+ * @is_ipv6:   is @address an IPv6 address?
+ * @sock_type: socket type (TCP, UDP or IP).
+ * @address:   pointer to IPv4/IPv6 address in network byte order.
+ * @port:      TCP or UDP's port number or IP's protocol number.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_network_bind_acl(const bool is_ipv6,
+			 const int sock_type,
+			 const u8 *address,
+			 const u16 port)
+{
+	int type;
+
+	switch (sock_type) {
+	case SOCK_STREAM:
+		type = TMY_NETWORK_ACL_TCP_BIND;
+		break;
+	case SOCK_DGRAM:
+		type = TMY_NETWORK_ACL_UDP_BIND;
+		break;
+	default:
+		type = TMY_NETWORK_ACL_RAW_BIND;
+		break;
+	}
+
+	return tmy_network_entry(is_ipv6, type,
+				 (const u32 *) address, ntohs(port));
+}
+
+/**
+ * tmy_network_sendmsg_acl - check permission for sendmsg(2) operation.
+ * @is_ipv6:   is @address an IPv6 address?
+ * @sock_type: socket type (UDP or IP).
+ * @address:   pointer to IPv4/IPv6 address in network byte order.
+ * @port:      UDP's port number or IP's protocol number.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_network_sendmsg_acl(const bool is_ipv6,
+			    const int sock_type,
+			    const u8 *address,
+			    const u16 port)
+{
+	int type;
+
+	if (sock_type == SOCK_DGRAM)
+		type = TMY_NETWORK_ACL_UDP_CONNECT;
+	else
+		type = TMY_NETWORK_ACL_RAW_CONNECT;
+
+	return tmy_network_entry(is_ipv6, type,
+				 (const u32 *) address, ntohs(port));
+}
+
+/**
+ * tmy_network_accept_acl - check permission for accept(2) operation.
+ * @is_ipv6:   is @address an IPv6 address?
+ * @address:   pointer to IPv4/IPv6 address in network byte order.
+ * @port:      TCP client's port number.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_network_accept_acl(const bool is_ipv6, const u8 *address,
+			   const u16 port)
+{
+	return tmy_network_entry(is_ipv6, TMY_NETWORK_ACL_TCP_ACCEPT,
+				 (const u32 *) address, ntohs(port));
+}
+
+/**
+ * tmy_network_recvmsg_acl - check permission for recvmsg(2) operation.
+ * @is_ipv6:   is @address an IPv6 address?
+ * @sock_type: socket type (UDP or IP).
+ * @address:   pointer to IPv4/IPv6 address in network byte order.
+ * @port:      UDP's port number or IP's protocol number.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_network_recvmsg_acl(const bool is_ipv6, const int sock_type,
+			    const u8 *address, const u16 port)
+{
+	return tmy_network_entry(is_ipv6, sock_type == SOCK_DGRAM ?
+				 TMY_NETWORK_ACL_UDP_CONNECT :
+				 TMY_NETWORK_ACL_RAW_CONNECT,
+				 (const u32 *) address, ntohs(port));
+}

-- 

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

* [TOMOYO #5 12/18] Namespace manipulation control functions.
  2007-11-16 17:34 [TOMOYO #5 00/18] TOMOYO Linux - MAC based on process invocation history penguin-kernel
                   ` (10 preceding siblings ...)
  2007-11-16 17:34 ` [TOMOYO #5 11/18] Network access control functions penguin-kernel
@ 2007-11-16 17:34 ` penguin-kernel
  2007-11-16 17:34 ` [TOMOYO #5 13/18] Signal " penguin-kernel
                   ` (5 subsequent siblings)
  17 siblings, 0 replies; 39+ messages in thread
From: penguin-kernel @ 2007-11-16 17:34 UTC (permalink / raw)
  To: akpm; +Cc: linux-kernel, linux-security-module, Kentaro Takeda, Tetsuo Handa

[-- Attachment #1: tomoyo-mount.diff --]
[-- Type: text/plain, Size: 23999 bytes --]

TOMOYO Linux checks mount permission based on
device name, mount point, filesystem type and optional flags.
TOMOYO Linux also checks permission in umount and pivot_root.

Each permission can be automatically accumulated into
the policy using 'learning mode'.

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

--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6-mm/security/tomoyo/mount.c	2007-11-14 15:59:44.000000000 +0900
@@ -0,0 +1,908 @@
+/*
+ * security/tomoyo/mount.c
+ *
+ * Mount access control functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+/***** KEYWORDS for mount restrictions. *****/
+
+#define MOUNT_BIND_KEYWORD    	      "--bind"
+#define MOUNT_MOVE_KEYWORD    	      "--move"
+#define MOUNT_REMOUNT_KEYWORD 	      "--remount"
+#define MOUNT_MAKE_UNBINDABLE_KEYWORD "--make-unbindable"
+#define MOUNT_MAKE_PRIVATE_KEYWORD    "--make-private"
+#define MOUNT_MAKE_SLAVE_KEYWORD      "--make-slave"
+#define MOUNT_MAKE_SHARED_KEYWORD     "--make-shared"
+
+/***** The structure for mount restrictions. *****/
+
+struct mount_entry {
+	struct list_head list;
+	const struct path_info *dev_name;
+	const struct path_info *dir_name;
+	const struct path_info *fs_type;
+	unsigned int flags; /* Mount flags. */
+	bool is_deleted;
+};
+
+struct no_umount_entry {
+	struct list_head list;
+	const struct path_info *dir;
+	bool is_deleted;
+};
+
+/************************  MOUNT RESTRICTION HANDLER  ************************/
+
+static LIST_HEAD(mount_list);
+
+/* Add or remove a mount entry. */
+static int tmy_add_mount_acl(const char *dev_name,
+			     const char *dir_name,
+			     const char *fs_type,
+			     const unsigned int flags,
+			     const bool is_delete)
+{
+	struct mount_entry *new_entry;
+	struct mount_entry  *ptr;
+	const struct path_info *fs;
+	const struct path_info *dev;
+	const struct path_info *dir;
+	static DEFINE_MUTEX(mutex);
+	int error = -ENOMEM;
+
+	fs = tmy_save_name(fs_type);
+	if (!fs)
+		return -EINVAL;
+
+	if (!dev_name)
+		/* Map dev_name to "<NULL>" for if no dev_name given. */
+		dev_name = "<NULL>";
+	if (strcmp(fs->name, MOUNT_REMOUNT_KEYWORD) == 0)
+		/* Fix dev_name to "any" for remount permission. */
+		dev_name = "any";
+	if (strcmp(fs->name, MOUNT_MAKE_UNBINDABLE_KEYWORD) == 0 ||
+	    strcmp(fs->name, MOUNT_MAKE_PRIVATE_KEYWORD) == 0 ||
+	    strcmp(fs->name, MOUNT_MAKE_SLAVE_KEYWORD) == 0 ||
+	    strcmp(fs->name, MOUNT_MAKE_SHARED_KEYWORD) == 0)
+		dev_name = "any";
+
+	if (!tmy_correct_path(dev_name, 0, 0, 0, __FUNCTION__) ||
+	    !tmy_correct_path(dir_name, 1, 0, 1, __FUNCTION__))
+		return -EINVAL;
+
+	dev = tmy_save_name(dev_name);
+	if (!dev)
+		return -ENOMEM;
+	dir = tmy_save_name(dir_name);
+	if (!dir)
+		return -ENOMEM;
+
+	mutex_lock(&mutex);
+
+	list_for_each_entry(ptr, &mount_list, list) {
+		if (ptr->flags != flags ||
+		    tmy_pathcmp(ptr->dev_name, dev) ||
+		    tmy_pathcmp(ptr->dir_name, dir) ||
+		    tmy_pathcmp(ptr->fs_type, fs))
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+
+	new_entry = tmy_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+
+	new_entry->dev_name = dev;
+	new_entry->dir_name = dir;
+	new_entry->fs_type = fs;
+	new_entry->flags = flags;
+	list_add_tail_mb(&new_entry->list, &mount_list);
+	error = 0;
+out: ;
+	mutex_unlock(&mutex);
+	return error;
+}
+
+/* Print error message for mount request. */
+static inline int tmy_mount_perm_error(char *dev_name,
+				       char *dir_name,
+				       char *type,
+				       unsigned long flags,
+				       const u8 profile,
+				       const unsigned int mode)
+{
+	int error = -EPERM;
+	const char *realname1 = tmy_realpath(dev_name);
+	const char *realname2 = tmy_realpath(dir_name);
+	const char *exename = tmy_get_exe();
+	const bool is_enforce = (mode == 3);
+
+	if (!strcmp(type, MOUNT_REMOUNT_KEYWORD)) {
+		tmy_audit(KERN_WARNING "TOMOYO-%s: mount -o remount %s 0x%lX "
+			  "(pid=%d:exe=%s): Permission denied.\n",
+			  tmy_getmsg(is_enforce),
+			  realname2 ? realname2 : dir_name, flags,
+			  current->pid, exename);
+		if (is_enforce &&
+		    !tmy_supervisor("# %s is requesting\nmount -o remount %s\n",
+				    exename, realname2 ? realname2 : dir_name))
+			error = 0;
+
+	} else if (!strcmp(type, MOUNT_BIND_KEYWORD) ||
+		   !strcmp(type, MOUNT_MOVE_KEYWORD)) {
+		tmy_audit(KERN_WARNING "TOMOYO-%s: mount %s %s %s 0x%lX "
+			  "(pid=%d:exe=%s): Permission denied.\n",
+			  tmy_getmsg(is_enforce), type,
+			  realname1 ? realname1 : dev_name,
+			  realname2 ? realname2 : dir_name,
+			  flags, current->pid, exename);
+		if (is_enforce &&
+		    tmy_supervisor("# %s is requesting\nmount %s %s %s 0x%lX\n",
+				   exename, type,
+				   realname1 ? realname1 : dev_name,
+				   realname2 ? realname2 : dir_name,
+				   flags) == 0)
+			error = 0;
+
+	} else if (!strcmp(type, MOUNT_MAKE_UNBINDABLE_KEYWORD) ||
+		   !strcmp(type, MOUNT_MAKE_PRIVATE_KEYWORD) ||
+		   !strcmp(type, MOUNT_MAKE_SLAVE_KEYWORD) ||
+		   !strcmp(type, MOUNT_MAKE_SHARED_KEYWORD)) {
+		tmy_audit(KERN_WARNING "TOMOYO-%s: mount %s %s 0x%lX "
+			  "(pid=%d:exe=%s): Permission denied.\n",
+			  tmy_getmsg(is_enforce), type,
+			  realname2 ? realname2 : dir_name,
+			  flags, current->pid, exename);
+		if (is_enforce &&
+		    tmy_supervisor("# %s is requesting\nmount %s %s 0x%lX",
+				   exename, type,
+				   realname2 ? realname2 : dir_name,
+				   flags) == 0)
+			error = 0;
+
+	} else {
+		tmy_audit(KERN_WARNING "TOMOYO-%s: mount -t %s %s %s 0x%lX "
+			  "(pid=%d:exe=%s): Permission denied.\n",
+			  tmy_getmsg(is_enforce), type,
+			  realname1 ? realname1 : dev_name,
+			  realname2 ? realname2 : dir_name,
+			  flags, current->pid, exename);
+		if (is_enforce &&
+		    tmy_supervisor("# %s is requesting\n"
+				   "mount -t %s %s %s 0x%lX\n",
+				   exename, type,
+				   realname1 ? realname1 : dev_name,
+				   realname2 ? realname2 : dir_name,
+				   flags) == 0)
+			error = 0;
+
+	}
+
+	tmy_free(exename);
+	tmy_free(realname2);
+	tmy_free(realname1);
+	return error;
+}
+
+/**
+ * tmy_mount_perm - check for mount permission.
+ * @dev_name: pointer to device name. May be NULL.
+ * @dir_name: pointer to mount point.
+ * @type:     pointer to filesystem. May be NULL.
+ * @flags:    mount flags.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_mount_perm(char *dev_name,
+		   char *dir_name,
+		   char *type,
+		   unsigned long flags)
+{
+	const u8 profile = TMY_SECURITY->domain->profile;
+	const unsigned int mode = tmy_flags(TMY_RESTRICT_MOUNT);
+	const bool is_enforce = (mode == 3);
+	int error = -EPERM;
+
+	if (!mode)
+		return 0;
+	if (!type)
+		type = "<NULL>";
+	if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
+		flags &= ~MS_MGC_MSK;
+
+	switch (flags & (MS_REMOUNT | MS_MOVE | MS_BIND)) {
+	case MS_REMOUNT:
+	case MS_MOVE:
+	case MS_BIND:
+	case 0:
+		break;
+	default:
+		tmy_audit(KERN_WARNING "TOMOYO-ERROR: %s%s%sare given "
+			  "for single mount operation.\n",
+			  flags & MS_REMOUNT ? "'remount' " : "",
+			  flags & MS_MOVE    ? "'move' " : "",
+			  flags & MS_BIND    ? "'bind' " : "");
+		return -EINVAL;
+	}
+
+	switch (flags & (MS_UNBINDABLE | MS_PRIVATE | MS_SLAVE | MS_SHARED)) {
+	case MS_UNBINDABLE:
+	case MS_PRIVATE:
+	case MS_SLAVE:
+	case MS_SHARED:
+	case 0:
+		break;
+	default:
+		tmy_audit(KERN_WARNING "TOMOYO-ERROR: %s%s%s%sare given "
+			  "for single mount operation.\n",
+			  flags & MS_UNBINDABLE ? "'unbindable' " : "",
+			  flags & MS_PRIVATE    ? "'private' " : "",
+			  flags & MS_SLAVE      ? "'slave' " : "",
+			  flags & MS_SHARED     ? "'shared' " : "");
+		return -EINVAL;
+	}
+
+	if (flags & MS_REMOUNT)
+		error = tmy_mount_perm(dev_name, dir_name,
+				       MOUNT_REMOUNT_KEYWORD,
+				       flags & ~MS_REMOUNT);
+	else if (flags & MS_MOVE)
+		error = tmy_mount_perm(dev_name, dir_name,
+				       MOUNT_MOVE_KEYWORD,
+				       flags & ~MS_MOVE);
+	else if (flags & MS_BIND)
+		error = tmy_mount_perm(dev_name, dir_name,
+				       MOUNT_BIND_KEYWORD,
+				       flags & ~MS_BIND);
+	else if (flags & MS_UNBINDABLE)
+		error = tmy_mount_perm(dev_name, dir_name,
+				       MOUNT_MAKE_UNBINDABLE_KEYWORD,
+				       flags & ~MS_UNBINDABLE);
+	else if (flags & MS_PRIVATE)
+		error = tmy_mount_perm(dev_name, dir_name,
+				       MOUNT_MAKE_PRIVATE_KEYWORD,
+				       flags & ~MS_PRIVATE);
+	else if (flags & MS_SLAVE)
+		error = tmy_mount_perm(dev_name, dir_name,
+				       MOUNT_MAKE_SLAVE_KEYWORD,
+				       flags & ~MS_SLAVE);
+	else if (flags & MS_SHARED)
+		error = tmy_mount_perm(dev_name, dir_name,
+				       MOUNT_MAKE_SHARED_KEYWORD,
+				       flags & ~MS_SHARED);
+	else {
+		struct mount_entry *ptr;
+		struct file_system_type *fstype = NULL;
+		const char *requested_dir_name = NULL;
+		const char *requested_dev_name = NULL;
+		struct path_info rdev;
+		struct path_info rdir;
+		int need_dev = 0;
+
+		requested_dir_name = tmy_realpath(dir_name);
+		if (!requested_dir_name) {
+			error = -ENOENT;
+			goto cleanup;
+		}
+		rdir.name = requested_dir_name;
+		tmy_fill_path_info(&rdir);
+
+		/* Compare fs name. */
+		fstype = get_fs_type(type);
+		if (strcmp(type, MOUNT_REMOUNT_KEYWORD) == 0)
+			/* Needn't to resolve dev_name */;
+		else if (strcmp(type, MOUNT_MAKE_UNBINDABLE_KEYWORD) == 0 ||
+			 strcmp(type, MOUNT_MAKE_PRIVATE_KEYWORD) == 0 ||
+			 strcmp(type, MOUNT_MAKE_SLAVE_KEYWORD) == 0 ||
+			 strcmp(type, MOUNT_MAKE_SHARED_KEYWORD) == 0)
+			/* Needn't to resolve dev_name */;
+		else if (strcmp(type, MOUNT_BIND_KEYWORD) == 0 ||
+			 strcmp(type, MOUNT_MOVE_KEYWORD) == 0) {
+			requested_dev_name = tmy_realpath(dev_name);
+			if (!requested_dev_name) {
+				error = -ENOENT;
+				goto cleanup;
+			}
+			rdev.name = requested_dev_name;
+			tmy_fill_path_info(&rdev);
+			need_dev = -1;
+		} else if (fstype) {
+			if (fstype->fs_flags & FS_REQUIRES_DEV) {
+				requested_dev_name = tmy_realpath(dev_name);
+				if (!requested_dev_name) {
+					error = -ENOENT;
+					goto cleanup;
+				}
+				rdev.name = requested_dev_name;
+				tmy_fill_path_info(&rdev);
+				need_dev = 1;
+			}
+		} else {
+			error = -ENODEV;
+			goto cleanup;
+		}
+
+		list_for_each_entry(ptr, &mount_list, list) {
+			if (ptr->is_deleted)
+				continue;
+
+			/* Compare options */
+			if (ptr->flags != flags)
+				continue;
+
+			/* Compare fs name. */
+			if (strcmp(type, ptr->fs_type->name))
+				continue;
+
+			/* Compare mount point. */
+			if (tmy_path_match(&rdir, ptr->dir_name) == 0)
+				continue;
+
+			/* Compare device name. */
+			if (requested_dev_name &&
+			    tmy_path_match(&rdev, ptr->dev_name) == 0)
+				continue;
+
+			/* OK. */
+			error = 0;
+
+			if (need_dev > 0)
+				tmy_audit(KERN_DEBUG "TOMOYO-NOTICE: "
+					  "'mount -t %s %s %s 0x%lX' "
+					  "accepted.\n",
+					  type, requested_dev_name,
+					  requested_dir_name, flags);
+			else if (need_dev < 0)
+				tmy_audit(KERN_DEBUG "TOMOYO-NOTICE: "
+					  "'mount %s %s %s 0x%lX' accepted.\n",
+					  type, requested_dev_name,
+					  requested_dir_name, flags);
+			else if (!strcmp(type, MOUNT_REMOUNT_KEYWORD))
+				tmy_audit(KERN_DEBUG "TOMOYO-NOTICE: "
+					  "'mount -o remount %s 0x%lX' "
+					  "accepted.\n",
+					  requested_dir_name, flags);
+			else if (!strcmp(type, MOUNT_MAKE_UNBINDABLE_KEYWORD) ||
+				 !strcmp(type, MOUNT_MAKE_PRIVATE_KEYWORD) ||
+				 !strcmp(type, MOUNT_MAKE_SLAVE_KEYWORD) ||
+				 !strcmp(type, MOUNT_MAKE_SHARED_KEYWORD))
+				tmy_audit(KERN_DEBUG "TOMOYO-NOTICE: "
+					  "'mount %s %s 0x%lX' accepted.\n",
+					  type, requested_dir_name, flags);
+			else
+				tmy_audit(KERN_DEBUG "TOMOYO-NOTICE: "
+					  "'mount %s on %s 0x%lX' accepted.\n",
+					  type, requested_dir_name, flags);
+			break;
+		}
+
+		if (error)
+			error = tmy_mount_perm_error(dev_name, dir_name, type,
+						     flags, profile, mode);
+
+		if (error && mode == 1) {
+			tmy_add_mount_acl(need_dev ?
+					  requested_dev_name : dev_name,
+					  requested_dir_name, type, flags, 0);
+			tmy_update_counter(TMY_UPDATE_SYSTEMPOLICY);
+		}
+
+cleanup:
+		tmy_free(requested_dev_name);
+		tmy_free(requested_dir_name);
+		if (fstype)
+			put_filesystem(fstype);
+
+	}
+
+	if (!is_enforce)
+		error = 0;
+	return error;
+
+}
+
+/**
+ * tmy_add_mount_policy - add or delete mount policy.
+ * @data:      a line to parse.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_mount_policy(char *data, const bool is_delete)
+{
+	char *cp;
+	char *cp2;
+	const char *fs;
+	const char *dev;
+	const char *dir;
+	unsigned int flags = 0;
+
+	cp2 = data;
+	cp = strchr(cp2, ' ');
+	if (!cp)
+		return -EINVAL;
+	*cp = '\0';
+	dev = cp2;
+
+	cp2 = cp + 1;
+	cp = strchr(cp2, ' ');
+	if (!cp)
+		return -EINVAL;
+	*cp = '\0';
+	dir = cp2;
+
+	cp2 = cp + 1;
+	cp = strchr(cp2, ' ');
+	if (!cp)
+		return -EINVAL;
+	*cp = '\0';
+	fs = cp2;
+
+	flags = simple_strtoul(cp + 1, NULL, 0);
+	return tmy_add_mount_acl(dev, dir, fs, flags, is_delete);
+}
+
+/**
+ * tmy_read_mount_policy - read mount policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_mount_policy(struct io_buffer *head)
+{
+	struct list_head *pos;
+	list_for_each_cookie(pos, head->read_var2, &mount_list) {
+		struct mount_entry *ptr;
+		ptr = list_entry(pos, struct mount_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (tmy_io_printf(head, TMY_ALLOW_MOUNT "%s %s %s 0x%x\n",
+				  ptr->dev_name->name, ptr->dir_name->name,
+				  ptr->fs_type->name, ptr->flags))
+			return -ENOMEM;
+	}
+	return 0;
+}
+
+static int tmy_find_conceal(struct nameidata *nd,
+			    struct vfsmount *vfsmnt,
+			    struct dentry *dentry)
+{
+	int flag = 0;
+
+	if (IS_ROOT(dentry) || !d_unhashed(dentry)) {
+		while (1) {
+
+			if (nd->path.mnt->mnt_root == vfsmnt->mnt_root &&
+			    nd->path.dentry == dentry) {
+				flag = 1;
+				break;
+			}
+
+			if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
+
+				spin_lock(&vfsmount_lock);
+
+				if (vfsmnt->mnt_parent == vfsmnt) {
+					spin_unlock(&vfsmount_lock);
+					break;
+				}
+				dentry = vfsmnt->mnt_mountpoint;
+				vfsmnt = vfsmnt->mnt_parent;
+
+				spin_unlock(&vfsmount_lock);
+
+				continue;
+			}
+			dentry = dentry->d_parent;
+
+		}
+	}
+
+	return flag;
+}
+
+/**
+ * tmy_conceal_mount - check for conceal mount permission.
+ * @nd: pointer to "struct nameidata".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ *
+ * People seldom mount on directries that have submounts.
+ * For example, you don't mount on /usr/ directory if /usr/local/ directory
+ * is already mounted, do you?
+ * This function forbids such mount requests.
+ */
+int tmy_conceal_mount(struct nameidata *nd)
+{
+	int flag = 0;
+	const unsigned int mode = tmy_flags(TMY_DENY_CONCEAL_MOUNT);
+	const bool is_enforce = (mode == 3);
+	struct mnt_namespace *namespace = current->nsproxy->mnt_ns;
+
+	if (!mode)
+		return 0;
+
+	if (namespace) {
+		struct list_head *p;
+
+		list_for_each(p, &namespace->list) {
+			struct vfsmount *vfsmnt =
+				list_entry(p, struct vfsmount, mnt_list);
+			struct dentry *dentry = vfsmnt->mnt_root;
+
+			spin_lock(&dcache_lock);
+
+			flag = tmy_find_conceal(nd, vfsmnt, dentry);
+
+			spin_unlock(&dcache_lock);
+
+			if (flag)
+				break;
+		}
+	}
+
+
+	if (flag) {
+		int error = -EPERM;
+		char *dir;
+		dir = tmy_realpath_dentry(nd->path.dentry, nd->path.mnt);
+		if (dir) {
+			const char *exename = tmy_get_exe();
+			tmy_audit("TOMOYO-%s: mount %s (pid=%d:exe=%s): "
+				  "Permission denied.\n",
+				  tmy_getmsg(is_enforce),
+				  dir, current->pid, exename);
+			if (is_enforce &&
+			    tmy_supervisor("# %s is requesting\nmount on %s\n",
+					   exename, dir) == 0)
+				error = 0;
+
+			tmy_free(exename);
+		}
+		tmy_free(dir);
+
+		if (is_enforce)
+			return error;
+	}
+
+	return 0;
+}
+
+/************************  UMOUNT RESTRICTION HANDLER  ************************/
+
+static LIST_HEAD(no_umount_list);
+
+/* Add or remove a no-unmount entry. */
+static int tmy_add_no_umount_acl(const char *dir, const bool is_delete)
+{
+	struct no_umount_entry *new_entry;
+	struct no_umount_entry *ptr;
+	const struct path_info *saved_dir;
+	static DEFINE_MUTEX(mutex);
+	int error = -ENOMEM;
+
+	if (!tmy_correct_path(dir, 1, 0, 1, __FUNCTION__))
+		return -EINVAL;
+	saved_dir = tmy_save_name(dir);
+	if (!saved_dir)
+		return -ENOMEM;
+
+	mutex_lock(&mutex);
+
+	list_for_each_entry(ptr, &no_umount_list, list) {
+		if (ptr->dir == saved_dir) {
+			ptr->is_deleted = is_delete;
+			error = 0;
+			goto out;
+		}
+	}
+
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+
+	new_entry = tmy_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+
+	new_entry->dir = saved_dir;
+	list_add_tail_mb(&new_entry->list, &no_umount_list);
+	error = 0;
+out: ;
+
+	mutex_unlock(&mutex);
+
+	return error;
+}
+
+/**
+ * tmy_umount_perm - check for no-unmount permission.
+ * @mnt: pointer to "struct vfsmount".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_umount_perm(struct vfsmount *mnt)
+{
+	int error = -EPERM;
+	const char *dir0;
+	const unsigned int mode = tmy_flags(TMY_RESTRICT_UMOUNT);
+	const bool is_enforce = (mode == 3);
+
+	if (!mode)
+		return 0;
+
+	dir0 = tmy_realpath_dentry(mnt->mnt_root, mnt);
+
+	if (dir0) {
+		struct no_umount_entry *ptr;
+		struct path_info dir;
+		bool found = 0;
+
+		dir.name = dir0;
+		tmy_fill_path_info(&dir);
+
+		list_for_each_entry(ptr, &no_umount_list, list) {
+			if (ptr->is_deleted)
+				continue;
+			if (tmy_path_match(&dir, ptr->dir)) {
+				found = 1;
+				break;
+			}
+		}
+
+		if (found) {
+			const char *exename = tmy_get_exe();
+			tmy_audit("TOMOYO-%s: umount %s (pid=%d:exe=%s): "
+				  "Permission denied.\n",
+				  tmy_getmsg(is_enforce),
+				  dir0, current->pid, exename);
+			if (is_enforce &&
+			    tmy_supervisor("# %s is requesting\nunmount %s\n",
+					   exename, dir0) == 0)
+				error = 0;
+
+			tmy_free(exename);
+		} else {
+			error = 0;
+		}
+
+		tmy_free(dir0);
+	}
+
+	if (!is_enforce)
+		error = 0;
+	return error;
+}
+
+/**
+ * tmy_add_no_umount_policy - add or delete no-unmount policy.
+ * @data:      a line to parse.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_no_umount_policy(char *data, const bool is_delete)
+{
+	return tmy_add_no_umount_acl(data, is_delete);
+}
+
+/**
+ * tmy_read_no_umount_policy - read no-unmount policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_no_umount_policy(struct io_buffer *head)
+{
+	struct list_head *pos;
+	list_for_each_cookie(pos, head->read_var2, &no_umount_list) {
+		struct no_umount_entry *ptr;
+		ptr = list_entry(pos, struct no_umount_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (tmy_io_printf(head, TMY_DENY_UNMOUNT "%s\n",
+				  ptr->dir->name))
+			return -ENOMEM;
+	}
+	return 0;
+}
+
+/***** The structure for pivot_root restrictions. *****/
+
+struct pivot_root_entry {
+	struct list_head list;
+	const struct path_info *old_root;
+	const struct path_info *new_root;
+	bool is_deleted;
+};
+
+/**********************  PIVOT_ROOT RESTRICTION HANDLER  **********************/
+
+static LIST_HEAD(pivot_root_list);
+
+/* Add or remove a pivot_root entry. */
+static int tmy_add_pivot_root_acl(const char *old_root,
+				  const char *new_root,
+				  const bool is_delete)
+{
+	struct pivot_root_entry *new_entry;
+	struct pivot_root_entry *ptr;
+	const struct path_info *saved_old_root;
+	const struct path_info *saved_new_root;
+	static DEFINE_MUTEX(mutex);
+	int error = -ENOMEM;
+
+	if (!tmy_correct_path(old_root, 1, 0, 1, __FUNCTION__) ||
+	    !tmy_correct_path(new_root, 1, 0, 1, __FUNCTION__))
+		return -EINVAL;
+
+	saved_old_root = tmy_save_name(old_root);
+	if (!saved_old_root)
+		return -ENOMEM;
+	saved_new_root = tmy_save_name(new_root);
+	if (!saved_new_root)
+		return -ENOMEM;
+
+	mutex_lock(&mutex);
+
+	list_for_each_entry(ptr, &pivot_root_list, list) {
+		if (ptr->old_root == saved_old_root &&
+		    ptr->new_root == saved_new_root) {
+			ptr->is_deleted = is_delete;
+			error = 0;
+			goto out;
+		}
+	}
+
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+
+	new_entry = tmy_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+
+	new_entry->old_root = saved_old_root;
+	new_entry->new_root = saved_new_root;
+	list_add_tail_mb(&new_entry->list, &pivot_root_list);
+	error = 0;
+out: ;
+
+	mutex_unlock(&mutex);
+
+	return error;
+}
+
+/**
+ * tmy_pivot_root_perm - check for pivot_root permission.
+ * @old_nd:  pointer to "struct nameidata".
+ * @new_nd:  pointer to "struct nameidata".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_pivot_root_perm(struct nameidata *old_nd, struct nameidata *new_nd)
+{
+	int error = -EPERM;
+	const unsigned int mode = tmy_flags(TMY_RESTRICT_PIVOT_ROOT);
+	const bool is_enforce = (mode == 3);
+	char *old_root;
+	char *new_root;
+	struct path_info old_root_dir;
+	struct path_info new_root_dir;
+
+	if (!mode)
+		return 0;
+
+	old_root = tmy_realpath_dentry(old_nd->path.dentry, old_nd->path.mnt);
+	new_root = tmy_realpath_dentry(new_nd->path.dentry, new_nd->path.mnt);
+
+	if (!old_root || !new_root)
+		goto out;
+
+	old_root_dir.name = old_root;
+	tmy_fill_path_info(&old_root_dir);
+	new_root_dir.name = new_root;
+	tmy_fill_path_info(&new_root_dir);
+
+	if (old_root_dir.is_dir && new_root_dir.is_dir) {
+		struct pivot_root_entry *ptr;
+		list_for_each_entry(ptr, &pivot_root_list, list) {
+			if (ptr->is_deleted)
+				continue;
+			if (tmy_path_match(&old_root_dir, ptr->old_root) &&
+			    tmy_path_match(&new_root_dir, ptr->new_root)) {
+				error = 0;
+				break;
+			}
+		}
+	}
+
+out: ;
+	if (error) {
+		const char *exename = tmy_get_exe();
+		tmy_audit("TOMOYO-%s: pivot_root %s %s (pid=%d:exe=%s): "
+			  "Permission denied.\n", tmy_getmsg(is_enforce),
+			  new_root, old_root, current->pid, exename);
+		if (is_enforce &&
+		    tmy_supervisor("# %s is requesting\npivot_root %s %s\n",
+				   exename, new_root, old_root) == 0)
+			error = 0;
+
+		if (exename)
+			tmy_free(exename);
+
+		if (!is_enforce && mode == 1 && old_root && new_root) {
+			tmy_add_pivot_root_acl(old_root, new_root, 0);
+			tmy_update_counter(TMY_UPDATE_SYSTEMPOLICY);
+		}
+
+		if (!is_enforce)
+			error = 0;
+	}
+
+	tmy_free(old_root);
+	tmy_free(new_root);
+	return error;
+}
+
+/**
+ * tmy_add_pivot_root_policy - add or delete pivot_root policy.
+ * @data:      a line to parse.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_pivot_root_policy(char *data, const bool is_delete)
+{
+	char *cp = strchr(data, ' ');
+
+	if (!cp)
+		return -EINVAL;
+	*cp++ = '\0';
+
+	return tmy_add_pivot_root_acl(cp, data, is_delete);
+}
+
+/**
+ * tmy_read_pivot_root_policy - read pivot_root policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_pivot_root_policy(struct io_buffer *head)
+{
+	struct list_head *pos;
+	list_for_each_cookie(pos, head->read_var2, &pivot_root_list) {
+		struct pivot_root_entry *ptr;
+		ptr = list_entry(pos, struct pivot_root_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (tmy_io_printf(head, TMY_ALLOW_PIVOT_ROOT "%s %s\n",
+				  ptr->new_root->name, ptr->old_root->name))
+			return -ENOMEM;
+	}
+	return 0;
+}

-- 

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

* [TOMOYO #5 13/18] Signal control functions.
  2007-11-16 17:34 [TOMOYO #5 00/18] TOMOYO Linux - MAC based on process invocation history penguin-kernel
                   ` (11 preceding siblings ...)
  2007-11-16 17:34 ` [TOMOYO #5 12/18] Namespace manipulation " penguin-kernel
@ 2007-11-16 17:34 ` penguin-kernel
  2007-11-16 17:34 ` [TOMOYO #5 14/18] Capability access " penguin-kernel
                   ` (4 subsequent siblings)
  17 siblings, 0 replies; 39+ messages in thread
From: penguin-kernel @ 2007-11-16 17:34 UTC (permalink / raw)
  To: akpm; +Cc: linux-kernel, linux-security-module, Kentaro Takeda, Tetsuo Handa

[-- Attachment #1: tomoyo-signal.diff --]
[-- Type: text/plain, Size: 6475 bytes --]

TOMOYO Linux checks sending signal by signal number and
the domain of target process. In order to check signal
permission, LSM expansion patch [TOMOYO 18/18] is needed.

Each permission can be automatically accumulated into
the policy of each domain using 'learning mode'.

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

--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6-mm/security/tomoyo/signal.c	2007-11-14 15:15:44.000000000 +0900
@@ -0,0 +1,227 @@
+/*
+ * security/tomoyo/signal.c
+ *
+ * Signal access contol functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+/*************************  AUDIT FUNCTIONS  *************************/
+
+static int tmy_audit_signal_log(const int signal,
+				const struct path_info *dest_domain,
+				const bool is_granted,
+				const u8 profile,
+				const unsigned int mode)
+{
+	char *buf;
+	int len;
+
+	if (is_granted) {
+		if (!tmy_audit_grant())
+			return 0;
+	} else {
+		if (!tmy_audit_reject())
+			return 0;
+	}
+
+	len = dest_domain->total_len;
+	buf = tmy_init_audit_log(&len, profile, mode);
+
+	if (!buf)
+		return -ENOMEM;
+
+	snprintf(buf + strlen(buf),
+		 len - strlen(buf) - 1,
+		 "%s%d %s\n",
+		 TMY_ALLOW_SIGNAL, signal, dest_domain->name);
+
+	return tmy_write_audit_log(buf, is_granted);
+}
+
+/*************************  SIGNAL ACL HANDLER  *************************/
+
+static int tmy_add_signal_entry(const u16 sig, const char *dest_pattern,
+				struct domain_info *domain,
+				const struct condition_list *cond,
+				const bool is_delete)
+{
+	struct acl_info *ptr;
+	struct signal_acl *acl;
+	const struct path_info *saved_dest_pattern;
+	int error = -ENOMEM;
+
+	if (!domain)
+		return -EINVAL;
+	if (!dest_pattern ||
+	    !tmy_is_correct_domain(dest_pattern, __FUNCTION__))
+		return -EINVAL;
+
+	saved_dest_pattern = tmy_save_name(dest_pattern);
+	if (!saved_dest_pattern)
+		return -ENOMEM;
+
+	mutex_lock(&domain_acl_lock);
+
+	if (is_delete)
+		goto remove;
+
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		acl = (struct signal_acl *) ptr;
+
+		if (ptr->type == TMY_TYPE_SIGNAL_ACL && acl->sig == sig
+		    && ptr->cond == cond
+		    && !tmy_pathcmp(acl->domainname, saved_dest_pattern)) {
+			ptr->is_deleted = 0;
+			/* Found. Nothing to do. */
+			error = 0;
+			goto ok;
+		}
+	}
+	/* Not found. Append it to the tail. */
+	acl = tmy_alloc_element(sizeof(*acl));
+	if (!acl)
+		goto ok;
+
+	acl->head.type = TMY_TYPE_SIGNAL_ACL;
+	acl->head.cond = cond;
+	acl->sig = sig;
+	acl->domainname = saved_dest_pattern;
+	error = tmy_add_acl(domain, (struct acl_info *) acl);
+	goto ok;
+remove: ;
+	error = -ENOENT;
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		acl = (struct signal_acl *) ptr;
+		if (ptr->type != TMY_TYPE_SIGNAL_ACL || ptr->cond != cond ||
+		    ptr->is_deleted || acl->sig != sig ||
+		    tmy_pathcmp(acl->domainname, saved_dest_pattern))
+			continue;
+		error = tmy_del_acl(ptr);
+		break;
+	}
+
+ok: ;
+	mutex_unlock(&domain_acl_lock);
+
+	return error;
+}
+
+/**
+ * tmy_signal_acl - check permission for kill(2)/tkill(2)/tgkill(2).
+ * @sig:  signal number.
+ * @pid:  pid of destination process.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_signal_acl(const int sig, const int pid)
+{
+	struct domain_info *domain = TMY_SECURITY->domain;
+	struct domain_info *dest = NULL;
+	const char *dest_pattern;
+	struct acl_info *ptr;
+	const u16 hash = sig;
+	const u8 profile = domain->profile;
+	const unsigned int mode = tmy_flags(TMY_MAC_FOR_SIGNAL);
+	const bool is_enforce = (mode == 3);
+	bool found = 0;
+
+	if (!mode)
+		return 0;
+	if (!sig)
+		return 0; /* No check for NULL signal. */
+	if (current->pid == pid) {
+		tmy_audit_signal_log(sig, domain->domainname, 1, profile, mode);
+		return 0; /* No check for self. */
+	}
+
+	{ /* Simplified checking. */
+		struct task_struct *p = NULL;
+		read_lock(&tasklist_lock);
+		if (pid > 0)
+			p = find_task_by_pid((pid_t) pid);
+		else if (pid == 0)
+			p = current;
+		else if (pid == -1)
+			dest = &KERNEL_DOMAIN;
+		else
+			p = find_task_by_pid((pid_t) -pid);
+		if (p)
+			/* "struct task_struct"->security is not NULL. */
+			dest = ((struct tmy_security *) p->security)->domain;
+		read_unlock(&tasklist_lock);
+		if (!dest)
+			return 0; /* I can't find destinatioin. */
+	}
+
+	if (domain == dest) {
+		tmy_audit_signal_log(sig, dest->domainname, 1, profile, mode);
+		return 0;
+	}
+
+	dest_pattern = dest->domainname->name;
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		struct signal_acl *acl = (struct signal_acl *) ptr;
+
+		if (ptr->type == TMY_TYPE_SIGNAL_ACL && ptr->is_deleted == 0
+		    && acl->sig == hash &&
+		    tmy_check_condition(ptr->cond, NULL) == 0) {
+			const int len = acl->domainname->total_len;
+
+			if (strncmp(acl->domainname->name,
+				    dest_pattern, len) == 0
+			    && (dest_pattern[len] == ' ' ||
+				dest_pattern[len] == '\0')) {
+				found = 1;
+				break;
+			}
+		}
+	}
+
+	tmy_audit_signal_log(sig, dest->domainname, found, profile, mode);
+
+	if (found)
+		return 0;
+
+	if (tmy_flags(TMY_VERBOSE))
+		tmy_audit("TOMOYO-%s: Signal %d to %s denied for %s\n",
+			  tmy_getmsg(is_enforce), sig,
+			  tmy_lastname(dest), tmy_lastname(domain));
+
+	if (is_enforce)
+		return tmy_supervisor("%s\n" TMY_ALLOW_SIGNAL "%d %s\n",
+				      domain->domainname->name,
+				      sig, dest_pattern);
+	if (mode == 1 && tmy_quota())
+		tmy_add_signal_entry(sig, dest_pattern, domain, NULL, 0);
+
+	return 0;
+}
+
+/**
+ * tmy_add_signal_policy - add or delete signal policy.
+ * @data:      a line to parse.
+ * @domain:    pointer to "struct domain_info".
+ * @cond:      pointer to "struct condition_list". May be NULL.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_signal_policy(char *data,
+			  struct domain_info *domain,
+			  const struct condition_list *cond,
+			  const bool is_delete)
+{
+	int sig;
+	char *domainname = strchr(data, ' ');
+
+	if (sscanf(data, "%d", &sig) == 1 && domainname &&
+	    tmy_is_domain_def(domainname + 1))
+		return tmy_add_signal_entry(sig, domainname + 1, domain,
+					    cond, is_delete);
+
+	return -EINVAL;
+}

-- 

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

* [TOMOYO #5 14/18] Capability access control functions.
  2007-11-16 17:34 [TOMOYO #5 00/18] TOMOYO Linux - MAC based on process invocation history penguin-kernel
                   ` (12 preceding siblings ...)
  2007-11-16 17:34 ` [TOMOYO #5 13/18] Signal " penguin-kernel
@ 2007-11-16 17:34 ` penguin-kernel
  2007-11-16 17:34 ` [TOMOYO #5 15/18] LSM adapter functions penguin-kernel
                   ` (3 subsequent siblings)
  17 siblings, 0 replies; 39+ messages in thread
From: penguin-kernel @ 2007-11-16 17:34 UTC (permalink / raw)
  To: akpm; +Cc: linux-kernel, linux-security-module, Kentaro Takeda, Tetsuo Handa

[-- Attachment #1: tomoyo-capability.diff --]
[-- Type: text/plain, Size: 10749 bytes --]

TOMOYO Linux checks permission for non-POSIX capability
so that the number of capabilities won't be limited to 32 or 64.

Each permission can be automatically accumulated into
the policy of each domain using 'learning mode'.

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

--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6-mm/security/tomoyo/capability.c	2007-11-14 15:15:44.000000000 +0900
@@ -0,0 +1,320 @@
+/*
+ * security/tomoyo/capability.c
+ *
+ * Capability access control functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+static struct {
+	const char *keyword;
+	unsigned int current_value;
+	const char *capability_name;
+} capability_control_array[TMY_MAX_CAPABILITY_INDEX] = {
+	[TMY_INET_STREAM_SOCKET_CREATE]  = /* OK */
+	{ "inet_tcp_create", 0, "socket(PF_INET, SOCK_STREAM)" },
+	[TMY_INET_STREAM_SOCKET_LISTEN]  = /* OK */
+	{ "inet_tcp_listen", 0, "listen(PF_INET, SOCK_STREAM)" },
+	[TMY_INET_STREAM_SOCKET_CONNECT] = /* OK */
+	{ "inet_tcp_connect", 0, "connect(PF_INET, SOCK_STREAM)" },
+	[TMY_USE_INET_DGRAM_SOCKET]      = /* OK */
+	{ "use_inet_udp", 0, "socket(PF_INET, SOCK_DGRAM)" },
+	[TMY_USE_INET_RAW_SOCKET]        = /* OK */
+	{ "use_inet_ip", 0, "socket(PF_INET, SOCK_RAW)" },
+	[TMY_USE_ROUTE_SOCKET]           = /* OK */
+	{ "use_route", 0, "socket(PF_ROUTE)" },
+	[TMY_USE_PACKET_SOCKET]          = /* OK */
+	{ "use_packet", 0, "socket(PF_PACKET)" },
+	[TMY_SYS_MOUNT]                  = /* OK */
+	{ "SYS_MOUNT", 0, "sys_mount()" },
+	[TMY_SYS_UMOUNT]                 = /* OK */
+	{ "SYS_UMOUNT", 0, "sys_umount()" },
+	[TMY_SYS_REBOOT]                 = /* Too many hooks. */
+	{ "SYS_REBOOT", 0, "sys_reboot()" },
+	[TMY_SYS_CHROOT]                 = /* OK */
+	{ "SYS_CHROOT", 0, "sys_chroot()" },
+	[TMY_SYS_KILL]                   = /* No appropriate hook. */
+	{ "SYS_KILL", 0, "sys_kill()" },
+	[TMY_SYS_VHANGUP]                = /* Too many hooks. */
+	{ "SYS_VHANGUP", 0, "sys_vhangup()" },
+	[TMY_SYS_SETTIME]                = /* Too many hooks. */
+	{ "SYS_TIME", 0, "sys_settimeofday()" },
+	[TMY_SYS_NICE]                   = /* No appropriate hook. */
+	{ "SYS_NICE", 0, "sys_nice()" },
+	[TMY_SYS_SETHOSTNAME]            = /* No appropriate hook. */
+	{ "SYS_SETHOSTNAME", 0, "sys_sethostname()" },
+	[TMY_USE_KERNEL_MODULE]          = /* Too many hooks. */
+	{ "use_kernel_module", 0, "kernel_module" },
+	[TMY_CREATE_FIFO]                = /* OK */
+	{ "create_fifo", 0, "mknod(FIFO)" },
+	[TMY_CREATE_BLOCK_DEV]           = /* OK */
+	{ "create_block_dev", 0, "mknod(BDEV)" },
+	[TMY_CREATE_CHAR_DEV]            = /* OK */
+	{ "create_char_dev", 0, "mknod(CDEV)" },
+	[TMY_CREATE_UNIX_SOCKET]         = /* OK */
+	{ "create_unix_socket", 0, "mknod(SOCKET)" },
+	[TMY_SYS_LINK]                   = /* OK */
+	{ "SYS_LINK", 0, "sys_link()" },
+	[TMY_SYS_SYMLINK]                = /* OK */
+	{ "SYS_SYMLINK", 0, "sys_symlink()" },
+	[TMY_SYS_RENAME]                 = /* OK */
+	{ "SYS_RENAME", 0, "sys_rename()" },
+	[TMY_SYS_UNLINK]                 = /* OK */
+	{ "SYS_UNLINK", 0, "sys_unlink()" },
+	[TMY_SYS_CHMOD]                  = /* OK */
+	{ "SYS_CHMOD", 0, "sys_chmod()" },
+	[TMY_SYS_CHOWN]                  = /* OK */
+	{ "SYS_CHOWN", 0, "sys_chown()" },
+	[TMY_SYS_IOCTL]                  = /* Too many hooks. */
+	{ "SYS_IOCTL", 0, "sys_ioctl()" },
+	[TMY_SYS_KEXEC_LOAD]             = /* No appropriate hook. */
+	{ "SYS_KEXEC_LOAD", 0, "sys_kexec_load()" },
+	[TMY_SYS_PIVOT_ROOT]             = /* OK */
+	{ "SYS_PIVOT_ROOT", 0, "sys_pivot_root()" },
+};
+
+struct profile {
+	unsigned char value[TMY_MAX_CAPABILITY_INDEX];
+};
+
+static struct profile *profile_ptr[TMY_MAX_PROFILES];
+
+/*************************  UTILITY FUNCTIONS  *************************/
+
+const char *tmy_capability2keyword(const unsigned int capability)
+{
+	return capability < TMY_MAX_CAPABILITY_INDEX ?
+		capability_control_array[capability].keyword : NULL;
+}
+
+static const char *tmy_capability2name(const unsigned int capability)
+{
+	return capability < TMY_MAX_CAPABILITY_INDEX ?
+		capability_control_array[capability].capability_name : NULL;
+}
+
+/* Check whether the given capability control is enabled. */
+static unsigned int tmy_capability_flags(const unsigned int index)
+{
+	const u8 profile = TMY_SECURITY->domain->profile;
+	/* All operations might sleep. See tmy_supervisor(). */
+	might_sleep();
+	if (in_interrupt())
+		return 0;
+	return sbin_init_started && index < TMY_MAX_CAPABILITY_INDEX
+#if TMY_MAX_PROFILES != 256
+		&& profile < TMY_MAX_PROFILES
+#endif
+		&& profile_ptr[profile] ?
+		profile_ptr[profile]->value[index] : 0;
+}
+
+static struct profile *tmy_new_capability_profile(const unsigned int profile)
+{
+	static DEFINE_MUTEX(mutex);
+	struct profile *ptr;
+	int i;
+	if (profile >= TMY_MAX_PROFILES)
+		return NULL;
+	mutex_lock(&mutex);
+	ptr = profile_ptr[profile];
+	if (ptr)
+		goto ok;
+	ptr = tmy_alloc_element(sizeof(*ptr));
+	if (!ptr)
+		goto ok;
+	for (i = 0; i < TMY_MAX_CAPABILITY_INDEX; i++)
+		ptr->value[i] = capability_control_array[i].current_value;
+	mb(); /* Avoid out-of-order execution. */
+	profile_ptr[profile] = ptr;
+ok: ;
+	mutex_unlock(&mutex);
+	return ptr;
+}
+
+int tmy_set_capability_profile(const char *data, unsigned int value,
+			       const unsigned int profile)
+{
+	int i;
+	struct profile *ptr;
+	ptr = tmy_new_capability_profile(profile);
+	if (!ptr)
+		return -EINVAL;
+	for (i = 0; i < TMY_MAX_CAPABILITY_INDEX; i++) {
+		if (strcmp(data, capability_control_array[i].keyword))
+			continue;
+		if (value > 3)
+			value = 3;
+		ptr->value[i] = value;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+int tmy_read_capability_profile(struct io_buffer *head)
+{
+	int step;
+	for (step = head->read_step;
+	     step < TMY_MAX_PROFILES * TMY_MAX_CAPABILITY_INDEX; step++) {
+		const int i = step / TMY_MAX_CAPABILITY_INDEX;
+		const int j = step % TMY_MAX_CAPABILITY_INDEX;
+		const struct profile *profile = profile_ptr[i];
+		head->read_step = step;
+		if (!profile)
+			continue;
+		if (tmy_io_printf(head, "%u-" TMY_MAC_FOR_CAPABILITY "%s=%u\n",
+				  i, capability_control_array[j].keyword,
+				  profile->value[j]))
+			break;
+	}
+	return step < TMY_MAX_PROFILES * TMY_MAX_CAPABILITY_INDEX ? -ENOMEM : 0;
+}
+
+/*************************  AUDIT FUNCTIONS  *************************/
+
+static int tmy_audit_capability_log(const unsigned int capability,
+				    const bool is_granted,
+				    const u8 profile, const unsigned int mode)
+{
+	char *buf;
+	int len = 64 ;
+
+	if (is_granted) {
+		if (!tmy_audit_grant())
+			return 0;
+	} else {
+		if (!tmy_audit_reject())
+			return 0;
+	}
+
+	buf = tmy_init_audit_log(&len, profile, mode);
+
+	if (!buf)
+		return -ENOMEM;
+
+	snprintf(buf + strlen(buf),
+		 len - strlen(buf) - 1,
+		 TMY_ALLOW_CAPABILITY "%s\n",
+		 tmy_capability2keyword(capability));
+
+	return tmy_write_audit_log(buf, is_granted);
+}
+
+/*************************  CAPABILITY ACL HANDLER  *************************/
+
+static int tmy_add_capability_acl(const unsigned int capability,
+				  struct domain_info *domain,
+				  const struct condition_list *condition,
+				  const bool is_delete)
+{
+	struct acl_info *ptr;
+	struct capability_acl *acl;
+	int error = -ENOMEM;
+	const u16 hash = capability;
+	if (!domain)
+		return -EINVAL;
+	mutex_lock(&domain_acl_lock);
+	if (is_delete)
+		goto remove;
+
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		acl = (struct capability_acl *) ptr;
+		if (ptr->type == TMY_TYPE_CAPABILITY_ACL
+		    && acl->capability == hash
+		    && ptr->cond == condition) {
+			ptr->is_deleted = 0;
+			/* Found. Nothing to do. */
+			error = 0;
+			tmy_update_counter(TMY_UPDATE_DOMAINPOLICY);
+			goto ok;
+		}
+	}
+	/* Not found. Append it to the tail. */
+	acl = tmy_alloc_element(sizeof(*acl));
+	if (!acl)
+		goto ok;
+	acl->head.type = TMY_TYPE_CAPABILITY_ACL;
+	acl->head.cond = condition;
+	acl->capability = hash;
+	error = tmy_add_acl(domain, (struct acl_info *) acl);
+	goto ok;
+remove: ;
+	error = -ENOENT;
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		acl = (struct capability_acl *) ptr;
+		if (ptr->type != TMY_TYPE_CAPABILITY_ACL || ptr->is_deleted ||
+		    acl->capability != hash || ptr->cond != condition) continue;
+		error = tmy_del_acl(ptr);
+		break;
+	}
+ok: ;
+	mutex_unlock(&domain_acl_lock);
+	return error;
+}
+
+/**
+ * tmy_capable - check permission for capability.
+ * @capability: capability index.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_capable(const unsigned int capability)
+{
+	struct domain_info * const domain = TMY_SECURITY->domain;
+	struct acl_info *ptr;
+	const u8 profile = domain->profile;
+	const unsigned int mode = tmy_capability_flags(capability);
+	const bool is_enforce = (mode == 3);
+	const u16 hash = capability;
+	if (!mode)
+		return 0;
+	list_for_each_entry(ptr, &domain->acl_info_list, list) {
+		struct capability_acl *acl = (struct capability_acl *) ptr;
+		if (ptr->type != TMY_TYPE_CAPABILITY_ACL || ptr->is_deleted
+		    || acl->capability != hash
+		    || tmy_check_condition(ptr->cond, NULL))
+			continue;
+		tmy_audit_capability_log(capability, 1, profile, mode);
+		return 0;
+	}
+	if (tmy_flags(TMY_VERBOSE))
+		tmy_audit("TOMOYO-%s: %s denied for %s\n",
+			  tmy_getmsg(is_enforce),
+			  tmy_capability2name(capability),
+			  tmy_lastname(domain));
+	tmy_audit_capability_log(capability, 0, profile, mode);
+	if (is_enforce)
+		return tmy_supervisor("%s\n" TMY_ALLOW_CAPABILITY "%s\n",
+				      domain->domainname->name,
+				      tmy_capability2keyword(capability));
+	if (mode == 1 && tmy_quota())
+		tmy_add_capability_acl(capability, domain, NULL, 0);
+	return 0;
+}
+
+/**
+ * tmy_add_capability_policy - add or delete capability policy.
+ * @data:      a line to parse.
+ * @domain:    pointer to "struct domain_info".
+ * @cond:      pointer to "struct condition_list". May be NULL.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_capability_policy(char *data, struct domain_info *domain,
+			      const struct condition_list *cond,
+			      const bool is_delete)
+{
+	unsigned int capability;
+	for (capability = 0; capability < TMY_MAX_CAPABILITY_INDEX;
+	     capability++) {
+		if (strcmp(data, capability_control_array[capability].keyword))
+			continue;
+		return tmy_add_capability_acl(capability, domain,
+					      cond, is_delete);
+	}
+	return -EINVAL;
+}

-- 

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

* [TOMOYO #5 15/18] LSM adapter functions.
  2007-11-16 17:34 [TOMOYO #5 00/18] TOMOYO Linux - MAC based on process invocation history penguin-kernel
                   ` (13 preceding siblings ...)
  2007-11-16 17:34 ` [TOMOYO #5 14/18] Capability access " penguin-kernel
@ 2007-11-16 17:34 ` penguin-kernel
  2007-11-16 17:34 ` [TOMOYO #5 16/18] Conditional permission support penguin-kernel
                   ` (2 subsequent siblings)
  17 siblings, 0 replies; 39+ messages in thread
From: penguin-kernel @ 2007-11-16 17:34 UTC (permalink / raw)
  To: akpm; +Cc: linux-kernel, linux-security-module, Kentaro Takeda, Tetsuo Handa

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

To avoid namespace_sem deadlock, this patch uses
"current->last_vfsmount" associated by wrapper functions.

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

--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6-mm/security/tomoyo/tomoyo.c	2007-11-14 15:56:26.000000000 +0900
@@ -0,0 +1,822 @@
+/*
+ * security/tomoyo/tomoyo.c
+ *
+ * LSM hooks for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+#define MAX_SOCK_ADDR 128 /* net/socket.c */
+
+LIST_HEAD(domain_list);
+
+static struct kmem_cache *tmy_cachep;
+
+static int tmy_task_alloc_security(struct task_struct *p)
+{
+	struct tmy_security *ptr = kmem_cache_alloc(tmy_cachep, GFP_KERNEL);
+
+	if (!ptr)
+		return -ENOMEM;
+	memcpy(ptr, TMY_SECURITY, sizeof(*ptr));
+	p->security = ptr;
+	return 0;
+}
+
+static void tmy_task_free_security(struct task_struct *p)
+{
+	kmem_cache_free(tmy_cachep, p->security);
+}
+
+static int tmy_bprm_alloc_security(struct linux_binprm *bprm)
+{
+	TMY_SECURITY->prev_domain = TMY_SECURITY->domain;
+	return 0;
+}
+
+static int tmy_bprm_check_security(struct linux_binprm *bprm)
+{
+	struct domain_info *next_domain = NULL;
+	int retval = 0;
+
+	tmy_load_policy(bprm->filename);
+
+	/*
+	 * TMY_CHECK_READ_FOR_OPEN_EXEC bit indicates whether this function is
+	 * called by do_execve() or not.
+	 * If called by do_execve(), I do domain transition.
+	 */
+	if (!(TMY_SECURITY->flags
+	      & TMY_CHECK_READ_FOR_OPEN_EXEC)) {
+		retval = tmy_find_next_domain(bprm, &next_domain);
+		if (retval == 0) {
+			TMY_SECURITY->domain = next_domain;
+			TMY_SECURITY->flags |=
+				TMY_CHECK_READ_FOR_OPEN_EXEC;
+		}
+	}
+
+	return retval;
+}
+
+static void tmy_bprm_post_apply_creds(struct linux_binprm *bprm)
+{
+	TMY_SECURITY->prev_domain = TMY_SECURITY->domain;
+}
+
+static void tmy_bprm_free_security(struct linux_binprm *bprm)
+{
+	TMY_SECURITY->domain = TMY_SECURITY->prev_domain;
+	TMY_SECURITY->flags &= ~TMY_CHECK_READ_FOR_OPEN_EXEC;
+}
+
+static int tmy_sysctl(struct ctl_table *table, int op)
+{
+	int error;
+	char *name;
+
+	if ((op & 6) == 0)
+		return 0;
+
+	name = sysctlpath_from_table(table);
+	if (!name)
+		return -ENOMEM;
+
+	error = tmy_file_perm(name, op & 6, "sysctl");
+	tmy_free(name);
+
+	return error;
+}
+
+static int tmy_inode_permission(struct inode *inode,
+				int mask,
+				struct nameidata *nd)
+{
+	int flag = 0;
+
+	if (S_ISDIR(inode->i_mode)) /* ignore because inode is directory */
+		return 0;
+	/*
+	if (!nd) {
+		printk("tmy_inode_permission: NULL nameidata\n");
+		dump_stack();
+		return 0;
+	} else if (!nd->mnt) {
+		printk("tmy_inode_permission: NULL vfsmount\n");
+		dump_stack();
+		return 0;
+	}
+	*/
+	if (!nd || !nd->path.dentry || !nd->path.mnt)
+		return 0;
+	/*
+	 * If called by other than do_execve(), I check for read permission of
+	 * interpreter.
+	 * Unlike DAC, I don't check for read permission of pathname passed to
+	 * do_execve().
+	 * TOMOYO Linux checks for program's execute permission and
+	 * interpreter's read permission.
+	 */
+	if ((mask == MAY_EXEC) &&
+	    (TMY_SECURITY->flags & TMY_CHECK_READ_FOR_OPEN_EXEC))
+		mask = MAY_READ;
+	if ((mask == MAY_EXEC) || (mask == 0))
+		return 0;
+
+	if (mask == (MAY_READ | MAY_EXEC))
+		flag |= O_RDONLY + 1;
+	else {
+		if (mask & MAY_READ)
+			flag |= O_RDONLY + 1;
+		if (mask & MAY_WRITE)
+			flag |= O_WRONLY + 1;
+		if ((mask & MAY_APPEND))
+			flag |= O_APPEND;
+	}
+
+	return tmy_open_perm(nd->path.dentry, nd->path.mnt, flag);
+}
+
+static int tmy_do_single_write_perm(int operation, struct dentry *dentry)
+{
+	struct vfsmount *mnt = current->last_vfsmount;
+	if (!dentry || !mnt)
+		return 0;
+	if (!sbin_init_started)
+		return 0;
+	return tmy_single_write_perm(operation, dentry, mnt);
+}
+
+static int tmy_inode_setattr(struct dentry *dentry, struct iattr *iattr)
+{
+	int err = 0;
+	unsigned int ia_valid = iattr->ia_valid;
+	if (ia_valid & ATTR_MODE)
+		err = tmy_capable(TMY_SYS_CHMOD);
+	if (!err && (ia_valid & (ATTR_UID | ATTR_GID)))
+		err = tmy_capable(TMY_SYS_CHOWN);
+	if (err)
+		return err;
+	if (ia_valid & ATTR_SIZE)
+		return tmy_do_single_write_perm(TMY_TYPE_TRUNCATE_ACL, dentry);
+	return 0;
+}
+
+static int tmy_inode_create(struct inode *dir, struct dentry *dentry, int mode)
+{
+	return tmy_do_single_write_perm(TMY_TYPE_CREATE_ACL, dentry);
+}
+
+static int tmy_inode_unlink(struct inode *dir, struct dentry *dentry)
+{
+	const int err = tmy_capable(TMY_SYS_UNLINK);
+	if (err)
+		return err;
+	return tmy_do_single_write_perm(TMY_TYPE_UNLINK_ACL, dentry);
+}
+
+static int tmy_inode_mkdir(struct inode *dir, struct dentry *dentry, int mode)
+{
+	return tmy_do_single_write_perm(TMY_TYPE_MKDIR_ACL, dentry);
+}
+
+static int tmy_inode_rmdir(struct inode *dir, struct dentry *dentry)
+{
+	return tmy_do_single_write_perm(TMY_TYPE_RMDIR_ACL, dentry);
+}
+
+static int tmy_inode_symlink(struct inode *dir,
+			     struct dentry *dentry,
+			     const char *old_name)
+{
+	const int err = tmy_capable(TMY_SYS_SYMLINK);
+	if (err)
+		return err;
+	return tmy_do_single_write_perm(TMY_TYPE_SYMLINK_ACL, dentry);
+}
+
+static int tmy_inode_mknod(struct inode *inode,
+			   struct dentry *dentry,
+			   int mode,
+			   dev_t dev)
+{
+	int err;
+	if (S_ISCHR(mode)) {
+		err = tmy_capable(TMY_CREATE_CHAR_DEV);
+		if (err)
+			return err;
+		return tmy_do_single_write_perm(TMY_TYPE_MKCHAR_ACL, dentry);
+	}
+	if (S_ISBLK(mode)) {
+		err = tmy_capable(TMY_CREATE_BLOCK_DEV);
+		if (err)
+			return err;
+		return tmy_do_single_write_perm(TMY_TYPE_MKBLOCK_ACL, dentry);
+	}
+	if (S_ISFIFO(mode)) {
+		err = tmy_capable(TMY_CREATE_FIFO);
+		if (err)
+			return err;
+		return tmy_do_single_write_perm(TMY_TYPE_MKFIFO_ACL, dentry);
+	}
+	if (S_ISSOCK(mode)) {
+		err = tmy_capable(TMY_CREATE_UNIX_SOCKET);
+		if (err)
+			return err;
+		return tmy_do_single_write_perm(TMY_TYPE_MKSOCK_ACL, dentry);
+	}
+
+	return 0;
+}
+
+static int tmy_do_double_write_perm(int operation,
+				    struct dentry *old_dentry,
+				    struct dentry *new_dentry)
+{
+	struct vfsmount *mnt = current->last_vfsmount;
+	if (!old_dentry || !new_dentry || !mnt)
+		return 0;
+	if (!sbin_init_started)
+		return 0;
+	return tmy_double_write_perm(operation, old_dentry, mnt,
+				     new_dentry, mnt);
+}
+
+static int tmy_inode_link(struct dentry *old_dentry,
+			  struct inode *inode,
+			  struct dentry *new_dentry)
+{
+	const int err = tmy_capable(TMY_SYS_LINK);
+	if (err)
+		return err;
+	return tmy_do_double_write_perm(TMY_TYPE_LINK_ACL,
+					old_dentry, new_dentry);
+}
+
+static int tmy_inode_rename(struct inode *old_inode,
+			    struct dentry *old_dentry,
+			    struct inode *new_inode,
+			    struct dentry *new_dentry)
+{
+	const int err = tmy_capable(TMY_SYS_RENAME);
+	if (err)
+		return err;
+	return tmy_do_double_write_perm(TMY_TYPE_RENAME_ACL,
+					old_dentry,
+					new_dentry);
+}
+
+static int tmy_file_fcntl(struct file *file,
+			  unsigned int cmd,
+			  unsigned long arg)
+{
+	if (!(arg & O_APPEND))
+		return tmy_rewrite_perm(file);
+	return 0;
+}
+
+static int tmy_file_ioctl(struct file *file, unsigned int cmd,
+			  unsigned long arg)
+{
+	switch (cmd) {
+	case FIOCLEX:
+	case FIONCLEX:
+	case FIONBIO:
+	case FIOASYNC:
+	case FIOQSIZE:
+		return 0;
+	case FIBMAP:
+	case FIGETBSZ:
+	case FIONREAD:
+		if (S_ISREG(file->f_path.dentry->d_inode->i_mode))
+			return 0;
+	}
+	return tmy_capable(TMY_SYS_IOCTL);
+}
+
+static int tmy_socket_listen(struct socket *sock, int backlog)
+{
+	char addr[MAX_SOCK_ADDR];
+	int addr_len;
+	int error;
+
+	/* I don't check if called by kernel process. */
+	if (segment_eq(get_fs(), KERNEL_DS))
+		return 0;
+
+	if (sock->type != SOCK_STREAM)
+		return 0;
+	if (sock->sk->sk_family != PF_INET && sock->sk->sk_family != PF_INET6)
+		return 0;
+
+	error = tmy_capable(TMY_INET_STREAM_SOCKET_LISTEN);
+	if (error)
+		return error;
+
+	if (sock->ops->getname(sock, (struct sockaddr *) addr, &addr_len, 0))
+		return -EPERM;
+
+	switch (((struct sockaddr *) addr)->sa_family) {
+		struct sockaddr_in6 *in6;
+		struct sockaddr_in *in;
+
+	case AF_INET6:
+		in6 = (struct sockaddr_in6 *) addr;
+		error = tmy_network_listen_acl(1, in6->sin6_addr.s6_addr,
+					       in6->sin6_port);
+		break;
+	case AF_INET:
+		in = (struct sockaddr_in *) addr;
+		error = tmy_network_listen_acl(0, (u8 *) &in->sin_addr,
+					       in->sin_port);
+		break;
+	}
+
+	return error;
+}
+
+static int tmy_socket_create(int family, int type, int protocol, int kern)
+{
+	int error = 0;
+	if (kern)
+		return 0;
+	if (family == PF_INET || family == PF_INET6) {
+		switch (type) {
+		case SOCK_STREAM:
+			return tmy_capable(TMY_INET_STREAM_SOCKET_CREATE);
+			break;
+		case SOCK_DGRAM:
+			return tmy_capable(TMY_USE_INET_DGRAM_SOCKET);
+			break;
+		case SOCK_RAW:
+			return tmy_capable(TMY_USE_INET_RAW_SOCKET);
+			break;
+		}
+	} else if (family == PF_PACKET) {
+		return tmy_capable(TMY_USE_PACKET_SOCKET);
+	} else if (family == PF_ROUTE) {
+		return tmy_capable(TMY_USE_ROUTE_SOCKET);
+	}
+	return error;
+}
+
+static int tmy_socket_connect(struct socket *sock,
+			      struct sockaddr *addr,
+			      int addr_len0)
+{
+	unsigned int addr_len = (unsigned int) addr_len0;
+	int error = 0;
+	const unsigned int type = sock->type;
+
+	/* I don't check if called by kernel process. */
+	if (segment_eq(get_fs(), KERNEL_DS))
+		return 0;
+
+	if (type == SOCK_STREAM) {
+		error = tmy_capable(TMY_INET_STREAM_SOCKET_CONNECT);
+		if (error)
+			return error;
+	}
+
+	if (type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_RAW)
+		return 0;
+
+	switch (addr->sa_family) {
+		struct sockaddr_in6 *in6;
+		struct sockaddr_in *in;
+
+	case AF_INET6:
+		if (addr_len < SIN6_LEN_RFC2133)
+			break;
+
+		in6 = (struct sockaddr_in6 *) addr;
+		if (type != SOCK_RAW)
+			error = tmy_network_connect_acl(1, type,
+							in6->sin6_addr.s6_addr,
+							in6->sin6_port);
+		else {
+			const u16 port = htons(sock->sk->sk_protocol);
+
+			error = tmy_network_connect_acl(1, SOCK_RAW,
+							in6->sin6_addr.s6_addr,
+							port);
+		}
+		break;
+
+	case AF_INET:
+		if (addr_len < sizeof(struct sockaddr_in))
+			break;
+
+		in = (struct sockaddr_in *) addr;
+		if (type != SOCK_RAW)
+			error = tmy_network_connect_acl(0, type,
+							(u8 *) &in->sin_addr,
+							in->sin_port);
+		else {
+			const u16 port = htons(sock->sk->sk_protocol);
+
+			error = tmy_network_connect_acl(0, SOCK_RAW,
+							(u8 *) &in->sin_addr,
+							port);
+		}
+		break;
+	}
+
+	return error;
+}
+
+static int tmy_socket_bind(struct socket *sock,
+			   struct sockaddr *addr,
+			   int addr_len0)
+{
+	unsigned int addr_len = (unsigned int) addr_len0;
+	int error = 0;
+	const unsigned int type = sock->type;
+
+	/* I don't check if called by kernel process. */
+	if (segment_eq(get_fs(), KERNEL_DS))
+		return 0;
+
+	if (type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_RAW)
+		return error;
+
+	switch (addr->sa_family) {
+		struct sockaddr_in6 *in6;
+		struct sockaddr_in *in;
+
+	case AF_INET6:
+		if (addr_len < SIN6_LEN_RFC2133)
+			break;
+
+		in6 = ((struct sockaddr_in6 *) addr);
+		if (type != SOCK_RAW)
+			error = tmy_network_bind_acl(1, type,
+						     in6->sin6_addr.s6_addr,
+						     in6->sin6_port);
+		else {
+			const u16 port = htons(sock->sk->sk_protocol);
+
+			error = tmy_network_bind_acl(1, SOCK_RAW,
+						     in6->sin6_addr.s6_addr,
+						     port);
+		}
+		break;
+
+	case AF_INET:
+		if (addr_len < sizeof(struct sockaddr_in))
+			break;
+
+		in = (struct sockaddr_in *) addr;
+		if (type != SOCK_RAW)
+			error = tmy_network_bind_acl(0, type,
+						     (u8 *) &in->sin_addr,
+						     in->sin_port);
+		else {
+			const u16 port = htons(sock->sk->sk_protocol);
+
+			error = tmy_network_bind_acl(0, SOCK_RAW,
+						     (u8 *) &in->sin_addr,
+						     port);
+		}
+		break;
+	}
+
+	return error;
+}
+
+static int tmy_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size)
+{
+	int error = 0;
+	const int type = sock->type;
+	struct sockaddr *addr = (struct sockaddr *) msg->msg_name;
+	const unsigned int addr_len = msg->msg_namelen;
+
+	/* I don't check if called by kernel process. */
+	if (segment_eq(get_fs(), KERNEL_DS))
+		return 0;
+
+	if (!addr || (type != SOCK_DGRAM && type != SOCK_RAW))
+		return error;
+
+	switch (addr->sa_family) {
+		struct sockaddr_in6 *in6;
+		struct sockaddr_in *in;
+		u16 port;
+
+	case AF_INET6:
+		if (addr_len < SIN6_LEN_RFC2133)
+			break;
+
+		in6 = (struct sockaddr_in6 *) addr;
+		port = htons(sock->sk->sk_protocol);
+		error = tmy_network_sendmsg_acl(1, type, in6->sin6_addr.s6_addr,
+						type == SOCK_DGRAM ?
+						in6->sin6_port : port);
+		break;
+
+	case AF_INET:
+		if (addr_len < sizeof(struct sockaddr_in))
+			break;
+
+		in = (struct sockaddr_in *) addr;
+		port = htons(sock->sk->sk_protocol);
+		error = tmy_network_sendmsg_acl(0, type, (u8 *) &in->sin_addr,
+						type == SOCK_DGRAM ?
+						in->sin_port : port);
+		break;
+	}
+
+	return error;
+}
+
+#ifdef TMY_LSM_EXPANSION
+static int tmy_socket_post_accept(struct socket *sock, struct socket *newsock)
+{
+	int error = 0;
+	int addr_len;
+	char addr[MAX_SOCK_ADDR];
+	struct sockaddr *sockaddr = (struct sockaddr *) addr;
+
+	/* I don't check if called by kernel process. */
+	if (segment_eq(get_fs(), KERNEL_DS))
+		return 0;
+
+	if ((newsock->sk->sk_family != PF_INET) &&
+	    (newsock->sk->sk_family != PF_INET6))
+		return error;
+
+	if (newsock->ops->getname(newsock, sockaddr, &addr_len, 2) == 0) {
+		switch (sockaddr->sa_family) {
+			struct sockaddr_in6 *in6;
+			struct sockaddr_in *in;
+
+		case AF_INET6:
+			in6 = (struct sockaddr_in6 *) addr;
+			error = tmy_network_accept_acl(1,
+						       in6->sin6_addr.s6_addr,
+						       in6->sin6_port);
+			break;
+
+		case AF_INET:
+			in = (struct sockaddr_in *) addr;
+			error = tmy_network_accept_acl(0, (u8 *) &in->sin_addr,
+						       in->sin_port);
+			break;
+		}
+	} else
+		error = -EPERM;
+
+	if (error)
+		return -ECONNABORTED;
+	return error;
+}
+
+static int tmy_post_recv_datagram(struct sock *sk,
+				  struct sk_buff *skb,
+				  unsigned int flags)
+{
+	int error = 0;
+	const unsigned int type = sk->sk_type;
+
+	/* skb_recv_datagram() didn't dequeue. */
+	if (!skb)
+		return 0;
+
+	/* skb_recv_datagram() can be called from interrupt context. */
+	if (in_interrupt())
+		return 0;
+	/* I don't check if called by kernel process. */
+	if (segment_eq(get_fs(), KERNEL_DS))
+		return 0;
+
+	if (type != SOCK_DGRAM && type != SOCK_RAW)
+		return 0;
+
+	switch (sk->sk_family) {
+		struct sockaddr_in6 sin6;
+		struct sockaddr_in sin;
+		u16 port;
+
+	case AF_INET6:
+
+		if (type == SOCK_DGRAM) {
+			/* UDP IPv6 */
+			sin6.sin6_family = AF_INET6;
+			sin6.sin6_port = udp_hdr(skb)->source;
+
+			if (skb->protocol == htons(ETH_P_IP))
+				ipv6_addr_set(&sin6.sin6_addr, 0, 0,
+					      htonl(0xffff),
+					      ip_hdr(skb)->saddr);
+			else
+				ipv6_addr_copy(&sin6.sin6_addr,
+					       &ipv6_hdr(skb)->saddr);
+
+			port = sin6.sin6_port;
+		} else {
+			/* RAW IPv6 */
+			sin6.sin6_family = AF_INET6;
+			sin6.sin6_port = 0;
+			ipv6_addr_copy(&sin6.sin6_addr, &ipv6_hdr(skb)->saddr);
+
+			port = htons(sk->sk_protocol);
+		}
+
+		error = tmy_network_recvmsg_acl(1, type,
+						sin6.sin6_addr.s6_addr,	port);
+
+		break;
+
+	case AF_INET:
+
+		if (type == SOCK_DGRAM) {
+			/* UDP IPv4 */
+			sin.sin_family = AF_INET;
+			sin.sin_port = udp_hdr(skb)->source;
+			sin.sin_addr.s_addr = ip_hdr(skb)->saddr;
+
+			port = sin.sin_port;
+		} else {
+			/* RAW IPv4 */
+			sin.sin_family = AF_INET;
+			sin.sin_addr.s_addr = ip_hdr(skb)->saddr;
+			sin.sin_port = 0;
+
+			port = htons(sk->sk_protocol);
+		}
+
+		error = tmy_network_recvmsg_acl(0, type,
+						(u8 *) &sin.sin_addr, port);
+
+		break;
+
+	}
+
+	if (error)
+		error = -EAGAIN;
+	return error;
+}
+#endif
+
+static int tmy_sb_mount(char *dev_name,
+			struct nameidata *nd,
+			char *type,
+			unsigned long flags,
+			void *data)
+{
+	char *buf;
+	char *dir_name;
+	int error;
+
+	error = tmy_capable(TMY_SYS_MOUNT);
+	if (error)
+		return error;
+
+	buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	dir_name = d_path(&nd->path, buf, PAGE_SIZE);
+
+	if (IS_ERR(dir_name))
+		error = PTR_ERR(dir_name);
+	else
+		error = tmy_mount_perm(dev_name, dir_name, type, flags);
+
+	if (!error && (flags & MS_REMOUNT) == 0)
+		error = tmy_conceal_mount(nd);
+
+	kfree(buf);
+	return error;
+}
+
+static int tmy_sb_umount(struct vfsmount *mnt, int flags)
+{
+	const int err = tmy_capable(TMY_SYS_UMOUNT);
+	if (err)
+		return err;
+	return tmy_umount_perm(mnt);
+}
+
+static int tmy_settime(struct timespec *ts, struct timezone *tz)
+{
+	return tmy_capable(TMY_SYS_SETTIME);
+}
+
+static int tmy_sb_pivotroot(struct nameidata *old_nd, struct nameidata *new_nd)
+{
+	const int err = tmy_capable(TMY_SYS_PIVOT_ROOT);
+	if (err)
+		return err;
+	return tmy_pivot_root_perm(old_nd, new_nd);
+}
+
+static int tmy_task_capable(struct task_struct *tsk, int cap)
+{
+	int err = 0;
+	if (cap == CAP_SYS_CHROOT)
+		err = tmy_capable(TMY_SYS_CHROOT);
+	else if (cap == CAP_SYS_TTY_CONFIG)
+		err = tmy_capable(TMY_SYS_VHANGUP);
+	else if (cap == CAP_SYS_BOOT)
+		err = tmy_capable(TMY_SYS_REBOOT);
+	else if (cap == CAP_SYS_TIME)
+		err = tmy_capable(TMY_SYS_SETTIME);
+	if (err)
+		return err;
+	if (cap_raised(tsk->cap_effective, cap))
+		return 0;
+	return -EPERM;
+}
+
+#ifdef TMY_LSM_EXPANSION
+static int tmy_task_kill_unlocked(int pid, int sig)
+{
+	const int err = tmy_capable(TMY_SYS_KILL);
+	if (err)
+		return err;
+	return tmy_signal_acl(sig, pid);
+}
+
+static int tmy_task_tkill_unlocked(int pid, int sig)
+{
+	const int err = tmy_capable(TMY_SYS_KILL);
+	if (err)
+		return err;
+	return tmy_signal_acl(sig, pid);
+}
+
+static int tmy_task_tgkill_unlocked(int tgid, int pid, int sig)
+{
+	const int err = tmy_capable(TMY_SYS_KILL);
+	if (err)
+		return err;
+	return tmy_signal_acl(sig, pid);
+}
+#endif
+
+static struct security_operations tomoyo_security_ops = {
+	.task_alloc_security   = tmy_task_alloc_security,
+	.task_free_security    = tmy_task_free_security,
+	.bprm_alloc_security   = tmy_bprm_alloc_security,
+	.bprm_check_security   = tmy_bprm_check_security,
+	.bprm_post_apply_creds = tmy_bprm_post_apply_creds,
+	.bprm_free_security    = tmy_bprm_free_security,
+	.sysctl                = tmy_sysctl,
+	.inode_permission      = tmy_inode_permission,
+	.inode_setattr         = tmy_inode_setattr,
+	.inode_create          = tmy_inode_create,
+	.inode_unlink          = tmy_inode_unlink,
+	.inode_mkdir           = tmy_inode_mkdir,
+	.inode_rmdir           = tmy_inode_rmdir,
+	.inode_symlink         = tmy_inode_symlink,
+	.inode_mknod           = tmy_inode_mknod,
+	.inode_link            = tmy_inode_link,
+	.inode_rename          = tmy_inode_rename,
+	.file_fcntl            = tmy_file_fcntl,
+	.file_ioctl            = tmy_file_ioctl,
+	.socket_listen 	       = tmy_socket_listen,
+	.socket_create         = tmy_socket_create,
+	.socket_connect        = tmy_socket_connect,
+	.socket_bind 	       = tmy_socket_bind,
+	.socket_sendmsg        = tmy_socket_sendmsg,
+	.sb_mount              = tmy_sb_mount,
+	.sb_umount             = tmy_sb_umount,
+	.sb_pivotroot          = tmy_sb_pivotroot,
+	.settime               = tmy_settime,
+	.capable               = tmy_task_capable,
+#ifdef TMY_LSM_EXPANSION
+	.socket_post_accept    = tmy_socket_post_accept,
+	.post_recv_datagram    = tmy_post_recv_datagram,
+	.task_kill_unlocked    = tmy_task_kill_unlocked,
+	.task_tkill_unlocked   = tmy_task_tkill_unlocked,
+	.task_tgkill_unlocked  = tmy_task_tgkill_unlocked,
+#endif
+};
+
+static int __init tmy_init(void)
+{
+
+	/* register ourselves with the security framework */
+	if (register_security(&tomoyo_security_ops))
+		panic("Failure registering TOMOYO Linux");
+
+	printk(KERN_INFO "TOMOYO Linux initialized\n");
+
+	INIT_LIST_HEAD(&KERNEL_DOMAIN.list);
+	INIT_LIST_HEAD(&KERNEL_DOMAIN.acl_info_list);
+	KERNEL_DOMAIN.domainname = tmy_save_name(TMY_ROOT_NAME);
+	list_add_tail_mb(&KERNEL_DOMAIN.list, &domain_list);
+	tmy_cachep = kmem_cache_create("tomoyo_security",
+				       sizeof(struct tmy_security),
+				       0, SLAB_PANIC, NULL);
+	init_task.security = kmem_cache_alloc(tmy_cachep, GFP_KERNEL);
+	((struct tmy_security *) init_task.security)->domain = &KERNEL_DOMAIN;
+	((struct tmy_security *) init_task.security)->prev_domain = NULL;
+	((struct tmy_security *) init_task.security)->flags = 0;
+
+	return 0;
+}
+
+security_initcall(tmy_init);

-- 

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

* [TOMOYO #5 16/18] Conditional permission support.
  2007-11-16 17:34 [TOMOYO #5 00/18] TOMOYO Linux - MAC based on process invocation history penguin-kernel
                   ` (14 preceding siblings ...)
  2007-11-16 17:34 ` [TOMOYO #5 15/18] LSM adapter functions penguin-kernel
@ 2007-11-16 17:34 ` penguin-kernel
  2007-11-16 17:34 ` [TOMOYO #5 17/18] Kconfig and Makefile penguin-kernel
  2007-11-16 17:34 ` [TOMOYO #5 18/18] LSM expansion for TOMOYO Linux penguin-kernel
  17 siblings, 0 replies; 39+ messages in thread
From: penguin-kernel @ 2007-11-16 17:34 UTC (permalink / raw)
  To: akpm; +Cc: linux-kernel, linux-security-module, Kentaro Takeda, Tetsuo Handa

[-- Attachment #1: tomoyo-condition.diff --]
[-- Type: text/plain, Size: 17964 bytes --]

This patch allows administrators use conditional permission.
TOMOYO Linux supports conditional permission based on
process's UID,GID etc. and/or requested pathname's UID/GID.

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

--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6-mm/security/tomoyo/condition.c	2007-11-14 15:15:44.000000000 +0900
@@ -0,0 +1,680 @@
+/*
+ * security/tomoyo/condition.c
+ *
+ * Functions to support conditional access control for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+/**
+ * tmy_find_condition_part - check whether a line contains condition part.
+ * @data: a line to check.
+ *
+ * Returns pointer to condition part if found.
+ * Returns NULL if not found.
+ *
+ * Since the trailing spaces are removed by tmy_normalize_line(),
+ * the last "\040if\040" sequence corresponds to condition part.
+ */
+char *tmy_find_condition_part(char *data)
+{
+	char *cp = strstr(data, " if ");
+	if (cp) {
+		char *cp2;
+		while ((cp2 = strstr(cp + 3, " if ")) != NULL)
+			cp = cp2;
+		*cp++ = '\0';
+	}
+	return cp;
+}
+
+#define VALUE_TYPE_DECIMAL     1 /* 01 */
+#define VALUE_TYPE_OCTAL       2 /* 10 */
+#define VALUE_TYPE_HEXADECIMAL 3 /* 11 */
+
+static int tmy_parse_ulong(unsigned long *result, const char **str)
+{
+	const char *cp = *str;
+	char *ep;
+	int base = 10;
+	if (*cp == '0') {
+		char c = *(cp + 1);
+		if (c == 'x' || c == 'X') {
+			base = 16; cp += 2;
+		} else if (c >= '0' && c <= '7') {
+			base = 8; cp++;
+		}
+	}
+	*result = simple_strtoul(cp, &ep, base);
+	if (cp == ep) return 0; /* 00 */
+	*str = ep;
+	return (base == 16 ? VALUE_TYPE_HEXADECIMAL :
+		(base == 8 ? VALUE_TYPE_OCTAL : VALUE_TYPE_DECIMAL));
+}
+
+static void tmy_print_ulong(char *buffer, const int buffer_len,
+			const unsigned long value, const int type)
+{
+	if (type == VALUE_TYPE_DECIMAL)
+		snprintf(buffer, buffer_len, "%lu", value);
+	else if (type == VALUE_TYPE_OCTAL)
+		snprintf(buffer, buffer_len, "0%lo", value);
+	else
+		snprintf(buffer, buffer_len, "0x%lX", value);
+}
+
+/* List of conditins. */
+static struct condition_list {
+	struct condition_list *next;
+	int length;
+	/* "unsigned long condition[length]" comes here.*/
+} head;
+
+#define TASK_UID          0
+#define TASK_EUID         1
+#define TASK_SUID         2
+#define TASK_FSUID        3
+#define TASK_GID          4
+#define TASK_EGID         5
+#define TASK_SGID         6
+#define TASK_FSGID        7
+#define TASK_PID          8
+#define TASK_PPID         9
+#define PATH1_UID        10
+#define PATH1_GID        11
+#define PATH1_INO        12
+#define PATH1_PARENT_UID 13
+#define PATH1_PARENT_GID 14
+#define PATH1_PARENT_INO 15
+#define PATH2_PARENT_UID 16
+#define PATH2_PARENT_GID 17
+#define PATH2_PARENT_INO 18
+#define MAX_KEYWORD      19
+
+static struct {
+	const char *keyword;
+	const int keyword_len; /* strlen(keyword) */
+} cc_keyword[MAX_KEYWORD] = {
+	[TASK_UID]         = { "task.uid",           8 },
+	[TASK_EUID]        = { "task.euid",          9 },
+	[TASK_SUID]        = { "task.suid",          9 },
+	[TASK_FSUID]       = { "task.fsuid",        10 },
+	[TASK_GID]         = { "task.gid",           8 },
+	[TASK_EGID]        = { "task.egid",          9 },
+	[TASK_SGID]        = { "task.sgid",          9 },
+	[TASK_FSGID]       = { "task.fsgid",        10 },
+	[TASK_PID]         = { "task.pid",           8 },
+	[TASK_PPID]        = { "task.ppid",          9 },
+	[PATH1_UID]        = { "path1.uid",          9 },
+	[PATH1_GID]        = { "path1.gid",          9 },
+	[PATH1_INO]        = { "path1.ino",          9 },
+	[PATH1_PARENT_UID] = { "path1.parent.uid",  16 },
+	[PATH1_PARENT_GID] = { "path1.parent.gid",  16 },
+	[PATH1_PARENT_INO] = { "path1.parent.ino",  16 },
+	[PATH2_PARENT_UID] = { "path2.parent.uid",  16 },
+	[PATH2_PARENT_GID] = { "path2.parent.gid",  16 },
+	[PATH2_PARENT_INO] = { "path2.parent.ino",  16 }
+};
+
+/**
+ * tmy_assign_condition - create condition part.
+ * @condition: pointer to condition part.
+ *
+ * Returns pointer to "struct condition_list" on success.
+ * Returns NULL on failure.
+ */
+const struct condition_list *tmy_assign_condition(const char *condition)
+{
+	const char *start;
+	struct condition_list *ptr;
+	struct condition_list *new_ptr;
+	unsigned long *ptr2;
+	int counter = 0;
+	int size;
+	int left;
+	int right;
+	unsigned long left_min = 0;
+	unsigned long left_max = 0;
+	unsigned long right_min = 0;
+	unsigned long right_max = 0;
+	if (strncmp(condition, "if ", 3))
+		return NULL;
+	condition += 3;
+	start = condition;
+	while (*condition) {
+		if (*condition == ' ')
+			condition++;
+		for (left = 0; left < MAX_KEYWORD; left++) {
+			if (strncmp(condition, cc_keyword[left].keyword,
+				    cc_keyword[left].keyword_len))
+				continue;
+			condition += cc_keyword[left].keyword_len;
+			break;
+		}
+		if (left == MAX_KEYWORD) {
+			if (!tmy_parse_ulong(&left_min, &condition))
+				goto out;
+			counter++; /* body */
+			if (*condition != '-')
+				goto not_range1;
+			condition++;
+			if (!tmy_parse_ulong(&left_max, &condition)
+			    || left_min > left_max)
+				goto out;
+			counter++; /* body */
+not_range1: ;
+		}
+		if (strncmp(condition, "!=", 2) == 0)
+			condition += 2;
+		else if (*condition == '=')
+			condition++;
+		else
+			goto out;
+		counter++; /* header */
+		for (right = 0; right < MAX_KEYWORD; right++) {
+			if (strncmp(condition, cc_keyword[right].keyword,
+				    cc_keyword[right].keyword_len))
+				continue;
+			condition += cc_keyword[right].keyword_len;
+			break;
+		}
+		if (right == MAX_KEYWORD) {
+			if (!tmy_parse_ulong(&right_min, &condition))
+				goto out;
+			counter++; /* body */
+			if (*condition != '-')
+				goto not_range2;
+			condition++;
+			if (!tmy_parse_ulong(&right_max, &condition)
+			    || right_min > right_max)
+				goto out;
+			counter++; /* body */
+not_range2: ;
+		}
+	}
+	size = sizeof(*new_ptr) + counter * sizeof(unsigned long);
+	new_ptr = tmy_alloc(size);
+	if (!new_ptr)
+		return NULL;
+	new_ptr->length = counter;
+	ptr2 = (unsigned long *) (((u8 *) new_ptr) + sizeof(*new_ptr));
+	condition = start;
+	while (*condition) {
+		unsigned int match = 0;
+		if (*condition == ' ')
+			condition++;
+		for (left = 0; left < MAX_KEYWORD; left++) {
+			if (strncmp(condition, cc_keyword[left].keyword,
+			    cc_keyword[left].keyword_len))
+				continue;
+			condition += cc_keyword[left].keyword_len;
+			break;
+		}
+		if (left == MAX_KEYWORD) {
+			match |= tmy_parse_ulong(&left_min, &condition) << 2;
+			counter--; /* body */
+			if (*condition != '-')
+				goto not_range3;
+			condition++;
+			match |= tmy_parse_ulong(&left_max, &condition) << 4;
+			counter--; /* body */
+			left++;
+not_range3: ;
+		}
+		if (strncmp(condition, "!=", 2) == 0)
+			condition += 2;
+		else if (*condition == '=') {
+			match |= 1; condition++;
+		} else
+			goto out2;
+		counter--; /* header */
+		for (right = 0; right < MAX_KEYWORD; right++) {
+			if (strncmp(condition, cc_keyword[right].keyword,
+			    cc_keyword[right].keyword_len))
+				continue;
+			condition += cc_keyword[right].keyword_len;
+			break;
+		}
+		if (right == MAX_KEYWORD) {
+			match |= tmy_parse_ulong(&right_min, &condition) << 6;
+			counter--; /* body */
+			if (*condition != '-')
+				goto not_range4;
+			condition++;
+			match |= tmy_parse_ulong(&right_max, &condition) << 8;
+			counter--; /* body */
+			right++;
+not_range4: ;
+		}
+		if (counter < 0)
+			goto out2;
+		*ptr2++ = (match << 16) | (left << 8) | right;
+		if (left >= MAX_KEYWORD)
+			*ptr2++ = left_min;
+		if (left == MAX_KEYWORD + 1)
+			*ptr2++ = left_max;
+		if (right >= MAX_KEYWORD)
+			*ptr2++ = right_min;
+		if (right == MAX_KEYWORD + 1)
+			*ptr2++ = right_max;
+	}
+	{
+		static DEFINE_MUTEX(mutex);
+		struct condition_list *prev = NULL;
+		mutex_lock(&mutex);
+		for (ptr = &head; ptr; prev = ptr, ptr = ptr->next) {
+			/* Don't compare if size differs. */
+			if (ptr->length != new_ptr->length)
+				continue;
+			/*
+			 * Compare ptr and new_ptr
+			 * except ptr->next and new_ptr->next .
+			 */
+			if (memcmp(((u8 *) ptr) + sizeof(ptr->next),
+				   ((u8 *) new_ptr) + sizeof(new_ptr->next),
+				   size - sizeof(ptr->next)))
+				continue;
+			/* Same entry found. Share this entry. */
+			tmy_free(new_ptr);
+			new_ptr = ptr;
+			goto ok;
+		}
+		/* Same entry not found. Save this entry. */
+		ptr = tmy_alloc_element(size);
+		if (ptr)
+			memmove(ptr, new_ptr, size);
+		tmy_free(new_ptr);
+		new_ptr = ptr;
+		/* Append to chain. */
+		prev->next = new_ptr;
+ok: ;
+		mutex_unlock(&mutex);
+	}
+	return new_ptr;
+out2: ;
+	tmy_free(new_ptr);
+out: ;
+	return NULL;
+}
+
+/* Get inode's attribute information. */
+static void tmy_get_attributes(struct obj_info *obj)
+{
+	struct vfsmount *mnt;
+	struct dentry *dentry;
+	struct inode *inode;
+	struct kstat stat;
+
+	mnt = obj->path1_vfsmnt;
+	dentry = obj->path1_dentry;
+	inode = dentry->d_inode;
+	if (inode) {
+		if (!inode->i_op || vfs_getattr(mnt, dentry, &stat)) {
+			/* Nothing to do. */
+		} else {
+			obj->path1_stat.uid = stat.uid;
+			obj->path1_stat.gid = stat.gid;
+			obj->path1_stat.ino = stat.ino;
+			obj->path1_valid = 1;
+		}
+	}
+
+	dentry = dget_parent(obj->path1_dentry);
+	inode = dentry->d_inode;
+	if (inode) {
+		if (!inode->i_op || vfs_getattr(mnt, dentry, &stat)) {
+			/* Nothing to do. */
+		} else {
+			obj->path1_parent_stat.uid = stat.uid;
+			obj->path1_parent_stat.gid = stat.gid;
+			obj->path1_parent_stat.ino = stat.ino;
+			obj->path1_parent_valid = 1;
+		}
+	}
+	dput(dentry);
+
+	mnt = obj->path2_vfsmnt;
+	if (mnt) {
+		dentry = dget_parent(obj->path2_dentry);
+		inode = dentry->d_inode;
+		if (inode) {
+			if (!inode->i_op || vfs_getattr(mnt, dentry, &stat)) {
+				/* Nothing to do. */
+			} else {
+				obj->path2_parent_stat.uid = stat.uid;
+				obj->path2_parent_stat.gid = stat.gid;
+				obj->path2_parent_stat.ino = stat.ino;
+				obj->path2_parent_valid = 1;
+			}
+		}
+		dput(dentry);
+	}
+}
+
+/**
+ * tmy_check_condition - check condition part.
+ * @ptr: pointer to "struct condition_list".
+ * @obj: pointer to "struct obj_info". May be NULL.
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_check_condition(const struct condition_list *ptr, struct obj_info *obj)
+{
+	struct task_struct *task = current;
+	int i;
+	unsigned long left_min = 0;
+	unsigned long left_max = 0;
+	unsigned long right_min = 0;
+	unsigned long right_max = 0;
+	const unsigned long *ptr2;
+	if (!ptr)
+		return 0;
+	ptr2 = (unsigned long *) (((u8 *) ptr) + sizeof(*ptr));
+	for (i = 0; i < ptr->length; i++) {
+		const bool match = ((*ptr2) >> 16) & 1;
+		const u8 left = (*ptr2) >> 8;
+		const u8 right = *ptr2;
+		ptr2++;
+		if ((left >= PATH1_UID && left < MAX_KEYWORD)
+		    || (right >= PATH1_UID && right < MAX_KEYWORD)) {
+			if (!obj)
+				goto out;
+			if (!obj->validate_done) {
+				tmy_get_attributes(obj);
+				obj->validate_done = 1;
+			}
+		}
+		switch (left) {
+		case TASK_UID:
+			left_min = task->uid;
+			left_max = left_min;
+			break;
+		case TASK_EUID:
+			left_min = task->euid;
+			left_max = left_min;
+			break;
+		case TASK_SUID:
+			left_min = task->suid;
+			left_max = left_min;
+			break;
+		case TASK_FSUID:
+			left_min = task->fsuid;
+			left_max = left_min;
+			break;
+		case TASK_GID:
+			left_min = task->gid;
+			left_max = left_min;
+			break;
+		case TASK_EGID:
+			left_min = task->egid;
+			left_max = left_min;
+			break;
+		case TASK_SGID:
+			left_min = task->sgid;
+			left_max = left_min;
+			break;
+		case TASK_FSGID:
+			left_min = task->fsgid;
+			left_max = left_min;
+			break;
+		case TASK_PID:
+			left_min = task->pid;
+			left_max = left_min;
+			break;
+		case TASK_PPID:
+			left_min = sys_getppid();
+			left_max = left_min;
+			break;
+		case PATH1_UID:
+			if (!obj->path1_valid)
+				goto out;
+			left_min = obj->path1_stat.uid;
+			left_max = left_min;
+			break;
+		case PATH1_GID:
+			if (!obj->path1_valid)
+				goto out;
+			left_min = obj->path1_stat.gid;
+			left_max = left_min;
+			break;
+		case PATH1_INO:
+			if (!obj->path1_valid)
+				goto out;
+			left_min = obj->path1_stat.ino;
+			left_max = left_min;
+			break;
+		case PATH1_PARENT_UID:
+			if (!obj->path1_parent_valid)
+				goto out;
+			left_min = obj->path1_parent_stat.uid;
+			left_max = left_min;
+			break;
+		case PATH1_PARENT_GID:
+			if (!obj->path1_parent_valid)
+				goto out;
+			left_min = obj->path1_parent_stat.gid;
+			left_max = left_min;
+			break;
+		case PATH1_PARENT_INO:
+			if (!obj->path1_parent_valid)
+				goto out;
+			left_min = obj->path1_parent_stat.ino;
+			left_max = left_min;
+			break;
+		case PATH2_PARENT_UID:
+			if (!obj->path2_parent_valid)
+				goto out;
+			left_min = obj->path2_parent_stat.uid;
+			left_max = left_min;
+			break;
+		case PATH2_PARENT_GID:
+			if (!obj->path2_parent_valid)
+				goto out;
+			left_min = obj->path2_parent_stat.gid;
+			left_max = left_min;
+			break;
+		case PATH2_PARENT_INO:
+			if (!obj->path2_parent_valid)
+				goto out;
+			left_min = obj->path2_parent_stat.ino;
+			left_max = left_min;
+			break;
+		case MAX_KEYWORD:
+			left_min = *ptr2++;
+			left_max = left_min;
+			i++;
+			break;
+		case MAX_KEYWORD + 1:
+			left_min = *ptr2++;
+			left_max = *ptr2++;
+			i += 2;
+			break;
+		}
+		switch (right) {
+		case TASK_UID:
+			right_min = task->uid;
+			right_max = right_min;
+			break;
+		case TASK_EUID:
+			right_min = task->euid;
+			right_max = right_min;
+			break;
+		case TASK_SUID:
+			right_min = task->suid;
+			right_max = right_min;
+			break;
+		case TASK_FSUID:
+			right_min = task->fsuid;
+			right_max = right_min;
+			break;
+		case TASK_GID:
+			right_min = task->gid;
+			right_max = right_min;
+			break;
+		case TASK_EGID:
+			right_min = task->egid;
+			right_max = right_min;
+			break;
+		case TASK_SGID:
+			right_min = task->sgid;
+			right_max = right_min;
+			break;
+		case TASK_FSGID:
+			right_min = task->fsgid;
+			right_max = right_min;
+			break;
+		case TASK_PID:
+			right_min = task->pid;
+			right_max = right_min;
+			break;
+		case TASK_PPID:
+			right_min = sys_getppid();
+			right_max = right_min;
+			break;
+		case PATH1_UID:
+			if (!obj->path1_valid)
+				goto out;
+			right_min = obj->path1_stat.uid;
+			right_max = right_min;
+			break;
+		case PATH1_GID:
+			if (!obj->path1_valid)
+				goto out;
+			right_min = obj->path1_stat.gid;
+			right_max = right_min;
+			break;
+		case PATH1_INO:
+			if (!obj->path1_valid)
+				goto out;
+			right_min = obj->path1_stat.ino;
+			right_max = right_min;
+			break;
+		case PATH1_PARENT_UID:
+			if (!obj->path1_parent_valid)
+				goto out;
+			right_min = obj->path1_parent_stat.uid;
+			right_max = right_min;
+			break;
+		case PATH1_PARENT_GID:
+			if (!obj->path1_parent_valid)
+				goto out;
+			right_min = obj->path1_parent_stat.gid;
+			right_max = right_min;
+			break;
+		case PATH1_PARENT_INO:
+			if (!obj->path1_parent_valid)
+				goto out;
+			right_min = obj->path1_parent_stat.ino;
+			right_max = right_min;
+			break;
+		case PATH2_PARENT_UID:
+			if (!obj->path2_parent_valid)
+				goto out;
+			right_min = obj->path2_parent_stat.uid;
+			right_max = right_min;
+			break;
+		case PATH2_PARENT_GID:
+			if (!obj->path2_parent_valid)
+				goto out;
+			right_min = obj->path2_parent_stat.gid;
+			right_max = right_min;
+			break;
+		case PATH2_PARENT_INO:
+			if (!obj->path2_parent_valid)
+				goto out;
+			right_min = obj->path2_parent_stat.ino;
+			right_max = right_min;
+			break;
+		case MAX_KEYWORD:
+			right_min = *ptr2++;
+			right_max = right_min;
+			i++;
+			break;
+		case MAX_KEYWORD + 1:
+			right_min = *ptr2++;
+			right_max = *ptr2++;
+			i += 2;
+			break;
+		}
+		if (match) {
+			if (left_min <= right_max && left_max >= right_min)
+				continue;
+		} else {
+			if (left_min > right_max || left_max < right_min)
+				continue;
+		}
+out: ;
+		return -EPERM;
+	}
+	return 0;
+}
+
+/**
+ * tmy_dump_condition - dump condition part.
+ * @head: pointer to "struct io_buffer".
+ * @ptr:  pointer to "struct condition_list". May be NULL.
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_dump_condition(struct io_buffer *head, const struct condition_list *ptr)
+{
+	int i;
+	const unsigned long *ptr2;
+	char buffer[32];
+	if (!ptr)
+		goto last;
+	ptr2 = (unsigned long *) (((u8 *) ptr) + sizeof(*ptr));
+	memset(buffer, 0, sizeof(buffer));
+	for (i = 0; i < ptr->length; i++) {
+		const u16 match = (*ptr2) >> 16;
+		const u8 left = (*ptr2) >> 8;
+		const u8 right = *ptr2;
+		ptr2++;
+		if (tmy_io_printf(head, "%s", i ? " " : " if "))
+			break;
+		if (left < MAX_KEYWORD) {
+			if (tmy_io_printf(head, "%s", cc_keyword[left].keyword))
+				break;
+		} else {
+			tmy_print_ulong(buffer, sizeof(buffer) - 1, *ptr2++,
+					(match >> 2) & 3);
+			if (tmy_io_printf(head, "%s", buffer))
+				break;
+			i++;
+			if (left == MAX_KEYWORD + 1) {
+				tmy_print_ulong(buffer, sizeof(buffer) - 1,
+						*ptr2++, (match >> 4) & 3);
+				if (tmy_io_printf(head, "-%s", buffer))
+					break;
+				i++;
+			}
+		}
+		if (tmy_io_printf(head, "%s", (match & 1) ? "=" : "!="))
+			break;
+		if (right < MAX_KEYWORD) {
+			if (tmy_io_printf(head, "%s",
+					  cc_keyword[right].keyword))
+				break;
+		} else {
+			tmy_print_ulong(buffer, sizeof(buffer) - 1, *ptr2++,
+					(match >> 6) & 3);
+			if (tmy_io_printf(head, "%s", buffer))
+				break;
+			i++;
+			if (right == MAX_KEYWORD + 1) {
+				tmy_print_ulong(buffer, sizeof(buffer) - 1,
+						*ptr2++, (match >> 8) & 3);
+				if (tmy_io_printf(head, "-%s", buffer))
+					break;
+				i++;
+			}
+		}
+	}
+	if (i < ptr->length)
+		return -ENOMEM;
+last: ;
+	return tmy_io_printf(head, "\n") ? -ENOMEM : 0;
+}

-- 

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

* [TOMOYO #5 17/18] Kconfig and Makefile
  2007-11-16 17:34 [TOMOYO #5 00/18] TOMOYO Linux - MAC based on process invocation history penguin-kernel
                   ` (15 preceding siblings ...)
  2007-11-16 17:34 ` [TOMOYO #5 16/18] Conditional permission support penguin-kernel
@ 2007-11-16 17:34 ` penguin-kernel
  2007-11-16 17:34 ` [TOMOYO #5 18/18] LSM expansion for TOMOYO Linux penguin-kernel
  17 siblings, 0 replies; 39+ messages in thread
From: penguin-kernel @ 2007-11-16 17:34 UTC (permalink / raw)
  To: akpm; +Cc: linux-kernel, linux-security-module, Kentaro Takeda, Tetsuo Handa

[-- Attachment #1: tomoyo-kconfig.diff --]
[-- Type: text/plain, Size: 2453 bytes --]

TOMOYO Linux is placed in security/tomoyo .

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        |    1 +
 security/tomoyo/Kconfig  |   26 ++++++++++++++++++++++++++
 security/tomoyo/Makefile |    2 ++
 4 files changed, 30 insertions(+)

--- linux-2.6-mm.orig/security/Kconfig	2007-11-14 15:12:08.000000000 +0900
+++ linux-2.6-mm/security/Kconfig	2007-11-14 15:15:44.000000000 +0900
@@ -104,6 +104,7 @@ config SECURITY_ROOTPLUG
 	  If you are unsure how to answer this question, answer N.
 
 source security/selinux/Kconfig
+source security/tomoyo/Kconfig
 
 endmenu
 
--- linux-2.6-mm.orig/security/Makefile	2007-10-10 05:31:38.000000000 +0900
+++ linux-2.6-mm/security/Makefile	2007-11-14 15:15:44.000000000 +0900
@@ -16,3 +16,4 @@ obj-$(CONFIG_SECURITY)			+= security.o d
 obj-$(CONFIG_SECURITY_SELINUX)		+= selinux/built-in.o
 obj-$(CONFIG_SECURITY_CAPABILITIES)	+= commoncap.o capability.o
 obj-$(CONFIG_SECURITY_ROOTPLUG)		+= commoncap.o root_plug.o
+obj-$(CONFIG_SECURITY_TOMOYO)           += tomoyo/
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6-mm/security/tomoyo/Kconfig	2007-11-14 15:15:44.000000000 +0900
@@ -0,0 +1,26 @@
+config SECURITY_TOMOYO
+	bool "TOMOYO Linux support"
+	depends on SECURITY
+	select SECURITY_NETWORK
+	default n
+	help
+	  This selects TOMOYO Linux.
+
+	  TOMOYO Linux is a domain-based access control method using LSM.
+	  If you answer Y, you will need a policy loader program
+	  (/sbin/tomoyo-init) and some configuration files.
+	  You can get them from
+	  <http://tomoyo.sourceforge.jp/en/2.1.x/>
+
+	  TOMOYO Linux is also applicable to figuring out the behavior
+	  of your system, for TOMOYO uses the canonicalized absolute
+	  pathnames and TreeView style domain transitions.
+
+config SECURITY_TOMOYO_USE_AUDITD
+	bool "Use standard auditing subsystem"
+	depends on SECURITY_TOMOYO && AUDIT
+	default y
+	help
+	  This makes messages sent to auditing subsystem.
+
+	  If you say 'N' here, messages will be sent to printk().
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6-mm/security/tomoyo/Makefile	2007-11-14 15:15:44.000000000 +0900
@@ -0,0 +1,2 @@
+obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo.o domain.o common.o realpath.o audit.o file.o exec.o net.o mount.o signal.o capability.o condition.o
+EXTRA_CFLAGS += -Isecurity/tomoyo/include

-- 

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

* [TOMOYO #5 18/18] LSM expansion for TOMOYO Linux.
  2007-11-16 17:34 [TOMOYO #5 00/18] TOMOYO Linux - MAC based on process invocation history penguin-kernel
                   ` (16 preceding siblings ...)
  2007-11-16 17:34 ` [TOMOYO #5 17/18] Kconfig and Makefile penguin-kernel
@ 2007-11-16 17:34 ` penguin-kernel
  2007-11-16 19:23   ` Paul Moore
  17 siblings, 1 reply; 39+ messages in thread
From: penguin-kernel @ 2007-11-16 17:34 UTC (permalink / raw)
  To: akpm; +Cc: linux-kernel, linux-security-module, Kentaro Takeda, Tetsuo Handa

[-- Attachment #1: tomoyo-lsm-expansion.diff --]
[-- Type: text/plain, Size: 13746 bytes --]

LSM hooks for sending signal:
   * task_kill_unlocked is added in sys_kill
   * task_tkill_unlocked is added in sys_tkill
   * task_tgkill_unlocked is added in sys_tgkill
LSM hooks for network accept and recv:
   * socket_post_accept is modified to return int.
   * post_recv_datagram is added in skb_recv_datagram.

You can try TOMOYO Linux without this patch, but in that case, you
can't use access control functionality for restricting signal
transmission and incoming network data.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
 include/linux/security.h |   74 +++++++++++++++++++++++++++++++++++++++++++----
 kernel/signal.c          |   17 ++++++++++
 net/core/datagram.c      |   22 +++++++++++++
 net/socket.c             |    7 +++-
 security/dummy.c         |   32 ++++++++++++++++++--
 security/security.c      |   25 ++++++++++++++-
 6 files changed, 165 insertions(+), 12 deletions(-)

--- linux-2.6-mm.orig/include/linux/security.h	2007-11-14 15:14:10.000000000 +0900
+++ linux-2.6-mm/include/linux/security.h	2007-11-14 15:15:44.000000000 +0900
@@ -657,6 +657,25 @@ struct request_sock;
  *	@sig contains the signal value.
  *	@secid contains the sid of the process where the signal originated
  *	Return 0 if permission is granted.
+ * @task_kill_unlocked:
+ *	Check permission before sending signal @sig to the process of @pid
+ *	with sys_kill.
+ *	@pid contains the pid of target process.
+ *	@sig contains the signal value.
+ *	Return 0 if permission is granted.
+ * @task_tkill_unlocked:
+ *	Check permission before sending signal @sig to the process of @pid
+ *	with sys_tkill.
+ *	@pid contains the pid of target process.
+ *	@sig contains the signal value.
+ *	Return 0 if permission is granted.
+ * @task_tgkill_unlocked:
+ *	Check permission before sending signal @sig to the process of @pid
+ *	with sys_tgkill.
+ *	@tgid contains the thread group id.
+ *	@pid contains the pid of target process.
+ *	@sig contains the signal value.
+ *	Return 0 if permission is granted.
  * @task_wait:
  *	Check permission before allowing a process to reap a child process @p
  *	and collect its status information.
@@ -778,8 +797,12 @@ struct request_sock;
  * @socket_post_accept:
  *	This hook allows a security module to copy security
  *	information into the newly created socket's inode.
+ *	This hook also allows a security module to filter connections
+ *	from unwanted peers.
+ *	The connection will be aborted if this hook returns nonzero.
  *	@sock contains the listening socket structure.
  *	@newsock contains the newly created server socket for connection.
+ *	Return 0 if permission is granted.
  * @socket_sendmsg:
  *	Check permission before transmitting a message to another socket.
  *	@sock contains the socket structure.
@@ -793,6 +816,12 @@ struct request_sock;
  *	@size contains the size of message structure.
  *	@flags contains the operational flags.
  *	Return 0 if permission is granted.  
+ * @post_recv_datagram:
+ *	Check permission after receiving a datagram.
+ *	@sk contains the socket.
+ *	@skb contains the socket buffer (may be NULL).
+ *	@flags contains the operational flags.
+ *	Return 0 if permission is granted.
  * @socket_getsockname:
  *	Check permission before the local address (name) of the socket object
  *	@sock is retrieved.
@@ -1319,6 +1348,9 @@ struct security_operations {
 	int (*task_movememory) (struct task_struct * p);
 	int (*task_kill) (struct task_struct * p,
 			  struct siginfo * info, int sig, u32 secid);
+	int (*task_kill_unlocked) (int pid, int sig);
+	int (*task_tkill_unlocked) (int pid, int sig);
+	int (*task_tgkill_unlocked) (int tgid, int pid, int sig);
 	int (*task_wait) (struct task_struct * p);
 	int (*task_prctl) (int option, unsigned long arg2,
 			   unsigned long arg3, unsigned long arg4,
@@ -1384,12 +1416,16 @@ struct security_operations {
 			       struct sockaddr * address, int addrlen);
 	int (*socket_listen) (struct socket * sock, int backlog);
 	int (*socket_accept) (struct socket * sock, struct socket * newsock);
-	void (*socket_post_accept) (struct socket * sock,
-				    struct socket * newsock);
+#define TMY_LSM_EXPANSION
+	int (*socket_post_accept) (struct socket *sock,
+				   struct socket *newsock);
 	int (*socket_sendmsg) (struct socket * sock,
 			       struct msghdr * msg, int size);
 	int (*socket_recvmsg) (struct socket * sock,
 			       struct msghdr * msg, int size, int flags);
+	int (*post_recv_datagram) (struct sock *sk,
+				   struct sk_buff *skb,
+				   unsigned int flags);
 	int (*socket_getsockname) (struct socket * sock);
 	int (*socket_getpeername) (struct socket * sock);
 	int (*socket_getsockopt) (struct socket * sock, int level, int optname);
@@ -1567,6 +1603,9 @@ int security_task_getscheduler(struct ta
 int security_task_movememory(struct task_struct *p);
 int security_task_kill(struct task_struct *p, struct siginfo *info,
 			int sig, u32 secid);
+int security_task_kill_unlocked(int pid, int sig);
+int security_task_tkill_unlocked(int pid, int sig);
+int security_task_tgkill_unlocked(int tgid, int pid, int sig);
 int security_task_wait(struct task_struct *p);
 int security_task_prctl(int option, unsigned long arg2, unsigned long arg3,
 			 unsigned long arg4, unsigned long arg5);
@@ -2112,6 +2151,21 @@ static inline int security_task_kill (st
 	return cap_task_kill(p, info, sig, secid);
 }
 
+static inline int security_task_kill_unlocked(int pid, int sig)
+{
+	return 0;
+}
+
+static inline int security_task_tkill_unlocked(int pid, int sig)
+{
+	return 0;
+}
+
+static inline int security_task_tgkill_unlocked(int tgid, int pid, int sig)
+{
+	return 0;
+}
+
 static inline int security_task_wait (struct task_struct *p)
 {
 	return 0;
@@ -2294,10 +2348,12 @@ int security_socket_bind(struct socket *
 int security_socket_connect(struct socket *sock, struct sockaddr *address, int addrlen);
 int security_socket_listen(struct socket *sock, int backlog);
 int security_socket_accept(struct socket *sock, struct socket *newsock);
-void security_socket_post_accept(struct socket *sock, struct socket *newsock);
+int security_socket_post_accept(struct socket *sock, struct socket *newsock);
 int security_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size);
 int security_socket_recvmsg(struct socket *sock, struct msghdr *msg,
 			    int size, int flags);
+int security_post_recv_datagram(struct sock *sk, struct sk_buff *skb,
+				unsigned int flags);
 int security_socket_getsockname(struct socket *sock);
 int security_socket_getpeername(struct socket *sock);
 int security_socket_getsockopt(struct socket *sock, int level, int optname);
@@ -2373,9 +2429,10 @@ static inline int security_socket_accept
 	return 0;
 }
 
-static inline void security_socket_post_accept(struct socket * sock, 
-					       struct socket * newsock)
+static inline int security_socket_post_accept(struct socket *sock,
+					      struct socket *newsock)
 {
+	return 0;
 }
 
 static inline int security_socket_sendmsg(struct socket * sock, 
@@ -2391,6 +2448,13 @@ static inline int security_socket_recvms
 	return 0;
 }
 
+static inline int security_post_recv_datagram(struct sock *sk,
+					      struct sk_buff *skb,
+					      unsigned int flags)
+{
+	return 0;
+}
+
 static inline int security_socket_getsockname(struct socket * sock)
 {
 	return 0;
--- linux-2.6-mm.orig/kernel/signal.c	2007-11-14 15:14:17.000000000 +0900
+++ linux-2.6-mm/kernel/signal.c	2007-11-14 15:15:44.000000000 +0900
@@ -2218,6 +2218,11 @@ asmlinkage long
 sys_kill(int pid, int sig)
 {
 	struct siginfo info;
+	int ret;
+
+	ret = security_task_kill_unlocked(pid, sig);
+	if (ret)
+		return ret;
 
 	info.si_signo = sig;
 	info.si_errno = 0;
@@ -2273,10 +2278,16 @@ static int do_tkill(int tgid, int pid, i
  */
 asmlinkage long sys_tgkill(int tgid, int pid, int sig)
 {
+	int ret;
+
 	/* This is only valid for single tasks */
 	if (pid <= 0 || tgid <= 0)
 		return -EINVAL;
 
+	ret = security_task_tgkill_unlocked(tgid, pid, sig);
+	if (ret)
+		return ret;
+
 	return do_tkill(tgid, pid, sig);
 }
 
@@ -2286,10 +2297,16 @@ asmlinkage long sys_tgkill(int tgid, int
 asmlinkage long
 sys_tkill(int pid, int sig)
 {
+	int ret;
+
 	/* This is only valid for single tasks */
 	if (pid <= 0)
 		return -EINVAL;
 
+	ret = security_task_tkill_unlocked(pid, sig);
+	if (ret)
+		return ret;
+
 	return do_tkill(0, pid, sig);
 }
 
--- linux-2.6-mm.orig/net/socket.c	2007-11-14 15:12:08.000000000 +0900
+++ linux-2.6-mm/net/socket.c	2007-11-14 15:15:44.000000000 +0900
@@ -1442,13 +1442,16 @@ asmlinkage long sys_accept(int fd, struc
 			goto out_fd;
 	}
 
+	/* Filter connections from unwanted peers like TCP Wrapper. */
+	err = security_socket_post_accept(sock, newsock);
+	if (err)
+		goto out_fd;
+
 	/* File flags are not inherited via accept() unlike another OSes. */
 
 	fd_install(newfd, newfile);
 	err = newfd;
 
-	security_socket_post_accept(sock, newsock);
-
 out_put:
 	fput_light(sock->file, fput_needed);
 out:
--- linux-2.6-mm.orig/security/dummy.c	2007-11-14 15:14:10.000000000 +0900
+++ linux-2.6-mm/security/dummy.c	2007-11-14 15:15:44.000000000 +0900
@@ -576,6 +576,21 @@ static int dummy_task_kill (struct task_
 	return 0;
 }
 
+static int dummy_task_kill_unlocked(int pid, int sig)
+{
+	return 0;
+}
+
+static int dummy_task_tkill_unlocked(int pid, int sig)
+{
+	return 0;
+}
+
+static int dummy_task_tgkill_unlocked(int tgid, int pid, int sig)
+{
+	return 0;
+}
+
 static int dummy_task_prctl (int option, unsigned long arg2, unsigned long arg3,
 			     unsigned long arg4, unsigned long arg5)
 {
@@ -753,10 +768,10 @@ static int dummy_socket_accept (struct s
 	return 0;
 }
 
-static void dummy_socket_post_accept (struct socket *sock, 
-				      struct socket *newsock)
+static int dummy_socket_post_accept(struct socket *sock,
+				    struct socket *newsock)
 {
-	return;
+	return 0;
 }
 
 static int dummy_socket_sendmsg (struct socket *sock, struct msghdr *msg,
@@ -771,6 +786,13 @@ static int dummy_socket_recvmsg (struct 
 	return 0;
 }
 
+static int dummy_post_recv_datagram(struct sock *sk,
+				    struct sk_buff *skb,
+				    unsigned int flags)
+{
+	return 0;
+}
+
 static int dummy_socket_getsockname (struct socket *sock)
 {
 	return 0;
@@ -1062,6 +1084,9 @@ void security_fixup_ops (struct security
 	set_to_dummy_if_null(ops, task_movememory);
 	set_to_dummy_if_null(ops, task_wait);
 	set_to_dummy_if_null(ops, task_kill);
+	set_to_dummy_if_null(ops, task_kill_unlocked);
+	set_to_dummy_if_null(ops, task_tkill_unlocked);
+	set_to_dummy_if_null(ops, task_tgkill_unlocked);
 	set_to_dummy_if_null(ops, task_prctl);
 	set_to_dummy_if_null(ops, task_reparent_to_init);
  	set_to_dummy_if_null(ops, task_to_inode);
@@ -1104,6 +1129,7 @@ void security_fixup_ops (struct security
 	set_to_dummy_if_null(ops, socket_post_accept);
 	set_to_dummy_if_null(ops, socket_sendmsg);
 	set_to_dummy_if_null(ops, socket_recvmsg);
+	set_to_dummy_if_null(ops, post_recv_datagram);
 	set_to_dummy_if_null(ops, socket_getsockname);
 	set_to_dummy_if_null(ops, socket_getpeername);
 	set_to_dummy_if_null(ops, socket_setsockopt);
--- linux-2.6-mm.orig/net/core/datagram.c	2007-10-10 05:31:38.000000000 +0900
+++ linux-2.6-mm/net/core/datagram.c	2007-11-14 15:15:44.000000000 +0900
@@ -55,6 +55,7 @@
 #include <net/checksum.h>
 #include <net/sock.h>
 #include <net/tcp_states.h>
+#include <linux/security.h>
 
 /*
  *	Is a socket 'connection oriented' ?
@@ -178,6 +179,27 @@ struct sk_buff *skb_recv_datagram(struct
 		} else
 			skb = skb_dequeue(&sk->sk_receive_queue);
 
+		error = security_post_recv_datagram(sk, skb, flags);
+		if (error) {
+			unsigned long cpu_flags;
+
+			if (!(flags & MSG_PEEK))
+				goto no_peek;
+
+			spin_lock_irqsave(&sk->sk_receive_queue.lock,
+					  cpu_flags);
+			if (skb == skb_peek(&sk->sk_receive_queue)) {
+				__skb_unlink(skb,
+					     &sk->sk_receive_queue);
+				atomic_dec(&skb->users);
+			}
+			spin_unlock_irqrestore(&sk->sk_receive_queue.lock,
+					       cpu_flags);
+no_peek:
+			skb_free_datagram(sk, skb);
+			goto no_packet;
+		}
+
 		if (skb)
 			return skb;
 
--- linux-2.6-mm.orig/security/security.c	2007-11-14 15:14:10.000000000 +0900
+++ linux-2.6-mm/security/security.c	2007-11-14 15:54:12.000000000 +0900
@@ -662,6 +662,21 @@ int security_task_kill(struct task_struc
 	return security_ops->task_kill(p, info, sig, secid);
 }
 
+int security_task_kill_unlocked(int pid, int sig)
+{
+	return security_ops->task_kill_unlocked(pid, sig);
+}
+
+int security_task_tkill_unlocked(int pid, int sig)
+{
+	return security_ops->task_tkill_unlocked(pid, sig);
+}
+
+int security_task_tgkill_unlocked(int tgid, int pid, int sig)
+{
+	return security_ops->task_tgkill_unlocked(tgid, pid, sig);
+}
+
 int security_task_wait(struct task_struct *p)
 {
 	return security_ops->task_wait(p);
@@ -869,9 +884,9 @@ int security_socket_accept(struct socket
 	return security_ops->socket_accept(sock, newsock);
 }
 
-void security_socket_post_accept(struct socket *sock, struct socket *newsock)
+int security_socket_post_accept(struct socket *sock, struct socket *newsock)
 {
-	security_ops->socket_post_accept(sock, newsock);
+	return security_ops->socket_post_accept(sock, newsock);
 }
 
 int security_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size)
@@ -885,6 +900,12 @@ int security_socket_recvmsg(struct socke
 	return security_ops->socket_recvmsg(sock, msg, size, flags);
 }
 
+int security_post_recv_datagram(struct sock *sk, struct sk_buff *skb,
+				unsigned int flags)
+{
+	return security_ops->post_recv_datagram(sk, skb, flags);
+}
+
 int security_socket_getsockname(struct socket *sock)
 {
 	return security_ops->socket_getsockname(sock);

-- 

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

* Re: [TOMOYO #5 02/18] Add wrapper functions for VFS helper functions.
  2007-11-16 17:34 ` [TOMOYO #5 02/18] Add wrapper functions for VFS helper functions penguin-kernel
@ 2007-11-16 17:47   ` Trond Myklebust
  2007-11-16 18:20     ` [TOMOYO #5 02/18] Add wrapper functions for VFS helperfunctions Tetsuo Handa
  2007-11-19 12:53     ` [TOMOYO #5 02/18] Add wrapper functions for VFS helper functions Christoph Hellwig
  0 siblings, 2 replies; 39+ messages in thread
From: Trond Myklebust @ 2007-11-16 17:47 UTC (permalink / raw)
  To: penguin-kernel; +Cc: akpm, linux-kernel, linux-security-module


On Sat, 2007-11-17 at 02:34 +0900, penguin-kernel@I-love.SAKURA.ne.jp
wrote:
> plain text document attachment
> (add-wrapper-functions-for-vfs-helper-functions.patch)
> This patch allows LSM hooks refer previously associated "struct vfsmount" parameter
> so that they can calculate pathname of given "struct dentry".

NACK to this. Passing function parameters through the task_struct is
definitely not an acceptable hack (unless you're doing it through the
stack).

Trond


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

* Re: [TOMOYO #5 11/18] Network access control functions.
  2007-11-16 17:34 ` [TOMOYO #5 11/18] Network access control functions penguin-kernel
@ 2007-11-16 17:57   ` YOSHIFUJI Hideaki / 吉藤英明
  2007-11-16 18:22     ` Tetsuo Handa
  0 siblings, 1 reply; 39+ messages in thread
From: YOSHIFUJI Hideaki / 吉藤英明 @ 2007-11-16 17:57 UTC (permalink / raw)
  To: penguin-kernel
  Cc: akpm, linux-kernel, linux-security-module, takedakn, yoshfuji

Hello.

In article <20071116173527.282964575@I-love.SAKURA.ne.jp> (at Sat, 17 Nov 2007 02:34:50 +0900), penguin-kernel@I-love.SAKURA.ne.jp says:

> +	*cp++ = '\0';
> +	count = sscanf(cp,
> +		       NIP6_FMT "-" NIP6_FMT,
> +		       &min[0], &min[1], &min[2], &min[3],
> +		       &min[4], &min[5], &min[6], &min[7],
> +		       &max[0], &max[1], &max[2], &max[3],
> +		       &max[4], &max[5], &max[6], &max[7]);
> +

I think you can use in6_pton() here.

> +	count = sscanf(cp,
> +		       NIPQUAD_FMT "-" NIPQUAD_FMT,
> +		       &min[0], &min[1],
> +		       &min[2], &min[3],
> +		       &max[0], &max[1],
> +		       &max[2], &max[3]);
> +

in4_pton().

> +
> +/**
> + * tmy_print_ipv6 - print ipv6 address
> + * @buffer:     pointer to buffer to save the result.
> + * @buffer_len: sizeof @buffer .
> + * @ip:         pointer to an IPv6 address in network byte order.
> + *
> + * Returns @buffer .
> + */
> +char *tmy_print_ipv6(char *buffer, const int buffer_len, const u16 *ip)
> +{
> +	memset(buffer, 0, buffer_len);
> +	snprintf(buffer, buffer_len - 1, NIP6_FMT,
> +		 ntohs(ip[0]), ntohs(ip[1]), ntohs(ip[2]), ntohs(ip[3]),
> +		 ntohs(ip[4]), ntohs(ip[5]), ntohs(ip[6]), ntohs(ip[7]));
> +	return buffer;
> +}
> +

snprintf(buffer, buffer_len - 1, NIP6_FMT, NIP6(*(struct in6_addr *)ip));

> +	count = sscanf(cp2,
> +		       NIP6_FMT "-" NIP6_FMT,
> +		       &min[0], &min[1], &min[2], &min[3],
> +		       &min[4], &min[5], &min[6], &min[7],
> +		       &max[0], &max[1], &max[2], &max[3],
> +		       &max[4], &max[5], &max[6], &max[7]);
> +

again, in6_pton().

> +	count = sscanf(cp2,
> +		       NIPQUAD_FMT "-" NIPQUAD_FMT,
> +		       &min[0], &min[1], &min[2], &min[3],
> +		       &max[0], &max[1], &max[2], &max[3]);
> +

in4_pton().

--yoshfuji

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

* Re: [TOMOYO #5 02/18] Add wrapper functions for VFS helperfunctions.
  2007-11-16 17:47   ` Trond Myklebust
@ 2007-11-16 18:20     ` Tetsuo Handa
  2007-11-16 18:33       ` Trond Myklebust
  2007-11-19 12:53     ` [TOMOYO #5 02/18] Add wrapper functions for VFS helper functions Christoph Hellwig
  1 sibling, 1 reply; 39+ messages in thread
From: Tetsuo Handa @ 2007-11-16 18:20 UTC (permalink / raw)
  To: trond.myklebust; +Cc: akpm, linux-kernel, linux-security-module

Hello.

Trond Myklebust wrote:
> > This patch allows LSM hooks refer previously associated "struct vfsmount" parameter
> > so that they can calculate pathname of given "struct dentry".
> 
> NACK to this. Passing function parameters through the task_struct is
> definitely not an acceptable hack (unless you're doing it through the
> stack).

Thanks for your advise.
But I don't know why it is not acceptable.
The topmost vfsmount's address is retrievable through the task_struct,
and non-topmost vfsmount's address (if any) are stored on the stack memory.
Could you show me why this is bad?

Regards.


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

* Re: [TOMOYO #5 11/18] Network access control functions.
  2007-11-16 17:57   ` YOSHIFUJI Hideaki / 吉藤英明
@ 2007-11-16 18:22     ` Tetsuo Handa
  0 siblings, 0 replies; 39+ messages in thread
From: Tetsuo Handa @ 2007-11-16 18:22 UTC (permalink / raw)
  To: yoshfuji; +Cc: akpm, linux-kernel, linux-security-module, takedakn

YOSHIFUJI Hideaki wrote:
> I think you can use in6_pton() here.
> in4_pton().
I see. I'll try it later.

Thanks.


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

* Re: [TOMOYO #5 02/18] Add wrapper functions for VFS helperfunctions.
  2007-11-16 18:20     ` [TOMOYO #5 02/18] Add wrapper functions for VFS helperfunctions Tetsuo Handa
@ 2007-11-16 18:33       ` Trond Myklebust
  2007-11-17  4:04         ` [TOMOYO #5 02/18] Add wrapper functions for VFShelperfunctions Tetsuo Handa
  0 siblings, 1 reply; 39+ messages in thread
From: Trond Myklebust @ 2007-11-16 18:33 UTC (permalink / raw)
  To: Tetsuo Handa; +Cc: akpm, linux-kernel, linux-security-module


On Sat, 2007-11-17 at 03:20 +0900, Tetsuo Handa wrote:
> Hello.
> 
> Trond Myklebust wrote:
> > > This patch allows LSM hooks refer previously associated "struct vfsmount" parameter
> > > so that they can calculate pathname of given "struct dentry".
> > 
> > NACK to this. Passing function parameters through the task_struct is
> > definitely not an acceptable hack (unless you're doing it through the
> > stack).
> 
> Thanks for your advise.
> But I don't know why it is not acceptable.
> The topmost vfsmount's address is retrievable through the task_struct,
> and non-topmost vfsmount's address (if any) are stored on the stack memory.
> Could you show me why this is bad?

The problem is that you have thrown away the main tool for documenting
the requirement, and for enforcing correctness (i.e. function argument
checking by the compiler).
The old functions are still there, are still exported, and still take
the same arguments as before, but you have now added a hidden
requirement that I have to set last_vfsmount when I call them. If I
haven't read your patch, and just call one of those vfs_* functions as
before, without setting last_vfsmount, I break your model, and I won't
find out until someone reports an obscure bug at runtime.

Trond


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

* Re: [TOMOYO #5 18/18] LSM expansion for TOMOYO Linux.
  2007-11-16 17:34 ` [TOMOYO #5 18/18] LSM expansion for TOMOYO Linux penguin-kernel
@ 2007-11-16 19:23   ` Paul Moore
  2007-11-17  3:45     ` Tetsuo Handa
  0 siblings, 1 reply; 39+ messages in thread
From: Paul Moore @ 2007-11-16 19:23 UTC (permalink / raw)
  To: penguin-kernel; +Cc: akpm, linux-kernel, linux-security-module, Kentaro Takeda

On Friday 16 November 2007 12:34:57 pm penguin-kernel@i-love.sakura.ne.jp 
wrote:
> LSM hooks for network accept and recv:
>    * socket_post_accept is modified to return int.
>    * post_recv_datagram is added in skb_recv_datagram.
>
> You can try TOMOYO Linux without this patch, but in that case, you
> can't use access control functionality for restricting signal
> transmission and incoming network data.

As discussed a few times before, I'm still not really excited about adding a 
new LSM hook in skb_recv_datagram() when we already have hooks to control 
locally consumed network traffic.  However, I will admit that these existing 
hooks do not allow the LSM to block and query userspace for an access 
decision like you are trying to do with TOMOYO.  I would prefer not to see 
this new LSM hook added but I do not have an alternative solution to your 
problem so I can't in good conscience completely object to this patch.

Regardless, I have a few comments which are included below ...

> --- linux-2.6-mm.orig/net/core/datagram.c	2007-10-10 05:31:38.000000000
> +0900 +++ linux-2.6-mm/net/core/datagram.c	2007-11-14 15:15:44.000000000
> +0900 @@ -55,6 +55,7 @@
>  #include <net/checksum.h>
>  #include <net/sock.h>
>  #include <net/tcp_states.h>
> +#include <linux/security.h>
>
>  /*
>   *	Is a socket 'connection oriented' ?
> @@ -178,6 +179,27 @@ struct sk_buff *skb_recv_datagram(struct
>  		} else
>  			skb = skb_dequeue(&sk->sk_receive_queue);
>
> +		error = security_post_recv_datagram(sk, skb, flags);
> +		if (error) {
> +			unsigned long cpu_flags;

With this patch the 'cpu_flags' variable will be used in two different 
if-blocks in this function and declared locally within each block.  Please 
move the 'cpu_flags' declaration to the top of the function so it only needs 
to be declared once.

> +
> +			if (!(flags & MSG_PEEK))
> +				goto no_peek;
> +
> +			spin_lock_irqsave(&sk->sk_receive_queue.lock,
> +					  cpu_flags);
> +			if (skb == skb_peek(&sk->sk_receive_queue)) {

I might be missing something here, but why do you need to do a skb_peek() 
again?  You already have the skb and the sock, just do the unlink.

> +				__skb_unlink(skb,
> +					     &sk->sk_receive_queue);
> +				atomic_dec(&skb->users);
> +			}
> +			spin_unlock_irqrestore(&sk->sk_receive_queue.lock,
> +					       cpu_flags);
> +no_peek:
> +			skb_free_datagram(sk, skb);
> +			goto no_packet;

Two things.  First you can probably just call kfree_skb() instead of 
skb_free_datagram().  Second, why not move the 'no_peek' code to just 
before 'no_packet'?

-- 
paul moore
linux security @ hp

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

* Re: [TOMOYO #5 18/18] LSM expansion for TOMOYO Linux.
  2007-11-16 19:23   ` Paul Moore
@ 2007-11-17  3:45     ` Tetsuo Handa
  2007-11-17 23:09       ` Paul Moore
  0 siblings, 1 reply; 39+ messages in thread
From: Tetsuo Handa @ 2007-11-17  3:45 UTC (permalink / raw)
  To: paul.moore; +Cc: akpm, linux-kernel, linux-security-module, takedakn

Hello.

Thank you for your feedback.

Paul Moore wrote:
> With this patch the 'cpu_flags' variable will be used in two different 
> if-blocks in this function and declared locally within each block.  Please 
> move the 'cpu_flags' declaration to the top of the function so it only needs 
> to be declared once.
I see.

> I might be missing something here, but why do you need to do a skb_peek() 
> again?  You already have the skb and the sock, just do the unlink.
The skb might be already dequeued by other thread while I slept inside
security_post_recv_datagram().

> Two things.  First you can probably just call kfree_skb() instead of 
> skb_free_datagram().
So far, there is no difference between skb_free_datagram() and kfree_skb().

| void skb_free_datagram(struct sock *sk, struct sk_buff *skb)
| {
|         kfree_skb(skb);
| }

udp_recvmsg() thinks it might not be ok to directly call kfree_skb().
But you and skb_kill_datagram() think it is ok to directly call kfree_skb(),
I will do so.

> Second, why not move the 'no_peek' code to just before 'no_packet'?
Oh, I didn't notice I can insert here. Now I can also move the rest code like

|		error = security_post_recv_datagram(sk, skb, flags);
|		if (error)
|                       goto force_dequeue;

|         } while (!wait_for_packet(sk, err, &timeo));
|
|         return NULL;
| force_dequeue:
|         /* dequeue if MSG_PEEK is set. */
| no_packet:
|         *err = error;
|         return NULL;

to reduce indentation.

Thank you.


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

* Re: [TOMOYO #5 02/18] Add wrapper functions for VFShelperfunctions.
  2007-11-16 18:33       ` Trond Myklebust
@ 2007-11-17  4:04         ` Tetsuo Handa
  2007-11-17  4:46           ` Trond Myklebust
  0 siblings, 1 reply; 39+ messages in thread
From: Tetsuo Handa @ 2007-11-17  4:04 UTC (permalink / raw)
  To: trond.myklebust; +Cc: akpm, linux-kernel, linux-security-module

Hello.

Trond Myklebust wrote:
> The problem is that you have thrown away the main tool for documenting
> the requirement, and for enforcing correctness (i.e. function argument
> checking by the compiler).
I'm sorry. I wanted to know whether the below approach is possible.

> The old functions are still there, are still exported, and still take
> the same arguments as before, but you have now added a hidden
> requirement that I have to set last_vfsmount when I call them. If I
> haven't read your patch, and just call one of those vfs_* functions as
> before, without setting last_vfsmount, I break your model, and I won't
> find out until someone reports an obscure bug at runtime.
I don't care if some kernel module calls vfs_* without setting last_vfsmount
because there is no reason to reject requests from kernel code.
Setting last_vfsmount is required for requests from userland process.
This approach makes it possible to
(1) tell whether the vfs_* calls are from userland or kernel
and
(2) leave kernel modules that are not called from userland unchanged.
This is why this patch doesn't and needn't to modify fs/*/ files.

Thanks.


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

* Re: [TOMOYO #5 02/18] Add wrapper functions for VFShelperfunctions.
  2007-11-17  4:04         ` [TOMOYO #5 02/18] Add wrapper functions for VFShelperfunctions Tetsuo Handa
@ 2007-11-17  4:46           ` Trond Myklebust
  2007-11-17  5:23             ` Tetsuo Handa
  0 siblings, 1 reply; 39+ messages in thread
From: Trond Myklebust @ 2007-11-17  4:46 UTC (permalink / raw)
  To: Tetsuo Handa; +Cc: akpm, linux-kernel, linux-security-module


On Sat, 2007-11-17 at 13:04 +0900, Tetsuo Handa wrote:
> Hello.
> 
> Trond Myklebust wrote:
> > The problem is that you have thrown away the main tool for documenting
> > the requirement, and for enforcing correctness (i.e. function argument
> > checking by the compiler).
> I'm sorry. I wanted to know whether the below approach is possible.

"Possible" is completely irrelevant. The question you _should_ be asking
is "is it maintainable".

> > The old functions are still there, are still exported, and still take
> > the same arguments as before, but you have now added a hidden
> > requirement that I have to set last_vfsmount when I call them. If I
> > haven't read your patch, and just call one of those vfs_* functions as
> > before, without setting last_vfsmount, I break your model, and I won't
> > find out until someone reports an obscure bug at runtime.
> I don't care if some kernel module calls vfs_* without setting last_vfsmount
> because there is no reason to reject requests from kernel code.
> Setting last_vfsmount is required for requests from userland process.
> This approach makes it possible to
> (1) tell whether the vfs_* calls are from userland or kernel
> and
> (2) leave kernel modules that are not called from userland unchanged.
> This is why this patch doesn't and needn't to modify fs/*/ files.

I'm confused. How do you tell the difference between a 'userland'
request and a 'kernel' request, and why is the latter safe from a
security perspective?

Trond


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

* Re: [TOMOYO #5 02/18] Add wrapper functions for VFShelperfunctions.
  2007-11-17  4:46           ` Trond Myklebust
@ 2007-11-17  5:23             ` Tetsuo Handa
  0 siblings, 0 replies; 39+ messages in thread
From: Tetsuo Handa @ 2007-11-17  5:23 UTC (permalink / raw)
  To: trond.myklebust; +Cc: akpm, linux-kernel, linux-security-module

Hello.

Trond Myklebust wrote:
> I'm confused. How do you tell the difference between a 'userland'
> request and a 'kernel' request, and why is the latter safe from a
> security perspective?
For example, if userland process wants to create a directory,
it will call mkdir(2).
In the kernel, mkdir(2) is mapped to sys_mkdir(),
sys_mkdir() calls sys_mkdirat(),
sys_mkdirat() calls vfs_mkdir().

Therefore, there is a chance to tell whether
vfs_mkdir() is triggered by userland process's request.
Some kernel code *might* call sys_mkdir() too.
But at least I think I can hook all requests
triggered by userland process's request
because routes to call vfs_mkdir() from userland is not infinite.

We are regarding the kernel code as trusted.
For example, no security checks for code that are called by only kernel.
http://www.mail-archive.com/linux-security-module@vger.kernel.org/msg01830.html

Regards.


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

* Re: [TOMOYO #5 18/18] LSM expansion for TOMOYO Linux.
  2007-11-17  3:45     ` Tetsuo Handa
@ 2007-11-17 23:09       ` Paul Moore
  2007-11-18  4:00         ` Tetsuo Handa
  0 siblings, 1 reply; 39+ messages in thread
From: Paul Moore @ 2007-11-17 23:09 UTC (permalink / raw)
  To: Tetsuo Handa; +Cc: akpm, linux-kernel, linux-security-module, takedakn

On Friday 16 November 2007 10:45:32 pm Tetsuo Handa wrote:
> Paul Moore wrote:
> > I might be missing something here, but why do you need to do a skb_peek()
> > again?  You already have the skb and the sock, just do the unlink.
>
> The skb might be already dequeued by other thread while I slept inside
> security_post_recv_datagram().

Okay, well if that is the case I think you are going to have another problem 
in that you could end up throwing away skbs that haven't been through your 
security_post_recv_datagram() hook because you _always_ throw away the result 
of the second skb_peek().  Once again, if I'm wrong please correct me.

> > Second, why not move the 'no_peek' code to just before 'no_packet'?
>
> Oh, I didn't notice I can insert here. Now I can also move the rest code
> like
>
> |		error = security_post_recv_datagram(sk, skb, flags);
> |		if (error)
> |                       goto force_dequeue;
> |
> |         } while (!wait_for_packet(sk, err, &timeo));

Where did the 'if (skb) return skb;' code go?  Don't you need to do you LSM 
call before you return the skb?

-- 
paul moore
linux security @ hp

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

* Re: [TOMOYO #5 18/18] LSM expansion for TOMOYO Linux.
  2007-11-17 23:09       ` Paul Moore
@ 2007-11-18  4:00         ` Tetsuo Handa
  2007-11-19 13:36           ` Paul Moore
  0 siblings, 1 reply; 39+ messages in thread
From: Tetsuo Handa @ 2007-11-18  4:00 UTC (permalink / raw)
  To: paul.moore; +Cc: akpm, linux-kernel, linux-security-module, takedakn

Hello.

Paul Moore wrote:
> Okay, well if that is the case I think you are going to have another problem 
> in that you could end up throwing away skbs that haven't been through your 
> security_post_recv_datagram() hook because you _always_ throw away the result 
> of the second skb_peek().  Once again, if I'm wrong please correct me.
I didn't understand what's wrong with throwing away the result of
the second skb_peek(). I'm doing similar things that udp_recvmsg() is doing.

| int udp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
|                 size_t len, int noblock, int flags, int *addr_len)
| {
<snipped>
| try_again:
|         skb = skb_recv_datagram(sk, flags, noblock, &err);
|         if (!skb)
|                 goto out;
<snipped>
| out_free:
|         skb_free_datagram(sk, skb);
| out:
|         return err;
| 
| csum_copy_err:
|         UDP_INC_STATS_BH(UDP_MIB_INERRORS, is_udplite);
| 
|         skb_kill_datagram(sk, skb, flags);
| 
|         if (noblock)
|                 return -EAGAIN;
|         goto try_again;
| }

The only difference is that I'm not using skb_kill_datagram()
because skb_kill_datagram() uses spin_lock_bh()
while skb_recv_datagram() needs to use spin_lock_irqsave().


> Where did the 'if (skb) return skb;' code go?  Don't you need to do you LSM 
> call before you return the skb?
Sorry, I should have explicitly inserted <snipped> rather than a blank line like:

|		error = security_post_recv_datagram(sk, skb, flags);
|		if (error)
|                       goto force_dequeue;
<snipped>
|         } while (!wait_for_packet(sk, err, &timeo));
|
|         return NULL;
| force_dequeue:
|         /* dequeue if MSG_PEEK is set. */
| no_packet:
|         *err = error;
|         return NULL;

The below is the updated patch.
Regards.
-----
Subject: LSM expansion for TOMOYO Linux.

LSM hooks for sending signal:
   * task_kill_unlocked is added in sys_kill
   * task_tkill_unlocked is added in sys_tkill
   * task_tgkill_unlocked is added in sys_tgkill
LSM hooks for network accept and recv:
   * socket_post_accept is modified to return int.
   * post_recv_datagram is added in skb_recv_datagram.

You can try TOMOYO Linux without this patch, but in that case, you
can't use access control functionality for restricting signal
transmission and incoming network data.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
 include/linux/security.h |   74 +++++++++++++++++++++++++++++++++++++++++++----
 kernel/signal.c          |   17 ++++++++++
 net/core/datagram.c      |   29 ++++++++++++++++--
 net/socket.c             |    7 +++-
 security/dummy.c         |   32 ++++++++++++++++++--
 security/security.c      |   25 ++++++++++++++-
 6 files changed, 169 insertions(+), 15 deletions(-)

--- linux-2.6.23.orig/include/linux/security.h	2007-11-17 00:35:44.000000000 +0900
+++ linux-2.6.23/include/linux/security.h	2007-11-17 00:37:26.000000000 +0900
@@ -657,6 +657,25 @@ struct request_sock;
  *	@sig contains the signal value.
  *	@secid contains the sid of the process where the signal originated
  *	Return 0 if permission is granted.
+ * @task_kill_unlocked:
+ *	Check permission before sending signal @sig to the process of @pid
+ *	with sys_kill.
+ *	@pid contains the pid of target process.
+ *	@sig contains the signal value.
+ *	Return 0 if permission is granted.
+ * @task_tkill_unlocked:
+ *	Check permission before sending signal @sig to the process of @pid
+ *	with sys_tkill.
+ *	@pid contains the pid of target process.
+ *	@sig contains the signal value.
+ *	Return 0 if permission is granted.
+ * @task_tgkill_unlocked:
+ *	Check permission before sending signal @sig to the process of @pid
+ *	with sys_tgkill.
+ *	@tgid contains the thread group id.
+ *	@pid contains the pid of target process.
+ *	@sig contains the signal value.
+ *	Return 0 if permission is granted.
  * @task_wait:
  *	Check permission before allowing a process to reap a child process @p
  *	and collect its status information.
@@ -778,8 +797,12 @@ struct request_sock;
  * @socket_post_accept:
  *	This hook allows a security module to copy security
  *	information into the newly created socket's inode.
+ *	This hook also allows a security module to filter connections
+ *	from unwanted peers.
+ *	The connection will be aborted if this hook returns nonzero.
  *	@sock contains the listening socket structure.
  *	@newsock contains the newly created server socket for connection.
+ *	Return 0 if permission is granted.
  * @socket_sendmsg:
  *	Check permission before transmitting a message to another socket.
  *	@sock contains the socket structure.
@@ -793,6 +816,12 @@ struct request_sock;
  *	@size contains the size of message structure.
  *	@flags contains the operational flags.
  *	Return 0 if permission is granted.  
+ * @post_recv_datagram:
+ *	Check permission after receiving a datagram.
+ *	@sk contains the socket.
+ *	@skb contains the socket buffer (may be NULL).
+ *	@flags contains the operational flags.
+ *	Return 0 if permission is granted.
  * @socket_getsockname:
  *	Check permission before the local address (name) of the socket object
  *	@sock is retrieved.
@@ -1319,6 +1348,9 @@ struct security_operations {
 	int (*task_movememory) (struct task_struct * p);
 	int (*task_kill) (struct task_struct * p,
 			  struct siginfo * info, int sig, u32 secid);
+	int (*task_kill_unlocked) (int pid, int sig);
+	int (*task_tkill_unlocked) (int pid, int sig);
+	int (*task_tgkill_unlocked) (int tgid, int pid, int sig);
 	int (*task_wait) (struct task_struct * p);
 	int (*task_prctl) (int option, unsigned long arg2,
 			   unsigned long arg3, unsigned long arg4,
@@ -1384,12 +1416,16 @@ struct security_operations {
 			       struct sockaddr * address, int addrlen);
 	int (*socket_listen) (struct socket * sock, int backlog);
 	int (*socket_accept) (struct socket * sock, struct socket * newsock);
-	void (*socket_post_accept) (struct socket * sock,
-				    struct socket * newsock);
+#define TMY_LSM_EXPANSION
+	int (*socket_post_accept) (struct socket *sock,
+				   struct socket *newsock);
 	int (*socket_sendmsg) (struct socket * sock,
 			       struct msghdr * msg, int size);
 	int (*socket_recvmsg) (struct socket * sock,
 			       struct msghdr * msg, int size, int flags);
+	int (*post_recv_datagram) (struct sock *sk,
+				   struct sk_buff *skb,
+				   unsigned int flags);
 	int (*socket_getsockname) (struct socket * sock);
 	int (*socket_getpeername) (struct socket * sock);
 	int (*socket_getsockopt) (struct socket * sock, int level, int optname);
@@ -1567,6 +1603,9 @@ int security_task_getscheduler(struct ta
 int security_task_movememory(struct task_struct *p);
 int security_task_kill(struct task_struct *p, struct siginfo *info,
 			int sig, u32 secid);
+int security_task_kill_unlocked(int pid, int sig);
+int security_task_tkill_unlocked(int pid, int sig);
+int security_task_tgkill_unlocked(int tgid, int pid, int sig);
 int security_task_wait(struct task_struct *p);
 int security_task_prctl(int option, unsigned long arg2, unsigned long arg3,
 			 unsigned long arg4, unsigned long arg5);
@@ -2112,6 +2151,21 @@ static inline int security_task_kill (st
 	return cap_task_kill(p, info, sig, secid);
 }
 
+static inline int security_task_kill_unlocked(int pid, int sig)
+{
+	return 0;
+}
+
+static inline int security_task_tkill_unlocked(int pid, int sig)
+{
+	return 0;
+}
+
+static inline int security_task_tgkill_unlocked(int tgid, int pid, int sig)
+{
+	return 0;
+}
+
 static inline int security_task_wait (struct task_struct *p)
 {
 	return 0;
@@ -2294,10 +2348,12 @@ int security_socket_bind(struct socket *
 int security_socket_connect(struct socket *sock, struct sockaddr *address, int addrlen);
 int security_socket_listen(struct socket *sock, int backlog);
 int security_socket_accept(struct socket *sock, struct socket *newsock);
-void security_socket_post_accept(struct socket *sock, struct socket *newsock);
+int security_socket_post_accept(struct socket *sock, struct socket *newsock);
 int security_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size);
 int security_socket_recvmsg(struct socket *sock, struct msghdr *msg,
 			    int size, int flags);
+int security_post_recv_datagram(struct sock *sk, struct sk_buff *skb,
+				unsigned int flags);
 int security_socket_getsockname(struct socket *sock);
 int security_socket_getpeername(struct socket *sock);
 int security_socket_getsockopt(struct socket *sock, int level, int optname);
@@ -2373,9 +2429,10 @@ static inline int security_socket_accept
 	return 0;
 }
 
-static inline void security_socket_post_accept(struct socket * sock, 
-					       struct socket * newsock)
+static inline int security_socket_post_accept(struct socket *sock,
+					      struct socket *newsock)
 {
+	return 0;
 }
 
 static inline int security_socket_sendmsg(struct socket * sock, 
@@ -2391,6 +2448,13 @@ static inline int security_socket_recvms
 	return 0;
 }
 
+static inline int security_post_recv_datagram(struct sock *sk,
+					      struct sk_buff *skb,
+					      unsigned int flags)
+{
+	return 0;
+}
+
 static inline int security_socket_getsockname(struct socket * sock)
 {
 	return 0;
--- linux-2.6.23.orig/kernel/signal.c	2007-11-17 00:35:51.000000000 +0900
+++ linux-2.6.23/kernel/signal.c	2007-11-17 00:37:26.000000000 +0900
@@ -2218,6 +2218,11 @@ asmlinkage long
 sys_kill(int pid, int sig)
 {
 	struct siginfo info;
+	int ret;
+
+	ret = security_task_kill_unlocked(pid, sig);
+	if (ret)
+		return ret;
 
 	info.si_signo = sig;
 	info.si_errno = 0;
@@ -2273,10 +2278,16 @@ static int do_tkill(int tgid, int pid, i
  */
 asmlinkage long sys_tgkill(int tgid, int pid, int sig)
 {
+	int ret;
+
 	/* This is only valid for single tasks */
 	if (pid <= 0 || tgid <= 0)
 		return -EINVAL;
 
+	ret = security_task_tgkill_unlocked(tgid, pid, sig);
+	if (ret)
+		return ret;
+
 	return do_tkill(tgid, pid, sig);
 }
 
@@ -2286,10 +2297,16 @@ asmlinkage long sys_tgkill(int tgid, int
 asmlinkage long
 sys_tkill(int pid, int sig)
 {
+	int ret;
+
 	/* This is only valid for single tasks */
 	if (pid <= 0)
 		return -EINVAL;
 
+	ret = security_task_tkill_unlocked(pid, sig);
+	if (ret)
+		return ret;
+
 	return do_tkill(0, pid, sig);
 }
 
--- linux-2.6.23.orig/net/socket.c	2007-11-17 00:35:03.000000000 +0900
+++ linux-2.6.23/net/socket.c	2007-11-17 00:37:26.000000000 +0900
@@ -1442,13 +1442,16 @@ asmlinkage long sys_accept(int fd, struc
 			goto out_fd;
 	}
 
+	/* Filter connections from unwanted peers like TCP Wrapper. */
+	err = security_socket_post_accept(sock, newsock);
+	if (err)
+		goto out_fd;
+
 	/* File flags are not inherited via accept() unlike another OSes. */
 
 	fd_install(newfd, newfile);
 	err = newfd;
 
-	security_socket_post_accept(sock, newsock);
-
 out_put:
 	fput_light(sock->file, fput_needed);
 out:
--- linux-2.6.23.orig/security/dummy.c	2007-11-17 00:35:44.000000000 +0900
+++ linux-2.6.23/security/dummy.c	2007-11-17 00:37:26.000000000 +0900
@@ -576,6 +576,21 @@ static int dummy_task_kill (struct task_
 	return 0;
 }
 
+static int dummy_task_kill_unlocked(int pid, int sig)
+{
+	return 0;
+}
+
+static int dummy_task_tkill_unlocked(int pid, int sig)
+{
+	return 0;
+}
+
+static int dummy_task_tgkill_unlocked(int tgid, int pid, int sig)
+{
+	return 0;
+}
+
 static int dummy_task_prctl (int option, unsigned long arg2, unsigned long arg3,
 			     unsigned long arg4, unsigned long arg5)
 {
@@ -753,10 +768,10 @@ static int dummy_socket_accept (struct s
 	return 0;
 }
 
-static void dummy_socket_post_accept (struct socket *sock, 
-				      struct socket *newsock)
+static int dummy_socket_post_accept(struct socket *sock,
+				    struct socket *newsock)
 {
-	return;
+	return 0;
 }
 
 static int dummy_socket_sendmsg (struct socket *sock, struct msghdr *msg,
@@ -771,6 +786,13 @@ static int dummy_socket_recvmsg (struct 
 	return 0;
 }
 
+static int dummy_post_recv_datagram(struct sock *sk,
+				    struct sk_buff *skb,
+				    unsigned int flags)
+{
+	return 0;
+}
+
 static int dummy_socket_getsockname (struct socket *sock)
 {
 	return 0;
@@ -1062,6 +1084,9 @@ void security_fixup_ops (struct security
 	set_to_dummy_if_null(ops, task_movememory);
 	set_to_dummy_if_null(ops, task_wait);
 	set_to_dummy_if_null(ops, task_kill);
+	set_to_dummy_if_null(ops, task_kill_unlocked);
+	set_to_dummy_if_null(ops, task_tkill_unlocked);
+	set_to_dummy_if_null(ops, task_tgkill_unlocked);
 	set_to_dummy_if_null(ops, task_prctl);
 	set_to_dummy_if_null(ops, task_reparent_to_init);
  	set_to_dummy_if_null(ops, task_to_inode);
@@ -1104,6 +1129,7 @@ void security_fixup_ops (struct security
 	set_to_dummy_if_null(ops, socket_post_accept);
 	set_to_dummy_if_null(ops, socket_sendmsg);
 	set_to_dummy_if_null(ops, socket_recvmsg);
+	set_to_dummy_if_null(ops, post_recv_datagram);
 	set_to_dummy_if_null(ops, socket_getsockname);
 	set_to_dummy_if_null(ops, socket_getpeername);
 	set_to_dummy_if_null(ops, socket_setsockopt);
--- linux-2.6.23.orig/net/core/datagram.c	2007-10-10 05:31:38.000000000 +0900
+++ linux-2.6.23/net/core/datagram.c	2007-11-18 12:17:59.000000000 +0900
@@ -55,6 +55,7 @@
 #include <net/checksum.h>
 #include <net/sock.h>
 #include <net/tcp_states.h>
+#include <linux/security.h>
 
 /*
  *	Is a socket 'connection oriented' ?
@@ -148,6 +149,7 @@ struct sk_buff *skb_recv_datagram(struct
 {
 	struct sk_buff *skb;
 	long timeo;
+	unsigned long cpu_flags;
 	/*
 	 * Caller is allowed not to check sk->sk_err before skb_recv_datagram()
 	 */
@@ -166,8 +168,6 @@ struct sk_buff *skb_recv_datagram(struct
 		 * However, this function was corrent in any case. 8)
 		 */
 		if (flags & MSG_PEEK) {
-			unsigned long cpu_flags;
-
 			spin_lock_irqsave(&sk->sk_receive_queue.lock,
 					  cpu_flags);
 			skb = skb_peek(&sk->sk_receive_queue);
@@ -178,6 +178,10 @@ struct sk_buff *skb_recv_datagram(struct
 		} else
 			skb = skb_dequeue(&sk->sk_receive_queue);
 
+		error = security_post_recv_datagram(sk, skb, flags);
+		if (error)
+			goto force_dequeue;
+
 		if (skb)
 			return skb;
 
@@ -189,7 +193,26 @@ struct sk_buff *skb_recv_datagram(struct
 	} while (!wait_for_packet(sk, err, &timeo));
 
 	return NULL;
-
+force_dequeue:
+	/* Drop this packet because LSM says "Don't pass it to the caller". */
+	if (!(flags & MSG_PEEK))
+		goto no_peek;
+	/*
+	 * If this packet is MSG_PEEK'ed, dequeue it forcibly
+	 * so that this packet won't prevent the caller from picking up
+	 * next packet.
+	 * Side effect: If this socket is shared by multiple processes
+	 * who have differnt policy, the process who is permitted to pick up
+	 * this packet can't pick up this packet.
+	 */
+	spin_lock_irqsave(&sk->sk_receive_queue.lock, cpu_flags);
+	if (skb == skb_peek(&sk->sk_receive_queue)) {
+		__skb_unlink(skb, &sk->sk_receive_queue);
+		atomic_dec(&skb->users);
+	}
+	spin_unlock_irqrestore(&sk->sk_receive_queue.lock, cpu_flags);
+no_peek:
+	kfree_skb(skb);
 no_packet:
 	*err = error;
 	return NULL;
--- linux-2.6.23.orig/security/security.c	2007-11-17 00:35:44.000000000 +0900
+++ linux-2.6.23/security/security.c	2007-11-17 00:37:26.000000000 +0900
@@ -662,6 +662,21 @@ int security_task_kill(struct task_struc
 	return security_ops->task_kill(p, info, sig, secid);
 }
 
+int security_task_kill_unlocked(int pid, int sig)
+{
+	return security_ops->task_kill_unlocked(pid, sig);
+}
+
+int security_task_tkill_unlocked(int pid, int sig)
+{
+	return security_ops->task_tkill_unlocked(pid, sig);
+}
+
+int security_task_tgkill_unlocked(int tgid, int pid, int sig)
+{
+	return security_ops->task_tgkill_unlocked(tgid, pid, sig);
+}
+
 int security_task_wait(struct task_struct *p)
 {
 	return security_ops->task_wait(p);
@@ -869,9 +884,9 @@ int security_socket_accept(struct socket
 	return security_ops->socket_accept(sock, newsock);
 }
 
-void security_socket_post_accept(struct socket *sock, struct socket *newsock)
+int security_socket_post_accept(struct socket *sock, struct socket *newsock)
 {
-	security_ops->socket_post_accept(sock, newsock);
+	return security_ops->socket_post_accept(sock, newsock);
 }
 
 int security_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size)
@@ -885,6 +900,12 @@ int security_socket_recvmsg(struct socke
 	return security_ops->socket_recvmsg(sock, msg, size, flags);
 }
 
+int security_post_recv_datagram(struct sock *sk, struct sk_buff *skb,
+				unsigned int flags)
+{
+	return security_ops->post_recv_datagram(sk, skb, flags);
+}
+
 int security_socket_getsockname(struct socket *sock)
 {
 	return security_ops->socket_getsockname(sock);


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

* Re: [TOMOYO #5 02/18] Add wrapper functions for VFS helper functions.
  2007-11-16 17:47   ` Trond Myklebust
  2007-11-16 18:20     ` [TOMOYO #5 02/18] Add wrapper functions for VFS helperfunctions Tetsuo Handa
@ 2007-11-19 12:53     ` Christoph Hellwig
  2007-11-19 13:18       ` Tetsuo Handa
  1 sibling, 1 reply; 39+ messages in thread
From: Christoph Hellwig @ 2007-11-19 12:53 UTC (permalink / raw)
  To: Trond Myklebust; +Cc: penguin-kernel, akpm, linux-kernel, linux-security-module

On Fri, Nov 16, 2007 at 12:47:35PM -0500, Trond Myklebust wrote:
> 
> On Sat, 2007-11-17 at 02:34 +0900, penguin-kernel@I-love.SAKURA.ne.jp
> wrote:
> > plain text document attachment
> > (add-wrapper-functions-for-vfs-helper-functions.patch)
> > This patch allows LSM hooks refer previously associated "struct vfsmount" parameter
> > so that they can calculate pathname of given "struct dentry".
> 
> NACK to this. Passing function parameters through the task_struct is
> definitely not an acceptable hack

Exactly.  Having a vfsmount other than the current processes root or
current working directory in task_struct doesn't make any sense.

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

* Re: [TOMOYO #5 02/18] Add wrapper functions for VFS helper functions.
  2007-11-19 12:53     ` [TOMOYO #5 02/18] Add wrapper functions for VFS helper functions Christoph Hellwig
@ 2007-11-19 13:18       ` Tetsuo Handa
  0 siblings, 0 replies; 39+ messages in thread
From: Tetsuo Handa @ 2007-11-19 13:18 UTC (permalink / raw)
  To: hch, trond.myklebust; +Cc: akpm, linux-kernel, linux-security-module

Hello.

Christoph Hellwig wrote:
> > NACK to this. Passing function parameters through the task_struct is
> > definitely not an acceptable hack
> Exactly.  Having a vfsmount other than the current processes root or
> current working directory in task_struct doesn't make any sense.
The process's root and cwd vfsmounts are used permanently, but
this last_vfsmount passed via task_struct behaves like temporary variable
pushed on stack memory.
In other words, last_vfsmount becomes NULL when it becomes invalid.

| static inline int vfs_create2(struct inode *dir, struct dentry *dentry,
| 			      int mode, struct nameidata *nd)
| {
| 	int ret;
| 	struct vfsmount *mnt = nd ? nd->path.mnt : NULL;
| 	struct task_struct *task = current;
| 	struct vfsmount *prev_mnt = task->last_vfsmount;
| 	task->last_vfsmount = mntget(mnt);
| 	ret = vfs_create(dir, dentry, mode, nd);
| 	task->last_vfsmount = prev_mnt;
| 	mntput(mnt);
| 	return ret;
| }

I agree that keeping last_vfsmount after it lost it's reference count is bad, but
I don't understand why keeping last_vfsmount while it has it's reference count is bad too.

Regards.


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

* Re: [TOMOYO #5 18/18] LSM expansion for TOMOYO Linux.
  2007-11-18  4:00         ` Tetsuo Handa
@ 2007-11-19 13:36           ` Paul Moore
  2007-11-19 14:29             ` Tetsuo Handa
  0 siblings, 1 reply; 39+ messages in thread
From: Paul Moore @ 2007-11-19 13:36 UTC (permalink / raw)
  To: Tetsuo Handa; +Cc: akpm, linux-kernel, linux-security-module, takedakn

On Saturday 17 November 2007 11:00:20 pm Tetsuo Handa wrote:
> Hello.

Hello.

> Paul Moore wrote:
> > Okay, well if that is the case I think you are going to have another
> > problem in that you could end up throwing away skbs that haven't been
> > through your security_post_recv_datagram() hook because you _always_
> > throw away the result of the second skb_peek().  Once again, if I'm wrong
> > please correct me.
>
> I didn't understand what's wrong with throwing away the result of
> the second skb_peek().

My concern is that you stated earlier that you needed to do the second 
skb_peek() because the first skb may have been removed from the socket queue 
while your LSM was making an access decision in 
security_post_recv_datagram().  If that is the case then the second call to 
skb_peek() will return a different skb then the one you passed to 
security_post_recv_datagram().  This is significant because you always throw 
away this second skb without first consulting the LSM via 
security_post_recv_datagram().

-- 
paul moore
linux security @ hp

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

* Re: [TOMOYO #5 18/18] LSM expansion for TOMOYO Linux.
  2007-11-19 13:36           ` Paul Moore
@ 2007-11-19 14:29             ` Tetsuo Handa
  2007-11-19 15:39               ` Paul Moore
  0 siblings, 1 reply; 39+ messages in thread
From: Tetsuo Handa @ 2007-11-19 14:29 UTC (permalink / raw)
  To: paul.moore; +Cc: akpm, linux-kernel, linux-security-module, takedakn

Hello.

Paul Moore wrote:
> If that is the case then the second call to 
> skb_peek() will return a different skb then the one you passed to 
> security_post_recv_datagram().
Yes. The second call to skb_peek() might return a different skb than the one
I passed to security_post_recv_datagram().

skb_peek() itself doesn't modify reference count nor queue.
| static inline struct sk_buff *skb_peek(struct sk_buff_head *list_)
| {
| 	struct sk_buff *list = ((struct sk_buff *)list_)->next;
| 	if (list == (struct sk_buff *)list_)
| 		list = NULL;
| 	return list;
| }

> This is significant because you always throw
> away this second skb without first consulting the LSM via 
> security_post_recv_datagram().
I just dequeue skb returned by *first* call to skb_peek().
I'm not handling skb returned by *second* call to skb_peek().
The skb returned by *second* call to skb_peek() is later handled by
somebody else's *first* call to skb_peek().

All skb returned by *first* call to skb_peek() are passed to security_post_recv_datagram().
Only skb returned by *first* call to skb_peek() can become candidate for that skb_recv_datagram() call.
No skb returned by *second* call to skb_peek() can become candidate for that skb_recv_datagram() call,
but can become candidate for subsequent skb_recv_datagram() call if the skb is returned by
*first* call to skb_peek().



Let's consider with named packets.

Suppose a socket has n packets in it's receive queue.
---------------
P0 P1 P2 ... Pn
---------------

Case 1:

  Thread T0 picks up P0 with MSG_PEEK flag and then calls security_post_recv_datagram().

  While T0 is in security_post_recv_datagram(),
  another thread T1 picks up P0 without MSG_PEEK flag and then calls security_post_recv_datagram().

  security_post_recv_datagram(P0) from T0 returns -EAGAIN (which means "Don't deliver this packet to caller."),
  T0 checks whether P0 is still in socket queue.
  T0 finds that P0 is already dequeued, and just drop reference count of P0.

  security_post_recv_datagram(P0) from T1 returns -EAGAIN,
  T1 drops reference count of P0.

Case 2:

  Thread T0 picks up P0 with MSG_PEEK flag and then calls security_post_recv_datagram().

  While T0 is in security_post_recv_datagram(),
  T1 picks up P0 with MSG_PEEK flag and then calls security_post_recv_datagram().

  security_post_recv_datagram(P0) from T0 returns -EAGAIN,
  T0 checks whether P0 is still in socket queue.
  T0 finds that P0 is still in queue, and dequeues P0 and drop reference count of P0.

  security_post_recv_datagram(P0) from T1 returns -EAGAIN,
  T1 checks whether P0 is still in socket queue,
  T1 finds that P0 is already dequeued, and just drop reference count of P0.

Case 3:

  Thread T0 picks up P0 with MSG_PEEK flag and then calls security_post_recv_datagram().

  While T0 is in security_post_recv_datagram(),
  T1 picks up P0 with MSG_PEEK flag and then calls security_post_recv_datagram().

  security_post_recv_datagram(P0) from T1 returns -EAGAIN,
  T1 checks whether P0 is still in socket queue.
  T1 finds that P0 is still in queue, and dequeues P0 and drop reference count of P0.

  security_post_recv_datagram(P0) from T0 returns -EAGAIN,
  T0 checks whether P0 is still in socket queue,
  T0 finds that P0 is already dequeued, and just drop reference count of P0.

In which case did you find racy condition that P0 is passed to userland without LSM check?

Regards.


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

* Re: [TOMOYO #5 18/18] LSM expansion for TOMOYO Linux.
  2007-11-19 14:29             ` Tetsuo Handa
@ 2007-11-19 15:39               ` Paul Moore
  2007-11-20  0:04                 ` Tetsuo Handa
  0 siblings, 1 reply; 39+ messages in thread
From: Paul Moore @ 2007-11-19 15:39 UTC (permalink / raw)
  To: Tetsuo Handa; +Cc: akpm, linux-kernel, linux-security-module, takedakn

On Monday 19 November 2007 9:29:52 am Tetsuo Handa wrote:
> Paul Moore wrote:
> > If that is the case then the second call to
> > skb_peek() will return a different skb then the one you passed to
> > security_post_recv_datagram().
>
> Yes. The second call to skb_peek() might return a different skb than the
> one I passed to security_post_recv_datagram().

My apologies, I mistakenly read the following if statement in your patch:

 +       if (skb == skb_peek(&sk->sk_receive_queue)) {
 +               __skb_unlink(skb, &sk->sk_receive_queue);
 +               atomic_dec(&skb->users);
 +       }

I read the conditional as the following:

 +       if (skb = skb_peek(&sk->sk_receive_queue)) {

... which would have caused the problems I was describing.  I'm sorry for all 
of the confusion/frustration, you patient explanations are correct; I was 
wrong in this particular case.

-- 
paul moore
linux security @ hp

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

* Re: [TOMOYO #5 18/18] LSM expansion for TOMOYO Linux.
  2007-11-19 15:39               ` Paul Moore
@ 2007-11-20  0:04                 ` Tetsuo Handa
  2007-11-20  0:52                   ` James Morris
  0 siblings, 1 reply; 39+ messages in thread
From: Tetsuo Handa @ 2007-11-20  0:04 UTC (permalink / raw)
  To: Paul Moore; +Cc: akpm, linux-kernel, linux-security-module, takedakn

Hello.

Paul Moore wrote:
> My apologies, I mistakenly read the following if statement in your patch:
> 
>  +       if (skb == skb_peek(&sk->sk_receive_queue)) {
>  +               __skb_unlink(skb, &sk->sk_receive_queue);
>  +               atomic_dec(&skb->users);
>  +       }
> 
> I read the conditional as the following:
> 
>  +       if (skb = skb_peek(&sk->sk_receive_queue)) {
> 
> ... which would have caused the problems I was describing.  I'm sorry for all 
> of the confusion/frustration, you patient explanations are correct; I was 
> wrong in this particular case.
No problem.



To everyone:

  Are there any remaining worries with skb_recv_datagram()/socket_post_accept()?

  If nobody has objection, I'd like to cut these skb_recv_datagram()/socket_post_accept() changes
  and submit to -mm tree.

Regards.

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

* Re: [TOMOYO #5 18/18] LSM expansion for TOMOYO Linux.
  2007-11-20  0:04                 ` Tetsuo Handa
@ 2007-11-20  0:52                   ` James Morris
  2007-11-20  4:50                     ` [PATCH] Add packet filtering based on process\'s security context Tetsuo Handa
  0 siblings, 1 reply; 39+ messages in thread
From: James Morris @ 2007-11-20  0:52 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: Paul Moore, akpm, linux-kernel, linux-security-module, takedakn

On Tue, 20 Nov 2007, Tetsuo Handa wrote:

> Hello.
> 
> Paul Moore wrote:
> > My apologies, I mistakenly read the following if statement in your patch:
> > 
> >  +       if (skb == skb_peek(&sk->sk_receive_queue)) {
> >  +               __skb_unlink(skb, &sk->sk_receive_queue);
> >  +               atomic_dec(&skb->users);
> >  +       }
> > 
> > I read the conditional as the following:
> > 
> >  +       if (skb = skb_peek(&sk->sk_receive_queue)) {
> > 
> > ... which would have caused the problems I was describing.  I'm sorry for all 
> > of the confusion/frustration, you patient explanations are correct; I was 
> > wrong in this particular case.
> No problem.
> 
> 
> 
> To everyone:
> 
>   Are there any remaining worries with skb_recv_datagram()/socket_post_accept()?
> 
>   If nobody has objection, I'd like to cut these skb_recv_datagram()/socket_post_accept() changes
>   and submit to -mm tree.

You should send anything which touches core networking to netdev, too, and 
get an ack from one of the core developers there.


-- 
James Morris <jmorris@namei.org>


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

* [PATCH] Add packet filtering based on process\'s security context.
  2007-11-20  0:52                   ` James Morris
@ 2007-11-20  4:50                     ` Tetsuo Handa
  0 siblings, 0 replies; 39+ messages in thread
From: Tetsuo Handa @ 2007-11-20  4:50 UTC (permalink / raw)
  To: paul.moore, jmorris, netdev; +Cc: linux-kernel, linux-security-module

This patch allows LSM modules filter incoming connections/datagrams
based on the process's security context who is attempting to pick up.

There are already hooks to filter incoming connections/datagrams
based on the socket's security context, but these hooks are not
applicable when one wants to do TCP Wrapper-like filtering
(e.g. App1 is permitted to accept TCP connections from 192.168.0.0/16).



There is a side effect which unlikely occurs.

If a socket is shared by multiple processes with different policy,
the process who should be able to accept this connection
will not be able to accept this connection
because socket_post_accept() aborts this connection.
But if socket_post_accept() doesn't abort this connection,
the process who must not be able to accept this connection
will repeat accept() forever, which is a worse side effect.

Similarly, if a socket is shared by multiple processes with different policy,
the process who should be able to pick up this datagram
will not be able to pick up this datagram
because socket_post_recv_datagram() discards this datagram.
But if socket_post_recv_datagram() doesn't discard this datagram,
the process who must not be able to pick up this datagram
will repeat recvmsg() forever, which is a worse side effect.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>

 include/linux/security.h |   34 +++++++++++++++++++++++++++++-----
 net/core/datagram.c      |   26 ++++++++++++++++++++++++--
 net/socket.c             |    7 +++++--
 security/dummy.c         |   13 ++++++++++---
 security/security.c      |   10 ++++++++--
 5 files changed, 76 insertions(+), 14 deletions(-)

--- linux-2.6.24-rc2-mm1.orig/include/linux/security.h
+++ linux-2.6.24-rc2-mm1/include/linux/security.h
@@ -778,8 +778,12 @@ struct request_sock;
  * @socket_post_accept:
  *	This hook allows a security module to copy security
  *	information into the newly created socket's inode.
+ *	This hook also allows a security module to filter connections
+ *	from unwanted peers based on the process accepting this connection.
+ *	The connection will be aborted if this hook returns nonzero.
  *	@sock contains the listening socket structure.
  *	@newsock contains the newly created server socket for connection.
+ *	Return 0 if permission is granted.
  * @socket_sendmsg:
  *	Check permission before transmitting a message to another socket.
  *	@sock contains the socket structure.
@@ -793,6 +797,15 @@ struct request_sock;
  *	@size contains the size of message structure.
  *	@flags contains the operational flags.
  *	Return 0 if permission is granted.  
+ * @socket_post_recv_datagram:
+ *	Check permission after receiving a datagram.
+ *	This hook allows a security module to filter packets
+ *	from unwanted peers based on the process receiving this datagram.
+ *	The packet will be discarded if this hook returns nonzero.
+ *	@sk contains the socket.
+ *	@skb contains the socket buffer (may be NULL).
+ *	@flags contains the operational flags.
+ *	Return 0 if permission is granted.
  * @socket_getsockname:
  *	Check permission before the local address (name) of the socket object
  *	@sock is retrieved.
@@ -1384,12 +1397,13 @@ struct security_operations {
 			       struct sockaddr * address, int addrlen);
 	int (*socket_listen) (struct socket * sock, int backlog);
 	int (*socket_accept) (struct socket * sock, struct socket * newsock);
-	void (*socket_post_accept) (struct socket * sock,
-				    struct socket * newsock);
+	int (*socket_post_accept) (struct socket *sock, struct socket *newsock);
 	int (*socket_sendmsg) (struct socket * sock,
 			       struct msghdr * msg, int size);
 	int (*socket_recvmsg) (struct socket * sock,
 			       struct msghdr * msg, int size, int flags);
+	int (*socket_post_recv_datagram) (struct sock *sk, struct sk_buff *skb,
+					  unsigned int flags);
 	int (*socket_getsockname) (struct socket * sock);
 	int (*socket_getpeername) (struct socket * sock);
 	int (*socket_getsockopt) (struct socket * sock, int level, int optname);
@@ -2294,10 +2308,12 @@ int security_socket_bind(struct socket *
 int security_socket_connect(struct socket *sock, struct sockaddr *address, int addrlen);
 int security_socket_listen(struct socket *sock, int backlog);
 int security_socket_accept(struct socket *sock, struct socket *newsock);
-void security_socket_post_accept(struct socket *sock, struct socket *newsock);
+int security_socket_post_accept(struct socket *sock, struct socket *newsock);
 int security_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size);
 int security_socket_recvmsg(struct socket *sock, struct msghdr *msg,
 			    int size, int flags);
+int security_socket_post_recv_datagram(struct sock *sk, struct sk_buff *skb,
+				       unsigned int flags);
 int security_socket_getsockname(struct socket *sock);
 int security_socket_getpeername(struct socket *sock);
 int security_socket_getsockopt(struct socket *sock, int level, int optname);
@@ -2373,9 +2389,10 @@ static inline int security_socket_accept
 	return 0;
 }
 
-static inline void security_socket_post_accept(struct socket * sock, 
-					       struct socket * newsock)
+static inline int security_socket_post_accept(struct socket *sock,
+					      struct socket *newsock)
 {
+	return 0;
 }
 
 static inline int security_socket_sendmsg(struct socket * sock, 
@@ -2391,6 +2408,13 @@ static inline int security_socket_recvms
 	return 0;
 }
 
+static inline int security_socket_post_recv_datagram(struct sock *sk,
+						     struct sk_buff *skb,
+						     unsigned int flags)
+{
+	return 0;
+}
+
 static inline int security_socket_getsockname(struct socket * sock)
 {
 	return 0;
--- linux-2.6.24-rc2-mm1.orig/net/socket.c
+++ linux-2.6.24-rc2-mm1/net/socket.c
@@ -1442,13 +1442,16 @@ asmlinkage long sys_accept(int fd, struc
 			goto out_fd;
 	}
 
+	/* Filter connections from unwanted peers. */
+	err = security_socket_post_accept(sock, newsock);
+	if (err)
+		goto out_fd;
+
 	/* File flags are not inherited via accept() unlike another OSes. */
 
 	fd_install(newfd, newfile);
 	err = newfd;
 
-	security_socket_post_accept(sock, newsock);
-
 out_put:
 	fput_light(sock->file, fput_needed);
 out:
--- linux-2.6.24-rc2-mm1.orig/security/dummy.c
+++ linux-2.6.24-rc2-mm1/security/dummy.c
@@ -753,10 +753,10 @@ static int dummy_socket_accept (struct s
 	return 0;
 }
 
-static void dummy_socket_post_accept (struct socket *sock, 
-				      struct socket *newsock)
+static int dummy_socket_post_accept(struct socket *sock,
+				    struct socket *newsock)
 {
-	return;
+	return 0;
 }
 
 static int dummy_socket_sendmsg (struct socket *sock, struct msghdr *msg,
@@ -771,6 +771,12 @@ static int dummy_socket_recvmsg (struct 
 	return 0;
 }
 
+static int dummy_socket_post_recv_datagram(struct sock *sk, struct sk_buff *skb,
+					   unsigned int flags)
+{
+	return 0;
+}
+
 static int dummy_socket_getsockname (struct socket *sock)
 {
 	return 0;
@@ -1104,6 +1110,7 @@ void security_fixup_ops (struct security
 	set_to_dummy_if_null(ops, socket_post_accept);
 	set_to_dummy_if_null(ops, socket_sendmsg);
 	set_to_dummy_if_null(ops, socket_recvmsg);
+	set_to_dummy_if_null(ops, socket_post_recv_datagram);
 	set_to_dummy_if_null(ops, socket_getsockname);
 	set_to_dummy_if_null(ops, socket_getpeername);
 	set_to_dummy_if_null(ops, socket_setsockopt);
--- linux-2.6.24-rc2-mm1.orig/net/core/datagram.c
+++ linux-2.6.24-rc2-mm1/net/core/datagram.c
@@ -55,6 +55,7 @@
 #include <net/checksum.h>
 #include <net/sock.h>
 #include <net/tcp_states.h>
+#include <linux/security.h>
 
 /*
  *	Is a socket 'connection oriented' ?
@@ -148,6 +149,7 @@ struct sk_buff *skb_recv_datagram(struct
 {
 	struct sk_buff *skb;
 	long timeo;
+	unsigned long cpu_flags;
 	/*
 	 * Caller is allowed not to check sk->sk_err before skb_recv_datagram()
 	 */
@@ -166,8 +168,6 @@ struct sk_buff *skb_recv_datagram(struct
 		 * However, this function was corrent in any case. 8)
 		 */
 		if (flags & MSG_PEEK) {
-			unsigned long cpu_flags;
-
 			spin_lock_irqsave(&sk->sk_receive_queue.lock,
 					  cpu_flags);
 			skb = skb_peek(&sk->sk_receive_queue);
@@ -178,6 +178,11 @@ struct sk_buff *skb_recv_datagram(struct
 		} else
 			skb = skb_dequeue(&sk->sk_receive_queue);
 
+		/* Filter packets from unwanted peers. */
+		error = security_socket_post_recv_datagram(sk, skb, flags);
+		if (error)
+			goto force_dequeue;
+
 		if (skb)
 			return skb;
 
@@ -190,6 +195,23 @@ struct sk_buff *skb_recv_datagram(struct
 
 	return NULL;
 
+force_dequeue:
+	/* Drop this packet because LSM says "Don't pass it to the caller". */
+	if (!(flags & MSG_PEEK))
+		goto no_peek;
+	/*
+	 * If this packet is MSG_PEEK'ed, dequeue it forcibly
+	 * so that this packet won't prevent the caller from picking up
+	 * next packet.
+	 */
+	spin_lock_irqsave(&sk->sk_receive_queue.lock, cpu_flags);
+	if (skb == skb_peek(&sk->sk_receive_queue)) {
+		__skb_unlink(skb, &sk->sk_receive_queue);
+		atomic_dec(&skb->users);
+	}
+	spin_unlock_irqrestore(&sk->sk_receive_queue.lock, cpu_flags);
+no_peek:
+	kfree_skb(skb);
 no_packet:
 	*err = error;
 	return NULL;
--- linux-2.6.24-rc2-mm1.orig/security/security.c
+++ linux-2.6.24-rc2-mm1/security/security.c
@@ -869,9 +869,9 @@ int security_socket_accept(struct socket
 	return security_ops->socket_accept(sock, newsock);
 }
 
-void security_socket_post_accept(struct socket *sock, struct socket *newsock)
+int security_socket_post_accept(struct socket *sock, struct socket *newsock)
 {
-	security_ops->socket_post_accept(sock, newsock);
+	return security_ops->socket_post_accept(sock, newsock);
 }
 
 int security_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size)
@@ -885,6 +885,12 @@ int security_socket_recvmsg(struct socke
 	return security_ops->socket_recvmsg(sock, msg, size, flags);
 }
 
+int security_socket_post_recv_datagram(struct sock *sk, struct sk_buff *skb,
+				       unsigned int flags)
+{
+	return security_ops->socket_post_recv_datagram(sk, skb, flags);
+}
+
 int security_socket_getsockname(struct socket *sock)
 {
 	return security_ops->socket_getsockname(sock);

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

end of thread, other threads:[~2007-11-20  4:50 UTC | newest]

Thread overview: 39+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2007-11-16 17:34 [TOMOYO #5 00/18] TOMOYO Linux - MAC based on process invocation history penguin-kernel
2007-11-16 17:34 ` [TOMOYO #5 01/18] Add struct vfsmount to struct task_struct penguin-kernel
2007-11-16 17:34 ` [TOMOYO #5 02/18] Add wrapper functions for VFS helper functions penguin-kernel
2007-11-16 17:47   ` Trond Myklebust
2007-11-16 18:20     ` [TOMOYO #5 02/18] Add wrapper functions for VFS helperfunctions Tetsuo Handa
2007-11-16 18:33       ` Trond Myklebust
2007-11-17  4:04         ` [TOMOYO #5 02/18] Add wrapper functions for VFShelperfunctions Tetsuo Handa
2007-11-17  4:46           ` Trond Myklebust
2007-11-17  5:23             ` Tetsuo Handa
2007-11-19 12:53     ` [TOMOYO #5 02/18] Add wrapper functions for VFS helper functions Christoph Hellwig
2007-11-19 13:18       ` Tetsuo Handa
2007-11-16 17:34 ` [TOMOYO #5 03/18] Replace VFS with wrapper functions penguin-kernel
2007-11-16 17:34 ` [TOMOYO #5 04/18] Data structures and prototype defitions penguin-kernel
2007-11-16 17:34 ` [TOMOYO #5 05/18] Memory and pathname management functions penguin-kernel
2007-11-16 17:34 ` [TOMOYO #5 06/18] Utility functions and policy manipulation interface penguin-kernel
2007-11-16 17:34 ` [TOMOYO #5 07/18] Domain transition functions penguin-kernel
2007-11-16 17:34 ` [TOMOYO #5 08/18] Auditing interface penguin-kernel
2007-11-16 17:34 ` [TOMOYO #5 09/18] File access control functions penguin-kernel
2007-11-16 17:34 ` [TOMOYO #5 10/18] argv0 check functions penguin-kernel
2007-11-16 17:34 ` [TOMOYO #5 11/18] Network access control functions penguin-kernel
2007-11-16 17:57   ` YOSHIFUJI Hideaki / 吉藤英明
2007-11-16 18:22     ` Tetsuo Handa
2007-11-16 17:34 ` [TOMOYO #5 12/18] Namespace manipulation " penguin-kernel
2007-11-16 17:34 ` [TOMOYO #5 13/18] Signal " penguin-kernel
2007-11-16 17:34 ` [TOMOYO #5 14/18] Capability access " penguin-kernel
2007-11-16 17:34 ` [TOMOYO #5 15/18] LSM adapter functions penguin-kernel
2007-11-16 17:34 ` [TOMOYO #5 16/18] Conditional permission support penguin-kernel
2007-11-16 17:34 ` [TOMOYO #5 17/18] Kconfig and Makefile penguin-kernel
2007-11-16 17:34 ` [TOMOYO #5 18/18] LSM expansion for TOMOYO Linux penguin-kernel
2007-11-16 19:23   ` Paul Moore
2007-11-17  3:45     ` Tetsuo Handa
2007-11-17 23:09       ` Paul Moore
2007-11-18  4:00         ` Tetsuo Handa
2007-11-19 13:36           ` Paul Moore
2007-11-19 14:29             ` Tetsuo Handa
2007-11-19 15:39               ` Paul Moore
2007-11-20  0:04                 ` Tetsuo Handa
2007-11-20  0:52                   ` James Morris
2007-11-20  4:50                     ` [PATCH] Add packet filtering based on process\'s security context Tetsuo Handa

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