linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [TOMOYO #8 (2.6.25-mm1) 0/7] TOMOYO Linux
@ 2008-05-01  5:54 Toshiharu Harada
  2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 1/7] Introduce new LSM hooks Toshiharu Harada
                   ` (6 more replies)
  0 siblings, 7 replies; 14+ messages in thread
From: Toshiharu Harada @ 2008-05-01  5:54 UTC (permalink / raw)
  To: akpm, chrisw, viro; +Cc: linux-security-module, linux-fsdevel, linux-kernel

This submission of the TOMOYO Linux security module is based
against 2.6.25-mm1.

TOMOYO Linux is a pathname-based MAC extension (LSM module)
for the Linux kernel. The patch files consist of two parts:

1. New hooks for LSM
2. TOMOYO Linux itself

The first part is needed because the vfsmount parameter which is
required for TOMOYO Linux is not passed to (current version of) LSM.
For that reason, AppArmor and TOMOYO Linux have been posting
proposals to pass it via VFS code several times, but neither has been
merged. In this posting, we have chosen a new, straightforward
approach to add new hooks in the namespace code (that was suggested by
Stephen Smalley of NSA. We thank him). What is good with this approach
is it causes fewer impacts on VFS and filesystem code and no impacts
on in-tree modules (SELinux and Smack).

The heart of TOMOYO Linux patch is to store "process invocation
history" for each process. Under TOMOYO Linux every process knows
how it was executed, and TOMOYO Linux kernel automatically
manage selecting processes that has the same "process invocation history"
as same domains. Furthermore, administrators can gather
access requests information for each domain on the fly.
Our Ubuntu based LiveCD is the most convenient way to
experience the above explanation.

http://tomoyo.sourceforge.jp/wiki-e/?TomoyoLive

Though we have a variety of MAC functionalities with the latest
(local) version of TOMOYO Linux, we carefully extracted the MAC for
file access, the most fundamental functionality to reduce
the patches and reviewers' time.

There have been arguments on pathname-based MACs.
TOMOYO Linux patch mainly resides in the namespace (so it's called
pathname-based) while SELinux and current LSM reside in the inode
space. From information flow control point of view, it is obvious
that label based MAC is the way to go. So let me emphasize that
I'm not claiming TOMOYO Linux as a better MAC than SELinux.

Then you might ask why we are developing TOMOYO Linux.
Because,

- TOMOYO Linux gives human-friendly "context" information to
  the processes. "Context" example can be viewed at
    http://tomoyo.sourceforge.jp/cgi-bin/lxr/source/ubuntu-7.10-ccs-1.5.3/domain_policy.conf?v=policy-sample
- There are cases where pathname-based MAC is needed.
- At the very least, TOMOYO Linux is one of the most
  useful tool to learn the Linux operating system.

Some URLs.
- http://elinux.org/TomoyoLinux
- http://lwn.net/Articles/263179/ (our security goal)
- http://tomoyo.sourceforge.jp/wiki-e/?WhatIs#comparison
  (my version of Linux MAC comparison chart)
- http://lwn.net/Articles/277833/ (Jonathan's great article on
  history of pathname-based MAC)

That's it and we are now ready to hear from the community and
improve the code. Thanks in advance.  :) 

Toshiharu Harada
haradats@nttdata.co.jp
NTT DATA CORPORATION

--


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

* [TOMOYO #8 (2.6.25-mm1) 1/7] Introduce new LSM hooks.
  2008-05-01  5:54 [TOMOYO #8 (2.6.25-mm1) 0/7] TOMOYO Linux Toshiharu Harada
@ 2008-05-01  5:54 ` Toshiharu Harada
  2008-05-01  8:01   ` Chris Wright
  2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 2/7] LSM adapter functions Toshiharu Harada
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 14+ messages in thread
From: Toshiharu Harada @ 2008-05-01  5:54 UTC (permalink / raw)
  To: akpm, chrisw, viro
  Cc: linux-security-module, linux-fsdevel, linux-kernel,
	Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

This patch allows LSM to check permission using "struct vfsmount"
without passing "struct vfsmount" to VFS helper functions.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 fs/exec.c                |   10 ++
 fs/namei.c               |  129 +++++++++++++++++++++++++++++++---
 fs/open.c                |    7 +
 include/linux/fs.h       |    8 ++
 include/linux/security.h |  178 +++++++++++++++++++++++++++++++++++++++++++++++
 net/unix/af_unix.c       |    4 +
 security/dummy.c         |   73 +++++++++++++++++++
 security/security.c      |   63 ++++++++++++++++
 8 files changed, 462 insertions(+), 10 deletions(-)

--- mm.orig/fs/exec.c
+++ mm/fs/exec.c
@@ -119,6 +119,16 @@ asmlinkage long sys_uselib(const char __
 	if (error)
 		goto exit;
 
+	/*
+	 * sys_access() with both R_OK and X_OK flags makes LSM's
+	 * security_inode_permission() unable to tell whether the request is
+	 * "just checking for permissions" or "actually loading a shared
+	 * library".
+	 */
+	error = security_path_uselib(&nd);
+	if (error)
+		goto exit;
+
 	file = nameidata_to_filp(&nd, O_RDONLY|O_LARGEFILE);
 	error = PTR_ERR(file);
 	if (IS_ERR(file))
--- mm.orig/fs/namei.c
+++ mm/fs/namei.c
@@ -1595,6 +1595,9 @@ int vfs_create(struct inode *dir, struct
 	error = security_inode_create(dir, dentry, mode);
 	if (error)
 		return error;
+	error = security_path_create(dir, dentry, mode, nd);
+	if (error)
+		return error;
 	DQUOT_INIT(dir);
 	error = dir->i_op->create(dir, dentry, mode, nd);
 	if (!error)
@@ -1650,6 +1653,17 @@ int may_open(struct nameidata *nd, int a
 			return -EPERM;
 
 	/*
+	 * security_inode_permission() called from vfs_permission()
+	 * can't know that the file is going to be truncated when
+	 * open(filename, O_WRONLY | O_TRUNC | O_APPEND) is used.
+	 * So, this hook checks O_APPEND and O_TRUNC flags as well
+	 * as MAY_READ and MAY_WRITE flags.
+	 */
+	error = security_path_open(nd->path.dentry, nd->path.mnt, flag);
+	if (error)
+		return error;
+
+	/*
 	 * Ensure there are no outstanding leases on the file.
 	 */
 	error = break_lease(inode, flag);
@@ -2021,7 +2035,12 @@ fail:
 }
 EXPORT_SYMBOL_GPL(lookup_create);
 
-int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
+/*
+ * These pre_vfs_*() functions are separated from vfs_*() functions so that
+ * LSM's security_path_*() functions can do DAC checks before MAC checks
+ * without duplicating may_create()/may_delete() functions.
+ */
+int pre_vfs_mknod(struct inode *dir, struct dentry *dentry, int mode)
 {
 	int error = may_create(dir, dentry, NULL);
 
@@ -2033,6 +2052,14 @@ int vfs_mknod(struct inode *dir, struct 
 
 	if (!dir->i_op || !dir->i_op->mknod)
 		return -EPERM;
+	return 0;
+}
+
+int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
+{
+	int error = pre_vfs_mknod(dir, dentry, mode);
+	if (error)
+		return error;
 
 	error = devcgroup_inode_mknod(mode, dev);
 	if (error)
@@ -2096,6 +2123,9 @@ asmlinkage long sys_mknodat(int dfd, con
 	error = mnt_want_write(nd.path.mnt);
 	if (error)
 		goto out_dput;
+	error = security_path_mknod(&nd.path, dentry, mode, dev);
+	if (error)
+		goto out_drop_write;
 	switch (mode & S_IFMT) {
 		case 0: case S_IFREG:
 			error = vfs_create(nd.path.dentry->d_inode,dentry,mode,&nd);
@@ -2108,6 +2138,7 @@ asmlinkage long sys_mknodat(int dfd, con
 			error = vfs_mknod(nd.path.dentry->d_inode,dentry,mode,0);
 			break;
 	}
+out_drop_write:
 	mnt_drop_write(nd.path.mnt);
 out_dput:
 	dput(dentry);
@@ -2125,7 +2156,7 @@ asmlinkage long sys_mknod(const char __u
 	return sys_mknodat(AT_FDCWD, filename, mode, dev);
 }
 
-int vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
+int pre_vfs_mkdir(struct inode *dir, struct dentry *dentry)
 {
 	int error = may_create(dir, dentry, NULL);
 
@@ -2134,6 +2165,14 @@ int vfs_mkdir(struct inode *dir, struct 
 
 	if (!dir->i_op || !dir->i_op->mkdir)
 		return -EPERM;
+	return 0;
+}
+
+int vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
+{
+	int error = pre_vfs_mkdir(dir, dentry);
+	if (error)
+		return error;
 
 	mode &= (S_IRWXUGO|S_ISVTX);
 	error = security_inode_mkdir(dir, dentry, mode);
@@ -2172,7 +2211,11 @@ asmlinkage long sys_mkdirat(int dfd, con
 	error = mnt_want_write(nd.path.mnt);
 	if (error)
 		goto out_dput;
+	error = security_path_mkdir(&nd.path, dentry, mode);
+	if (error)
+		goto out_drop_write;
 	error = vfs_mkdir(nd.path.dentry->d_inode, dentry, mode);
+out_drop_write:
 	mnt_drop_write(nd.path.mnt);
 out_dput:
 	dput(dentry);
@@ -2217,7 +2260,7 @@ void dentry_unhash(struct dentry *dentry
 	spin_unlock(&dcache_lock);
 }
 
-int vfs_rmdir(struct inode *dir, struct dentry *dentry)
+int pre_vfs_rmdir(struct inode *dir, struct dentry *dentry)
 {
 	int error = may_delete(dir, dentry, 1);
 
@@ -2226,6 +2269,14 @@ int vfs_rmdir(struct inode *dir, struct 
 
 	if (!dir->i_op || !dir->i_op->rmdir)
 		return -EPERM;
+	return 0;
+}
+
+int vfs_rmdir(struct inode *dir, struct dentry *dentry)
+{
+	int error = pre_vfs_rmdir(dir, dentry);
+	if (error)
+		return error;
 
 	DQUOT_INIT(dir);
 
@@ -2284,7 +2335,11 @@ static long do_rmdir(int dfd, const char
 	error = mnt_want_write(nd.path.mnt);
 	if (error)
 		goto exit3;
+	error = security_path_rmdir(&nd.path, dentry);
+	if (error)
+		goto exit4;
 	error = vfs_rmdir(nd.path.dentry->d_inode, dentry);
+exit4:
 	mnt_drop_write(nd.path.mnt);
 exit3:
 	dput(dentry);
@@ -2302,7 +2357,7 @@ asmlinkage long sys_rmdir(const char __u
 	return do_rmdir(AT_FDCWD, pathname);
 }
 
-int vfs_unlink(struct inode *dir, struct dentry *dentry)
+int pre_vfs_unlink(struct inode *dir, struct dentry *dentry)
 {
 	int error = may_delete(dir, dentry, 0);
 
@@ -2311,6 +2366,14 @@ int vfs_unlink(struct inode *dir, struct
 
 	if (!dir->i_op || !dir->i_op->unlink)
 		return -EPERM;
+	return 0;
+}
+
+int vfs_unlink(struct inode *dir, struct dentry *dentry)
+{
+	int error = pre_vfs_unlink(dir, dentry);
+	if (error)
+		return error;
 
 	DQUOT_INIT(dir);
 
@@ -2370,7 +2433,11 @@ static long do_unlinkat(int dfd, const c
 		error = mnt_want_write(nd.path.mnt);
 		if (error)
 			goto exit2;
+		error = security_path_unlink(&nd.path, dentry);
+		if (error)
+			goto exit3;
 		error = vfs_unlink(nd.path.dentry->d_inode, dentry);
+exit3:
 		mnt_drop_write(nd.path.mnt);
 	exit2:
 		dput(dentry);
@@ -2406,7 +2473,7 @@ asmlinkage long sys_unlink(const char __
 	return do_unlinkat(AT_FDCWD, pathname);
 }
 
-int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname, int mode)
+int pre_vfs_symlink(struct inode *dir, struct dentry *dentry)
 {
 	int error = may_create(dir, dentry, NULL);
 
@@ -2415,6 +2482,15 @@ int vfs_symlink(struct inode *dir, struc
 
 	if (!dir->i_op || !dir->i_op->symlink)
 		return -EPERM;
+	return 0;
+}
+
+int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname,
+		int mode)
+{
+	int error = pre_vfs_symlink(dir, dentry);
+	if (error)
+		return error;
 
 	error = security_inode_symlink(dir, dentry, oldname);
 	if (error)
@@ -2455,7 +2531,11 @@ asmlinkage long sys_symlinkat(const char
 	error = mnt_want_write(nd.path.mnt);
 	if (error)
 		goto out_dput;
+	error = security_path_symlink(&nd.path, dentry, from);
+	if (error)
+		goto out_drop_write;
 	error = vfs_symlink(nd.path.dentry->d_inode, dentry, from, S_IALLUGO);
+out_drop_write:
 	mnt_drop_write(nd.path.mnt);
 out_dput:
 	dput(dentry);
@@ -2474,7 +2554,8 @@ asmlinkage long sys_symlink(const char _
 	return sys_symlinkat(oldname, AT_FDCWD, newname);
 }
 
-int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry)
+int pre_vfs_link(struct dentry *old_dentry, struct inode *dir,
+		 struct dentry *new_dentry)
 {
 	struct inode *inode = old_dentry->d_inode;
 	int error;
@@ -2498,6 +2579,15 @@ int vfs_link(struct dentry *old_dentry, 
 		return -EPERM;
 	if (S_ISDIR(old_dentry->d_inode->i_mode))
 		return -EPERM;
+	return 0;
+}
+
+int vfs_link(struct dentry *old_dentry, struct inode *dir,
+	     struct dentry *new_dentry)
+{
+	int error = pre_vfs_link(old_dentry, dir, new_dentry);
+	if (error)
+		return error;
 
 	error = security_inode_link(old_dentry, dir, new_dentry);
 	if (error)
@@ -2555,7 +2645,11 @@ asmlinkage long sys_linkat(int olddfd, c
 	error = mnt_want_write(nd.path.mnt);
 	if (error)
 		goto out_dput;
+	error = security_path_link(&old_nd.path, &nd.path, new_dentry);
+	if (error)
+		goto out_drop_write;
 	error = vfs_link(old_nd.path.dentry, nd.path.dentry->d_inode, new_dentry);
+out_drop_write:
 	mnt_drop_write(nd.path.mnt);
 out_dput:
 	dput(new_dentry);
@@ -2679,16 +2773,15 @@ static int vfs_rename_other(struct inode
 	return error;
 }
 
-int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
-	       struct inode *new_dir, struct dentry *new_dentry)
+int pre_vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
+		   struct inode *new_dir, struct dentry *new_dentry)
 {
 	int error;
 	int is_dir = S_ISDIR(old_dentry->d_inode->i_mode);
-	const char *old_name;
 
 	if (old_dentry->d_inode == new_dentry->d_inode)
  		return 0;
- 
+
 	error = may_delete(old_dir, old_dentry, is_dir);
 	if (error)
 		return error;
@@ -2702,6 +2795,17 @@ int vfs_rename(struct inode *old_dir, st
 
 	if (!old_dir->i_op || !old_dir->i_op->rename)
 		return -EPERM;
+	return 0;
+}
+
+int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
+	       struct inode *new_dir, struct dentry *new_dentry)
+{
+	int error = pre_vfs_rename(old_dir, old_dentry, new_dir, new_dentry);
+	int is_dir = S_ISDIR(old_dentry->d_inode->i_mode);
+	const char *old_name;
+	if (error)
+		return error;
 
 	DQUOT_INIT(old_dir);
 	DQUOT_INIT(new_dir);
@@ -2786,8 +2890,13 @@ static int do_rename(int olddfd, const c
 	error = mnt_want_write(oldnd.path.mnt);
 	if (error)
 		goto exit5;
+	error = security_path_rename(&oldnd.path, old_dentry,
+				     &newnd.path, new_dentry);
+	if (error)
+		goto exit6;
 	error = vfs_rename(old_dir->d_inode, old_dentry,
 				   new_dir->d_inode, new_dentry);
+exit6:
 	mnt_drop_write(oldnd.path.mnt);
 exit5:
 	dput(new_dentry);
--- mm.orig/fs/open.c
+++ mm/fs/open.c
@@ -268,6 +268,9 @@ static long do_sys_truncate(const char _
 	if (error)
 		goto put_write_and_out;
 
+	error = security_path_truncate(&nd.path, length, 0, NULL);
+	if (error)
+		goto put_write_and_out;
 	error = locks_verify_truncate(inode, NULL, length);
 	if (!error) {
 		DQUOT_INIT(inode);
@@ -324,6 +327,10 @@ static long do_sys_ftruncate(unsigned in
 	if (IS_APPEND(inode))
 		goto out_putf;
 
+	error = security_path_truncate(&file->f_path, length,
+				       ATTR_MTIME|ATTR_CTIME, file);
+	if (error)
+		goto out_putf;
 	error = locks_verify_truncate(inode, file, length);
 	if (!error)
 		error = do_truncate(dentry, length, ATTR_MTIME|ATTR_CTIME, file);
--- mm.orig/include/linux/fs.h
+++ mm/include/linux/fs.h
@@ -1124,12 +1124,20 @@ extern void unlock_super(struct super_bl
  */
 extern int vfs_permission(struct nameidata *, int);
 extern int vfs_create(struct inode *, struct dentry *, int, struct nameidata *);
+extern int pre_vfs_mkdir(struct inode *, struct dentry *);
 extern int vfs_mkdir(struct inode *, struct dentry *, int);
+extern int pre_vfs_mknod(struct inode *, struct dentry *, int);
 extern int vfs_mknod(struct inode *, struct dentry *, int, dev_t);
+extern int pre_vfs_symlink(struct inode *, struct dentry *);
 extern int vfs_symlink(struct inode *, struct dentry *, const char *, int);
+extern int pre_vfs_link(struct dentry *, struct inode *, struct dentry *);
 extern int vfs_link(struct dentry *, struct inode *, struct dentry *);
+extern int pre_vfs_rmdir(struct inode *, struct dentry *);
 extern int vfs_rmdir(struct inode *, struct dentry *);
+extern int pre_vfs_unlink(struct inode *, struct dentry *);
 extern int vfs_unlink(struct inode *, struct dentry *);
+extern int pre_vfs_rename(struct inode *, struct dentry *, struct inode *,
+			  struct dentry *);
 extern int vfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *);
 
 /*
--- mm.orig/include/linux/security.h
+++ mm/include/linux/security.h
@@ -343,23 +343,50 @@ static inline void security_free_mnt_opt
  *	@dentry contains the dentry structure for the file to be created.
  *	@mode contains the file mode of the file to be created.
  *	Return 0 if permission is granted.
+ * @path_create:
+ *	Check permission to create a regular file.
+ *	@dir contains inode structure of the parent of the new file.
+ *	@dentry contains the dentry structure for the file to be created.
+ *	@mode contains the file mode of the file to be created.
+ *	@nd contains the nameidata structure (may be NULL).
+ *	Return 0 if permission is granted.
  * @inode_link:
  *	Check permission before creating a new hard link to a file.
  *	@old_dentry contains the dentry structure for an existing link to the file.
  *	@dir contains the inode structure of the parent directory of the new link.
  *	@new_dentry contains the dentry structure for the new link.
  *	Return 0 if permission is granted.
+ * @path_link:
+ *	Check permission before creating a new hard link to a file.
+ *	@old_path contains the path structure for an existing link
+ *	to the file.
+ *	@new_dir contains the path structure of the parent directory of
+ *	the new link.
+ *	@new_dentry contains the dentry structure for the new link.
+ *	Return 0 if permission is granted.
  * @inode_unlink:
  *	Check the permission to remove a hard link to a file. 
  *	@dir contains the inode structure of parent directory of the file.
  *	@dentry contains the dentry structure for file to be unlinked.
  *	Return 0 if permission is granted.
+ * @path_unlink:
+ *	Check the permission to remove a hard link to a file.
+ *	@dir contains the path structure of parent directory of the file.
+ *	@dentry contains the dentry structure for file to be unlinked.
+ *	Return 0 if permission is granted.
  * @inode_symlink:
  *	Check the permission to create a symbolic link to a file.
  *	@dir contains the inode structure of parent directory of the symbolic link.
  *	@dentry contains the dentry structure of the symbolic link.
  *	@old_name contains the pathname of file.
  *	Return 0 if permission is granted.
+ * @path_symlink:
+ *	Check the permission to create a symbolic link to a file.
+ *	@dir contains the path structure of parent directory of
+ *	the symbolic link.
+ *	@dentry contains the dentry structure of the symbolic link.
+ *	@old_name contains the pathname of file.
+ *	Return 0 if permission is granted.
  * @inode_mkdir:
  *	Check permissions to create a new directory in the existing directory
  *	associated with inode strcture @dir. 
@@ -367,11 +394,25 @@ static inline void security_free_mnt_opt
  *	@dentry contains the dentry structure of new directory.
  *	@mode contains the mode of new directory.
  *	Return 0 if permission is granted.
+ * @path_mkdir:
+ *	Check permissions to create a new directory in the existing directory
+ *	associated with path strcture @path.
+ *	@dir containst the path structure of parent of the directory
+ *	to be created.
+ *	@dentry contains the dentry structure of new directory.
+ *	@mode contains the mode of new directory.
+ *	Return 0 if permission is granted.
  * @inode_rmdir:
  *	Check the permission to remove a directory.
  *	@dir contains the inode structure of parent of the directory to be removed.
  *	@dentry contains the dentry structure of directory to be removed.
  *	Return 0 if permission is granted.
+ * @path_rmdir:
+ *	Check the permission to remove a directory.
+ *	@dir contains the path structure of parent of the directory to be
+ *	removed.
+ *	@dentry contains the dentry structure of directory to be removed.
+ *	Return 0 if permission is granted.
  * @inode_mknod:
  *	Check permissions when creating a special file (or a socket or a fifo
  *	file created via the mknod system call).  Note that if mknod operation
@@ -382,6 +423,15 @@ static inline void security_free_mnt_opt
  *	@mode contains the mode of the new file.
  *	@dev contains the device number.
  *	Return 0 if permission is granted.
+ * @path_mknod:
+ *	Check permissions when creating a file. Note that this hook is called
+ *	even if mknod operation is being done for a regular file.
+ *	@dir contains the path structure of parent of the new file.
+ *	@dentry contains the dentry structure of the new file.
+ *	@mode contains the mode of the new file.
+ *	@dev contains the undecoded device number. Use new_decode_dev() to get
+ *	the decoded device number.
+ *	Return 0 if permission is granted.
  * @inode_rename:
  *	Check for permission to rename a file or directory.
  *	@old_dir contains the inode structure for parent of the old link.
@@ -389,6 +439,13 @@ static inline void security_free_mnt_opt
  *	@new_dir contains the inode structure for parent of the new link.
  *	@new_dentry contains the dentry structure of the new link.
  *	Return 0 if permission is granted.
+ * @path_rename:
+ *	Check for permission to rename a file or directory.
+ *	@old_dir contains the path structure for parent of the old link.
+ *	@old_dentry contains the dentry structure of the old link.
+ *	@new_dir contains the path structure for parent of the new link.
+ *	@new_dentry contains the dentry structure of the new link.
+ *	Return 0 if permission is granted.
  * @inode_readlink:
  *	Check the permission to read the symbolic link.
  *	@dentry contains the dentry structure for the file link.
@@ -409,6 +466,12 @@ static inline void security_free_mnt_opt
  *	@mask contains the permission mask.
  *     @nd contains the nameidata (may be NULL).
  *	Return 0 if permission is granted.
+ * @path_open:
+ *	Check permission to open a file.
+ *	@dentry contains the dentry structure for the file to be opened.
+ *	@mnt contains the vfsmount structure for the file to be opened.
+ *	@flags contains the open flags.
+ *	Return 0 if permission is granted.
  * @inode_setattr:
  *	Check permission before setting file attributes.  Note that the kernel
  *	call to notify_change is performed from several locations, whenever
@@ -417,6 +480,17 @@ static inline void security_free_mnt_opt
  *	@dentry contains the dentry structure for the file.
  *	@attr is the iattr structure containing the new file attributes.
  *	Return 0 if permission is granted.
+ * @path_truncate:
+ *	Check permission before truncating a file.
+ *	@path contains the path structure for the file.
+ *	@length is the new length of the file.
+ *	@time_attrs is the flags passed to do_truncate().
+ *	@filp is the file structure (may be NULL).
+ *	Return 0 if permission is granted.
+ * @path_uselib:
+ *      Check permission before using a library.
+ *      @nd contains the nameidata.
+ *      Return 0 if permission is granted.
  * @inode_getattr:
  *	Check permission before obtaining file attributes.
  *	@mnt is the vfsmount where the dentry was looked up
@@ -1351,6 +1425,25 @@ struct security_operations {
 				   struct super_block *newsb);
 	int (*sb_parse_opts_str) (char *options, struct security_mnt_opts *opts);
 
+	int (*path_create) (struct inode *dir, struct dentry *dentry, int mode,
+			    struct nameidata *nd);
+	int (*path_unlink) (struct path *dir, struct dentry *dentry);
+	int (*path_mkdir) (struct path *dir, struct dentry *dentry, int mode);
+	int (*path_rmdir) (struct path *dir, struct dentry *dentry);
+	int (*path_mknod) (struct path *dir, struct dentry *dentry, int mode,
+			   unsigned int dev);
+	int (*path_truncate) (struct path *path, loff_t length,
+			      unsigned int time_attrs, struct file *filp);
+	int (*path_uselib) (struct nameidata *nd);
+	int (*path_symlink) (struct path *dir, struct dentry *dentry,
+			     const char *old_name);
+	int (*path_link) (struct path *old_path, struct path *new_dir,
+			  struct dentry *new_dentry);
+	int (*path_rename) (struct path *old_dir, struct dentry *old_dentry,
+			    struct path *new_dir, struct dentry *new_dentry);
+	int (*path_open) (struct dentry *dentry, struct vfsmount *mnt,
+			  int flags);
+
 	int (*inode_alloc_security) (struct inode *inode);	
 	void (*inode_free_security) (struct inode *inode);
 	int (*inode_init_security) (struct inode *inode, struct inode *dir,
@@ -1625,6 +1718,24 @@ void security_sb_clone_mnt_opts(const st
 				struct super_block *newsb);
 int security_sb_parse_opts_str(char *options, struct security_mnt_opts *opts);
 
+int security_path_create(struct inode *dir, struct dentry *dentry, int mode,
+			 struct nameidata *nd);
+int security_path_unlink(struct path *dir, struct dentry *dentry);
+int security_path_mkdir(struct path *dir, struct dentry *dentry, int mode);
+int security_path_rmdir(struct path *dir, struct dentry *dentry);
+int security_path_mknod(struct path *dir, struct dentry *dentry, int mode,
+			unsigned int dev);
+int security_path_truncate(struct path *path, loff_t length,
+			   unsigned int time_attrs, struct file *filp);
+int security_path_uselib(struct nameidata *nd);
+int security_path_symlink(struct path *dir, struct dentry *dentry,
+			  const char *old_name);
+int security_path_link(struct path *old_path, struct path *new_dir,
+		       struct dentry *new_dentry);
+int security_path_rename(struct path *old_dir, struct dentry *old_dentry,
+			 struct path *new_dir, struct dentry *new_dentry);
+int security_path_open(struct dentry *dentry, struct vfsmount *mnt, int flags);
+
 int security_inode_alloc(struct inode *inode);
 void security_inode_free(struct inode *inode);
 int security_inode_init_security(struct inode *inode, struct inode *dir,
@@ -1950,6 +2061,73 @@ static inline int security_sb_parse_opts
 	return 0;
 }
 
+static inline int security_path_create(struct inode *dir, struct dentry *dentry,
+				       int mode, struct nameidata *nd)
+{
+	return 0;
+}
+static inline int security_path_unlink(struct path *dir, struct dentry *dentry)
+{
+	return 0;
+}
+
+static inline int security_path_mkdir(struct path *dir, struct dentry *dentry,
+				      int mode)
+{
+	return 0;
+}
+
+static inline int security_path_rmdir(struct path *dir, struct dentry *dentry)
+{
+	return 0;
+}
+
+static inline int security_path_mknod(struct path *dir, struct dentry *dentry,
+				      int mode, unsigned int dev)
+{
+	return 0;
+}
+
+static inline int security_path_truncate(struct path *path, loff_t length,
+					 unsigned int time_attrs,
+					 struct file *filp)
+{
+	return 0;
+}
+
+static inline int security_path_uselib(struct nameidata *nd)
+{
+	return 0;
+}
+
+static inline int security_path_symlink(struct path *dir, struct dentry *dentry,
+					const char *old_name)
+{
+	return 0;
+}
+
+static inline int security_path_link(struct path *old_path,
+				     struct path *new_dir,
+				     struct dentry *new_dentry)
+{
+	return 0;
+}
+
+static inline int security_path_rename(struct path *old_dir,
+				       struct dentry *old_dentry,
+				       struct path *new_dir,
+				       struct dentry *new_dentry)
+{
+	return 0;
+}
+
+static inline int security_path_open(struct dentry *dentry,
+				     struct vfsmount *mnt,
+				     int flags)
+{
+	return 0;
+}
+
 static inline int security_inode_alloc (struct inode *inode)
 {
 	return 0;
--- mm.orig/net/unix/af_unix.c
+++ mm/net/unix/af_unix.c
@@ -822,7 +822,11 @@ static int unix_bind(struct socket *sock
 		err = mnt_want_write(nd.path.mnt);
 		if (err)
 			goto out_mknod_dput;
+		err = security_path_mknod(&nd.path, dentry, mode, 0);
+		if (err)
+			goto out_drop_write;
 		err = vfs_mknod(nd.path.dentry->d_inode, dentry, mode, 0);
+out_drop_write:
 		mnt_drop_write(nd.path.mnt);
 		if (err)
 			goto out_mknod_dput;
--- mm.orig/security/dummy.c
+++ mm/security/dummy.c
@@ -270,6 +270,68 @@ static int dummy_sb_parse_opts_str(char 
 	return 0;
 }
 
+static int dummy_path_create(struct inode *inode, struct dentry *dentry,
+			     int mask, struct nameidata *nd)
+{
+	return 0;
+}
+
+static int dummy_path_mknod(struct path *dir, struct dentry *dentry, int mode,
+			    unsigned int dev)
+{
+	return 0;
+}
+
+static int dummy_path_mkdir(struct path *dir, struct dentry *dentry, int mode)
+{
+	return 0;
+}
+
+static int dummy_path_rmdir(struct path *dir, struct dentry *dentry)
+{
+	return 0;
+}
+
+static int dummy_path_unlink(struct path *dir, struct dentry *dentry)
+{
+	return 0;
+}
+
+static int dummy_path_symlink(struct path *dir, struct dentry *dentry,
+			      const char *old_name)
+{
+	return 0;
+}
+
+static int dummy_path_link(struct path *old_path, struct path *new_dir,
+			   struct dentry *new_dentry)
+{
+	return 0;
+}
+
+static int dummy_path_rename(struct path *old_path, struct dentry *old_dentry,
+			     struct path *new_path, struct dentry *new_dentry)
+{
+	return 0;
+}
+
+static int dummy_path_open(struct dentry *dentry, struct vfsmount *mnt,
+			   int flags)
+{
+	return 0;
+}
+
+static int dummy_path_truncate(struct path *path, loff_t length,
+			       unsigned int time_attrs, struct file *filp)
+{
+	return 0;
+}
+
+static int dummy_path_uselib(struct nameidata *nd)
+{
+	return 0;
+}
+
 static int dummy_inode_alloc_security (struct inode *inode)
 {
 	return 0;
@@ -1079,6 +1141,17 @@ void security_fixup_ops (struct security
 	set_to_dummy_if_null(ops, sb_set_mnt_opts);
 	set_to_dummy_if_null(ops, sb_clone_mnt_opts);
 	set_to_dummy_if_null(ops, sb_parse_opts_str);
+	set_to_dummy_if_null(ops, path_create);
+	set_to_dummy_if_null(ops, path_mknod);
+	set_to_dummy_if_null(ops, path_mkdir);
+	set_to_dummy_if_null(ops, path_rmdir);
+	set_to_dummy_if_null(ops, path_unlink);
+	set_to_dummy_if_null(ops, path_symlink);
+	set_to_dummy_if_null(ops, path_link);
+	set_to_dummy_if_null(ops, path_rename);
+	set_to_dummy_if_null(ops, path_open);
+	set_to_dummy_if_null(ops, path_truncate);
+	set_to_dummy_if_null(ops, path_uselib);
 	set_to_dummy_if_null(ops, inode_alloc_security);
 	set_to_dummy_if_null(ops, inode_free_security);
 	set_to_dummy_if_null(ops, inode_init_security);
--- mm.orig/security/security.c
+++ mm/security/security.c
@@ -388,6 +388,69 @@ int security_inode_init_security(struct 
 }
 EXPORT_SYMBOL(security_inode_init_security);
 
+int security_path_create(struct inode *dir, struct dentry *dentry, int mode,
+			 struct nameidata *nd)
+{
+	if (unlikely(IS_PRIVATE(dir)))
+		return 0;
+	return security_ops->path_create(dir, dentry, mode, nd);
+}
+int security_path_mknod(struct path *path, struct dentry *dentry, int mode,
+			unsigned int dev)
+{
+	return security_ops->path_mknod(path, dentry, mode, dev);
+}
+
+int security_path_mkdir(struct path *path, struct dentry *dentry, int mode)
+{
+	return security_ops->path_mkdir(path, dentry, mode);
+}
+
+int security_path_rmdir(struct path *path, struct dentry *dentry)
+{
+	return security_ops->path_rmdir(path, dentry);
+}
+
+int security_path_unlink(struct path *path, struct dentry *dentry)
+{
+	return security_ops->path_unlink(path, dentry);
+}
+
+int security_path_symlink(struct path *path, struct dentry *dentry,
+			  const char *old_name)
+{
+	return security_ops->path_symlink(path, dentry, old_name);
+}
+
+int security_path_link(struct path *old_path, struct path *new_dir,
+		       struct dentry *new_dentry)
+{
+	return security_ops->path_link(old_path, new_dir, new_dentry);
+}
+
+int security_path_rename(struct path *old_dir, struct dentry *old_dentry,
+			 struct path *new_dir, struct dentry *new_dentry)
+{
+	return security_ops->path_rename(old_dir, old_dentry, new_dir,
+					 new_dentry);
+}
+
+int security_path_open(struct dentry *dentry, struct vfsmount *mnt, int flags)
+{
+	return security_ops->path_open(dentry, mnt, flags);
+}
+
+int security_path_truncate(struct path *path, loff_t length,
+			   unsigned int time_attrs, struct file *filp)
+{
+	return security_ops->path_truncate(path, length, time_attrs, filp);
+}
+
+int security_path_uselib(struct nameidata *nd)
+{
+	return security_ops->path_uselib(nd);
+}
+
 int security_inode_create(struct inode *dir, struct dentry *dentry, int mode)
 {
 	if (unlikely(IS_PRIVATE(dir)))

--


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

* [TOMOYO #8 (2.6.25-mm1) 2/7] LSM adapter functions.
  2008-05-01  5:54 [TOMOYO #8 (2.6.25-mm1) 0/7] TOMOYO Linux Toshiharu Harada
  2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 1/7] Introduce new LSM hooks Toshiharu Harada
@ 2008-05-01  5:54 ` Toshiharu Harada
  2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 3/7] Memory and pathname management functions Toshiharu Harada
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 14+ messages in thread
From: Toshiharu Harada @ 2008-05-01  5:54 UTC (permalink / raw)
  To: akpm, chrisw, viro
  Cc: linux-security-module, linux-fsdevel, linux-kernel,
	Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

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

