Skip to contentSkip to conversation

TOMOYO Linux

PATCH In review Toshiharu Harada 4 participants 14 messages May 1, 2008 – May 7, 2008

Subsystems (6)
lore
Share & tools
lore
#0 lore raw

cover TOMOYO Linux

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
[email protected]
NTT DATA CORPORATION

--

#1 lore raw

PATCH 1/7 Introduce new LSM hooks.

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

---
 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(-)
fs/exec.c +10 -0
@@ -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))
fs/namei.c +119 -10

286 lines not on this page · raw message

fs/open.c +7 -0
@@ -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);
include/linux/fs.h +8 -0
@@ -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 *);
 
 /*
include/linux/security.h +178 -0

254 lines not on this page · raw message

net/unix/af_unix.c +4 -0
@@ -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;
security/dummy.c +73 -0

85 lines not on this page · raw message

security/security.c +63 -0

69 lines not on this page · raw message

--

#2 lore raw

Re: [TOMOYO #8 (2.6.25-mm1) 1/7] Introduce new LSM hooks.

Quoted 2 lines · * Toshiharu Harada ([email protected]) 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.

fs/namei.c int vfs_create(struct inode *dir, struct · line 1600

Quoted 9 lines
fs/namei.c +3 -0
@@ -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).

fs/namei.c int may_open(struct nameidata *nd, int a

Quoted 4 lines
fs/namei.c +0 -0
 	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;
	...

fs/namei.c int may_open(struct nameidata *nd, int a · line 1664

Quoted 12 lines
fs/namei.c +9 -0
 			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?

Quoted 21 lines
fs/namei.c +8 -1
+
+	/*
 	 * 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 
 

fs/namei.c int vfs_mknod(struct inode *dir, struct · line 2062

Quoted 10 lines
fs/namei.c +8 -0
 	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

maintainer
#3 lore raw

Re: [TOMOYO #8 (2.6.25-mm1) 1/7] Introduce new LSM hooks.

Hello.

Quoted 7 lines · Chris Wright
Quoted 2 lines · * Toshiharu Harada ([email protected]) 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.

Quoted 12 lines

fs/namei.c int vfs_create(struct inode *dir, struct · line 1600

Quoted 9 lines
fs/namei.c +3 -0
@@ -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.

Quoted 12 lines

fs/namei.c int may_open(struct nameidata *nd, int a

Quoted 5 lines
fs/namei.c +0 -0
 	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;
	...
Quoted 2 lines
fs/namei.c +0 -0
 			return -EPERM;
 
Quoted 13 lines

fs/namei.c int may_open(struct nameidata *nd, int a · line 1664

Quoted 10 lines
fs/namei.c +9 -0
 	/*
+	 * 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.

Quoted 35 lines
Quoted 22 lines
fs/namei.c +8 -1
+
+	/*
 	 * 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 
 

fs/namei.c int vfs_mknod(struct inode *dir, struct · line 2062

Quoted 10 lines
fs/namei.c +8 -0
 	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:

Quoted 7 lines

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.

#4 lore raw

Re: [TOMOYO #8 (2.6.25-mm1) 1/7] Introduce new LSM hooks.

Quoted 23 lines · Tetsuo Handa

Hello.

Quoted 7 lines · Chris Wright
Quoted 2 lines · * Toshiharu Harada ([email protected]) 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.

Quoted 12 lines

fs/namei.c int vfs_create(struct inode *dir, struct · line 1600

Quoted 9 lines
fs/namei.c +3 -0
@@ -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).

Quoted 4 lines

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

Quoted 26 lines
Quoted 12 lines

fs/namei.c int may_open(struct nameidata *nd, int a

Quoted 5 lines
fs/namei.c +0 -0
 	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;
	...
Quoted 2 lines
fs/namei.c +0 -0
 			return -EPERM;
 
Quoted 13 lines

fs/namei.c int may_open(struct nameidata *nd, int a · line 1664

Quoted 10 lines
fs/namei.c +9 -0
 	/*
+	 * 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?

Quoted 6 lines

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?

Quoted 39 lines
Quoted 29 lines
Quoted 16 lines
fs/namei.c +6 -1
@@ -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 
 

fs/namei.c int vfs_mknod(struct inode *dir, struct · line 2062

Quoted 10 lines
fs/namei.c +8 -0
 	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:

Quoted 7 lines

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.

Quoted 9 lines

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.

Signature · 2 lines
Stephen Smalley
National Security Agency
#5 lore raw

Re: [TOMOYO #8 (2.6.25-mm1) 1/7] Introduce new LSM hooks.

In reply to Tetsuo Handa
Quoted 7 lines · * Tetsuo Handa ([email protected]) 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).

Quoted 6 lines

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>

Quoted 7 lines

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

#6 lore raw

Re: [TOMOYO #8 (2.6.25-mm1) 1/7] Introduce new LSM hooks.

Quoted 25 lines · Chris Wright
Quoted 7 lines · * Tetsuo Handa ([email protected]) 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).

Quoted 6 lines

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>

Quoted 3 lines

This is an inevitable duplication since I want to do conventional checks (DAC checks and inode operation existence checks) before TOMOYO Linux's check.

Quoted 7 lines
Quoted 4 lines

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.

Quoted 2 lines

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

Signature · 2 lines
Stephen Smalley
National Security Agency
maintainer
#7 lore raw

Re: [TOMOYO #8 (2.6.25-mm1) 1/7] Introduce new LSM hooks.

In reply to Chris Wright

Hello.

Quoted 7 lines · Chris Wright
Quoted 2 lines · * Toshiharu Harada ([email protected]) 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.

Quoted 12 lines

fs/namei.c int vfs_create(struct inode *dir, struct · line 1600

Quoted 9 lines
fs/namei.c +3 -0
@@ -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().

Quoted 11 lines

fs/namei.c int may_open(struct nameidata *nd, int a

Quoted 4 lines
fs/namei.c +0 -0
 	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;
	...
Quoted 2 lines
fs/namei.c +0 -0
 			return -EPERM;
 
Quoted 13 lines

fs/namei.c int may_open(struct nameidata *nd, int a · line 1664

Quoted 10 lines
fs/namei.c +9 -0
 	/*
+	 * 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.

#8 lore raw

PATCH 2/7 LSM adapter functions.

In reply to Toshiharu Harada
---
 security/tomoyo/tomoyo.c |  279 +++++++++++++++++++++++++++++++++++++++++++++++
 security/tomoyo/tomoyo.h |  102 +++++++++++++++++
 2 files changed, 381 insertions(+)
security/tomoyo/tomoyo.h new file +102 -0
@@ -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
security/tomoyo/tomoyo.c new file +279 -0

279 lines not on this page · raw message

--

#9 lore raw

PATCH 3/7 Memory and pathname management functions.

In reply to 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.

---
 security/tomoyo/realpath.c |  655 +++++++++++++++++++++++++++++++++++++++++++++
 security/tomoyo/realpath.h |   62 ++++
 2 files changed, 717 insertions(+)
security/tomoyo/realpath.h new file +62 -0
@@ -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
security/tomoyo/realpath.c new file +655 -0

655 lines not on this page · raw message

--

#10 lore raw

PATCH 4/7 Common functions for TOMOYO Linux.

In reply to Toshiharu Harada

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

---
 include/linux/list.h     |   78 +
 security/tomoyo/common.c | 2149 +++++++++++++++++++++++++++++++++++++++++++++++
 security/tomoyo/common.h |  311 ++++++
 3 files changed, 2538 insertions(+)
include/linux/list.h +78 -0
@@ -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
security/tomoyo/common.h new file +311 -0

311 lines not on this page · raw message

security/tomoyo/common.c new file +2149 -0

2,149 lines not on this page · raw message

--

#11 lore raw

PATCH 5/7 Domain transition handler.

In reply to 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.

---
 security/tomoyo/domain.c |  856 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 856 insertions(+)
security/tomoyo/domain.c new file +856 -0
@@ -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;
+}

--

#12 lore raw

PATCH 6/7 File operation restriction part.

In reply to Toshiharu Harada

This file controls file related operations.

---
 security/tomoyo/file.c | 1130 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1130 insertions(+)
security/tomoyo/file.c new file +1130 -0
@@ -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;
+}

--

#13 lore raw

PATCH 7/7 Kconfig and Makefile

In reply to Toshiharu Harada
---
 security/Kconfig         |    1 +
 security/Makefile        |    2 ++
 security/tomoyo/Kconfig  |    9 +++++++++
 security/tomoyo/Makefile |    1 +
 4 files changed, 13 insertions(+)
security/Kconfig +1 -0
@@ -124,6 +124,7 @@ config SECURITY_DEFAULT_MMAP_MIN_ADDR
 
 source security/selinux/Kconfig
 source security/smack/Kconfig
+source security/tomoyo/Kconfig
 
 endmenu
 
security/Makefile +2 -0
@@ -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
security/tomoyo/Kconfig new file +9 -0
@@ -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.
security/tomoyo/Makefile new file +1 -0
@@ -0,0 +1 @@
+obj-y = common.o realpath.o tomoyo.o domain.o file.o

--