--- /dev/null
+++ mm/security/tomoyo/tomoyo.h
@@ -0,0 +1,102 @@
+/*
+ * security/tomoyo/tomoyo.h
+ *
+ * Implementation of the Domain-Based Mandatory Access Control.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 2.2.0-pre   2008/04/30
+ *
+ */
+
+#ifndef _LINUX_TOMOYO_H
+#define _LINUX_TOMOYO_H
+
+struct path_info;
+struct dentry;
+struct vfsmount;
+struct inode;
+struct linux_binprm;
+struct pt_regs;
+struct tmy_page_buffer;
+
+char *sysctlpath_from_table(struct ctl_table *table);
+int tmy_check_file_perm(const char *filename, const u8 perm,
+			const char *operation);
+int tmy_check_exec_perm(const struct path_info *filename,
+			struct tmy_page_buffer *buf);
+int tmy_check_open_permission(struct dentry *dentry, struct vfsmount *mnt,
+			      const int flag);
+int tmy_check_1path_perm(const u8 operation,
+			 struct dentry *dentry,
+			 struct vfsmount *mnt);
+int tmy_check_2path_perm(const u8 operation,
+			 struct dentry *dentry1,
+			 struct vfsmount *mnt1,
+			 struct dentry *dentry2,
+			 struct vfsmount *mnt2);
+int tmy_check_rewrite_permission(struct file *filp);
+int tmy_find_next_domain(struct linux_binprm *bprm,
+			 struct domain_info **next_domain);
+
+#define TMY_CHECK_READ_FOR_OPEN_EXEC 1
+
+/* Index numbers for Access Controls. */
+
+#define TYPE_SINGLE_PATH_ACL                 0
+#define TYPE_DOUBLE_PATH_ACL                 1
+
+/* Index numbers for File Controls. */
+
+/*
+ * TYPE_READ_WRITE_ACL is special. TYPE_READ_WRITE_ACL is automatically set
+ * if both TYPE_READ_ACL and TYPE_WRITE_ACL are set. Both TYPE_READ_ACL and
+ * TYPE_WRITE_ACL are automatically set if TYPE_READ_WRITE_ACL is set.
+ * TYPE_READ_WRITE_ACL is automatically cleared if either TYPE_READ_ACL or
+ * TYPE_WRITE_ACL is cleared. Both TYPE_READ_ACL and TYPE_WRITE_ACL are
+ * automatically cleared if TYPE_READ_WRITE_ACL is cleared.
+ */
+
+#define TMY_TYPE_READ_WRITE_ACL        0
+#define TMY_TYPE_EXECUTE_ACL           1
+#define TMY_TYPE_READ_ACL              2
+#define TMY_TYPE_WRITE_ACL             3
+#define TMY_TYPE_CREATE_ACL            4
+#define TMY_TYPE_UNLINK_ACL            5
+#define TMY_TYPE_MKDIR_ACL             6
+#define TMY_TYPE_RMDIR_ACL             7
+#define TMY_TYPE_MKFIFO_ACL            8
+#define TMY_TYPE_MKSOCK_ACL            9
+#define TMY_TYPE_MKBLOCK_ACL          10
+#define TMY_TYPE_MKCHAR_ACL           11
+#define TMY_TYPE_TRUNCATE_ACL         12
+#define TMY_TYPE_SYMLINK_ACL          13
+#define TMY_TYPE_REWRITE_ACL          14
+#define MAX_SINGLE_PATH_OPERATION 15
+
+#define TMY_TYPE_LINK_ACL             0
+#define TMY_TYPE_RENAME_ACL           1
+#define MAX_DOUBLE_PATH_OPERATION 2
+
+struct tmy_security {
+	struct domain_info *domain;
+	struct domain_info *prev_domain;
+	u32 flags;
+};
+
+#define TMY_SECURITY ((struct tmy_security *) current->security)
+
+#define TMY_DOMAINPOLICY          0
+#define TMY_EXCEPTIONPOLICY       1
+#define TMY_DOMAIN_STATUS         2
+#define TMY_PROCESS_STATUS        3
+#define TMY_MEMINFO               4
+#define TMY_SELFDOMAIN            5
+#define TMY_VERSION               6
+#define TMY_PROFILE               7
+#define TMY_MANAGER               8
+#define TMY_UPDATESCOUNTER        9
+
+extern struct domain_info KERNEL_DOMAIN;
+
+#endif
--- /dev/null
+++ mm/security/tomoyo/tomoyo.c
@@ -0,0 +1,279 @@
+/*
+ * security/tomoyo/tomoyo.c
+ *
+ * LSM hooks for TOMOYO Linux.
+ */
+
+#include <linux/security.h>
+#include "common.h"
+#include "tomoyo.h"
+#include "realpath.h"
+
+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))
+		goto out;
+	retval = tmy_find_next_domain(bprm, &next_domain);
+	if (retval)
+		goto out;
+	TMY_SECURITY->domain = next_domain;
+	TMY_SECURITY->flags |= TMY_CHECK_READ_FOR_OPEN_EXEC;
+out:
+	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_check_file_perm(name, op & 6, "sysctl");
+	tmy_free(name);
+
+	return error;
+}
+
+static int tmy_inode_permission(struct inode *inode, int mask,
+				struct nameidata *nd)
+{
+	if (S_ISDIR(inode->i_mode)) /* ignore because inode is directory */
+		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))
+		return 0;
+
+	/* called from open_exec() other than do_execve() */;
+	return tmy_check_open_permission(nd->path.dentry, nd->path.mnt,
+					 O_RDONLY + 1);
+}
+
+static int tmy_path_open(struct dentry *dentry, struct vfsmount *mnt, int flags)
+{
+	return tmy_check_open_permission(dentry, mnt, flags);
+}
+
+static int tmy_path_truncate(struct path *path, loff_t length,
+			     unsigned int time_attrs, struct file *filp)
+{
+	return tmy_check_1path_perm(TMY_TYPE_TRUNCATE_ACL, path->dentry,
+				    path->mnt);
+}
+
+static int tmy_path_create(struct inode *dir, struct dentry *dentry, int mode,
+			   struct nameidata *nd)
+{
+	if (!nd || !nd->path.mnt)
+		return 0;
+	return tmy_check_1path_perm(TMY_TYPE_CREATE_ACL, dentry, nd->path.mnt);
+}
+
+static int tmy_path_unlink(struct path *dir, struct dentry *dentry)
+{
+	const int err = pre_vfs_unlink(dir->dentry->d_inode, dentry);
+	if (err)
+		return err;
+	return tmy_check_1path_perm(TMY_TYPE_UNLINK_ACL, dentry, dir->mnt);
+}
+
+static int tmy_path_mkdir(struct path *dir, struct dentry *dentry, int mode)
+{
+	const int err = pre_vfs_mkdir(dir->dentry->d_inode, dentry);
+	if (err)
+		return err;
+	return tmy_check_1path_perm(TMY_TYPE_MKDIR_ACL, dentry, dir->mnt);
+}
+
+static int tmy_path_rmdir(struct path *dir, struct dentry *dentry)
+{
+	const int err = pre_vfs_rmdir(dir->dentry->d_inode, dentry);
+	if (err)
+		return err;
+	return tmy_check_1path_perm(TMY_TYPE_RMDIR_ACL, dentry, dir->mnt);
+}
+
+static int tmy_path_symlink(struct path *dir, struct dentry *dentry,
+			    const char *old_name)
+{
+	const int err = pre_vfs_symlink(dir->dentry->d_inode, dentry);
+	if (err)
+		return err;
+	return tmy_check_1path_perm(TMY_TYPE_SYMLINK_ACL, dentry, dir->mnt);
+}
+
+static int tmy_path_mknod(struct path *dir, struct dentry *dentry, int mode,
+			  unsigned int dev)
+{
+	struct vfsmount *mnt = dir->mnt;
+	int err = 0;
+	switch (mode & S_IFMT) {
+	case S_IFCHR:
+	case S_IFBLK:
+	case S_IFIFO:
+	case S_IFSOCK:
+		break;
+	default:
+		return 0;
+	}
+	err = pre_vfs_mknod(dir->dentry->d_inode, dentry, mode);
+	if (err)
+		return err;
+	if (S_ISCHR(mode))
+		return tmy_check_1path_perm(TMY_TYPE_MKCHAR_ACL, dentry, mnt);
+	if (S_ISBLK(mode))
+		return tmy_check_1path_perm(TMY_TYPE_MKBLOCK_ACL, dentry, mnt);
+	if (S_ISFIFO(mode))
+		return tmy_check_1path_perm(TMY_TYPE_MKFIFO_ACL, dentry, mnt);
+	if (S_ISSOCK(mode))
+		return tmy_check_1path_perm(TMY_TYPE_MKSOCK_ACL, dentry, mnt);
+	return 0;
+}
+
+static int tmy_path_link(struct path *old_path, struct path *new_dir,
+			 struct dentry *new_dentry)
+{
+	const int err = pre_vfs_link(old_path->dentry, new_dir->dentry->d_inode,
+				     new_dentry);
+	if (err)
+		return err;
+	return tmy_check_2path_perm(TMY_TYPE_LINK_ACL,
+				    old_path->dentry, old_path->mnt,
+				    new_dentry, new_dir->mnt);
+}
+
+static int tmy_path_rename(struct path *old_dir, struct dentry *old_dentry,
+			   struct path *new_dir, struct dentry *new_dentry)
+{
+	const int err = pre_vfs_rename(old_dir->dentry->d_inode, old_dentry,
+				       new_dir->dentry->d_inode, new_dentry);
+	if (err)
+		return err;
+	return tmy_check_2path_perm(TMY_TYPE_RENAME_ACL,
+				    old_dentry, old_dir->mnt,
+				    new_dentry, new_dir->mnt);
+}
+
+static int tmy_path_uselib(struct nameidata *nd)
+{
+	return tmy_check_open_permission(nd->path.dentry, nd->path.mnt,
+					 O_RDONLY + 1);
+}
+
+static int tmy_file_fcntl(struct file *file, unsigned int cmd,
+			  unsigned long arg)
+{
+	if (cmd == F_SETFL && ((arg ^ file->f_flags) & O_APPEND))
+		return tmy_check_rewrite_permission(file);
+	return 0;
+}
+
+static struct security_operations tomoyo_security_ops = {
+	.name                      = "tomoyo",
+
+	.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,
+	.path_truncate             = tmy_path_truncate,
+	.path_create               = tmy_path_create,
+	.path_unlink               = tmy_path_unlink,
+	.path_mkdir                = tmy_path_mkdir,
+	.path_rmdir                = tmy_path_rmdir,
+	.path_symlink              = tmy_path_symlink,
+	.path_mknod                = tmy_path_mknod,
+	.path_link                 = tmy_path_link,
+	.path_rename               = tmy_path_rename,
+	.path_open                 = tmy_path_open,
+	.path_uselib               = tmy_path_uselib,
+	.file_fcntl                = tmy_file_fcntl,
+};
+
+static int __init tmy_init(void)
+{
+	struct tmy_security *tmy_security;
+	if (!security_module_enable(&tomoyo_security_ops))
+		return 0;
+
+	/* register ourselves with the security framework */
+	if (register_security(&tomoyo_security_ops))
+		panic("Failure registering TOMOYO Linux");
+
+	printk(KERN_INFO "TOMOYO Linux initialized\n");
+	tmy_cachep = kmem_cache_create("tomoyo_security",
+				       sizeof(struct tmy_security),
+				       0, SLAB_PANIC, NULL);
+	tmy_security = kmem_cache_alloc(tmy_cachep, GFP_KERNEL);
+	BUG_ON(!tmy_security);
+	memset(tmy_security, 0, sizeof(*tmy_security));
+	tmy_security->domain = &KERNEL_DOMAIN;
+	init_task.security = tmy_security;
+	return 0;
+}
+
+security_initcall(tmy_init);

--


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

* [TOMOYO #8 (2.6.25-mm1) 3/7] Memory and pathname management functions.
  2008-05-01  5:54 [TOMOYO #8 (2.6.25-mm1) 0/7] TOMOYO Linux Toshiharu Harada
  2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 1/7] Introduce new LSM hooks Toshiharu Harada
  2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 2/7] LSM adapter functions Toshiharu Harada
@ 2008-05-01  5:54 ` Toshiharu Harada
  2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 4/7] Common functions for TOMOYO Linux Toshiharu Harada
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 14+ messages in thread
From: Toshiharu Harada @ 2008-05-01  5:54 UTC (permalink / raw)
  To: akpm, chrisw, viro
  Cc: linux-security-module, linux-fsdevel, linux-kernel,
	Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

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

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

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

--- /dev/null
+++ mm/security/tomoyo/realpath.h
@@ -0,0 +1,62 @@
+/*
+ * security/tomoyo/realpath.h
+ *
+ * Get the canonicalized absolute pathnames. The basis for TOMOYO.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 2.2.0-pre   2008/04/30
+ *
+ */
+
+#ifndef _LINUX_REALPATH_H
+#define _LINUX_REALPATH_H
+
+struct dentry;
+struct vfsmount;
+struct condition_list;
+struct path_info;
+
+/* Returns realpath(3) of the given pathname but ignores chroot'ed root. */
+int tmy_realpath_from_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);
+/* Same with tmy_realpath() except that it doesn't follow the final symlink. */
+char *tmy_realpath_nofollow(const char *pathname);
+/* Same with tmy_realpath() except that the pathname is already solved. */
+char *tmy_realpath_from_dentry(struct dentry *dentry, struct vfsmount *mnt);
+
+/*
+ * Allocate memory for ACL entry.
+ * The RAM is chunked, so NEVER try to kfree() the returned pointer.
+ */
+void *tmy_alloc_element(const unsigned int size);
+
+/* 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);
+
+/* Allocate memory for temporary use (e.g. permission checks). */
+void *tmy_alloc(const size_t size);
+
+/* Get used RAM size for tmy_alloc(). */
+unsigned int tmy_get_memory_used_for_dynamic(void);
+
+/* Free memory allocated by tmy_alloc(). */
+void tmy_free(const void *p);
+
+#endif
--- /dev/null
+++ mm/security/tomoyo/realpath.c
@@ -0,0 +1,655 @@
+/*
+ * security/tomoyo/realpath.c
+ *
+ * Get the canonicalized absolute pathnames. The basis for TOMOYO.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 2.2.0-pre   2008/04/30
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/mount.h>
+#include <linux/magic.h>
+#include <linux/sysctl.h>
+#include "common.h"
+#include "realpath.h"
+
+/**
+ * get_absolute_path - Get the path of a dentry but ignores chroot'ed root.
+ *
+ * @dentry: Pointer to "struct dentry".
+ * @vfsmnt: Pointer to "struct vfsmount".
+ * @buffer: Pointer to buffer to return value in.
+ * @buflen: Sizeof @buffer.
+ *
+ * Returns 0 on success, -ENOMEM otherwise.
+ *
+ * Caller holds the dcache_lock and vfsmount_lock.
+ * Based on __d_path() in fs/dcache.c
+ *
+ * If dentry is a directory, trailing '/' is appended.
+ * Characters out of 0x20 < c < 0x7F range are converted to
+ * \ooo style octal string.
+ * Character \ is converted to \\ string.
+ */
+static int get_absolute_path(struct dentry *dentry, struct vfsmount *vfsmnt,
+			     char *buffer, int buflen)
+{
+	/***** CRITICAL SECTION START *****/
+	char *start = buffer;
+	char *end = buffer + buflen;
+	bool is_dir = (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode));
+
+	if (buflen < 256)
+		goto out;
+
+	*--end = '\0';
+	buflen--;
+
+	for (;;) {
+		struct dentry *parent;
+
+		if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
+			/* Global root? */
+			if (vfsmnt->mnt_parent == vfsmnt)
+				break;
+			dentry = vfsmnt->mnt_mountpoint;
+			vfsmnt = vfsmnt->mnt_parent;
+			continue;
+		}
+		if (is_dir) {
+			is_dir = false;
+			*--end = '/';
+			buflen--;
+		}
+		parent = dentry->d_parent;
+		{
+			const char *sp = dentry->d_name.name;
+			const char *cp = sp + dentry->d_name.len - 1;
+			unsigned char c;
+
+			/*
+			 * 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;
+				}
+			}
+
+			while (sp <= cp) {
+				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--;
+			}
+			if (--buflen < 0)
+				goto out;
+			*--end = '/';
+		}
+		dentry = parent;
+	}
+	if (*end == '/') {
+		buflen++;
+		end++;
+	}
+	{
+		const char *sp = dentry->d_name.name;
+		const char *cp = sp + dentry->d_name.len - 1;
+		unsigned char c;
+		while (sp <= cp) {
+			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--;
+		}
+	}
+	/* Move the pathname to the top of the buffer. */
+	memmove(start, end, strlen(end) + 1);
+	return 0;
+out:
+	return -ENOMEM;
+	/***** CRITICAL SECTION END *****/
+}
+
+/**
+ * tmy_realpath_from_dentry2 - Returns realpath(3) of the given dentry but ignores chroot'ed root.
+ *
+ * @dentry:      Pointer to "struct dentry".
+ * @mnt:         Pointer to "struct vfsmount".
+ * @newname:     Pointer to buffer to return value in.
+ * @newname_len: Size of @newname.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tmy_realpath_from_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);
+	spin_lock(&vfsmount_lock);
+	error = get_absolute_path(d_dentry, d_mnt, newname, newname_len);
+	spin_unlock(&vfsmount_lock);
+	spin_unlock(&dcache_lock);
+	/***** CRITICAL SECTION END *****/
+	dput(d_dentry);
+	mntput(d_mnt);
+	if (error)
+		printk(KERN_WARNING "tmy_realpath: Pathname too long.\n");
+	return error;
+}
+
+/**
+ * tmy_realpath_from_dentry - Returns realpath(3) of the given pathname but ignores chroot'ed root.
+ *
+ * @dentry: Pointer to "struct dentry".
+ * @mnt:    Pointer to "struct vfsmount".
+ *
+ * Returns the realpath of the given @dentry and @mnt on success,
+ * NULL otherwise.
+ *
+ * These functions use tmy_alloc(), so caller must tmy_free()
+ * if these functions didn't return NULL.
+ */
+char *tmy_realpath_from_dentry(struct dentry *dentry, struct vfsmount *mnt)
+{
+	char *buf = tmy_alloc(sizeof(struct tmy_page_buffer));
+	if (buf && tmy_realpath_from_dentry2(dentry, mnt, buf,
+					     TMY_MAX_PATHNAME_LEN - 1) == 0)
+		return buf;
+	tmy_free(buf);
+	return NULL;
+}
+
+/**
+ * tmy_realpath - Get realpath of a pathname.
+ *
+ * @pathname: The pathname to solve.
+ *
+ * Returns the realpath of @pathname on success, NULL otherwise.
+ */
+char *tmy_realpath(const char *pathname)
+{
+	struct nameidata nd;
+	if (pathname && path_lookup(pathname, LOOKUP_FOLLOW, &nd) == 0) {
+		char *buf = tmy_realpath_from_dentry(nd.path.dentry,
+						     nd.path.mnt);
+		path_put(&nd.path);
+		return buf;
+	}
+	return NULL;
+}
+
+/**
+ * tmy_realpath_nofollow - Get realpath of a pathname.
+ *
+ * @pathname: The pathname to solve.
+ *
+ * Returns the realpath of @pathname on success, NULL otherwise.
+ */
+char *tmy_realpath_nofollow(const char *pathname)
+{
+	struct nameidata nd;
+	if (pathname && path_lookup(pathname, 0, &nd) == 0) {
+		char *buf = tmy_realpath_from_dentry(nd.path.dentry,
+						     nd.path.mnt);
+		path_put(&nd.path);
+		return buf;
+	}
+	return NULL;
+}
+
+/**
+ * round_up - Round up an integer so that the returned pointers are appropriately aligned.
+ *
+ * @size: Size in bytes.
+ *
+ * Returns rounded value of @size.
+ *
+ * FIXME: Are there more requirements that is needed for assigning value
+ * atomically?
+ */
+static inline unsigned int round_up(const unsigned int size)
+{
+	if (sizeof(void *) >= sizeof(long))
+		return ((size + sizeof(void *) - 1)
+			/ sizeof(void *)) * sizeof(void *);
+	else
+		return ((size + sizeof(long) - 1)
+			/ sizeof(long)) * sizeof(long);
+}
+
+static unsigned int allocated_memory_for_elements;
+
+/**
+ * tmy_get_memory_used_for_elements - Get memory used for keeping ACL structures.
+ *
+ * Returns memory used for keeping ACL structures.
+ */
+unsigned int tmy_get_memory_used_for_elements(void)
+{
+	return allocated_memory_for_elements;
+}
+
+/**
+ * tmy_alloc_element - Allocate permanent memory for structures.
+ *
+ * @size: Size in bytes.
+ *
+ * Returns pointer to allocated memory on success, NULL otherwise.
+ *
+ * The RAM is chunked, so NEVER try to kfree() the returned pointer.
+ */
+void *tmy_alloc_element(const unsigned int size)
+{
+	static DEFINE_MUTEX(lock);
+	static char *buf;
+	static unsigned int buf_used_len = PAGE_SIZE;
+	char *ptr = NULL;
+	const unsigned int word_aligned_size = round_up(size);
+	if (word_aligned_size > PAGE_SIZE)
+		return NULL;
+	mutex_lock(&lock);
+	if (buf_used_len + word_aligned_size > PAGE_SIZE) {
+		ptr = kzalloc(PAGE_SIZE, GFP_KERNEL);
+		if (!ptr) {
+			printk(KERN_WARNING "ERROR: Out of memory "
+			       "for tmy_alloc_element().\n");
+			if (!sbin_init_started)
+				panic("MAC Initialization failed.\n");
+		} else {
+			buf = ptr;
+			allocated_memory_for_elements += PAGE_SIZE;
+			buf_used_len = word_aligned_size;
+			ptr = buf;
+		}
+	} else if (word_aligned_size) {
+		int i;
+		ptr = buf + buf_used_len;
+		buf_used_len += word_aligned_size;
+		for (i = 0; i < word_aligned_size; i++) {
+			if (!ptr[i])
+				continue;
+			printk(KERN_ERR "WARNING: Reserved memory was tainted! "
+			       "The system might go wrong.\n");
+			ptr[i] = '\0';
+		}
+	}
+	mutex_unlock(&lock);
+	return ptr;
+}
+
+static unsigned int allocated_memory_for_savename;
+
+/**
+ * tmy_get_memory_used_for_save_name - Get memory used for keeping string data.
+ *
+ * Returns memory used for keeping string data.
+ */
+unsigned int tmy_get_memory_used_for_save_name(void)
+{
+	return allocated_memory_for_savename;
+}
+
+#define MAX_HASH 256
+
+/* Structure for string data. */
+struct name_entry {
+	struct list1_head list;
+	struct path_info entry;
+};
+
+/* Structure for available memory region. */
+struct free_memory_block_list {
+	struct list_head list;
+	char *ptr;             /* Pointer to a free area. */
+	int len;               /* Length of the area.     */
+};
+
+/* The list for "struct name_entry". */
+static struct list1_head name_list[MAX_HASH];
+
+/**
+ * tmy_save_name - Allocate permanent memory for string data.
+ *
+ * @name: The string to store into the permernent memory.
+ *
+ * Returns pointer to "struct path_info" on success, NULL otherwise.
+ *
+ * The RAM is shared, so NEVER try to modify or kfree() the returned name.
+ */
+const struct path_info *tmy_save_name(const char *name)
+{
+	static LIST_HEAD(fmb_list);
+	static DEFINE_MUTEX(lock);
+	struct name_entry *ptr;
+	unsigned int hash;
+	struct free_memory_block_list *fmb;
+	int len;
+	char *cp;
+	if (!name)
+		return NULL;
+	len = strlen(name) + 1;
+	if (len > TMY_MAX_PATHNAME_LEN) {
+		printk(KERN_WARNING "ERROR: Name too long "
+		       "for tmy_save_name().\n");
+		return NULL;
+	}
+	hash = full_name_hash((const unsigned char *) name, len - 1);
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &name_list[hash % MAX_HASH], list) {
+		if (hash == ptr->entry.hash && !strcmp(name, ptr->entry.name))
+			goto out;
+	}
+	list_for_each_entry(fmb, &fmb_list, list) {
+		if (len <= fmb->len)
+			goto ready;
+	}
+	cp = kzalloc(PAGE_SIZE, GFP_KERNEL);
+	fmb = kzalloc(sizeof(*fmb), GFP_KERNEL);
+	if (!cp || !fmb) {
+		kfree(cp);
+		kfree(fmb);
+		printk(KERN_WARNING "ERROR: Out of memory "
+		       "for tmy_save_name().\n");
+		if (!sbin_init_started)
+			panic("MAC Initialization failed.\n");
+		ptr = NULL;
+		goto out;
+	}
+	allocated_memory_for_savename += PAGE_SIZE;
+	list_add(&fmb->list, &fmb_list);
+	fmb->ptr = cp;
+	fmb->len = PAGE_SIZE;
+ready:
+	ptr = tmy_alloc_element(sizeof(*ptr));
+	if (!ptr)
+		goto out;
+	ptr->entry.name = fmb->ptr;
+	memmove(fmb->ptr, name, len);
+	tmy_fill_path_info(&ptr->entry);
+	fmb->ptr += len;
+	fmb->len -= len;
+	list1_add_tail_mb(&ptr->list, &name_list[hash % MAX_HASH]);
+	if (fmb->len == 0) {
+		list_del(&fmb->list);
+		kfree(fmb);
+	}
+out:
+	mutex_unlock(&lock);
+	return ptr ? &ptr->entry : NULL;
+}
+
+/* Structure for temporarily allocated memory. */
+struct cache_entry {
+	struct list_head list;
+	void *ptr;
+	int size;
+};
+
+static struct kmem_cache *tmy_cachep;
+
+/**
+ * tmy_realpath_init - Initialize realpath related code.
+ *
+ * Returns 0.
+ */
+static int __init tmy_realpath_init(void)
+{
+	int i;
+	if (TMY_MAX_PATHNAME_LEN > PAGE_SIZE)
+		panic("Bad size.");
+	tmy_cachep = kmem_cache_create("tmy_cache", sizeof(struct cache_entry),
+				       0, 0, NULL);
+	if (!tmy_cachep)
+		panic("Can't create cache.\n");
+	for (i = 0; i < MAX_HASH; i++)
+		INIT_LIST1_HEAD(&name_list[i]);
+	INIT_LIST1_HEAD(&KERNEL_DOMAIN.acl_info_list);
+	KERNEL_DOMAIN.domainname = tmy_save_name(ROOT_NAME);
+	list1_add_tail_mb(&KERNEL_DOMAIN.list, &domain_list);
+	if (tmy_find_domain(ROOT_NAME) != &KERNEL_DOMAIN)
+		panic("Can't register KERNEL_DOMAIN");
+	return 0;
+}
+
+security_initcall(tmy_realpath_init);
+
+/* The list for "struct cache_entry". */
+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 used for temporal purpose.
+ *
+ * Returns memory used for temporal purpose.
+ */
+unsigned int tmy_get_memory_used_for_dynamic(void)
+{
+	return dynamic_memory_size;
+}
+
+/**
+ * tmy_alloc - Allocate memory for temporal purpose.
+ *
+ * @size: Size in bytes.
+ *
+ * Returns pointer to allocated memory on success, NULL otherwise.
+ */
+void *tmy_alloc(const size_t size)
+{
+	struct cache_entry *new_entry;
+	void *ret = kzalloc(size, GFP_KERNEL);
+	if (!ret)
+		goto out;
+	new_entry = kmem_cache_alloc(tmy_cachep, GFP_KERNEL);
+	if (!new_entry) {
+		kfree(ret);
+		ret = NULL;
+		goto out;
+	}
+	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 *****/
+out:
+	return ret;
+}
+
+/**
+ * tmy_free - Release memory allocated by tmy_alloc().
+ *
+ * @p: Pointer returned by tmy_alloc(). May be NULL.
+ *
+ * Returns nothing.
+ */
+void tmy_free(const void *p)
+{
+	struct list_head *v;
+	struct cache_entry *entry = NULL;
+	if (!p)
+		return;
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&cache_list_lock);
+	list_for_each(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_WARNING "BUG: tmy_free() with invalid pointer.\n");
+	}
+}
+
+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() 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;
+}

--


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

* [TOMOYO #8 (2.6.25-mm1) 4/7] Common functions for TOMOYO Linux.
  2008-05-01  5:54 [TOMOYO #8 (2.6.25-mm1) 0/7] TOMOYO Linux Toshiharu Harada
                   ` (2 preceding siblings ...)
  2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 3/7] Memory and pathname management functions Toshiharu Harada
@ 2008-05-01  5:54 ` Toshiharu Harada
  2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 5/7] Domain transition handler Toshiharu Harada
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 14+ messages in thread
From: Toshiharu Harada @ 2008-05-01  5:54 UTC (permalink / raw)
  To: akpm, chrisw, viro
  Cc: linux-security-module, linux-fsdevel, linux-kernel,
	Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

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

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 include/linux/list.h     |   78 +
 security/tomoyo/common.c | 2149 +++++++++++++++++++++++++++++++++++++++++++++++
 security/tomoyo/common.h |  311 ++++++
 3 files changed, 2538 insertions(+)

--- mm.orig/include/linux/list.h
+++ mm/include/linux/list.h
@@ -614,4 +614,82 @@ static inline void hlist_add_after(struc
 		({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
 	     pos = n)
 
+/*
+ * Singly linked list.
+ */
+struct list1_head {
+	struct list1_head *next;
+};
+
+#define LIST1_HEAD_INIT(name) { &(name) }
+#define LIST1_HEAD(name) struct list1_head name = LIST1_HEAD_INIT(name)
+
+static inline void INIT_LIST1_HEAD(struct list1_head *list)
+{
+	list->next = list;
+}
+
+/**
+ * list1_entry - get the struct for this entry
+ * @ptr:        the &struct list1_head pointer.
+ * @type:       the type of the struct this is embedded in.
+ * @member:     the name of the list1_struct within the struct.
+ */
+#define list1_entry(ptr, type, member) container_of(ptr, type, member)
+
+/**
+ * list1_for_each - iterate over a list
+ * @pos:        the &struct list1_head to use as a loop cursor.
+ * @head:       the head for your list.
+ */
+#define list1_for_each(pos, head)					\
+	for (pos = (head)->next; prefetch(pos->next), pos != (head);	\
+	     pos = pos->next)
+
+/**
+ * list1_for_each_entry - iterate over list of given type
+ * @pos:        the type * to use as a loop cursor.
+ * @head:       the head for your list.
+ * @member:     the name of the list1_struct within the struct.
+ */
+#define list1_for_each_entry(pos, head, member)				\
+	for (pos = list1_entry((head)->next, typeof(*pos), member);	\
+	     prefetch(pos->member.next), &pos->member != (head);        \
+	     pos = list1_entry(pos->member.next, typeof(*pos), member))
+
+/**
+ * list1_for_each_cookie - iterate over a list with cookie.
+ * @pos:        the &struct list1_head to use as a loop cursor.
+ * @cookie:     the &struct list1_head to use as a cookie.
+ * @head:       the head for your list.
+ *
+ * Same with list_for_each except that this primitive uses cookie
+ * so that we can continue iteration.
+ */
+#define list1_for_each_cookie(pos, cookie, head)			\
+	for (({ if (!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 list1_add_tail_mb(struct list1_head *new,
+				     struct list1_head *head)
+{
+	struct list1_head *pos = head;
+	new->next = head;
+	mb(); /* Avoid out-of-order execution. */
+	while (pos->next != head)
+		pos = pos->next;
+	pos->next = new;
+}
+
 #endif
--- /dev/null
+++ mm/security/tomoyo/common.h
@@ -0,0 +1,311 @@
+/*
+ * security/tomoyo/common.h
+ *
+ * Common functions for TOMOYO.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 2.2.0-pre   2008/04/30
+ *
+ */
+
+#ifndef _LINUX_TMY_COMMON_H
+#define _LINUX_TMY_COMMON_H
+
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/file.h>
+#include <linux/kmod.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+
+struct dentry;
+struct vfsmount;
+
+#define false 0
+#define true 1
+
+/* Temporary buffer for holding pathnames. */
+struct tmy_page_buffer {
+	char buffer[4096];
+};
+
+/* Structure for attribute checks in addition to pathname checks. */
+struct obj_info {
+	struct tmy_page_buffer *tmp;
+};
+
+/* Structure for holding a token. */
+struct path_info {
+	const char *name;
+	u32 hash;          /* = full_name_hash(name, strlen(name)) */
+	u16 total_len;     /* = strlen(name)                       */
+	u16 const_len;     /* = const_part_length(name)            */
+	bool is_dir;       /* = strendswith(name, "/")             */
+	bool is_patterned; /* = path_contains_pattern(name)        */
+	u16 depth;         /* = path_depth(name)                   */
+};
+
+/*
+ * This is the max length of a token.
+ *
+ * A token consists of only ASCII printable characters.
+ * Non printable characters in a token is represented in \ooo style
+ * octal string. Thus, \ itself is represented as \\.
+ */
+#define TMY_MAX_PATHNAME_LEN 4000
+
+/* Structure for holding requested pathname. */
+struct path_info_with_data {
+	/* Keep "head" first, for this pointer is passed to tmy_free(). */
+	struct path_info head;
+	char bariier1[16]; /* Safeguard for overrun. */
+	char body[TMY_MAX_PATHNAME_LEN];
+	char barrier2[16]; /* Safeguard for overrun. */
+};
+
+/* Common header for holding ACL entries. */
+struct acl_info {
+	struct list1_head list;
+	/*
+	 * Type of this ACL entry.
+	 *
+	 * MSB is is_deleted flag.
+	 */
+	u8 type;
+} __attribute__((__packed__));
+
+/* This ACL entry is deleted.           */
+#define ACL_DELETED        0x80
+
+/* Structure for domain information. */
+struct domain_info {
+	struct list1_head list;
+	struct list1_head acl_info_list;
+	/* Name of this domain. Never NULL.          */
+	const struct path_info *domainname;
+	u8 profile;        /* Profile number to use. */
+	u8 is_deleted;     /* Delete flag.           */
+	bool quota_warned; /* Quota warnning flag.   */
+	/* DOMAIN_FLAGS_IGNORE_*. Use tmy_set_domain_flag() to modify. */
+	u8 flags;
+};
+
+/* Profile number is an integer between 0 and 255. */
+#define MAX_PROFILES 256
+
+/* Ignore "allow_read" directive in exception policy. */
+#define DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ 1
+
+/*
+ * Structure for "allow_read/write", "allow_execute", "allow_read",
+ * "allow_write", "allow_create", "allow_unlink", "allow_mkdir", "allow_rmdir",
+ * "allow_mkfifo", "allow_mksock", "allow_mkblock", "allow_mkchar",
+ * "allow_truncate", "allow_symlink" and "allow_rewrite" directive.
+ */
+struct single_path_acl_record {
+	struct acl_info head; /* type = TYPE_SINGLE_PATH_ACL */
+	u16 perm;
+	/* Pointer to single pathname. */
+	const struct path_info *filename;
+};
+
+/* Structure for "allow_rename" and "allow_link" directive. */
+struct double_path_acl_record {
+	struct acl_info head; /* type = TYPE_DOUBLE_PATH_ACL */
+	u8 perm;
+	/* Pointer to single pathname. */
+	const struct path_info *filename1;
+	/* Pointer to single pathname. */
+	const struct path_info *filename2;
+};
+
+/* Keywords for ACLs. */
+#define KEYWORD_ALIAS                     "alias "
+#define KEYWORD_ALLOW_READ                "allow_read "
+#define KEYWORD_DELETE                    "delete "
+#define KEYWORD_DENY_REWRITE              "deny_rewrite "
+#define KEYWORD_FILE_PATTERN              "file_pattern "
+#define KEYWORD_INITIALIZE_DOMAIN         "initialize_domain "
+#define KEYWORD_KEEP_DOMAIN               "keep_domain "
+#define KEYWORD_NO_INITIALIZE_DOMAIN      "no_initialize_domain "
+#define KEYWORD_NO_KEEP_DOMAIN            "no_keep_domain "
+#define KEYWORD_SELECT                    "select "
+#define KEYWORD_UNDELETE                  "undelete "
+#define KEYWORD_USE_PROFILE               "use_profile "
+#define KEYWORD_IGNORE_GLOBAL_ALLOW_READ  "ignore_global_allow_read"
+/* A domain definition starts with <kernel>. */
+#define ROOT_NAME                         "<kernel>"
+#define ROOT_NAME_LEN                     (sizeof(ROOT_NAME) - 1)
+
+/* Index numbers for Access Controls. */
+#define TMY_TOMOYO_MAC_FOR_FILE                  0  /* domain_policy.conf */
+#define TMY_TOMOYO_MAX_ACCEPT_ENTRY              1
+#define TMY_TOMOYO_VERBOSE                       2
+#define TMY_MAX_CONTROL_INDEX                    3
+
+/* Index numbers for updates counter. */
+#define TMY_UPDATES_COUNTER_DOMAIN_POLICY    0
+#define TMY_UPDATES_COUNTER_EXCEPTION_POLICY 1
+#define TMY_UPDATES_COUNTER_PROFILE          2
+#define TMY_UPDATES_COUNTER_MANAGER          3
+#define MAX_TMY_UPDATES_COUNTER              4
+
+/* Structure for reading/writing policy via securityfs interfaces. */
+struct tmy_io_buffer {
+	int (*read) (struct tmy_io_buffer *);
+	int (*write) (struct tmy_io_buffer *);
+	/* Exclusive lock for read_buf.         */
+	struct mutex read_sem;
+	/* Exclusive lock for write_buf.        */
+	struct mutex write_sem;
+	/* The position currently reading from. */
+	struct list1_head *read_var1;
+	/* Extra variables for reading.         */
+	struct list1_head *read_var2;
+	/* The position currently writing to.   */
+	struct domain_info *write_var1;
+	/* The step for reading.                */
+	int read_step;
+	/* Buffer for reading.                  */
+	char *read_buf;
+	/* EOF flag for reading.                */
+	bool read_eof;
+	/* Extra variable for reading.          */
+	u8 read_bit;
+	/* Bytes available for reading.         */
+	int read_avail;
+	/* Size of read buffer.                 */
+	int readbuf_size;
+	/* Buffer for writing.                  */
+	char *write_buf;
+	/* Bytes available for writing.         */
+	int write_avail;
+	/* Size of write buffer.                */
+	int writebuf_size;
+};
+
+/* Check whether the domain has too many ACL entries to hold. */
+bool tmy_check_domain_quota(struct domain_info * const domain);
+/* Transactional sprintf() for policy dump. */
+bool tmy_io_printf(struct tmy_io_buffer *head, const char *fmt, ...)
+	__attribute__ ((format(printf, 2, 3)));
+/* Check whether the domainname is correct. */
+bool tmy_is_correct_domain(const unsigned char *domainname,
+			   const char *function);
+/* Check whether the token is correct. */
+bool tmy_is_correct_path(const char *filename, const s8 start_type,
+			 const s8 pattern_type, const s8 end_type,
+			 const char *function);
+/* Check whether the token can be a domainname. */
+bool tmy_is_domain_def(const unsigned char *buffer);
+/* Check whether the given filename matches the given pattern. */
+bool tmy_path_matches_pattern(const struct path_info *filename,
+			      const struct path_info *pattern);
+/* Read "alias" entry in exception policy. */
+bool tmy_read_alias_policy(struct tmy_io_buffer *head);
+/*
+ * Read "initialize_domain" and "no_initialize_domain" entry
+ * in exception policy.
+ */
+bool tmy_read_domain_initializer_policy(struct tmy_io_buffer *head);
+/* Read "keep_domain" and "no_keep_domain" entry in exception policy. */
+bool tmy_read_domain_keeper_policy(struct tmy_io_buffer *head);
+/* Read "file_pattern" entry in exception policy. */
+bool tmy_read_file_pattern(struct tmy_io_buffer *head);
+/* Read "allow_read" entry in exception policy. */
+bool tmy_read_globally_readable_policy(struct tmy_io_buffer *head);
+/* Read "deny_rewrite" entry in exception policy. */
+bool tmy_read_no_rewrite_policy(struct tmy_io_buffer *head);
+/* Write domain policy violation warning message to console? */
+bool tmy_verbose_mode(void);
+/* Convert double path operation to operation name. */
+const char *tmy_dp2keyword(const u8 operation);
+/* Get the last component of the given domainname. */
+const char *tmy_get_last_name(const struct domain_info *domain);
+/* Get warning message. */
+const char *tmy_get_msg(const bool is_enforce);
+/* Convert single path operation to operation name. */
+const char *tmy_sp2keyword(const u8 operation);
+/* Add an ACL entry to domain's ACL list. */
+int tmy_add_domain_acl(struct domain_info *domain, struct acl_info *acl);
+/* Delete an ACL entry from domain's ACL list. */
+int tmy_del_domain_acl(struct acl_info *acl);
+/* Delete a domain. */
+int tmy_delete_domain(char *data);
+/* Create "alias" entry in exception policy. */
+int tmy_write_alias_policy(char *data, const bool is_delete);
+/*
+ * Create "initialize_domain" and "no_initialize_domain" entry
+ * in exception policy.
+ */
+int tmy_write_domain_initializer_policy(char *data, const bool is_not,
+					const bool is_delete);
+/* Create "keep_domain" and "no_keep_domain" entry in exception policy. */
+int tmy_write_domain_keeper_policy(char *data, const bool is_not,
+				   const bool is_delete);
+/*
+ * Create "allow_read/write", "allow_execute", "allow_read", "allow_write",
+ * "allow_create", "allow_unlink", "allow_mkdir", "allow_rmdir",
+ * "allow_mkfifo", "allow_mksock", "allow_mkblock", "allow_mkchar",
+ * "allow_truncate", "allow_symlink", "allow_rewrite", "allow_rename" and
+ * "allow_link" entry in domain policy.
+ */
+int tmy_write_file_policy(char *data, struct domain_info *domain,
+			  const bool is_delete);
+/* Create "allow_read" entry in exception policy. */
+int tmy_write_globally_readable_policy(char *data, const bool is_delete);
+/* Create "deny_rewrite" entry in exception policy. */
+int tmy_write_no_rewrite_policy(char *data, const bool is_delete);
+/* Create "file_pattern" entry in exception policy. */
+int tmy_write_pattern_policy(char *data, const bool is_delete);
+/* Find a domain by the given name. */
+struct domain_info *tmy_find_domain(const char *domainname);
+/* Find or create a domain by the given name. */
+struct domain_info *tmy_find_or_assign_new_domain(const char *domainname,
+						  const u8 profile);
+/* Undelete a domain. */
+struct domain_info *tmy_undelete_domain(const char *domainname);
+/* Check mode for specified functionality. */
+unsigned int tmy_check_flags(const u8 index);
+/* Allocate memory for structures. */
+void *tmy_alloc_acl_element(const u8 acl_type);
+/* Fill in "struct path_info" members. */
+void tmy_fill_path_info(struct path_info *ptr);
+/* Run policy loader when /sbin/init starts. */
+void tmy_load_policy(const char *filename);
+/* Change "struct domain_info"->flags. */
+void tmy_set_domain_flag(struct domain_info *domain, const bool is_delete,
+			 const u8 flags);
+/* Update the policy change counter. */
+void tmy_update_counter(const unsigned char index);
+
+/* strcmp() for "struct path_info" structure. */
+static inline bool tmy_pathcmp(const struct path_info *a,
+			       const struct path_info *b)
+{
+	return a->hash != b->hash || strcmp(a->name, b->name);
+}
+
+/* Get type of an ACL entry. */
+static inline u8 tmy_acl_type1(struct acl_info *ptr)
+{
+	return (ptr->type & ~ACL_DELETED);
+}
+
+/* Get type of an ACL entry. */
+static inline u8 tmy_acl_type2(struct acl_info *ptr)
+{
+	return (ptr->type);
+}
+
+/* A linked list of domains. */
+extern struct list1_head domain_list;
+/* Has /sbin/init started? */
+extern bool sbin_init_started;
+/* The kernel's domain. */
+extern struct domain_info KERNEL_DOMAIN;
+/* Exclusive lock for updating domain policy. */
+extern struct mutex domain_acl_lock;
+
+#endif
--- /dev/null
+++ mm/security/tomoyo/common.c
@@ -0,0 +1,2149 @@
+/*
+ * security/tomoyo/common.c
+ *
+ * Common functions for TOMOYO.
+ *
+ * Copyright (C) 2005-2008  NTT DATA CORPORATION
+ *
+ * Version: 2.2.0-pre   2008/04/30
+ *
+ */
+
+#include <linux/uaccess.h>
+#include <linux/security.h>
+#include <linux/hardirq.h>
+#include "realpath.h"
+#include "common.h"
+#include "tomoyo.h"
+
+/* Set default specified by the kernel config. */
+#define MAX_ACCEPT_ENTRY 2048
+
+/* Has /sbin/init started? */
+bool sbin_init_started;
+
+/* String table for functionality that takes 4 modes. */
+static const char *mode_4[4] = {
+	"disabled", "learning", "permissive", "enforcing"
+};
+/* String table for functionality that takes 2 modes. */
+static const char *mode_2[4] = {
+	"disabled", "enabled", "enabled", "enabled"
+};
+
+/* Table for profile. */
+static struct {
+	const char *keyword;
+	unsigned int current_value;
+	const unsigned int max_value;
+} tmy_control_array[TMY_MAX_CONTROL_INDEX] = {
+	[TMY_TOMOYO_MAC_FOR_FILE]        = { "MAC_FOR_FILE",        0, 3 },
+	[TMY_TOMOYO_MAX_ACCEPT_ENTRY]
+	= { "MAX_ACCEPT_ENTRY",    MAX_ACCEPT_ENTRY, INT_MAX },
+	[TMY_TOMOYO_VERBOSE]             = { "TOMOYO_VERBOSE",      1, 1 },
+};
+
+/* Profile table. Memory is allocated as needed. */
+static struct profile {
+	unsigned int value[TMY_MAX_CONTROL_INDEX];
+	const struct path_info *comment;
+} *profile_ptr[MAX_PROFILES];
+
+/* Permit policy management by non-root user? */
+static bool manage_by_non_root;
+
+/* Utility functions. */
+
+/* Open operation for /sys/kernel/security/tomoyo/ interface. */
+static int tmy_open_control(const u8 type, struct file *file);
+/* Close /sys/kernel/security/tomoyo/ interface. */
+static int tmy_close_control(struct file *file);
+/* Read operation for /sys/kernel/security/tomoyo/ interface. */
+static int tmy_read_control(struct file *file, char __user *buffer,
+			    const int buffer_len);
+/* Write operation for /sys/kernel/security/tomoyo/ interface. */
+static int tmy_write_control(struct file *file, const char __user *buffer,
+			     const int buffer_len);
+
+/**
+ * is_byte_range - Check whether the string isa \ooo style octal value.
+ *
+ * @str: Pointer to the string.
+ *
+ * Returns true if @str is a \ooo style octal value, false otherwise.
+ */
+static bool is_byte_range(const char *str)
+{
+	return *str >= '0' && *str++ <= '3' &&
+		*str >= '0' && *str++ <= '7' &&
+		*str >= '0' && *str <= '7';
+}
+
+/**
+ * is_decimal - Check whether the character is a decimal character.
+ *
+ * @c: The character to check.
+ *
+ * Returns true if @c is a decimal character, false otherwise.
+ */
+static bool is_decimal(const char c)
+{
+	return (c >= '0' && c <= '9');
+}
+
+/**
+ * is_hexadecimal - Check whether the character is a hexadecimal character.
+ *
+ * @c: The character to check.
+ *
+ * Returns true if @c is a hexadecimal character, false otherwise.
+ */
+static bool is_hexadecimal(const char c)
+{
+	return ((c >= '0' && c <= '9') ||
+		(c >= 'A' && c <= 'F') ||
+		(c >= 'a' && c <= 'f'));
+}
+
+/**
+ * is_alphabet_char - Check whether the character is an alphabet.
+ *
+ * @c: The character to check.
+ *
+ * Returns true if @c is an alphabet character, false otherwise.
+ */
+static bool is_alphabet_char(const char c)
+{
+	return ((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'));
+}
+
+/**
+ * make_byte - Make byte value from three octal characters.
+ *
+ * @c1: The first character.
+ * @c2: The second character.
+ * @c3: The third character.
+ *
+ * Returns byte value.
+ */
+static u8 make_byte(const u8 c1, const u8 c2, const u8 c3)
+{
+	return ((c1 - '0') << 6) + ((c2 - '0') << 3) + (c3 - '0');
+}
+
+/**
+ * str_starts - Check whether the given string starts with the given keyword.
+ *
+ * @src:  Pointer to pointer to the string.
+ * @find: Pointer to the keyword.
+ *
+ * Returns true if @src starts with @find, false otherwise.
+ *
+ * The @src is updated to point the first character after the @find
+ * if @src starts with @find.
+ */
+static bool str_starts(char **src, const char *find)
+{
+	const int len = strlen(find);
+	char *tmp = *src;
+	if (strncmp(tmp, find, len))
+		return false;
+	tmp += len;
+	*src = tmp;
+	return true;
+}
+
+/**
+ * normalize_line - Format string.
+ *
+ * @buffer: The line to normalize.
+ *
+ * Leading and trailing whitespaces are removed.
+ * Multiple whitespaces are packed into single space.
+ *
+ * Returns nothing.
+ */
+static void normalize_line(unsigned char *buffer)
+{
+	unsigned char *sp = buffer;
+	unsigned char *dp = buffer;
+	bool first = true;
+	while (*sp && (*sp <= ' ' || *sp >= 127))
+		sp++;
+	while (*sp) {
+		if (!first)
+			*dp++ = ' ';
+		first = false;
+		while (*sp > ' ' && *sp < 127)
+			*dp++ = *sp++;
+		while (*sp && (*sp <= ' ' || *sp >= 127))
+			sp++;
+	}
+	*dp = '\0';
+}
+
+/**
+ * tmy_is_correct_path - Validate a pathname.
+ * @filename:     The pathname to check.
+ * @start_type:   Should the pathname start with '/'?
+ *                1 = must / -1 = must not / 0 = don't care
+ * @pattern_type: Can the pathname contain a wildcard?
+ *                1 = must / -1 = must not / 0 = don't care
+ * @end_type:     Should the pathname end with '/'?
+ *                1 = must / -1 = must not / 0 = don't care
+ * @function:     The name of function calling me.
+ *
+ * Check whether the given filename follows the naming rules.
+ * Returns true if @filename follows the naming rules, false otherwise.
+ */
+bool tmy_is_correct_path(const char *filename, const s8 start_type,
+			 const s8 pattern_type, const s8 end_type,
+			 const char *function)
+{
+	bool contains_pattern = false;
+	unsigned char c;
+	unsigned char d;
+	unsigned char e;
+	const char *original_filename = filename;
+	if (!filename)
+		goto out;
+	c = *filename;
+	if (start_type == 1) { /* Must start with '/' */
+		if (c != '/')
+			goto out;
+	} else if (start_type == -1) { /* Must not start with '/' */
+		if (c == '/')
+			goto out;
+	}
+	if (c)
+		c = *(strchr(filename, '\0') - 1);
+	if (end_type == 1) { /* Must end with '/' */
+		if (c != '/')
+			goto out;
+	} else if (end_type == -1) { /* Must not end with '/' */
+		if (c == '/')
+			goto out;
+	}
+	while ((c = *filename++) != '\0') {
+		if (c == '\\') {
+			switch ((c = *filename++)) {
+			case '\\':  /* "\\" */
+				continue;
+			case '$':   /* "\$" */
+			case '+':   /* "\+" */
+			case '?':   /* "\?" */
+			case '*':   /* "\*" */
+			case '@':   /* "\@" */
+			case 'x':   /* "\x" */
+			case 'X':   /* "\X" */
+			case 'a':   /* "\a" */
+			case 'A':   /* "\A" */
+			case '-':   /* "\-" */
+				if (pattern_type == -1)
+					break; /* Must not contain pattern */
+				contains_pattern = true;
+				continue;
+			case '0':   /* "\ooo" */
+			case '1':
+			case '2':
+			case '3':
+				d = *filename++;
+				if (d < '0' || d > '7')
+					break;
+				e = *filename++;
+				if (e < '0' || e > '7')
+					break;
+				c = make_byte(c, d, e);
+				if (c && (c <= ' ' || c >= 127))
+					continue; /* pattern is not \000 */
+			}
+			goto out;
+		} else if (c <= ' ' || c >= 127) {
+			goto out;
+		}
+	}
+	if (pattern_type == 1) { /* Must contain pattern */
+		if (!contains_pattern)
+			goto out;
+	}
+	return true;
+out:
+	printk(KERN_DEBUG "%s: Invalid pathname '%s'\n", function,
+	       original_filename);
+	return false;
+}
+
+/**
+ * tmy_is_correct_domain - Check whether the given domainname follows the naming rules.
+ * @domainname:   The domainname to check.
+ * @function:     The name of function calling me.
+ *
+ * Returns true if @domainname follows the naming rules, false otherwise.
+ */
+bool tmy_is_correct_domain(const unsigned char *domainname,
+			   const char *function)
+{
+	unsigned char c;
+	unsigned char d;
+	unsigned char e;
+	const char *org_domainname = domainname;
+	if (!domainname || strncmp(domainname, ROOT_NAME, ROOT_NAME_LEN))
+		goto out;
+	domainname += ROOT_NAME_LEN;
+	if (!*domainname)
+		return true;
+	do {
+		if (*domainname++ != ' ')
+			goto out;
+		if (*domainname++ != '/')
+			goto out;
+		while ((c = *domainname) != '\0' && c != ' ') {
+			domainname++;
+			if (c == '\\') {
+				c = *domainname++;
+				switch ((c)) {
+				case '\\':  /* "\\" */
+					continue;
+				case '0':   /* "\ooo" */
+				case '1':
+				case '2':
+				case '3':
+					d = *domainname++;
+					if (d < '0' || d > '7')
+						break;
+					e = *domainname++;
+					if (e < '0' || e > '7')
+						break;
+					c = make_byte(c, d, e);
+					if (c && (c <= ' ' || c >= 127))
+						/* pattern is not \000 */
+						continue;
+				}
+				goto out;
+			} else if (c < ' ' || c >= 127) {
+				goto out;
+			}
+		}
+	} while (*domainname);
+	return true;
+out:
+	printk(KERN_DEBUG "%s: Invalid domainname '%s'\n", function,
+	       org_domainname);
+	return false;
+}
+
+/**
+ * tmy_is_domain_def - Check whether the given token can be a domainname.
+ *
+ * @buffer: The token to check.
+ *
+ * Returns true if @buffer possibly be a domainname, false otherwise.
+ */
+bool tmy_is_domain_def(const unsigned char *buffer)
+{
+	return !strncmp(buffer, ROOT_NAME, ROOT_NAME_LEN);
+}
+
+/**
+ * tmy_find_domain - Find a domain by the given name.
+ *
+ * @domainname: The domainname to find.
+ *
+ * Returns pointer to "struct domain_info" if found, NULL otherwise.
+ */
+struct domain_info *tmy_find_domain(const char *domainname)
+{
+	struct domain_info *domain;
+	struct path_info name;
+	name.name = domainname;
+	tmy_fill_path_info(&name);
+	list1_for_each_entry(domain, &domain_list, list) {
+		if (!domain->is_deleted &&
+		    !tmy_pathcmp(&name, domain->domainname))
+			return domain;
+	}
+	return NULL;
+}
+
+/**
+ * path_depth - Evaluate the number of '/' in a string.
+ *
+ * @pathname: The string to evaluate.
+ *
+ * Returns path depth of the string.
+ *
+ * I score 2 for each of the '/' in the @pathname
+ * and score 1 if the @pathname ends with '/'.
+ */
+static int path_depth(const char *pathname)
+{
+	int i = 0;
+	if (pathname) {
+		char *ep = strchr(pathname, '\0');
+		if (pathname < ep--) {
+			if (*ep != '/')
+				i++;
+			while (pathname <= ep)
+				if (*ep-- == '/')
+					i += 2;
+		}
+	}
+	return i;
+}
+
+/**
+ * const_part_length - Evaluate the initial length without a pattern in a token.
+ *
+ * @filename: The string to evaluate.
+ *
+ * Returns the initial length without a pattern in @filename.
+ */
+static int const_part_length(const char *filename)
+{
+	char c;
+	int len = 0;
+	if (!filename)
+		return 0;
+	while ((c = *filename++) != '\0') {
+		if (c != '\\') {
+			len++;
+			continue;
+		}
+		c = *filename++;
+		switch (c) {
+		case '\\':  /* "\\" */
+			len += 2;
+			continue;
+		case '0':   /* "\ooo" */
+		case '1':
+		case '2':
+		case '3':
+			c = *filename++;
+			if (c < '0' || c > '7')
+				break;
+			c = *filename++;
+			if (c < '0' || c > '7')
+				break;
+			len += 4;
+			continue;
+		}
+		break;
+	}
+	return len;
+}
+
+/**
+ * tmy_fill_path_info - Fill in "struct path_info" members.
+ *
+ * @ptr: Pointer to "struct path_info" to fill in.
+ *
+ * The caller sets "struct path_info"->name.
+ */
+void tmy_fill_path_info(struct path_info *ptr)
+{
+	const char *name = ptr->name;
+	const int len = strlen(name);
+	ptr->total_len = len;
+	ptr->const_len = const_part_length(name);
+	ptr->is_dir = len && (name[len - 1] == '/');
+	ptr->is_patterned = (ptr->const_len < len);
+	ptr->hash = full_name_hash(name, len);
+	ptr->depth = path_depth(name);
+}
+
+/**
+ * file_matches_to_pattern2 - Pattern matching without '/' character
+ * and "\-" pattern.
+ *
+ * @filename:     The start of string to check.
+ * @filename_end: The end of string to check.
+ * @pattern:      The start of pattern to compare.
+ * @pattern_end:  The end of pattern to compare.
+ *
+ * Returns true if @filename matches @pattern, false otherwise.
+ */
+static bool file_matches_to_pattern2(const char *filename,
+				     const char *filename_end,
+				     const char *pattern,
+				     const char *pattern_end)
+{
+	while (filename < filename_end && pattern < pattern_end) {
+		char c;
+		if (*pattern != '\\') {
+			if (*filename++ != *pattern++)
+				return false;
+			continue;
+		}
+		c = *filename;
+		pattern++;
+		switch (*pattern) {
+			int i;
+			int j;
+		case '?':
+			if (c == '/') {
+				return false;
+			} else if (c == '\\') {
+				if (filename[1] == '\\')
+					filename++;
+				else if (is_byte_range(filename + 1))
+					filename += 3;
+				else
+					return false;
+			}
+			break;
+		case '\\':
+			if (c != '\\')
+				return false;
+			if (*++filename != '\\')
+				return false;
+			break;
+		case '+':
+			if (!is_decimal(c))
+				return false;
+			break;
+		case 'x':
+			if (!is_hexadecimal(c))
+				return false;
+			break;
+		case 'a':
+			if (!is_alphabet_char(c))
+				return false;
+			break;
+		case '0':
+		case '1':
+		case '2':
+		case '3':
+			if (c == '\\' && is_byte_range(filename + 1)
+			    && strncmp(filename + 1, pattern, 3) == 0) {
+				filename += 3;
+				pattern += 2;
+				break;
+			}
+			return false; /* Not matched. */
+		case '*':
+		case '@':
+			for (i = 0; i <= filename_end - filename; i++) {
+				if (file_matches_to_pattern2(filename + i,
+							     filename_end,
+							     pattern + 1,
+							     pattern_end))
+					return true;
+				c = filename[i];
+				if (c == '.' && *pattern == '@')
+					break;
+				if (c != '\\')
+					continue;
+				if (filename[i + 1] == '\\')
+					i++;
+				else if (is_byte_range(filename + i + 1))
+					i += 3;
+				else
+					break; /* Bad pattern. */
+			}
+			return false; /* Not matched. */
+		default:
+			j = 0;
+			c = *pattern;
+			if (c == '$') {
+				while (is_decimal(filename[j]))
+					j++;
+			} else if (c == 'X') {
+				while (is_hexadecimal(filename[j]))
+					j++;
+			} else if (c == 'A') {
+				while (is_alphabet_char(filename[j]))
+					j++;
+			}
+			for (i = 1; i <= j; i++) {
+				if (file_matches_to_pattern2(filename + i,
+							     filename_end,
+							     pattern + 1,
+							     pattern_end))
+					return true;
+			}
+			return false; /* Not matched or bad pattern. */
+		}
+		filename++;
+		pattern++;
+	}
+	while (*pattern == '\\' &&
+	       (*(pattern + 1) == '*' || *(pattern + 1) == '@'))
+		pattern += 2;
+	return (filename == filename_end && pattern == pattern_end);
+}
+
+/**
+ * file_matches_to_pattern - Pattern matching without without '/' character.
+ *
+ * @filename:     The start of string to check.
+ * @filename_end: The end of string to check.
+ * @pattern:      The start of pattern to compare.
+ * @pattern_end:  The end of pattern to compare.
+ *
+ * Returns true if @filename matches @pattern, false otherwise.
+ */
+static bool file_matches_to_pattern(const char *filename,
+				    const char *filename_end,
+				    const char *pattern,
+				    const char *pattern_end)
+{
+	const char *pattern_start = pattern;
+	bool first = true;
+	bool result;
+	while (pattern < pattern_end - 1) {
+		/* Split at "\-" pattern. */
+		if (*pattern++ != '\\' || *pattern++ != '-')
+			continue;
+		result = file_matches_to_pattern2(filename, filename_end,
+						  pattern_start, pattern - 2);
+		if (first)
+			result = !result;
+		if (result)
+			return false;
+		first = false;
+		pattern_start = pattern;
+	}
+	result = file_matches_to_pattern2(filename, filename_end,
+					  pattern_start, pattern_end);
+	return first ? result : !result;
+}
+
+/**
+ * tmy_path_matches_pattern - Check whether the given filename matches the given pattern.
+ * @filename: The filename to check.
+ * @pattern:  The pattern to compare.
+ *
+ * Returns true if matches, false otherwise.
+ *
+ * The following patterns are available.
+ *   \\     \ itself.
+ *   \ooo   Octal representation of a byte.
+ *   \*     More than or equals to 0 character other than '/'.
+ *   \@     More than or equals to 0 character other than '/' or '.'.
+ *   \?     1 byte character other than '/'.
+ *   \$     More than or equals to 1 decimal digit.
+ *   \+     1 decimal digit.
+ *   \X     More than or equals to 1 hexadecimal digit.
+ *   \x     1 hexadecimal digit.
+ *   \A     More than or equals to 1 alphabet character.
+ *   \a     1 alphabet character.
+ *   \-     Subtraction operator.
+ */
+bool tmy_path_matches_pattern(const struct path_info *filename,
+			      const struct path_info *pattern)
+{
+	/*
+	  if (!filename || !pattern)
+	  return false;
+	*/
+	const char *f = filename->name;
+	const char *p = pattern->name;
+	const int len = pattern->const_len;
+	/* If @pattern doesn't contain pattern, I can use strcmp(). */
+	if (!pattern->is_patterned)
+		return !tmy_pathcmp(filename, pattern);
+	/* Dont compare if the number of '/' differs. */
+	if (filename->depth != pattern->depth)
+		return false;
+	/* Compare the initial length without patterns. */
+	if (strncmp(f, p, len))
+		return false;
+	f += len;
+	p += len;
+	/* Main loop. Compare each directory component. */
+	while (*f && *p) {
+		const char *f_delimiter = strchr(f, '/');
+		const char *p_delimiter = strchr(p, '/');
+		if (!f_delimiter)
+			f_delimiter = strchr(f, '\0');
+		if (!p_delimiter)
+			p_delimiter = strchr(p, '\0');
+		if (!file_matches_to_pattern(f, f_delimiter, p, p_delimiter))
+			return false;
+		f = f_delimiter;
+		if (*f)
+			f++;
+		p = p_delimiter;
+		if (*p)
+			p++;
+	}
+	/* Ignore trailing "\*" and "\@" in @pattern. */
+	while (*p == '\\' &&
+	       (*(p + 1) == '*' || *(p + 1) == '@'))
+		p += 2;
+	return (!*f && !*p);
+}
+
+/**
+ * tmy_io_printf - Transactional printf() to "struct tmy_io_buffer" structure.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ * @fmt:  The printf()'s format string, followed by parameters.
+ *
+ * Returns true on success, false otherwise.
+ *
+ * The snprintf() will truncate, but tmy_io_printf() won't.
+ */
+bool tmy_io_printf(struct tmy_io_buffer *head, const char *fmt, ...)
+{
+	va_list args;
+	int len;
+	int pos = head->read_avail;
+	int size = head->readbuf_size - pos;
+	if (size <= 0)
+		return false;
+	va_start(args, fmt);
+	len = vsnprintf(head->read_buf + pos, size, fmt, args);
+	va_end(args);
+	if (pos + len >= head->readbuf_size)
+		return false;
+	head->read_avail += len;
+	return true;
+}
+
+/**
+ * tmy_get_exe - Get tmy_realpath() of current process.
+ *
+ * Returns the tmy_realpath() of current process on success, NULL otherwise.
+ *
+ * This function uses tmy_alloc(), so the caller must tmy_free()
+ * if this function didn't return NULL.
+ */
+static const char *tmy_get_exe(void)
+{
+	struct mm_struct *mm = current->mm;
+	struct vm_area_struct *vma;
+	const char *cp = NULL;
+	if (!mm)
+		return NULL;
+	down_read(&mm->mmap_sem);
+	for (vma = mm->mmap; vma; vma = vma->vm_next) {
+		if ((vma->vm_flags & VM_EXECUTABLE) && vma->vm_file) {
+			cp = tmy_realpath_from_dentry(vma->vm_file->f_dentry,
+						      vma->vm_file->f_vfsmnt);
+			break;
+		}
+	}
+	up_read(&mm->mmap_sem);
+	return cp;
+}
+
+/**
+ * tmy_get_msg - Get warning message.
+ *
+ * @is_enforce: Is it enforcing mode?
+ *
+ * Returns "ERROR" or "WARNING".
+ */
+const char *tmy_get_msg(const bool is_enforce)
+{
+	if (is_enforce)
+		return "ERROR";
+	else
+		return "WARNING";
+}
+
+/**
+ * tmy_check_flags_no_sleep_check - Check mode for specified functionality.
+ *
+ * @index: The functionality to check mode.
+ *
+ * Returns the mode of specified functionality.
+ */
+static unsigned int tmy_check_flags_no_sleep_check(const u8 index)
+{
+	const u8 profile = TMY_SECURITY->domain->profile;
+	return sbin_init_started && index < TMY_MAX_CONTROL_INDEX
+#if MAX_PROFILES != 256
+		&& profile < MAX_PROFILES
+#endif
+		&& profile_ptr[profile] ?
+		profile_ptr[profile]->value[index] : 0;
+}
+
+/**
+ * sleep_check - Check whether it is permitted to do operations that may sleep.
+ *
+ * Returns true if it is permitted to do operations that may sleep,
+ * false otherwise.
+ *
+ * TOMOYO Linux supports interactive enforcement that lets processes
+ * wait for the administrator's decision.
+ * All hooks but the one for tmy_may_autobind() are inserted where
+ * it is permitted to do operations that may sleep.
+ * Thus, this warning should not happen.
+ */
+static bool sleep_check(void)
+{
+	static u8 count = 20;
+	if (likely(!in_interrupt()))
+		return true;
+	if (count) {
+		count--;
+		printk(KERN_ERR "BUG: sleeping function called "
+		       "from invalid context.\n");
+		dump_stack();
+	}
+	return false;
+}
+
+/**
+ * tmy_check_flags - Check mode for specified functionality.
+ *
+ * @index: The functionality to check mode.
+ *
+ * Returns the mode of specified functionality.
+ */
+unsigned int tmy_check_flags(const u8 index)
+{
+	return sleep_check() ? tmy_check_flags_no_sleep_check(index) : 0;
+}
+
+/**
+ * tmy_verbose_mode - Check whether TOMOYO is verbose mode.
+ *
+ * Returns true if domain policy violation warning should be printed to
+ * console.
+ */
+bool tmy_verbose_mode(void)
+{
+	return tmy_check_flags(TMY_TOMOYO_VERBOSE) != 0;
+}
+
+/**
+ * tmy_check_domain_quota - Check for domain's quota.
+ *
+ * @domain: Pointer to "struct domain_info".
+ *
+ * Returns true if the domain is not exceeded quota, false otherwise.
+ */
+bool tmy_check_domain_quota(struct domain_info * const domain)
+{
+	unsigned int count = 0;
+	struct acl_info *ptr;
+	if (!domain)
+		return true;
+	list1_for_each_entry(ptr, &domain->acl_info_list, list) {
+		if (ptr->type & ACL_DELETED)
+			continue;
+		switch (tmy_acl_type2(ptr)) {
+			struct single_path_acl_record *acl1;
+			struct double_path_acl_record *acl2;
+			u16 perm;
+		case TYPE_SINGLE_PATH_ACL:
+			acl1 = container_of(ptr, struct single_path_acl_record,
+					    head);
+			perm = acl1->perm;
+			if (perm & (1 << TMY_TYPE_EXECUTE_ACL))
+				count++;
+			if (perm &
+			    ((1 << TMY_TYPE_READ_ACL) |
+			     (1 << TMY_TYPE_WRITE_ACL)))
+				count++;
+			if (perm & (1 << TMY_TYPE_CREATE_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_UNLINK_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_MKDIR_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_RMDIR_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_MKFIFO_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_MKSOCK_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_MKBLOCK_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_MKCHAR_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_TRUNCATE_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_SYMLINK_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_REWRITE_ACL))
+				count++;
+			break;
+		case TYPE_DOUBLE_PATH_ACL:
+			acl2 = container_of(ptr, struct double_path_acl_record,
+					    head);
+			perm = acl2->perm;
+			if (perm & (1 << TMY_TYPE_LINK_ACL))
+				count++;
+			if (perm & (1 << TMY_TYPE_RENAME_ACL))
+				count++;
+			break;
+		}
+	}
+	if (count < tmy_check_flags(TMY_TOMOYO_MAX_ACCEPT_ENTRY))
+		return true;
+	if (!domain->quota_warned) {
+		domain->quota_warned = true;
+		printk(KERN_WARNING "TOMOYO-WARNING: "
+		       "Domain '%s' has so many ACLs to hold. "
+		       "Stopped learning mode.\n", domain->domainname->name);
+	}
+	return false;
+}
+
+/**
+ * tmy_find_or_assign_new_profile - Create a new profile.
+ *
+ * @profile: Profile number to create.
+ *
+ * Returns pointer to "struct profile" on success, NULL otherwise.
+ */
+static struct profile *tmy_find_or_assign_new_profile(const unsigned int
+						      profile)
+{
+	static DEFINE_MUTEX(profile_lock);
+	struct profile *ptr = NULL;
+	mutex_lock(&profile_lock);
+	if (profile < MAX_PROFILES) {
+		ptr = profile_ptr[profile];
+		if (ptr)
+			goto ok;
+		ptr = tmy_alloc_element(sizeof(*ptr));
+		if (ptr) {
+			int i;
+			for (i = 0; i < TMY_MAX_CONTROL_INDEX; i++)
+				ptr->value[i]
+					= tmy_control_array[i].current_value;
+			mb(); /* Avoid out-of-order execution. */
+			profile_ptr[profile] = ptr;
+		}
+	}
+ok:
+	mutex_unlock(&profile_lock);
+	return ptr;
+}
+
+/**
+ * write_profile - Write profile table.
+ *
+ * @head: Pointer to "struct tmy_io_buffer"
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int write_profile(struct tmy_io_buffer *head)
+{
+	char *data = head->write_buf;
+	unsigned int i;
+	unsigned int value;
+	char *cp;
+	struct profile *profile;
+	i = simple_strtoul(data, &cp, 10);
+	if (data != cp) {
+		if (*cp != '-')
+			return -EINVAL;
+		data = cp + 1;
+	}
+	profile = tmy_find_or_assign_new_profile(i);
+	if (!profile)
+		return -EINVAL;
+	cp = strchr(data, '=');
+	if (!cp)
+		return -EINVAL;
+	*cp = '\0';
+	tmy_update_counter(TMY_UPDATES_COUNTER_PROFILE);
+	if (!strcmp(data, "COMMENT")) {
+		profile->comment = tmy_save_name(cp + 1);
+		return 0;
+	}
+	for (i = 0; i < TMY_MAX_CONTROL_INDEX; i++) {
+		if (strcmp(data, tmy_control_array[i].keyword))
+			continue;
+		if (sscanf(cp + 1, "%u", &value) != 1) {
+			int j;
+			const char **modes;
+			switch (i) {
+			case TMY_TOMOYO_VERBOSE:
+				modes = mode_2;
+				break;
+			default:
+				modes = mode_4;
+				break;
+			}
+			for (j = 0; j < 4; j++) {
+				if (strcmp(cp + 1, modes[j]))
+					continue;
+				value = j;
+				break;
+			}
+			if (j == 4)
+				return -EINVAL;
+		} else if (value > tmy_control_array[i].max_value) {
+			value = tmy_control_array[i].max_value;
+		}
+		profile->value[i] = value;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+/**
+ * read_profile - Read profile table.
+ *
+ * @head: Pointer to "struct tmy_io_buffer"
+ *
+ * Returns 0.
+ */
+static int read_profile(struct tmy_io_buffer *head)
+{
+	static const int total
+		= TMY_MAX_CONTROL_INDEX + 1;
+	int step;
+	if (head->read_eof)
+		return 0;
+	for (step = head->read_step; step < MAX_PROFILES * total; step++) {
+		const u8 index = step / total;
+		u8 type = step % total;
+		const struct profile *profile = profile_ptr[index];
+		head->read_step = step;
+		if (!profile)
+			continue;
+		if (!type) { /* Print profile' comment tag. */
+			if (!tmy_io_printf(head, "%u-COMMENT=%s\n",
+					   index, profile->comment ?
+					   profile->comment->name : ""))
+				break;
+			continue;
+		}
+		type--;
+		if (type >= TMY_MAX_CONTROL_INDEX) {
+		} else {
+			const unsigned int value = profile->value[type];
+			const char **modes = NULL;
+			const char *keyword = tmy_control_array[type].keyword;
+			switch (tmy_control_array[type].max_value) {
+			case 3:
+				modes = mode_4;
+				break;
+			case 1:
+				modes = mode_2;
+				break;
+			}
+			if (modes) {
+				if (!tmy_io_printf(head, "%u-%s=%s\n", index,
+						   keyword, modes[value]))
+					break;
+			} else {
+				if (!tmy_io_printf(head, "%u-%s=%u\n", index,
+						   keyword, value))
+					break;
+			}
+		}
+	}
+	if (step == MAX_PROFILES * total)
+		head->read_eof = true;
+	return 0;
+}
+
+/* Structure for policy manager. */
+struct policy_manager_entry {
+	struct list1_head list;
+	/* A path to program or a domainname. */
+	const struct path_info *manager;
+	bool is_domain;  /* True if manager is a domainname. */
+	bool is_deleted; /* True if this entry is deleted. */
+};
+
+/* The list for "struct policy_manager_entry". */
+static LIST1_HEAD(policy_manager_list);
+
+/**
+ * update_manager_entry - Add a manager entry.
+ *
+ * @manager:   The path to manager or the domainnamme.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int update_manager_entry(const char *manager, const bool is_delete)
+{
+	struct policy_manager_entry *new_entry;
+	struct policy_manager_entry *ptr;
+	static DEFINE_MUTEX(lock);
+	const struct path_info *saved_manager;
+	int error = -ENOMEM;
+	bool is_domain = false;
+	if (tmy_is_domain_def(manager)) {
+		if (!tmy_is_correct_domain(manager, __func__))
+			return -EINVAL;
+		is_domain = true;
+	} else {
+		if (!tmy_is_correct_path(manager, 1, -1, -1, __func__))
+			return -EINVAL;
+	}
+	saved_manager = tmy_save_name(manager);
+	if (!saved_manager)
+		return -ENOMEM;
+	mutex_lock(&lock);
+	list1_for_each_entry(ptr, &policy_manager_list, list) {
+		if (ptr->manager != saved_manager)
+			continue;
+		ptr->is_deleted = is_delete;
+		error = 0;
+		goto out;
+	}
+	if (is_delete) {
+		error = -ENOENT;
+		goto out;
+	}
+	new_entry = tmy_alloc_element(sizeof(*new_entry));
+	if (!new_entry)
+		goto out;
+	new_entry->manager = saved_manager;
+	new_entry->is_domain = is_domain;
+	list1_add_tail_mb(&new_entry->list, &policy_manager_list);
+	error = 0;
+out:
+	mutex_unlock(&lock);
+	if (!error)
+		tmy_update_counter(TMY_UPDATES_COUNTER_MANAGER);
+	return error;
+}
+
+/**
+ * write_manager_policy - Write manager policy.
+ *
+ * @head: Pointer to "struct tmy_io_buffer"
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int write_manager_policy(struct tmy_io_buffer *head)
+{
+	char *data = head->write_buf;
+	bool is_delete = str_starts(&data, KEYWORD_DELETE);
+	if (!strcmp(data, "manage_by_non_root")) {
+		manage_by_non_root = !is_delete;
+		return 0;
+	}
+	return update_manager_entry(data, is_delete);
+}
+
+/**
+ * read_manager_policy - Read manager policy.
+ *
+ * @head: Pointer to "struct tmy_io_buffer"
+ *
+ * Returns 0.
+ */
+static int read_manager_policy(struct tmy_io_buffer *head)
+{
+	struct list1_head *pos;
+	if (head->read_eof)
+		return 0;
+	list1_for_each_cookie(pos, head->read_var2, &policy_manager_list) {
+		struct policy_manager_entry *ptr;
+		ptr = list1_entry(pos, struct policy_manager_entry, list);
+		if (ptr->is_deleted)
+			continue;
+		if (!tmy_io_printf(head, "%s\n", ptr->manager->name))
+			return 0;
+	}
+	head->read_eof = true;
+	return 0;
+}
+
+/**
+ * is_policy_manager - Check whether the current process is a policy manager.
+ *
+ * Returns true if the current process is permitted to modify policy
+ * via /sys/kernel/security/tomoyo/ interface.
+ */
+static bool is_policy_manager(void)
+{
+	struct policy_manager_entry *ptr;
+	const char *exe;
+	const struct task_struct *task = current;
+	const struct path_info *domainname = TMY_SECURITY->domain->domainname;
+	bool found = false;
+	if (!sbin_init_started)
+		return true;
+	if (!manage_by_non_root && (task->uid || task->euid))
+		return false;
+	list1_for_each_entry(ptr, &policy_manager_list, list) {
+		if (!ptr->is_deleted && ptr->is_domain
+		    && !tmy_pathcmp(domainname, ptr->manager))
+			return true;
+	}
+	exe = tmy_get_exe();
+	if (!exe)
+		return false;
+	list1_for_each_entry(ptr, &policy_manager_list, list) {
+		if (!ptr->is_deleted && !ptr->is_domain
+		    && !strcmp(exe, ptr->manager->name)) {
+			found = true;
+			break;
+		}
+	}
+	if (!found) { /* Reduce error messages. */
+		static pid_t last_pid;
+		const pid_t pid = current->pid;
+		if (last_pid != pid) {
+			printk(KERN_WARNING "%s ( %s ) is not permitted to "
+			       "update policies.\n", domainname->name, exe);
+			last_pid = pid;
+		}
+	}
+	tmy_free(exe);
+	return found;
+}
+
+/**
+ * write_domain_policy - Write domain policy.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int write_domain_policy(struct tmy_io_buffer *head)
+{
+	char *data = head->write_buf;
+	struct domain_info *domain = head->write_var1;
+	bool is_delete = false;
+	bool is_select = false;
+	bool is_undelete = false;
+	unsigned int profile;
+	if (str_starts(&data, KEYWORD_DELETE))
+		is_delete = true;
+	else if (str_starts(&data, KEYWORD_SELECT))
+		is_select = true;
+	else if (str_starts(&data, KEYWORD_UNDELETE))
+		is_undelete = true;
+	if (tmy_is_domain_def(data)) {
+		domain = NULL;
+		if (is_delete)
+			tmy_delete_domain(data);
+		else if (is_select)
+			domain = tmy_find_domain(data);
+		else if (is_undelete)
+			domain = tmy_undelete_domain(data);
+		else
+			domain = tmy_find_or_assign_new_domain(data, 0);
+		head->write_var1 = domain;
+		tmy_update_counter(TMY_UPDATES_COUNTER_DOMAIN_POLICY);
+		return 0;
+	}
+	if (!domain)
+		return -EINVAL;
+
+	if (sscanf(data, KEYWORD_USE_PROFILE "%u", &profile) == 1
+	    && profile < MAX_PROFILES) {
+		if (profile_ptr[profile] || !sbin_init_started)
+			domain->profile = (u8) profile;
+		return 0;
+	}
+	if (!strcmp(data, KEYWORD_IGNORE_GLOBAL_ALLOW_READ)) {
+		tmy_set_domain_flag(domain, is_delete,
+				    DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ);
+		return 0;
+	}
+	return tmy_write_file_policy(data, domain, is_delete);
+}
+
+/**
+ * print_single_path_acl - Print a single path ACL entry.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ * @ptr:  Pointer to "struct single_path_acl_record".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool print_single_path_acl(struct tmy_io_buffer *head,
+				  struct single_path_acl_record *ptr)
+{
+	int pos;
+	u8 bit;
+	const char *atmark = "";
+	const char *filename;
+	const u16 perm = ptr->perm;
+	filename = ptr->filename->name;
+	for (bit = head->read_bit; bit < MAX_SINGLE_PATH_OPERATION; bit++) {
+		const char *msg;
+		if (!(perm & (1 << bit)))
+			continue;
+		/* Print "read/write" instead of "read" and "write". */
+		if ((bit == TMY_TYPE_READ_ACL || bit == TMY_TYPE_WRITE_ACL)
+		    && (perm & (1 << TMY_TYPE_READ_WRITE_ACL)))
+			continue;
+		msg = tmy_sp2keyword(bit);
+		pos = head->read_avail;
+		if (!tmy_io_printf(head, "allow_%s %s%s\n", msg,
+				   atmark, filename))
+			goto out;
+	}
+	head->read_bit = 0;
+	return true;
+out:
+	head->read_bit = bit;
+	head->read_avail = pos;
+	return false;
+}
+
+/**
+ * print_double_path_acl - Print a double path ACL entry.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ * @ptr:  Pointer to "struct double_path_acl_record".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool print_double_path_acl(struct tmy_io_buffer *head,
+				  struct double_path_acl_record *ptr)
+{
+	int pos;
+	const char *atmark1 = "";
+	const char *atmark2 = "";
+	const char *filename1;
+	const char *filename2;
+	const u8 perm = ptr->perm;
+	u8 bit;
+	filename1 = ptr->filename1->name;
+	filename2 = ptr->filename2->name;
+	for (bit = head->read_bit; bit < MAX_DOUBLE_PATH_OPERATION; bit++) {
+		const char *msg;
+		if (!(perm & (1 << bit)))
+			continue;
+		msg = tmy_dp2keyword(bit);
+		pos = head->read_avail;
+		if (!tmy_io_printf(head, "allow_%s %s%s %s%s\n", msg,
+				   atmark1, filename1, atmark2, filename2))
+			goto out;
+	}
+	head->read_bit = 0;
+	return true;
+out:
+	head->read_bit = bit;
+	head->read_avail = pos;
+	return false;
+}
+
+/**
+ * print_entry - Print an ACL entry.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ * @ptr:  Pointer to an ACL entry.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool print_entry(struct tmy_io_buffer *head, struct acl_info *ptr)
+{
+	const u8 acl_type = tmy_acl_type2(ptr);
+	if (acl_type & ACL_DELETED)
+		return true;
+	if (acl_type == TYPE_SINGLE_PATH_ACL) {
+		struct single_path_acl_record *acl
+			= container_of(ptr, struct single_path_acl_record,
+				       head);
+		return print_single_path_acl(head, acl);
+	}
+	if (acl_type == TYPE_DOUBLE_PATH_ACL) {
+		struct double_path_acl_record *acl
+			= container_of(ptr, struct double_path_acl_record,
+				       head);
+		return print_double_path_acl(head, acl);
+	}
+	BUG(); /* This must not happen. */
+	return false;
+}
+
+/**
+ * read_domain_policy - Read domain policy.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns 0.
+ */
+static int read_domain_policy(struct tmy_io_buffer *head)
+{
+	struct list1_head *dpos;
+	struct list1_head *apos;
+	if (head->read_eof)
+		return 0;
+	if (head->read_step == 0)
+		head->read_step = 1;
+	list1_for_each_cookie(dpos, head->read_var1, &domain_list) {
+		struct domain_info *domain;
+		const char *quota_exceeded = "";
+		const char *ignore_global_allow_read = "";
+		domain = list1_entry(dpos, struct domain_info, list);
+		if (head->read_step != 1)
+			goto acl_loop;
+		if (domain->is_deleted)
+			continue;
+		/* Print domainname and flags. */
+		if (domain->quota_warned)
+			quota_exceeded = "quota_exceeded\n";
+		if (domain->flags & DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ)
+			ignore_global_allow_read
+				= KEYWORD_IGNORE_GLOBAL_ALLOW_READ "\n";
+		if (!tmy_io_printf(head, "%s\n" KEYWORD_USE_PROFILE "%u\n"
+				   "%s%s\n", domain->domainname->name,
+				   domain->profile, quota_exceeded,
+				   ignore_global_allow_read))
+			return 0;
+		head->read_step = 2;
+acl_loop:
+		if (head->read_step == 3)
+			goto tail_mark;
+		/* Print ACL entries in the domain. */
+		list1_for_each_cookie(apos, head->read_var2,
+				      &domain->acl_info_list) {
+			struct acl_info *ptr
+				= list1_entry(apos, struct acl_info, list);
+			if (!print_entry(head, ptr))
+				return 0;
+		}
+		head->read_step = 3;
+tail_mark:
+		if (!tmy_io_printf(head, "\n"))
+			return 0;
+		head->read_step = 1;
+	}
+	head->read_eof = true;
+	return 0;
+}
+
+/**
+ * write_domain_profile - Assign profile for specified domain.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns 0 on success, -EINVAL otherwise.
+ *
+ * This is equivalent to doing
+ *
+ *     ( echo "select " $domainname; echo "use_profile " $profile ) |
+ *     /usr/lib/ccs/loadpolicy -d
+ */
+static int write_domain_profile(struct tmy_io_buffer *head)
+{
+	char *data = head->write_buf;
+	char *cp = strchr(data, ' ');
+	struct domain_info *domain;
+	unsigned int profile;
+	if (!cp)
+		return -EINVAL;
+	*cp = '\0';
+	domain = tmy_find_domain(cp + 1);
+	profile = simple_strtoul(data, NULL, 10);
+	if (domain && profile < MAX_PROFILES
+	    && (profile_ptr[profile] || !sbin_init_started))
+		domain->profile = (u8) profile;
+	tmy_update_counter(TMY_UPDATES_COUNTER_DOMAIN_POLICY);
+	return 0;
+}
+
+/**
+ * read_domain_profile - Read only domainname and profile.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns list of profile number and domainname pairs.
+ *
+ * This is equivalent to doing
+ *
+ *     grep -A 1 '^<kernel>' /sys/kernel/security/tomoyo/domain_policy |
+ *     awk ' { if ( domainname == "" ) { if ( $1 == "<kernel>" )
+ *     domainname = $0; } else if ( $1 == "use_profile" ) {
+ *     print $2 " " domainname; domainname = ""; } } ; '
+ */
+static int read_domain_profile(struct tmy_io_buffer *head)
+{
+	struct list1_head *pos;
+	if (head->read_eof)
+		return 0;
+	list1_for_each_cookie(pos, head->read_var1, &domain_list) {
+		struct domain_info *domain;
+		domain = list1_entry(pos, struct domain_info, list);
+		if (domain->is_deleted)
+			continue;
+		if (!tmy_io_printf(head, "%u %s\n", domain->profile,
+				   domain->domainname->name))
+			return 0;
+	}
+	head->read_eof = true;
+	return 0;
+}
+
+/**
+ * write_pid: Specify PID to obtain domainname.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns 0.
+ */
+static int write_pid(struct tmy_io_buffer *head)
+{
+	head->read_step = (int) simple_strtoul(head->write_buf, NULL, 10);
+	head->read_eof = false;
+	return 0;
+}
+
+/**
+ * read_pid - Get domainname of the specified PID.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns the domainname which the specified PID is in on success,
+ * empty string otherwise.
+ * The PID is specified by write_pid() so that the user can obtain
+ * using read()/write() interface rather than sysctl() interface.
+ */
+static int read_pid(struct tmy_io_buffer *head)
+{
+	if (head->read_avail == 0 && !head->read_eof) {
+		const int pid = head->read_step;
+		struct task_struct *p;
+		struct domain_info *domain = NULL;
+		/***** CRITICAL SECTION START *****/
+		read_lock(&tasklist_lock);
+		p = find_task_by_vpid(pid);
+		if (p)
+			domain =
+				((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 = true;
+	}
+	return 0;
+}
+
+/**
+ * write_exception_policy - Write exception policy.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int write_exception_policy(struct tmy_io_buffer *head)
+{
+	char *data = head->write_buf;
+	bool is_delete = str_starts(&data, KEYWORD_DELETE);
+	if (str_starts(&data, KEYWORD_KEEP_DOMAIN))
+		return tmy_write_domain_keeper_policy(data, false, is_delete);
+	if (str_starts(&data, KEYWORD_NO_KEEP_DOMAIN))
+		return tmy_write_domain_keeper_policy(data, true, is_delete);
+	if (str_starts(&data, KEYWORD_INITIALIZE_DOMAIN))
+		return tmy_write_domain_initializer_policy(data, false,
+							   is_delete);
+	if (str_starts(&data, KEYWORD_NO_INITIALIZE_DOMAIN))
+		return tmy_write_domain_initializer_policy(data, true,
+							   is_delete);
+	if (str_starts(&data, KEYWORD_ALIAS))
+		return tmy_write_alias_policy(data, is_delete);
+	if (str_starts(&data, KEYWORD_ALLOW_READ))
+		return tmy_write_globally_readable_policy(data, is_delete);
+	if (str_starts(&data, KEYWORD_FILE_PATTERN))
+		return tmy_write_pattern_policy(data, is_delete);
+	if (str_starts(&data, KEYWORD_DENY_REWRITE))
+		return tmy_write_no_rewrite_policy(data, is_delete);
+	return -EINVAL;
+}
+
+/**
+ * read_exception_policy - Read exception policy.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns 0 on success, -EINVAL otherwise.
+ */
+static int read_exception_policy(struct tmy_io_buffer *head)
+{
+	if (!head->read_eof) {
+		switch (head->read_step) {
+		case 0:
+			head->read_var2 = NULL;
+			head->read_step = 1;
+		case 1:
+			if (!tmy_read_domain_keeper_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 2;
+		case 2:
+			if (!tmy_read_globally_readable_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 3;
+		case 3:
+			head->read_var2 = NULL;
+			head->read_step = 4;
+		case 4:
+			if (!tmy_read_domain_initializer_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 5;
+		case 5:
+			if (!tmy_read_alias_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 6;
+		case 6:
+			head->read_var2 = NULL;
+			head->read_step = 7;
+		case 7:
+			if (!tmy_read_file_pattern(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 8;
+		case 8:
+			if (!tmy_read_no_rewrite_policy(head))
+				break;
+			head->read_var2 = NULL;
+			head->read_step = 9;
+		case 9:
+			head->read_eof = true;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+/* path to policy loader */
+static const char *tmy_loader = "/sbin/tomoyo-init";
+
+/**
+ * policy_loader_exists - Check whether /sbin/tomoyo-init exists.
+ *
+ * Returns true if /sbin/tomoyo-init exists, false otherwise.
+ */
+static bool policy_loader_exists(void)
+{
+	/*
+	 * Don't activate MAC if the policy loader doesn't exist.
+	 * If the initrd includes /sbin/init but real-root-dev has not
+	 * mounted on / yet, activating MAC will block the system since
+	 * policies are not loaded yet.
+	 * Thus, let do_execve() call this function everytime.
+	 */
+	struct nameidata nd;
+	if (path_lookup(tmy_loader, LOOKUP_FOLLOW, &nd)) {
+		printk(KERN_INFO "Not activating Mandatory Access Control now "
+		       "since %s doesn't exist.\n", tmy_loader);
+		return false;
+	}
+	path_put(&nd.path);
+	return true;
+}
+
+/**
+ * tmy_load_policy - Run external policy loader to load policy.
+ *
+ * @filename: The program about to start.
+ *
+ * This function checks whether @filename is /sbin/init , and if so
+ * invoke /sbin/tomoyo-init and wait for the termination of /sbin/tomoyo-init
+ * and then continues invocation of /sbin/init.
+ * /sbin/tomoyo-init reads policy files in /etc/tomoyo/ directory and
+ * writes to /sys/kernel/security/tomoyo/ interfaces.
+ *
+ * Returns nothing.
+ */
+void tmy_load_policy(const char *filename)
+{
+	char *argv[2];
+	char *envp[3];
+	if (sbin_init_started)
+		return;
+	/*
+	 * Check filename is /sbin/init or /sbin/tomoyo-start.
+	 * /sbin/tomoyo-start is a dummy filename in case where /sbin/init can't
+	 * be passed.
+	 * You can create /sbin/tomoyo-start by
+	 * "ln -s /bin/true /sbin/tomoyo-start".
+	 */
+	if (strcmp(filename, "/sbin/init") &&
+	    strcmp(filename, "/sbin/tomoyo-start"))
+		return;
+	if (!policy_loader_exists())
+		return;
+
+	printk(KERN_INFO "Calling %s to load policy. Please wait.\n",
+	       tmy_loader);
+	argv[0] = (char *) tmy_loader;
+	argv[1] = NULL;
+	envp[0] = "HOME=/";
+	envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
+	envp[2] = NULL;
+	call_usermodehelper(argv[0], argv, envp, 1);
+
+	printk(KERN_INFO "TOMOYO: 2.2.0-pre   2008/04/30\n");
+	printk(KERN_INFO "Mandatory Access Control activated.\n");
+	sbin_init_started = true;
+	{ /* Check all profiles currently assigned to domains are defined. */
+		struct domain_info *domain;
+		list1_for_each_entry(domain, &domain_list, list) {
+			const u8 profile = domain->profile;
+			if (profile_ptr[profile])
+				continue;
+			panic("Profile %u (used by '%s') not defined.\n",
+			      profile, domain->domainname->name);
+		}
+	}
+}
+
+/* Policy updates counter. */
+static unsigned int updates_counter[MAX_TMY_UPDATES_COUNTER];
+
+/* Policy updates counter lock. */
+static DEFINE_SPINLOCK(updates_counter_lock);
+
+/**
+ * tmy_update_counter - Increment policy change counter.
+ *
+ * @index: Type of policy.
+ *
+ * Returns nothing.
+ */
+void tmy_update_counter(const unsigned char index)
+{
+	/***** CRITICAL SECTION START *****/
+	spin_lock(&updates_counter_lock);
+	if (index < MAX_TMY_UPDATES_COUNTER)
+		updates_counter[index]++;
+	spin_unlock(&updates_counter_lock);
+	/***** CRITICAL SECTION END *****/
+}
+
+/**
+ * read_updates_counter - Check for policy change counter.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns how many times policy has changed since the previous check.
+ */
+static int read_updates_counter(struct tmy_io_buffer *head)
+{
+	unsigned int counter[MAX_TMY_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/domain_policy:    %10u\n"
+		      "/sys/kernel/security/tomoyo/exception_policy: %10u\n"
+		      "/sys/kernel/security/tomoyo/profile:          %10u\n"
+		      "/sys/kernel/security/tomoyo/manager:          %10u\n",
+		      counter[TMY_UPDATES_COUNTER_DOMAIN_POLICY],
+		      counter[TMY_UPDATES_COUNTER_EXCEPTION_POLICY],
+		      counter[TMY_UPDATES_COUNTER_PROFILE],
+		      counter[TMY_UPDATES_COUNTER_MANAGER]);
+	head->read_eof = true;
+	return 0;
+}
+
+/**
+ * read_version: Get version.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns version information.
+ */
+static int read_version(struct tmy_io_buffer *head)
+{
+	if (!head->read_eof) {
+		tmy_io_printf(head, "2.2.0-pre");
+		head->read_eof = true;
+	}
+	return 0;
+}
+
+/**
+ * read_memory_counter - Check for memory usage.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns memory usage.
+ */
+static int read_memory_counter(struct tmy_io_buffer *head)
+{
+	if (!head->read_eof) {
+		const int shared = tmy_get_memory_used_for_save_name();
+		const int private = tmy_get_memory_used_for_elements();
+		const int dynamic = tmy_get_memory_used_for_dynamic();
+		tmy_io_printf(head, "Shared:  %10u\nPrivate: %10u\n"
+			      "Dynamic: %10u\nTotal:   %10u\n",
+			      shared, private, dynamic,
+			      shared + private + dynamic);
+		head->read_eof = true;
+	}
+	return 0;
+}
+
+/**
+ * read_self_domain - Get the current process's domainname.
+ *
+ * @head: Pointer to "struct tmy_io_buffer".
+ *
+ * Returns the current process's domainname.
+ */
+static int read_self_domain(struct tmy_io_buffer *head)
+{
+	if (!head->read_eof) {
+		/*
+		 * TMY_SECURITY->domain->domainname != NULL
+		 * because every process belongs to a domain and
+		 * the domain's name cannot be NULL.
+		 */
+		tmy_io_printf(head, "%s",
+			      TMY_SECURITY->domain->domainname->name);
+		head->read_eof = true;
+	}
+	return 0;
+}
+
+/**
+ * tmy_open_control - open() for /sys/kernel/security/tomoyo/ interface.
+ *
+ * @type: Type of interface.
+ * @file: Pointer to "struct file".
+ *
+ * Associates policy handler and returns 0 on success, -ENOMEM otherwise.
+ */
+static int tmy_open_control(const u8 type, struct file *file)
+{
+	struct tmy_io_buffer *head = tmy_alloc(sizeof(*head));
+	if (!head)
+		return -ENOMEM;
+	mutex_init(&head->read_sem);
+	mutex_init(&head->write_sem);
+	switch (type) {
+	case TMY_DOMAINPOLICY:
+		/* /sys/kernel/security/tomoyo/domain_policy */
+		head->write = write_domain_policy;
+		head->read = read_domain_policy;
+		break;
+	case TMY_EXCEPTIONPOLICY:
+		/* /sys/kernel/security/tomoyo/exception_policy */
+		head->write = write_exception_policy;
+		head->read = read_exception_policy;
+		break;
+	case TMY_SELFDOMAIN:
+		/* /sys/kernel/security/tomoyo/self_domain */
+		head->read = read_self_domain;
+		break;
+	case TMY_DOMAIN_STATUS:
+		/* /sys/kernel/security/tomoyo/.domain_status */
+		head->write = write_domain_profile;
+		head->read = read_domain_profile;
+		break;
+	case TMY_PROCESS_STATUS:
+		/* /sys/kernel/security/tomoyo/.process_status */
+		head->write = write_pid;
+		head->read = read_pid;
+		break;
+	case TMY_VERSION:
+		/* /sys/kernel/security/tomoyo/version */
+		head->read = read_version;
+		head->readbuf_size = 128;
+		break;
+	case TMY_MEMINFO:
+		/* /sys/kernel/security/tomoyo/meminfo */
+		head->read = read_memory_counter;
+		head->readbuf_size = 128;
+		break;
+	case TMY_PROFILE:
+		/* /sys/kernel/security/tomoyo/profile */
+		head->write = write_profile;
+		head->read = read_profile;
+		break;
+	case TMY_MANAGER:
+		/* /sys/kernel/security/tomoyo/manager */
+		head->write = write_manager_policy;
+		head->read = read_manager_policy;
+		break;
+	case TMY_UPDATESCOUNTER:
+		/* /sys/kernel/security/tomoyo/.updates_counter */
+		head->read = read_updates_counter;
+		break;
+	}
+	if (head->read) {
+		if (!head->readbuf_size)
+			head->readbuf_size = 4096 * 2;
+		head->read_buf = tmy_alloc(head->readbuf_size);
+		if (!head->read_buf) {
+			tmy_free(head);
+			return -ENOMEM;
+		}
+	}
+	if (head->write) {
+		head->writebuf_size = 4096 * 2;
+		head->write_buf = tmy_alloc(head->writebuf_size);
+		if (!head->write_buf) {
+			tmy_free(head->read_buf);
+			tmy_free(head);
+			return -ENOMEM;
+		}
+	}
+	file->private_data = head;
+	/*
+	 * Call the handler now if the file is
+	 * /sys/kernel/security/tomoyo/self_domain
+	 * so that the user can use
+	 * cat < /sys/kernel/security/tomoyo/self_domain"
+	 * to know the current process's domainname.
+	 */
+	if (type == TMY_SELFDOMAIN)
+		tmy_read_control(file, NULL, 0);
+	return 0;
+}
+
+/**
+ * tmy_read_control - read() for /sys/kernel/security/tomoyo/ interface.
+ *
+ * @file:       Pointer to "struct file".
+ * @buffer:     Poiner to buffer to write to.
+ * @buffer_len: Size of @buffer.
+ *
+ * Returns bytes read on success, negative value otherwise.
+ */
+static int tmy_read_control(struct file *file, char __user *buffer,
+			    const int buffer_len)
+{
+	int len = 0;
+	struct tmy_io_buffer *head = file->private_data;
+	char *cp;
+	if (!head->read)
+		return -ENOSYS;
+	if (!access_ok(VERIFY_WRITE, buffer, buffer_len))
+		return -EFAULT;
+	if (mutex_lock_interruptible(&head->read_sem))
+		return -EINTR;
+	/* Call the policy handler. */
+	len = head->read(head);
+	if (len < 0)
+		goto out;
+	/* Write to buffer. */
+	len = head->read_avail;
+	if (len > buffer_len)
+		len = buffer_len;
+	if (!len)
+		goto out;
+	/* head->read_buf changes by some functions. */
+	cp = head->read_buf;
+	if (copy_to_user(buffer, cp, len)) {
+		len = -EFAULT;
+		goto out;
+	}
+	head->read_avail -= len;
+	memmove(cp, cp + len, head->read_avail);
+out:
+	mutex_unlock(&head->read_sem);
+	return len;
+}
+
+/**
+ * tmy_write_control - write() for /sys/kernel/security/tomoyo/ interface.
+ *
+ * @file:       Pointer to "struct file".
+ * @buffer:     Pointer to buffer to read from.
+ * @buffer_len: Size of @buffer.
+ *
+ * Returns @buffer_len on success, negative value otherwise.
+ */
+static int tmy_write_control(struct file *file, const char __user *buffer,
+			     const int buffer_len)
+{
+	struct tmy_io_buffer *head = file->private_data;
+	int error = buffer_len;
+	int avail_len = buffer_len;
+	char *cp0 = head->write_buf;
+	if (!head->write)
+		return -ENOSYS;
+	if (!access_ok(VERIFY_READ, buffer, buffer_len))
+		return -EFAULT;
+	/* Don't allow updating policies by non manager programs. */
+	if (head->write != write_pid && !is_policy_manager())
+		return -EPERM;
+	if (mutex_lock_interruptible(&head->write_sem))
+		return -EINTR;
+	/* Read a line and dispatch it to the policy handler. */
+	while (avail_len > 0) {
+		char c;
+		if (head->write_avail >= head->writebuf_size - 1) {
+			error = -ENOMEM;
+			break;
+		} else if (get_user(c, buffer)) {
+			error = -EFAULT;
+			break;
+		}
+		buffer++;
+		avail_len--;
+		cp0[head->write_avail++] = c;
+		if (c != '\n')
+			continue;
+		cp0[head->write_avail - 1] = '\0';
+		head->write_avail = 0;
+		normalize_line(cp0);
+		head->write(head);
+	}
+	mutex_unlock(&head->write_sem);
+	return error;
+}
+
+/**
+ * tmy_close_control - close() for /sys/kernel/security/tomoyo/ interface.
+ *
+ * @file: Pointer to "struct file".
+ *
+ * Releases memory and returns 0.
+ */
+static int tmy_close_control(struct file *file)
+{
+	struct tmy_io_buffer *head = file->private_data;
+	/* Release memory used for policy I/O. */
+	tmy_free(head->read_buf);
+	head->read_buf = NULL;
+	tmy_free(head->write_buf);
+	head->write_buf = NULL;
+	tmy_free(head);
+	head = NULL;
+	file->private_data = NULL;
+	return 0;
+}
+
+/**
+ * tmy_alloc_acl_element - Allocate permanent memory for ACL entry.
+ *
+ * @acl_type:  Type of ACL entry.
+ *
+ * Returns pointer to the ACL entry on success, NULL otherwise.
+ */
+void *tmy_alloc_acl_element(const u8 acl_type)
+{
+	int len;
+	struct acl_info *ptr;
+	switch (acl_type) {
+	case TYPE_SINGLE_PATH_ACL:
+		len = sizeof(struct single_path_acl_record);
+		break;
+	case TYPE_DOUBLE_PATH_ACL:
+		len = sizeof(struct double_path_acl_record);
+		break;
+	default:
+		return NULL;
+	}
+	ptr = tmy_alloc_element(len);
+	if (!ptr)
+		return NULL;
+	ptr->type = acl_type;
+	return ptr;
+}
+
+/**
+ * tmy_open - open() for /sys/kernel/security/tomoyo/ interface.
+ *
+ * @inode: Pointer to "struct inode".
+ * @file:  Pointer to "struct file".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tmy_open(struct inode *inode, struct file *file)
+{
+	return tmy_open_control(((u8 *) file->f_dentry->d_inode->i_private)
+				- ((u8 *) NULL), file);
+}
+
+/**
+ * tmy_release - close() for /sys/kernel/security/tomoyo/ interface.
+ *
+ * @inode: Pointer to "struct inode".
+ * @file:  Pointer to "struct file".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tmy_release(struct inode *inode, struct file *file)
+{
+	return tmy_close_control(file);
+}
+
+/**
+ * tmy_read - read() for /sys/kernel/security/tomoyo/ interface.
+ *
+ * @file:  Pointer to "struct file".
+ * @buf:   Pointer to buffer.
+ * @count: Size of @buf.
+ * @ppos:  Unused.
+ *
+ * Returns bytes read on success, negative value otherwise.
+ */
+static ssize_t tmy_read(struct file *file, char __user *buf, size_t count,
+			loff_t *ppos)
+{
+	return tmy_read_control(file, buf, count);
+}
+
+/**
+ * tmy_write - write() for /sys/kernel/security/tomoyo/ interface.
+ *
+ * @file:  Pointer to "struct file".
+ * @buf:   Pointer to buffer.
+ * @count: Size of @buf.
+ * @ppos:  Unused.
+ *
+ * Returns @count on success, negative value otherwise.
+ */
+static ssize_t tmy_write(struct file *file, const char __user *buf,
+			 size_t count, loff_t *ppos)
+{
+	return tmy_write_control(file, buf, count);
+}
+
+/* Operations for /sys/kernel/security/tomoyo/interface. */
+static struct file_operations tmy_operations = {
+	.open    = tmy_open,
+	.release = tmy_release,
+	.read    = tmy_read,
+	.write   = tmy_write,
+};
+
+/**
+ * create_entry - Create interface files under /sys/kernel/security/tomoyo/ directory.
+ *
+ * @name:   The name of the interface file.
+ * @mode:   The permission of the interface file.
+ * @parent: The parent directory.
+ * @key:    Type of interface.
+ *
+ * Returns nothing.
+ */
+static void __init create_entry(const char *name, const mode_t mode,
+				struct dentry *parent, const u8 key)
+{
+	securityfs_create_file(name, mode, parent, ((u8 *) NULL) + key,
+			       &tmy_operations);
+}
+
+/**
+ * tmy_initerface_init - Initialize /sys/kernel/security/tomoyo/ interface.
+ *
+ * Returns 0.
+ */
+static int __init tmy_initerface_init(void)
+{
+	struct dentry *tmy_dir;
+	tmy_dir = securityfs_create_dir("tomoyo", NULL);
+	create_entry("domain_policy",    0600, tmy_dir, TMY_DOMAINPOLICY);
+	create_entry("exception_policy", 0600, tmy_dir, TMY_EXCEPTIONPOLICY);
+	create_entry("self_domain",      0400, tmy_dir, TMY_SELFDOMAIN);
+	create_entry(".domain_status",   0600, tmy_dir, TMY_DOMAIN_STATUS);
+	create_entry(".process_status",  0600, tmy_dir, TMY_PROCESS_STATUS);
+	create_entry("meminfo",          0400, tmy_dir, TMY_MEMINFO);
+	create_entry("profile",          0600, tmy_dir, TMY_PROFILE);
+	create_entry("manager",          0600, tmy_dir, TMY_MANAGER);
+	create_entry(".updates_counter", 0400, tmy_dir, TMY_UPDATESCOUNTER);
+	create_entry("version",          0400, tmy_dir, TMY_VERSION);
+	return 0;
+}
+
+fs_initcall(tmy_initerface_init);

--


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

* [TOMOYO #8 (2.6.25-mm1) 5/7] Domain transition handler.
  2008-05-01  5:54 [TOMOYO #8 (2.6.25-mm1) 0/7] TOMOYO Linux Toshiharu Harada
                   ` (3 preceding siblings ...)
  2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 4/7] Common functions for TOMOYO Linux Toshiharu Harada
@ 2008-05-01  5:54 ` Toshiharu Harada
  2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 6/7] File operation restriction part Toshiharu Harada
  2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 7/7] Kconfig and Makefile Toshiharu Harada
  6 siblings, 0 replies; 14+ messages in thread
From: Toshiharu Harada @ 2008-05-01  5:54 UTC (permalink / raw)
  To: akpm, chrisw, viro
  Cc: linux-security-module, linux-fsdevel, linux-kernel,
	Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

This file controls domain creation/deletion/transition.

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

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

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

--


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

* [TOMOYO #8 (2.6.25-mm1) 6/7] File operation restriction part.
  2008-05-01  5:54 [TOMOYO #8 (2.6.25-mm1) 0/7] TOMOYO Linux Toshiharu Harada
                   ` (4 preceding siblings ...)
  2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 5/7] Domain transition handler Toshiharu Harada
@ 2008-05-01  5:54 ` Toshiharu Harada
  2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 7/7] Kconfig and Makefile Toshiharu Harada
  6 siblings, 0 replies; 14+ messages in thread
From: Toshiharu Harada @ 2008-05-01  5:54 UTC (permalink / raw)
  To: akpm, chrisw, viro
  Cc: linux-security-module, linux-fsdevel, linux-kernel,
	Kentaro Takeda, Tetsuo Handa, Toshiharu Harada

This file controls file related operations.

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

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

--


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

* [TOMOYO #8 (2.6.25-mm1) 7/7] Kconfig and Makefile
  2008-05-01  5:54 [TOMOYO #8 (2.6.25-mm1) 0/7] TOMOYO Linux Toshiharu Harada
                   ` (5 preceding siblings ...)
  2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 6/7] File operation restriction part Toshiharu Harada
@ 2008-05-01  5:54 ` Toshiharu Harada
  6 siblings, 0 replies; 14+ messages in thread
From: Toshiharu Harada @ 2008-05-01  5:54 UTC (permalink / raw)
  To: akpm, chrisw, viro
  Cc: linux-security-module, linux-fsdevel, linux-kernel,
	Kentaro Takeda, Tetsuo Handa

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

--- mm.orig/security/Kconfig
+++ mm/security/Kconfig
@@ -124,6 +124,7 @@ config SECURITY_DEFAULT_MMAP_MIN_ADDR
 
 source security/selinux/Kconfig
 source security/smack/Kconfig
+source security/tomoyo/Kconfig
 
 endmenu
 
--- mm.orig/security/Makefile
+++ mm/security/Makefile
@@ -5,6 +5,7 @@
 obj-$(CONFIG_KEYS)			+= keys/
 subdir-$(CONFIG_SECURITY_SELINUX)	+= selinux
 subdir-$(CONFIG_SECURITY_SMACK)		+= smack
+subdir-$(CONFIG_SECURITY_TOMOYO)        += tomoyo
 
 # if we don't select a security model, use the default capabilities
 ifneq ($(CONFIG_SECURITY),y)
@@ -16,6 +17,7 @@ obj-$(CONFIG_SECURITY)			+= security.o d
 # Must precede capability.o in order to stack properly.
 obj-$(CONFIG_SECURITY_SELINUX)		+= selinux/built-in.o
 obj-$(CONFIG_SECURITY_SMACK)		+= commoncap.o smack/built-in.o
+obj-$(CONFIG_SECURITY_TOMOYO)		+= tomoyo/built-in.o
 obj-$(CONFIG_SECURITY_CAPABILITIES)	+= commoncap.o capability.o
 obj-$(CONFIG_SECURITY_ROOTPLUG)		+= commoncap.o root_plug.o
 obj-$(CONFIG_CGROUP_DEVICE)		+= device_cgroup.o
--- /dev/null
+++ mm/security/tomoyo/Kconfig
@@ -0,0 +1,9 @@
+config SECURITY_TOMOYO
+	bool "TOMOYO Linux Support"
+	depends on SECURITY
+	default n
+	help
+	  This selects TOMOYO Linux, pathname-based access control.
+	  Required userspace tools and further information may be
+          found at <http://tomoyo.sourceforge.jp/>.
+	  If you are unsure how to answer this question, answer N.
--- /dev/null
+++ mm/security/tomoyo/Makefile
@@ -0,0 +1 @@
+obj-y = common.o realpath.o tomoyo.o domain.o file.o

--


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

* Re: [TOMOYO #8 (2.6.25-mm1) 1/7] Introduce new LSM hooks.
  2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 1/7] Introduce new LSM hooks Toshiharu Harada
@ 2008-05-01  8:01   ` Chris Wright
  2008-05-01 15:07     ` Tetsuo Handa
  2008-05-07 15:15     ` Tetsuo Handa
  0 siblings, 2 replies; 14+ messages in thread
From: Chris Wright @ 2008-05-01  8:01 UTC (permalink / raw)
  To: Toshiharu Harada
  Cc: akpm, chrisw, viro, linux-security-module, linux-fsdevel,
	linux-kernel, Kentaro Takeda, Tetsuo Handa

* Toshiharu Harada (haradats@nttdata.co.jp) wrote:
> This patch allows LSM to check permission using "struct vfsmount"
> without passing "struct vfsmount" to VFS helper functions.

This is simply duplicating many of the existing checks.
I don't see how this is an improvement.

> --- mm.orig/fs/namei.c
> +++ mm/fs/namei.c
> @@ -1595,6 +1595,9 @@ int vfs_create(struct inode *dir, struct
>  	error = security_inode_create(dir, dentry, mode);
>  	if (error)
>  		return error;
> +	error = security_path_create(dir, dentry, mode, nd);
> +	if (error)
> +		return error;

Pure duplication (of course adding nameidata, although I think you just
want path).

>  	DQUOT_INIT(dir);
>  	error = dir->i_op->create(dir, dentry, mode, nd);
>  	if (!error)
> @@ -1650,6 +1653,17 @@ int may_open(struct nameidata *nd, int a
	...
	error = vfs_permission(nd, acc_mode);
	if (error)
		return error;
	...
>  			return -EPERM;
>  
>  	/*
> +	 * security_inode_permission() called from vfs_permission()
> +	 * can't know that the file is going to be truncated when
> +	 * open(filename, O_WRONLY | O_TRUNC | O_APPEND) is used.
> +	 * So, this hook checks O_APPEND and O_TRUNC flags as well
> +	 * as MAY_READ and MAY_WRITE flags.
> +	 */
> +	error = security_path_open(nd->path.dentry, nd->path.mnt, flag);
> +	if (error)
> +		return error;

Also duplication.  And why the unique flag handling, you don't seem to
ever check?

> +
> +	/*
>  	 * Ensure there are no outstanding leases on the file.
>  	 */
>  	error = break_lease(inode, flag);
> @@ -2021,7 +2035,12 @@ fail:
>  }
>  EXPORT_SYMBOL_GPL(lookup_create);
>  
> -int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
> +/*
> + * These pre_vfs_*() functions are separated from vfs_*() functions so that
> + * LSM's security_path_*() functions can do DAC checks before MAC checks
> + * without duplicating may_create()/may_delete() functions.
> + */
> +int pre_vfs_mknod(struct inode *dir, struct dentry *dentry, int mode)
>  {
>  	int error = may_create(dir, dentry, NULL);
>  
> @@ -2033,6 +2052,14 @@ int vfs_mknod(struct inode *dir, struct 
>  
>  	if (!dir->i_op || !dir->i_op->mknod)
>  		return -EPERM;
> +	return 0;
> +}
> +
> +int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
> +{
> +	int error = pre_vfs_mknod(dir, dentry, mode);
> +	if (error)
> +		return error;

More duplication, you'll get a call chain like:

sys_mknod
  security_path_mknod
    pre_vfs_mknod
  vfs_mknod
   pre_vfs_mknod
   security_inode_mknod



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

* Re: [TOMOYO #8 (2.6.25-mm1) 1/7] Introduce new LSM hooks.
  2008-05-01  8:01   ` Chris Wright
@ 2008-05-01 15:07     ` Tetsuo Handa
  2008-05-01 15:17       ` Stephen Smalley
  2008-05-01 15:47       ` Chris Wright
  2008-05-07 15:15     ` Tetsuo Handa
  1 sibling, 2 replies; 14+ messages in thread
From: Tetsuo Handa @ 2008-05-01 15:07 UTC (permalink / raw)
  To: chrisw, viro
  Cc: akpm, linux-security-module, linux-fsdevel, linux-kernel,
	takedakn, haradats

Hello.

Chris Wright wrote:
> * Toshiharu Harada (haradats@nttdata.co.jp) wrote:
> > This patch allows LSM to check permission using "struct vfsmount"
> > without passing "struct vfsmount" to VFS helper functions.
> 
> This is simply duplicating many of the existing checks.
> I don't see how this is an improvement.
> 
> > --- mm.orig/fs/namei.c
> > +++ mm/fs/namei.c
> > @@ -1595,6 +1595,9 @@ int vfs_create(struct inode *dir, struct
> >  	error = security_inode_create(dir, dentry, mode);
> >  	if (error)
> >  		return error;
> > +	error = security_path_create(dir, dentry, mode, nd);
> > +	if (error)
> > +		return error;
> 
> Pure duplication (of course adding nameidata, although I think you just
> want path).

Stephen Smalley advised me to add parameter to existing hook rather than
introducing a new hook if the location of existing hook is appropriate.
OK. I'd like to add "struct nameidata" to security_inode_create()
rather than introducing security_path_create() in the next patch.

> 
> >  	DQUOT_INIT(dir);
> >  	error = dir->i_op->create(dir, dentry, mode, nd);
> >  	if (!error)
> > @@ -1650,6 +1653,17 @@ int may_open(struct nameidata *nd, int a
> 	...
> 	error = vfs_permission(nd, acc_mode);
> 	if (error)
> 		return error;
> 	...
> >  			return -EPERM;
> >  
> >  	/*
> > +	 * security_inode_permission() called from vfs_permission()
> > +	 * can't know that the file is going to be truncated when
> > +	 * open(filename, O_WRONLY | O_TRUNC | O_APPEND) is used.
> > +	 * So, this hook checks O_APPEND and O_TRUNC flags as well
> > +	 * as MAY_READ and MAY_WRITE flags.
> > +	 */
> > +	error = security_path_open(nd->path.dentry, nd->path.mnt, flag);
> > +	if (error)
> > +		return error;
> 
> Also duplication.  And why the unique flag handling, you don't seem to
> ever check?

The MAY_WRITE flag is not passed to security_inode_permission()
if security_inode_permission() is called from __open_namei_create().
Since TOMOYO Linux doesn't check MAY_READ/MAY_WRITE permissions for individual
read()/write() requests, the permission checks at open() time (i.e. may_open())
is the only chance to check MAY_WRITE flag. If I can't check MAY_WRITE flag
here, TOMOYO Linux can't control open(O_WRONLY | O_CREATE | O_EXCL).

Also, the O_TRUNC flag is not passed to security_inode_permission() because
vfs_permission() receives only MAY_READ/MAY_WRITE/MAY_APPEND flags, but
I have to check O_TRUNC flag before do_truncate().

So, I inserted a new hook here so that this hook can check all
MAY_READ/MAY_WRITE/O_APPEND/O_TRUNC flags together in a single place.

> 
> > +
> > +	/*
> >  	 * Ensure there are no outstanding leases on the file.
> >  	 */
> >  	error = break_lease(inode, flag);
> > @@ -2021,7 +2035,12 @@ fail:
> >  }
> >  EXPORT_SYMBOL_GPL(lookup_create);
> >  
> > -int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
> > +/*
> > + * These pre_vfs_*() functions are separated from vfs_*() functions so that
> > + * LSM's security_path_*() functions can do DAC checks before MAC checks
> > + * without duplicating may_create()/may_delete() functions.
> > + */
> > +int pre_vfs_mknod(struct inode *dir, struct dentry *dentry, int mode)
> >  {
> >  	int error = may_create(dir, dentry, NULL);
> >  
> > @@ -2033,6 +2052,14 @@ int vfs_mknod(struct inode *dir, struct 
> >  
> >  	if (!dir->i_op || !dir->i_op->mknod)
> >  		return -EPERM;
> > +	return 0;
> > +}
> > +
> > +int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
> > +{
> > +	int error = pre_vfs_mknod(dir, dentry, mode);
> > +	if (error)
> > +		return error;
> 
> More duplication, you'll get a call chain like:
> 
> sys_mknod
>   security_path_mknod
>     pre_vfs_mknod
>   vfs_mknod
>    pre_vfs_mknod
>    security_inode_mknod
> 
This is an inevitable duplication since I want to do conventional checks
(DAC checks and inode operation existence checks) before TOMOYO Linux's check.

By the way, Stephen Smalley thinks it is better to copy codes which is needed by
pre_vfs_*() (i.e. may_create()/may_delete()/check_sticky()) into
security/tomoyo/ directory and leave vfs_*() untouched rather than
extract pre_vfs_*() from vfs_*() and call pre_vfs_*() from vfs_*().
Question to Al Viro: Do you prefer "copying may_create()/may_delete()/
check_sticky() functions into security/tomoyo/ directory and leaving vfs_*()
untouched" to "extracting pre_vfs_*() and making them accessible from
security/tomoyo/ directory"? If you prefer copying, I'd like to copy them and
remove pre_vfs_*() in the next patch.

Regards.

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

* Re: [TOMOYO #8 (2.6.25-mm1) 1/7] Introduce new LSM hooks.
  2008-05-01 15:07     ` Tetsuo Handa
@ 2008-05-01 15:17       ` Stephen Smalley
  2008-05-01 15:47       ` Chris Wright
  1 sibling, 0 replies; 14+ messages in thread
From: Stephen Smalley @ 2008-05-01 15:17 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: chrisw, viro, akpm, linux-security-module, linux-fsdevel,
	linux-kernel, takedakn, haradats


On Fri, 2008-05-02 at 00:07 +0900, Tetsuo Handa wrote:
> Hello.
> 
> Chris Wright wrote:
> > * Toshiharu Harada (haradats@nttdata.co.jp) wrote:
> > > This patch allows LSM to check permission using "struct vfsmount"
> > > without passing "struct vfsmount" to VFS helper functions.
> > 
> > This is simply duplicating many of the existing checks.
> > I don't see how this is an improvement.
> > 
> > > --- mm.orig/fs/namei.c
> > > +++ mm/fs/namei.c
> > > @@ -1595,6 +1595,9 @@ int vfs_create(struct inode *dir, struct
> > >  	error = security_inode_create(dir, dentry, mode);
> > >  	if (error)
> > >  		return error;
> > > +	error = security_path_create(dir, dentry, mode, nd);
> > > +	if (error)
> > > +		return error;
> > 
> > Pure duplication (of course adding nameidata, although I think you just
> > want path).
> 
> Stephen Smalley advised me to add parameter to existing hook rather than
> introducing a new hook if the location of existing hook is appropriate.
> OK. I'd like to add "struct nameidata" to security_inode_create()
> rather than introducing security_path_create() in the next patch.

I had thought you were going to add new hooks in the callers, not try to
use the nameidata here.  And I wouldn't pass the nameidata there, just
what you actually need (e.g. the vfsmount).

> > 
> > >  	DQUOT_INIT(dir);
> > >  	error = dir->i_op->create(dir, dentry, mode, nd);
> > >  	if (!error)
> > > @@ -1650,6 +1653,17 @@ int may_open(struct nameidata *nd, int a
> > 	...
> > 	error = vfs_permission(nd, acc_mode);
> > 	if (error)
> > 		return error;
> > 	...
> > >  			return -EPERM;
> > >  
> > >  	/*
> > > +	 * security_inode_permission() called from vfs_permission()
> > > +	 * can't know that the file is going to be truncated when
> > > +	 * open(filename, O_WRONLY | O_TRUNC | O_APPEND) is used.
> > > +	 * So, this hook checks O_APPEND and O_TRUNC flags as well
> > > +	 * as MAY_READ and MAY_WRITE flags.
> > > +	 */
> > > +	error = security_path_open(nd->path.dentry, nd->path.mnt, flag);
> > > +	if (error)
> > > +		return error;
> > 
> > Also duplication.  And why the unique flag handling, you don't seem to
> > ever check?
> 
> The MAY_WRITE flag is not passed to security_inode_permission()
> if security_inode_permission() is called from __open_namei_create().
> Since TOMOYO Linux doesn't check MAY_READ/MAY_WRITE permissions for individual
> read()/write() requests, the permission checks at open() time (i.e. may_open())
> is the only chance to check MAY_WRITE flag. If I can't check MAY_WRITE flag
> here, TOMOYO Linux can't control open(O_WRONLY | O_CREATE | O_EXCL).

You can apply whatever checks you want from your hook in the create
path, right?

> > > @@ -2021,7 +2035,12 @@ fail:
> > >  }
> > >  EXPORT_SYMBOL_GPL(lookup_create);
> > >  
> > > -int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
> > > +/*
> > > + * These pre_vfs_*() functions are separated from vfs_*() functions so that
> > > + * LSM's security_path_*() functions can do DAC checks before MAC checks
> > > + * without duplicating may_create()/may_delete() functions.
> > > + */
> > > +int pre_vfs_mknod(struct inode *dir, struct dentry *dentry, int mode)
> > >  {
> > >  	int error = may_create(dir, dentry, NULL);
> > >  
> > > @@ -2033,6 +2052,14 @@ int vfs_mknod(struct inode *dir, struct 
> > >  
> > >  	if (!dir->i_op || !dir->i_op->mknod)
> > >  		return -EPERM;
> > > +	return 0;
> > > +}
> > > +
> > > +int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
> > > +{
> > > +	int error = pre_vfs_mknod(dir, dentry, mode);
> > > +	if (error)
> > > +		return error;
> > 
> > More duplication, you'll get a call chain like:
> > 
> > sys_mknod
> >   security_path_mknod
> >     pre_vfs_mknod
> >   vfs_mknod
> >    pre_vfs_mknod
> >    security_inode_mknod
> > 
> This is an inevitable duplication since I want to do conventional checks
> (DAC checks and inode operation existence checks) before TOMOYO Linux's check.
> 
> By the way, Stephen Smalley thinks it is better to copy codes which is needed by
> pre_vfs_*() (i.e. may_create()/may_delete()/check_sticky()) into
> security/tomoyo/ directory and leave vfs_*() untouched rather than
> extract pre_vfs_*() from vfs_*() and call pre_vfs_*() from vfs_*().
> Question to Al Viro: Do you prefer "copying may_create()/may_delete()/
> check_sticky() functions into security/tomoyo/ directory and leaving vfs_*()
> untouched" to "extracting pre_vfs_*() and making them accessible from
> security/tomoyo/ directory"? If you prefer copying, I'd like to copy them and
> remove pre_vfs_*() in the next patch.

I don't see how splitting out the parts that you are putting in the pre_
functions is especially useful.  Making the may_create()/may_delete()
helpers non-static might make sense.

-- 
Stephen Smalley
National Security Agency


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

* Re: [TOMOYO #8 (2.6.25-mm1) 1/7] Introduce new LSM hooks.
  2008-05-01 15:07     ` Tetsuo Handa
  2008-05-01 15:17       ` Stephen Smalley
@ 2008-05-01 15:47       ` Chris Wright
  2008-05-01 16:45         ` Stephen Smalley
  1 sibling, 1 reply; 14+ messages in thread
From: Chris Wright @ 2008-05-01 15:47 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: chrisw, viro, akpm, linux-security-module, linux-fsdevel,
	linux-kernel, takedakn, haradats

* Tetsuo Handa (penguin-kernel@I-love.SAKURA.ne.jp) wrote:
> The MAY_WRITE flag is not passed to security_inode_permission()
> if security_inode_permission() is called from __open_namei_create().
> Since TOMOYO Linux doesn't check MAY_READ/MAY_WRITE permissions for individual
> read()/write() requests, the permission checks at open() time (i.e. may_open())
> is the only chance to check MAY_WRITE flag. If I can't check MAY_WRITE flag
> here, TOMOYO Linux can't control open(O_WRONLY | O_CREATE | O_EXCL).
> 
> Also, the O_TRUNC flag is not passed to security_inode_permission() because
> vfs_permission() receives only MAY_READ/MAY_WRITE/MAY_APPEND flags, but
> I have to check O_TRUNC flag before do_truncate().
> 
> So, I inserted a new hook here so that this hook can check all
> MAY_READ/MAY_WRITE/O_APPEND/O_TRUNC flags together in a single place.

The reason I ask is because it doesn't check.  It only checks O_APPEND,
but that's already passed in (MAY_APPEND).  So AFAICT, it's only O_TRUNC
that you are trying to special case.  But in that case...all that is
being asked for is MAY_WRITE permission.  Anything else doesn't make
sense, especially since that's all you get from the truncate(2) path.

<snip>
> This is an inevitable duplication since I want to do conventional checks
> (DAC checks and inode operation existence checks) before TOMOYO Linux's check.
> 
> By the way, Stephen Smalley thinks it is better to copy codes which is needed by
> pre_vfs_*() (i.e. may_create()/may_delete()/check_sticky()) into
> security/tomoyo/ directory and leave vfs_*() untouched rather than
> extract pre_vfs_*() from vfs_*() and call pre_vfs_*() from vfs_*().

I'm not sure he means literally copy.  Typically we take existing
functionality and make it externally usable.

Also, all the changes you make that are not in vfs_* helpers won't get
picked up by NFS.

thanks,
-chris

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

* Re: [TOMOYO #8 (2.6.25-mm1) 1/7] Introduce new LSM hooks.
  2008-05-01 15:47       ` Chris Wright
@ 2008-05-01 16:45         ` Stephen Smalley
  0 siblings, 0 replies; 14+ messages in thread
From: Stephen Smalley @ 2008-05-01 16:45 UTC (permalink / raw)
  To: Chris Wright
  Cc: Tetsuo Handa, viro, akpm, linux-security-module, linux-fsdevel,
	linux-kernel, takedakn, haradats


On Thu, 2008-05-01 at 08:47 -0700, Chris Wright wrote:
> * Tetsuo Handa (penguin-kernel@I-love.SAKURA.ne.jp) wrote:
> > The MAY_WRITE flag is not passed to security_inode_permission()
> > if security_inode_permission() is called from __open_namei_create().
> > Since TOMOYO Linux doesn't check MAY_READ/MAY_WRITE permissions for individual
> > read()/write() requests, the permission checks at open() time (i.e. may_open())
> > is the only chance to check MAY_WRITE flag. If I can't check MAY_WRITE flag
> > here, TOMOYO Linux can't control open(O_WRONLY | O_CREATE | O_EXCL).
> > 
> > Also, the O_TRUNC flag is not passed to security_inode_permission() because
> > vfs_permission() receives only MAY_READ/MAY_WRITE/MAY_APPEND flags, but
> > I have to check O_TRUNC flag before do_truncate().
> > 
> > So, I inserted a new hook here so that this hook can check all
> > MAY_READ/MAY_WRITE/O_APPEND/O_TRUNC flags together in a single place.
> 
> The reason I ask is because it doesn't check.  It only checks O_APPEND,
> but that's already passed in (MAY_APPEND).  So AFAICT, it's only O_TRUNC
> that you are trying to special case.  But in that case...all that is
> being asked for is MAY_WRITE permission.  Anything else doesn't make
> sense, especially since that's all you get from the truncate(2) path.
> 
> <snip>
> > This is an inevitable duplication since I want to do conventional checks
> > (DAC checks and inode operation existence checks) before TOMOYO Linux's check.
> > 
> > By the way, Stephen Smalley thinks it is better to copy codes which is needed by
> > pre_vfs_*() (i.e. may_create()/may_delete()/check_sticky()) into
> > security/tomoyo/ directory and leave vfs_*() untouched rather than
> > extract pre_vfs_*() from vfs_*() and call pre_vfs_*() from vfs_*().
> 
> I'm not sure he means literally copy.  Typically we take existing
> functionality and make it externally usable.

I didn't think splitting pre functions out of all of the vfs helpers was
such a good idea.   Making may_create/delete() available might make
sense.  And for complex combinations of DAC logic, perhaps introducing
may_link(), may_rename(), etc. might make sense.  

> Also, all the changes you make that are not in vfs_* helpers won't get
> picked up by NFS.

-- 
Stephen Smalley
National Security Agency


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

* Re: [TOMOYO #8 (2.6.25-mm1) 1/7] Introduce new LSM hooks.
  2008-05-01  8:01   ` Chris Wright
  2008-05-01 15:07     ` Tetsuo Handa
@ 2008-05-07 15:15     ` Tetsuo Handa
  1 sibling, 0 replies; 14+ messages in thread
From: Tetsuo Handa @ 2008-05-07 15:15 UTC (permalink / raw)
  To: chrisw
  Cc: akpm, viro, linux-security-module, linux-fsdevel, linux-kernel,
	haradats, takedakn

Hello.

Chris Wright wrote:

> * Toshiharu Harada (haradats@nttdata.co.jp) wrote:
> > This patch allows LSM to check permission using "struct vfsmount"
> > without passing "struct vfsmount" to VFS helper functions.
> 
> This is simply duplicating many of the existing checks.
> I don't see how this is an improvement.
> 
> > --- mm.orig/fs/namei.c
> > +++ mm/fs/namei.c
> > @@ -1595,6 +1595,9 @@ int vfs_create(struct inode *dir, struct
> >  	error = security_inode_create(dir, dentry, mode);
> >  	if (error)
> >  		return error;
> > +	error = security_path_create(dir, dentry, mode, nd);
> > +	if (error)
> > +		return error;
> 
> Pure duplication (of course adding nameidata, although I think you just
> want path).

Right. I should have added "path" parameter rather than adding a new hook.
But to avoid touching vfs_*(), I moved security_path_create() to
the callers of vfs_create().

> >  	DQUOT_INIT(dir);
> >  	error = dir->i_op->create(dir, dentry, mode, nd);
> >  	if (!error)
> > @@ -1650,6 +1653,17 @@ int may_open(struct nameidata *nd, int a
> 	...
> 	error = vfs_permission(nd, acc_mode);
> 	if (error)
> 		return error;
> 	...
> >  			return -EPERM;
> >  
> >  	/*
> > +	 * security_inode_permission() called from vfs_permission()
> > +	 * can't know that the file is going to be truncated when
> > +	 * open(filename, O_WRONLY | O_TRUNC | O_APPEND) is used.
> > +	 * So, this hook checks O_APPEND and O_TRUNC flags as well
> > +	 * as MAY_READ and MAY_WRITE flags.
> > +	 */
> > +	error = security_path_open(nd->path.dentry, nd->path.mnt, flag);
> > +	if (error)
> > +		return error;
> 
> Also duplication.  And why the unique flag handling, you don't seem to
> ever check?

Sorry. I didn't know security_dentry_open() is available.
Now, security_path_open() and security_path_uselib() have gone away.
But it also turned out that security_dentry_open() is too late for
checking open(O_CREAT) and open(O_TRUNC) cases. Thus, somehow,
I want to check O_CREAT before entering vfs_create() and
check O_TRUNC before entering do_truncate() from may_open().

Regards.

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

end of thread, other threads:[~2008-05-07 15:16 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2008-05-01  5:54 [TOMOYO #8 (2.6.25-mm1) 0/7] TOMOYO Linux Toshiharu Harada
2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 1/7] Introduce new LSM hooks Toshiharu Harada
2008-05-01  8:01   ` Chris Wright
2008-05-01 15:07     ` Tetsuo Handa
2008-05-01 15:17       ` Stephen Smalley
2008-05-01 15:47       ` Chris Wright
2008-05-01 16:45         ` Stephen Smalley
2008-05-07 15:15     ` Tetsuo Handa
2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 2/7] LSM adapter functions Toshiharu Harada
2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 3/7] Memory and pathname management functions Toshiharu Harada
2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 4/7] Common functions for TOMOYO Linux Toshiharu Harada
2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 5/7] Domain transition handler Toshiharu Harada
2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 6/7] File operation restriction part Toshiharu Harada
2008-05-01  5:54 ` [TOMOYO #8 (2.6.25-mm1) 7/7] Kconfig and Makefile Toshiharu Harada

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).