TOMOYO Linux
Subsystems (17)
Tetsuo Handa
maintainercover TOMOYO Linux
TOMOYO Linux is a name-based MAC extension (LSM module) for the Linux kernel.
This patchset is for mmotm 2008-11-19-02-19.
Andrew's comments and our answers on #12 are at
http://lkml.org/lkml/2008/11/6/409
http://lkml.org/lkml/2008/11/10/106
http://lkml.org/lkml/2008/11/10/107
http://lkml.org/lkml/2008/11/11/41
http://lkml.org/lkml/2008/11/17/18 .
How to try:
1. Compile kernel with CONFIG_SECURITY_TOMOYO=y.
2. 'make' and 'make install' userspace tools (ccs-tools) available at
http://osdn.dl.sourceforge.jp/tomoyo/30298/ .
3. Run /usr/lib/ccs/tomoyo_init_policy.sh .
4. Run following commands to set learning-mode as default. (This step is optional but recommended on your first try.)
# echo '<kernel>' > /etc/tomoyo/domain_policy.conf
# echo 'use_profile 1' >> /etc/tomoyo/domain_policy.conf
4. Reboot.
(If you compiled kernel with CONFIG_SECURITY_{SELINUX,SMACK}=y,
add 'security=tomoyo' to kernel's command line.)
5. Run /usr/sbin/ccs-editpolicy to browse and edit policy.LiveCD-based tutorials are available at
http://tomoyo.sourceforge.jp/en/1.6.x/1st-step/ubuntu8.04-live/
http://tomoyo.sourceforge.jp/en/1.6.x/1st-step/centos5-live/ . Though these tutorials use non-LSM version of TOMOYO, they are useful for you to know what TOMOYO is.
Regards.
--
Tetsuo Handa
maintainerPATCH 1/11 Introduce security_path_clear() hook.
To perform DAC performed in vfs_foo() before MAC, we let security_path_foo() save a result into our own hash table and return 0, and let security_inode_foo() return the saved result. Since security_inode_foo() is not always called after security_path_foo(), we need security_path_clear() to clear the hash table.
---
fs/namei.c | 9 +++++++++ fs/open.c | 2 ++ include/linux/security.h | 12 ++++++++++++ net/unix/af_unix.c | 1 + security/capability.c | 5 +++++ security/security.c | 6 ++++++ 6 files changed, 35 insertions(+)
fs/namei.c +9 -0
@@ -1566,6 +1566,7 @@ int may_open(struct nameidata *nd, int a error =do_truncate (dentry,0 ,ATTR_MTIME |ATTR_CTIME |ATTR_OPEN ,NULL ); +security_path_clear (); }put_write_access (inode);if (error)
@@ -1594,6 +1595,7 @@ static int __open_namei_create(struct naif (error) goto out_unlock; error =vfs_create (dir->d_inode, path->dentry, mode, nd); +security_path_clear (); out_unlock:mutex_unlock (&dir->d_inode->i_mutex);dput (nd->path.dentry);
@@ -2022,6 +2024,7 @@ asmlinkage long sys_mknodat(int dfd, con error =vfs_mknod (nd.path.dentry->d_inode,dentry,mode,0 );break ; } +security_path_clear (); out_drop_write:mnt_drop_write (nd.path.mnt); out_dput:
@@ -2086,6 +2089,7 @@ asmlinkage long sys_mkdirat(int dfd, conif (error) goto out_drop_write; error =vfs_mkdir (nd.path.dentry->d_inode, dentry, mode); +security_path_clear (); out_drop_write:mnt_drop_write (nd.path.mnt); out_dput:
@@ -2200,6 +2204,7 @@ static long do_rmdir(int dfd, const charif (error) goto exit4; error =vfs_rmdir (nd.path.dentry->d_inode, dentry); +security_path_clear (); exit4:mnt_drop_write (nd.path.mnt); exit3:
@@ -2289,6 +2294,7 @@ static long do_unlinkat(int dfd, const cif (error) goto exit3; error =vfs_unlink (nd.path.dentry->d_inode, dentry); +security_path_clear (); exit3:mnt_drop_write (nd.path.mnt); exit2:
@@ -2374,6 +2380,7 @@ asmlinkage long sys_symlinkat(const charif (error) goto out_drop_write; error =vfs_symlink (nd.path.dentry->d_inode, dentry, from); +security_path_clear (); out_drop_write:mnt_drop_write (nd.path.mnt); out_dput:
@@ -2475,6 +2482,7 @@ asmlinkage long sys_linkat(int olddfd, cif (error) goto out_drop_write; error =vfs_link (old_path.dentry, nd.path.dentry->d_inode, new_dentry); +security_path_clear (); out_drop_write:mnt_drop_write (nd.path.mnt); out_dput:
@@ -2715,6 +2723,7 @@ asmlinkage long sys_renameat(int olddfd, goto exit6; error =vfs_rename (old_dir->d_inode, old_dentry, new_dir->d_inode, new_dentry); +security_path_clear (); exit6:mnt_drop_write (oldnd.path.mnt); exit5:
fs/open.c +2 -0
@@ -277,6 +277,7 @@ static long do_sys_truncate(const char _if (!error) {DQUOT_INIT (inode); error =do_truncate (path.dentry, length,0 ,NULL ); +security_path_clear (); } put_write_and_out:
@@ -335,6 +336,7 @@ static long do_sys_ftruncate(unsigned in ATTR_MTIME|ATTR_CTIME, file);if (!error) error =do_truncate (dentry, length,ATTR_MTIME |ATTR_CTIME , file); +security_path_clear (); out_putf:fput (file); out:
include/linux/security.h +12 -0
@@ -527,6 +527,12 @@ static inline void security_free_mnt_opt* @inode contains a pointer to the inode. * @secid contains a pointer to the location where result will be saved. * In case of failure, @secid will be set to zero. +* @path_clear: +* Clear error code stored by security_path_*() in case +* security_inode_*() was not called when DAC returned an error. +* This hook allows LSM modules which use security_path_*() defer +* returning LSM's error code till security_inode_*() is called so that +* DAC's error (if any) is returned to the caller instead of LSM's error. * * Security hooks for file operations *
@@ -1402,6 +1408,7 @@ struct security_operations { struct dentry *new_dentry);int (*path_rename) (struct path *old_dir,struct dentry *old_dentry,struct path *new_dir,struct dentry *new_dentry); +void (*path_clear) (void );#endif int (*inode_alloc_security) (struct inode *inode);
@@ -2792,6 +2799,7 @@ int security_path_link(struct dentry *ol struct dentry *new_dentry);int security_path_rename (struct path *old_dir,struct dentry *old_dentry,struct path *new_dir,struct dentry *new_dentry); +void security_path_clear (void );#else /* CONFIG_SECURITY_PATH */ static inline int security_path_unlink (struct path *dir,struct dentry *dentry) {
@@ -2842,6 +2850,10 @@ static inline int security_path_rename(s {return 0 ; } + +static inline void security_path_clear (void ) +{ +}#endif /* CONFIG_SECURITY_PATH */ #ifdef CONFIG_KEYS
net/unix/af_unix.c +1 -0
@@ -832,6 +832,7 @@ static int unix_bind(struct socket *sockif (err) goto out_mknod_drop_write; err =vfs_mknod (nd.path.dentry->d_inode, dentry, mode,0 ); +security_path_clear (); out_mknod_drop_write:mnt_drop_write (nd.path.mnt);if (err)
security/capability.c +5 -0
@@ -308,6 +308,10 @@ static int cap_path_truncate(struct path {return 0 ; } + +static void cap_path_clear (void ) +{ +}#endif static int cap_file_permission (struct file *file,int mask)
@@ -939,6 +943,7 @@ void security_fixup_ops(struct security_set_to_cap_if_null (ops, path_link);set_to_cap_if_null (ops, path_rename);set_to_cap_if_null (ops, path_truncate); +set_to_cap_if_null (ops, path_clear);#endif set_to_cap_if_null (ops, file_permission);set_to_cap_if_null (ops, file_alloc_security);
security/security.c +6 -0
@@ -419,6 +419,12 @@ int security_path_truncate(struct path *return 0 ; return security_ops->path_truncate(path, length, time_attrs, filp); } + +void security_path_clear (void ) +{ +return security_ops->path_clear (); +} +EXPORT_SYMBOL (security_path_clear);#endif int security_inode_create (struct inode *dir,struct dentry *dentry,int mode)
Re: [TOMOYO #13 (mmotm 2008-11-19-02-19) 01/11] Introduce security_path_clear() hook.
Quoted 5 lines · Tetsuo Handa
plain text document attachment (introduce-security_path_clear.patch) To perform DAC performed in
vfs_foo()before MAC, we letsecurity_path_foo()save a result into our own hash table and return 0, and letsecurity_inode_foo()return the saved result. Sincesecurity_inode_foo()is not always called aftersecurity_path_foo(), we needsecurity_path_clear()to clear the hash table.
This seems very fragile and unmaintainable to me. The fact that you even need a security_path_clear() hook suggests that something is wrong with the other security_path* hooks. I'd suggest that you explicitly pass the result of the security_path* hooks down to the security_inode* hooks instead. What do others think?
Signature · 2 lines
Stephen Smalley National Security Agency
Tetsuo Handa
maintainerRe: [TOMOYO #13 (mmotm 2008-11-19-02-19) 01/11] Introduce security_path_clear() hook.
Hello.
Quoted 7 lines · Stephen Smalley
Quoted 5 lines · Tetsuo Handa
plain text document attachment (introduce-security_path_clear.patch) To perform DAC performed in
vfs_foo()before MAC, we letsecurity_path_foo()save a result into our own hash table and return 0, and letsecurity_inode_foo()return the saved result. Sincesecurity_inode_foo()is not always called aftersecurity_path_foo(), we needsecurity_path_clear()to clear the hash table.
Quoted 5 lines
This seems very fragile and unmaintainable to me. The fact that you even need a
security_path_clear()hook suggests that something is wrong with the other security_path* hooks. I'd suggest that you explicitly pass the result of the security_path* hooks down to the security_inode* hooks instead. What do others think?
You are recommending us to pass variables required for security_inode_*() via stack memory rather than private hash, aren't you?
I think there are two problems.
One is that the variable passed via stack memory won't be used by SELinux and SMACK and "CONFIG_SECURITY=n kernels", which will be a waste of stack memory.
The other one is that TOMOYO will need another variable for telling how the security_inode_*() are called. Passing the variable via stack memory requires modification of all vfs_*() calls, but TOMOYO doesn't check requests issued by (e.g.) stackable filesystems.
By the way, this security_path_clear() is intended to be able to return DAC's error code in priority to MAC's error code, but there are two problems for TOMOYO.
One is that pathnames which will be later denied by DAC are appended by TOMOYO's learning mode (i.e. garbage entries appears in the learned policy). The other is that warning messages on pathnames which will be later denied by DAC are generated by TOMOYO's enforcing mode.
Thus, it will be preferable for TOMOYO to "do MAC checks after DAC checks" rather than to "return DAC's error in priority to MAC's error while doing MAC checks before DAC checks".
To do so, "security_path_*() should be replaced by security_path_set(vfsmount)" and "let security_inode_*() do MAC checks using the result of security_path_set()" and "let security_path_clear() clear the result of security_path_set() in case security_inode_*() was not called".
So, I think storing the pathname of "struct vfsmount" in the form of "char *" into private hash at security_path_set() and clearing the private hash at security_path_clear() should be most preferable.
Regards.
Re: [TOMOYO #13 (mmotm 2008-11-19-02-19) 01/11] Introduce security_path_clear() hook.
Quoted 10 lines · Tetsuo Handa
Hello.
Quoted 7 lines · Stephen Smalley
Quoted 5 lines · Tetsuo Handa
plain text document attachment (introduce-security_path_clear.patch) To perform DAC performed in
vfs_foo()before MAC, we letsecurity_path_foo()save a result into our own hash table and return 0, and letsecurity_inode_foo()return the saved result. Sincesecurity_inode_foo()is not always called aftersecurity_path_foo(), we needsecurity_path_clear()to clear the hash table.
Quoted 8 lines
Quoted 5 lines
This seems very fragile and unmaintainable to me. The fact that you even need a
security_path_clear()hook suggests that something is wrong with the other security_path* hooks. I'd suggest that you explicitly pass the result of the security_path* hooks down to the security_inode* hooks instead. What do others think?You are recommending us to pass variables required for security_inode_*() via stack memory rather than private hash, aren't you?
To be precise, I was recommending passing the return value of security_path* down to security_inode* explicitly rather than doing it implicitly as you presently do. Thereby making the actual control flow and relationship between the security_path* and security_inode* hooks evident. However, I guess that is moot given your statements below.
Quoted 4 lines
I think there are two problems.
One is that the variable passed via stack memory won't be used by SELinux and SMACK and "
CONFIG_SECURITY=nkernels", which will be a waste of stack memory.
I'm more concerned with the hook interface being understandable and maintainable.
Quoted 4 lines
The other one is that TOMOYO will need another variable for telling how the security_inode_*() are called. Passing the variable via stack memory requires modification of all vfs_*() calls, but TOMOYO doesn't check requests issued by (e.g.) stackable filesystems.
I'm not clear on why that requires a separate argument; if the caller is passing in the access decision result as an input, then certain callers (e.g. stackable filesystems) can always pass 0 (success).
Quoted 11 lines
By the way, this
security_path_clear()is intended to be able to return DAC's error code in priority to MAC's error code, but there are two problems for TOMOYO.
One is that pathnames which will be later denied by DAC are appended by TOMOYO's learning mode (i.e. garbage entries appears in the learned policy). The other is that warning messages on pathnames which will be later denied by DAC are generated by TOMOYO's enforcing mode.Thus, it will be preferable for TOMOYO to "do MAC checks after DAC checks" rather than to "return DAC's error in priority to MAC's error while doing MAC checks before DAC checks".
It sounds like the existing security_path* hooks are not adequate for your needs then, and that patch should not in fact be merged. Yes?
Quoted 8 lines
To do so, "security_path_*() should be replaced by
security_path_set(vfsmount)" and "let security_inode_*() do MAC checks using the result ofsecurity_path_set()" and "letsecurity_path_clear()clear the result ofsecurity_path_set()in case security_inode_*() was not called".So, I think storing the pathname of "struct vfsmount" in the form of "char *" into private hash at
security_path_set()and clearing the private hash atsecurity_path_clear()should be most preferable.
Then I guess you need to redo your patches along those lines and re-submit them. Likely starting with just a patch adding the security_path_set/clear hooks, posted to lsm and fsdevel.
Signature · 2 lines
Stephen Smalley National Security Agency
Kentaro Takeda
maintainerRe: [TOMOYO #13 (mmotm 2008-11-19-02-19) 01/11] Introduce security_path_clear() hook.
Quoted 5 lines · Stephen Smalley
To be precise, I was recommending passing the return value of security_path* down to security_inode* explicitly rather than doing it implicitly as you presently do. Thereby making the actual control flow and relationship between the security_path* and security_inode* hooks evident. However, I guess that is moot given your statements below.
I think so too.
Quoted 7 lines
Quoted 4 lines
I think there are two problems.
One is that the variable passed via stack memory won't be used by SELinux and SMACK and "
CONFIG_SECURITY=nkernels", which will be a waste of stack memory.I'm more concerned with the hook interface being understandable and maintainable.
I see.
Quoted 8 lines
Quoted 4 lines
The other one is that TOMOYO will need another variable for telling how the security_inode_*() are called. Passing the variable via stack memory requires modification of all vfs_*() calls, but TOMOYO doesn't check requests issued by (e.g.) stackable filesystems.
I'm not clear on why that requires a separate argument; if the caller is passing in the access decision result as an input, then certain callers (e.g. stackable filesystems) can always pass 0 (success).
If we use stack memory to pass the access decision result from security_path_*() to security_inode_*(), this method seems possible.
Quoted 8 lines
Quoted 8 lines
By the way, this
security_path_clear()is intended to be able to return DAC's error code in priority to MAC's error code, but there are two problems for TOMOYO.
One is that pathnames which will be later denied by DAC are appended by TOMOYO's learning mode (i.e. garbage entries appears in the learned policy). The other is that warning messages on pathnames which will be later denied by DAC are generated by TOMOYO's enforcing mode.
Quoted 6 lines
Quoted 3 lines
Thus, it will be preferable for TOMOYO to "do MAC checks after DAC checks" rather than to "return DAC's error in priority to MAC's error while doing MAC checks before DAC checks".
It sounds like the existing security_path* hooks are not adequate for your needs then, and that patch should not in fact be merged. Yes?
Sorry for confusing you. security_path_*() hooks are adequate for TOMOYO functionality itself. But they are inadequate for performing DAC before MAC, which we eventually want to do. We've reached this "passing vfsmount's pathname" approach after proposing the previous patch. The new approach is a little divergence of the existing approach, which Serge has patiently advised us. It should be more suitable for DAC-before-MAC than the previous patch, we think.
Quoted 5 lines
Quoted 5 lines
To do so, "security_path_*() should be replaced by
security_path_set(vfsmount)" and "let security_inode_*() do MAC checks using the result ofsecurity_path_set()" and "letsecurity_path_clear()clear the result ofsecurity_path_set()in case security_inode_*() was not called".
Quoted 7 lines
Quoted 3 lines
So, I think storing the pathname of "struct vfsmount" in the form of "char *" into private hash at
security_path_set()and clearing the private hash atsecurity_path_clear()should be most preferable.Then I guess you need to redo your patches along those lines and re-submit them. Likely starting with just a patch adding the security_path_set/clear hooks, posted to lsm and fsdevel.
I'll post it soon.
If we pass the access decision result through stack memory, we don't need security_path_clear() as you mentioned. However, if we pass the vfsmount's pathname, security_path_clear() is still needed in order to free the pathname.
Regards,
Kentaro Takeda
maintainerPATCH 1/1 Introduce security_path_set/clear() hooks.
Stephen, Serge,
Here is the patch for introducing new security_path_set()/clear() hooks.
This patch enables LSM module to remember vfsmount's pathname so that it can calculate absolute pathname in security_inode_*(). Since actual MAC can be performed after DAC, there will not be any noise in auditing and learning features. This patch currently assumes that the vfsmount's pathname is stored in hash table in LSM module. (Should I use stack memory?)
Since security_inode_*() are not always called after security_path_set(), security_path_clear() hook is needed to free the remembered pathname.
Andrew,
If lsm and fs guys accept this patch, please replace introduce-new-lsm-hooks-where-vfsmount-is-available.patch with this patch.
Otherwise, if we could call DAC functions, such as may_create(), in LSM module, security_path_clear() hook would not be needed. This approach is simple, but it might be objected because of layering. ;-)
Regards,
----- What is this patch for? -----
There are security_inode_*() LSM hooks for attribute-based MAC, but they are not suitable for pathname-based MAC because they don't receive "struct vfsmount" information.
----- How this patch was developed? -----
Two pathname-based MACs, AppArmor and TOMOYO Linux, are trying to merge upstream. But because of "struct vfsmount" problem, they have been unable to merge upstream.
Here are the list of approaches and the reasons of denial.
(1) Not using LSM
http://lwn.net/Articles/277833/
This approach was rejected because security modules should use LSM because the whole idea behind LSM was to have a single set of hooks for all security modules; if every module now adds its own set of hooks, that purpose will have been defeated and the kernel will turn into a big mess of security hooks.
(2) Retrieving "struct vfsmount" from "struct task_struct".
http://lkml.org/lkml/2007/11/5/388
Since "struct task_struct" contains list of "struct vfsmount", "struct vfsmount" which corresponds to "struct dentry" can be retrieved from the list unless "mount --bind" is used.
This approach turned out to cause a critical problem that getting namespace_sem lock from security_inode_*() triggers AB-BA deadlock.
(3) Adding "struct vfsmount" parameter to VFS helper functions.
http://lkml.org/lkml/2008/5/29/207
This approach adds "struct vfsmount" to VFS helper functions (e.g. vfs_mkdir() and vfs_symlink()) and LSM hooks inside VFS helper functions. This approach is helpful for not only AppArmor and TOMOYO Linux 2.x but also SELinux and auditing purpose, for this approach allows existent LSM users to use pathnames in their access control and audit logs.
This approach was rejected by Al Viro, the VFS maintainer, because he thinks individual filesystem should remain "struct vfsmount"-unaware and VFS helper functions should not receive "struct vfsmount".
Al Viro also suggested to move existing security_inode_*() to out of VFS helper functions so that security_inode_*() can receive "struct vfsmount" without modifying VFS helper functions, but this suggestion was opposed by Stephen Smalley because changing the order of permission checks (i.e. MAC checks before DAC checks) is not acceptable.
(4) Passing "struct vfsmount" via "struct task_struct".
http://lkml.org/lkml/2007/11/16/157
Since we didn't understand the reason why accessing "struct vfsmount" from LSM hooks inside VFS helper functions is not acceptable, we thought the reason why VFS helper functions don't receive "struct vfsmount" is the amount of modifications needed to do so. Thus, we proposed to pass "struct vfsmount" via "struct task_struct" so that modifications remain minimal.
This approach was rejected because this is an abuse of "struct task_struct".
(5) Remembering pathname of "struct vfsmount" via "struct task_struct".
http://lkml.org/lkml/2008/8/19/16
Since pathname of a "struct dentry" up to the mount point can be calculated without "struct vfsmount", absolute pathname of a "struct dentry" can be calculated if "struct task_struct" can remember absolute pathname of a "struct vfsmount" which corresponds to "struct dentry".
As we now understand that Al Viro is opposing to access "struct vfsmount" from LSM hooks inside VFS helper functions, we gave up delivering "struct vfsmount" to LSM hooks inside VFS helper functions.
Kernel 2.6.26 introduced read-only bind mount feature, and hooks for that feature (i.e. mnt_want_write() and mnt_drop_write()) were inserted around VFS helper functions call. Since mnt_want_write() receives "struct vfsmount" which corresponds to "struct dentry" that will be passed to subsequent VFS helper functions call, we associated pathname of "struct vfsmount" with "struct task_struct" instead of associating "struct vfsmount" itself.
This approach was not explicitly rejected, but there seems to be performance problem.
(6) Introducing new LSM hooks.
http://lkml.org/lkml/2008/9/24/48
We understand that adding new LSM hooks which receive "struct vfsmount" outside VFS helper functions is the most straightforward approach. This approach has less impact to existing LSM module and no impact to VFS helper functions.
(7) Remembering pathname of "struct vfsmount" via new LSM hooks. (this patch)
We proposed (6) so that we can implement MAC which can take an absolute pathname of a requested file into account. We embedded DAC's code copied from VFS helper functions into (6). But we received a comment that copying DAC's code is not a good thing. Thus, we once gave up doing DAC checks before MAC checks.
But, we do want to do DAC checks before MAC checks so that we can avoid MAC's noisy error messages generated by access requests which will be rejected by DAC.
There are two restrictions for now.
One is that we should avoid accessing "struct vfsmount" inside VFS helper functions and security_inode_*() so that we can keep clear separation between vfsmount-aware layer and vfsmount-unaware layer. Thus, there is no chance to pass "struct vfsmount" parameter to VFS helper functions and security_inode_*().
The other is that we should avoid copying DAC's code into security_path_*(). The security_path_*() which was proposed in (6) allows us to implement MAC which can take an absolute pathname of a requested file into account, but keeps us away from doing DAC checks before MAC checks.
We were using security_path_*(), but we still want to do DAC checks before MAC checks. Thus, we propose this patch which is a variant of (5) (which was not explicitly rejected). This patch introduces security_path_set(struct vfsmount *) and security_path_clear(). We want to use these hooks as follows.
(a) Let security_path_set() which are inserted before calling VFS helper functions remember the absolute pathname of "struct vfsmount" and store the result which are in the form of "char *" into private hash.
(b) Let security_inode_*() do MAC using the pathname stored by security_path_set().
(c) Let security_path_clear() which are inserted after calling VFS helper functions clear the pathname from private hash.
This approach is similar to (5), but to avoid performance problem, this patch inserts into minimal locations that TOMOYO Linux needs.
---
fs/namei.c | 43 +++++++++++++++++++++++++++++++++++++++++++ fs/open.c | 6 ++++++ include/linux/security.h | 24 ++++++++++++++++++++++++ net/unix/af_unix.c | 5 +++++ security/Kconfig | 10 ++++++++++ security/capability.c | 17 +++++++++++++++++ security/security.c | 16 ++++++++++++++++ 7 files changed, 121 insertions(+)
fs/namei.c +43 -0
@@ -1556,12 +1556,15 @@ int may_open(struct nameidata *nd, int a* Refuse to truncate files with mandatory locks held on them. */ error =locks_verify_locked (inode); +if (!error) + error =security_path_set (nd->path.mnt);if (!error) {DQUOT_INIT (inode); error =do_truncate (dentry,0 ,ATTR_MTIME |ATTR_CTIME |ATTR_OPEN ,NULL ); +security_path_clear (); }put_write_access (inode);if (error)
@@ -1586,7 +1589,12 @@ static int __open_namei_create(struct naif (!IS_POSIXACL (dir->d_inode)) mode &= ~current->fs->umask; + error =security_path_set (nd->path.mnt); +if (error) + goto out_unlock; error =vfs_create (dir->d_inode, path->dentry, mode, nd); +security_path_clear (); +out_unlock:mutex_unlock (&dir->d_inode->i_mutex);dput (nd->path.dentry); nd->path.dentry = path->dentry;
@@ -1999,6 +2007,9 @@ asmlinkage long sys_mknodat(int dfd, con error =mnt_want_write (nd.path.mnt);if (error) goto out_dput; + error =security_path_set (nd.path.mnt); +if (error) + goto out_drop_write;switch (mode &S_IFMT ) {case 0 :case S_IFREG : error =vfs_create (nd.path.dentry->d_inode,dentry,mode,&nd);
@@ -2011,6 +2022,8 @@ asmlinkage long sys_mknodat(int dfd, con error =vfs_mknod (nd.path.dentry->d_inode,dentry,mode,0 );break ; } +security_path_clear (); +out_drop_write:mnt_drop_write (nd.path.mnt); out_dput:dput (dentry);
@@ -2070,7 +2083,12 @@ asmlinkage long sys_mkdirat(int dfd, con error =mnt_want_write (nd.path.mnt);if (error) goto out_dput; + error =security_path_set (nd.path.mnt); +if (error) + goto out_drop_write; error =vfs_mkdir (nd.path.dentry->d_inode, dentry, mode); +security_path_clear (); +out_drop_write:mnt_drop_write (nd.path.mnt); out_dput:dput (dentry);
@@ -2180,7 +2198,12 @@ static long do_rmdir(int dfd, const char error =mnt_want_write (nd.path.mnt);if (error) goto exit3; + error =security_path_set (nd.path.mnt); +if (error) + goto exit4; error =vfs_rmdir (nd.path.dentry->d_inode, dentry); +security_path_clear (); +exit4:mnt_drop_write (nd.path.mnt); exit3:dput (dentry);
@@ -2265,7 +2288,12 @@ static long do_unlinkat(int dfd, const c error =mnt_want_write (nd.path.mnt);if (error) goto exit2; + error =security_path_set (nd.path.mnt); +if (error) + goto exit3; error =vfs_unlink (nd.path.dentry->d_inode, dentry); +security_path_clear (); +exit3:mnt_drop_write (nd.path.mnt); exit2:dput (dentry);
@@ -2346,7 +2374,12 @@ asmlinkage long sys_symlinkat(const char error =mnt_want_write (nd.path.mnt);if (error) goto out_dput; + error =security_path_set (nd.path.mnt); +if (error) + goto out_drop_write; error =vfs_symlink (nd.path.dentry->d_inode, dentry, from); +security_path_clear (); +out_drop_write:mnt_drop_write (nd.path.mnt); out_dput:dput (dentry);
@@ -2443,7 +2476,12 @@ asmlinkage long sys_linkat(int olddfd, c error =mnt_want_write (nd.path.mnt);if (error) goto out_dput; + error =security_path_set (nd.path.mnt); +if (error) + goto out_drop_write; error =vfs_link (old_path.dentry, nd.path.dentry->d_inode, new_dentry); +security_path_clear (); +out_drop_write:mnt_drop_write (nd.path.mnt); out_dput:dput (new_dentry);
@@ -2677,8 +2715,13 @@ asmlinkage long sys_renameat(int olddfd, error =mnt_want_write (oldnd.path.mnt);if (error) goto exit5; + error =security_path_set (oldnd.path.mnt); +if (error) + goto exit6; error =vfs_rename (old_dir->d_inode, old_dentry, new_dir->d_inode, new_dentry); +security_path_clear (); +exit6:mnt_drop_write (oldnd.path.mnt); exit5:dput (new_dentry);
fs/open.c +6 -0
@@ -272,9 +272,12 @@ static long do_sys_truncate(const char _ goto put_write_and_out; error =locks_verify_truncate (inode,NULL , length); +if (!error) + error =security_path_set (path.mnt);if (!error) {DQUOT_INIT (inode); error =do_truncate (path.dentry, length,0 ,NULL ); +security_path_clear (); } put_write_and_out:
@@ -329,7 +332,10 @@ static long do_sys_ftruncate(unsigned in error =locks_verify_truncate (inode, file, length);if (!error) + error =security_path_set (file->f_path.mnt); +if (!error) error =do_truncate (dentry, length,ATTR_MTIME |ATTR_CTIME , file); +security_path_clear (); out_putf:fput (file); out:
include/linux/security.h +24 -0
@@ -470,6 +470,12 @@ static inline void security_free_mnt_opt* @inode contains a pointer to the inode. * @secid contains a pointer to the location where result will be saved. * In case of failure, @secid will be set to zero. +* @path_set: +* Calculate pathname of vfsmount for subsequent vfs operation. +* @vfsmnt contains the vfsmount structure. +* Return 0 on success, negative value otherwise. +* @path_clear: +* Clear pathname of vfsmount calculated by @path_set. * * Security hooks for file operations *
@@ -1331,6 +1337,11 @@ struct security_operations { struct super_block *newsb);int (*sb_parse_opts_str) (char *options,struct security_mnt_opts *opts); +#ifdef CONFIG_SECURITY_PATH +int (*path_set) (struct vfsmount *vfsmnt); +void (*path_clear) (void ); +#endif +int (*inode_alloc_security) (struct inode *inode);void (*inode_free_security) (struct inode *inode); int (*inode_init_security) (struct inode *inode, struct inode *dir,
@@ -2705,6 +2716,19 @@ static inline void security_skb_classify#endif /* CONFIG_SECURITY_NETWORK_XFRM */ +#ifdef CONFIG_SECURITY_PATH +int security_path_set (struct vfsmount *vfsmnt); +void security_path_clear (void ); +#else /* CONFIG_SECURITY_PATH */ +static inline int security_path_set (struct vfsmount *vfsmnt) +{ +return 0 ; +} +static inline void security_path_clear (void ) +{ +} +#endif /* CONFIG_SECURITY_PATH */ +#ifdef CONFIG_KEYS #ifdef CONFIG_SECURITY
net/unix/af_unix.c +5 -0
@@ -836,7 +836,12 @@ static int unix_bind(struct socket *sock err =mnt_want_write (nd.path.mnt);if (err) goto out_mknod_dput; + err =security_path_set (nd.path.mnt); +if (err) + goto out_mknod_drop_write; err =vfs_mknod (nd.path.dentry->d_inode, dentry, mode,0 ); +security_path_clear (); +out_mknod_drop_write:mnt_drop_write (nd.path.mnt);if (err) goto out_mknod_dput;
security/Kconfig +10 -0
@@ -81,6 +81,16 @@ config SECURITY_NETWORK_XFRM IPSec. If you are unsure how to answer this question, answer N. +config SECURITY_PATH + bool "Security hooks for pathname based access control" + depends on SECURITY + help + This adds security_path_set() and security_path_clear() + hooks for pathname based access control. + If enabled, a security module can use these hooks to + implement pathname based access controls. + If you are unsure how to answer this question, answer N. + config SECURITY_FILE_CAPABILITIES bool "File POSIX Capabilities" default n
security/capability.c +17 -0
@@ -263,6 +263,19 @@ static void cap_inode_getsecid(const str *secid =0 ; } +#ifdef CONFIG_SECURITY_PATH + +static int cap_path_set (struct vfsmount *vfsmnt) +{ +return 0 ; +} + +static void cap_path_clear (void ) +{ +} + +#endif +static int cap_file_permission (struct file *file,int mask) {return 0 ;
@@ -883,6 +896,10 @@ void security_fixup_ops(struct security_set_to_cap_if_null (ops, inode_setsecurity);set_to_cap_if_null (ops, inode_listsecurity);set_to_cap_if_null (ops, inode_getsecid); +#ifdef CONFIG_SECURITY_PATH +set_to_cap_if_null (ops, path_set); +set_to_cap_if_null (ops, path_clear); +#endif set_to_cap_if_null (ops, file_permission);set_to_cap_if_null (ops, file_alloc_security);set_to_cap_if_null (ops, file_free_security);
security/security.c +16 -0
@@ -355,6 +355,22 @@ int security_inode_init_security(struct }EXPORT_SYMBOL (security_inode_init_security); +#ifdef CONFIG_SECURITY_PATH + +int security_path_set (struct vfsmount *vfsmnt) +{ +return security_ops->path_set (vfsmnt); +} +EXPORT_SYMBOL (security_path_set); + +void security_path_clear (void ) +{ +return security_ops->path_clear (); +} +EXPORT_SYMBOL (security_path_clear); + +#endif +int security_inode_create (struct inode *dir,struct dentry *dentry,int mode) {if (unlikely (IS_PRIVATE (dir)))
Re: [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks.
Quoted 11 lines · Kentaro Takeda
Stephen, Serge,
Here is the patch for introducing new security_path_set()/clear() hooks.This patch enables LSM module to remember vfsmount's pathname so that it can calculate absolute pathname in security_inode_*(). Since actual MAC can be performed after DAC, there will not be any noise in auditing and learning features. This patch currently assumes that the vfsmount's pathname is stored in hash table in LSM module. (Should I use stack memory?)
Since security_inode_*() are not always called after
security_path_set(),security_path_clear()hook is needed to free the remembered pathname.
Your security_path_set()/security_path_clear() pairs look rather similar to mnt_want_write()/mnt_drop_write() pairs. What if you were to call your hooks from those functions, and then you would only need to add further hook calls in the case of read-only and execute/search checks?
Signature · 2 lines
Stephen Smalley National Security Agency
Tetsuo Handa
maintainerRe: [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks.
Hello.
Quoted 10 lines · Stephen Smalley
Quoted 9 lines · Kentaro Takeda
Stephen, Serge,
Here is the patch for introducing new security_path_set()/clear() hooks.This patch enables LSM module to remember vfsmount's pathname so that it can calculate absolute pathname in security_inode_*(). Since actual MAC can be performed after DAC, there will not be any noise in auditing and learning features. This patch currently assumes that the vfsmount's pathname is stored in hash table in LSM module. (Should I use stack memory?)
Quoted 7 lines
Quoted 2 lines
Since security_inode_*() are not always called after
security_path_set(),security_path_clear()hook is needed to free the remembered pathname.Your security_path_set()/security_path_clear() pairs look rather similar to mnt_want_write()/mnt_drop_write() pairs. What if you were to call your hooks from those functions, and then you would only need to add further hook calls in the case of read-only and execute/search checks?
Right. Locations of inserting security_path_set()/security_path_clear() pairs are subset of mnt_want_write()/mnt_drop_write() pairs. Thus, we can insert security_path_set()/security_path_clear() pairs into mnt_want_write()/mnt_drop_write() pairs, if we can tolerate performance regression. According to our rough measurement, there is about 8 - 22% of performance regression. But this approach needs minimum modification to the existing kernel (only two hooks to be inserted).
The attached patch embeds security_path_set()/security_path_clear() into mnt_want_write()/mnt_drop_write() and adds an example LSM module which calculates vfsmount's pathname.
If LSM and FS people can accept this approach, we want to use it.
(----- When below patch is enabled -----)
# time dd status=noxfer if=/dev/zero of=/tmp/file bs=1 count=10485760 10485760+0 records in 10485760+0 records out
real 0m32.139s user 0m2.303s sys 0m29.756s # time dd status=noxfer if=/dev/zero of=/tmp/file bs=512 count=20480 20480+0 records in 20480+0 records out
real 0m0.087s user 0m0.002s sys 0m0.085s # time dd status=noxfer if=/dev/zero of=/tmp/file bs=4096 count=2560 2560+0 records in 2560+0 records out
real 0m0.028s user 0m0.001s sys 0m0.027s
(----- When below patch is disbled -----)
# time dd status=noxfer if=/dev/zero of=/tmp/file bs=1 count=10485760 10485760+0 records in 10485760+0 records out
real 0m26.776s user 0m2.281s sys 0m24.373s # time dd status=noxfer if=/dev/zero of=/tmp/file bs=512 count=20480 20480+0 records in 20480+0 records out
real 0m0.077s user 0m0.002s sys 0m0.073s # time dd status=noxfer if=/dev/zero of=/tmp/file bs=4096 count=2560 2560+0 records in 2560+0 records out
real 0m0.025s user 0m0.001s sys 0m0.024s
Regards.
--------------------
Subject: Embed security_path_set()/security_path_clear() into mnt_want_write()/mnt_drop_write().
This is a LSM version of http://lkml.org/lkml/2008/8/19/16 .
---
fs/namespace.c | 11 +++++ include/linux/security.h | 24 +++++++++++ security/Kconfig | 10 ++++ security/Makefile | 1 security/capability.c | 17 +++++++ security/mnt_path.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++ security/security.c | 14 ++++++ 7 files changed, 177 insertions(+)
fs/namespace.c +11 -0
@@ -254,6 +254,10 @@ int mnt_want_write(struct vfsmount *mnt)int ret =0 ;struct mnt_writer *cpu_writer; +#ifdef CONFIG_SECURITY_PATH +if (security_path_set (mnt) <0 ) +return -ENOMEM ; +#endif cpu_writer = &get_cpu_var (mnt_writers);spin_lock (&cpu_writer->lock);if (__mnt_is_readonly (mnt)) {
@@ -265,6 +269,10 @@ int mnt_want_write(struct vfsmount *mnt) out:spin_unlock (&cpu_writer->lock);put_cpu_var (mnt_writers); +#ifdef CONFIG_SECURITY_PATH +if (ret) +security_path_clear (); +#endif return ret; }EXPORT_SYMBOL_GPL (mnt_want_write);
@@ -362,6 +370,9 @@ void mnt_drop_write(struct vfsmount *mnt* we could theoretically wrap __mnt_writers. */ put_cpu_var (mnt_writers); +#ifdef CONFIG_SECURITY_PATH +security_path_clear (); +#endif }EXPORT_SYMBOL_GPL (mnt_drop_write);
include/linux/security.h +24 -0
@@ -470,6 +470,12 @@ static inline void security_free_mnt_opt* @inode contains a pointer to the inode. * @secid contains a pointer to the location where result will be saved. * In case of failure, @secid will be set to zero. +* @path_set: +* Calculate pathname of vfsmount for subsequent vfs operation. +* @vfsmnt contains the vfsmount structure. +* Return 0 on success, negative value otherwise. +* @path_clear: +* Clear pathname of vfsmount calculated by @path_set. * * Security hooks for file operations *
@@ -1331,6 +1337,11 @@ struct security_operations { struct super_block *newsb);int (*sb_parse_opts_str) (char *options,struct security_mnt_opts *opts); +#ifdef CONFIG_SECURITY_PATH +int (*path_set) (struct vfsmount *vfsmnt); +void (*path_clear) (void ); +#endif +int (*inode_alloc_security) (struct inode *inode);void (*inode_free_security) (struct inode *inode); int (*inode_init_security) (struct inode *inode, struct inode *dir,
@@ -2705,6 +2716,19 @@ static inline void security_skb_classify#endif /* CONFIG_SECURITY_NETWORK_XFRM */ +#ifdef CONFIG_SECURITY_PATH +int security_path_set (struct vfsmount *vfsmnt); +void security_path_clear (void ); +#else /* CONFIG_SECURITY_PATH */ +static inline int security_path_set (struct vfsmount *vfsmnt) +{ +return 0 ; +} +static inline void security_path_clear (void ) +{ +} +#endif /* CONFIG_SECURITY_PATH */ +#ifdef CONFIG_KEYS #ifdef CONFIG_SECURITY
security/Kconfig +10 -0
@@ -81,6 +81,16 @@ config SECURITY_NETWORK_XFRM IPSec. If you are unsure how to answer this question, answer N. +config SECURITY_PATH + bool "Security hooks for pathname based access control" + depends on SECURITY + help + This adds security_path_set() and security_path_clear() + hooks for pathname based access control. + If enabled, a security module can use these hooks to + implement pathname based access controls. + If you are unsure how to answer this question, answer N. + config SECURITY_FILE_CAPABILITIES bool "File POSIX Capabilities" default n
security/Makefile +1 -0
@@ -17,3 +17,4 @@ obj-$(CONFIG_SECURITY_SELINUX) += selin obj-$(CONFIG_SECURITY_SMACK) += smack/built-in.o obj-$(CONFIG_SECURITY_ROOTPLUG) += root_plug.o obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o +obj-$(CONFIG_SECURITY_PATH) += mnt_path.o
security/capability.c +17 -0
@@ -263,6 +263,19 @@ static void cap_inode_getsecid(const str *secid =0 ; } +#ifdef CONFIG_SECURITY_PATH + +static int cap_path_set (struct vfsmount *vfsmnt) +{ +return 0 ; +} + +static void cap_path_clear (void ) +{ +} + +#endif +static int cap_file_permission (struct file *file,int mask) {return 0 ;
@@ -883,6 +896,10 @@ void security_fixup_ops(struct security_set_to_cap_if_null (ops, inode_setsecurity);set_to_cap_if_null (ops, inode_listsecurity);set_to_cap_if_null (ops, inode_getsecid); +#ifdef CONFIG_SECURITY_PATH +set_to_cap_if_null (ops, path_set); +set_to_cap_if_null (ops, path_clear); +#endif set_to_cap_if_null (ops, file_permission);set_to_cap_if_null (ops, file_alloc_security);set_to_cap_if_null (ops, file_free_security);
security/mnt_path.c new file +100 -0
100 lines not on this page · raw message
security/security.c +14 -0
@@ -355,6 +355,20 @@ int security_inode_init_security(struct }EXPORT_SYMBOL (security_inode_init_security); +#ifdef CONFIG_SECURITY_PATH + +int security_path_set (struct vfsmount *vfsmnt) +{ +return security_ops->path_set (vfsmnt); +} + +void security_path_clear (void ) +{ +return security_ops->path_clear (); +} + +#endif +int security_inode_create (struct inode *dir,struct dentry *dentry,int mode) {if (unlikely (IS_PRIVATE (dir)))
Re: [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks.
Quoted 21 lines · Tetsuo Handa
Hello.
Quoted 10 lines · Stephen Smalley
Quoted 9 lines · Kentaro Takeda
Stephen, Serge,
Here is the patch for introducing new security_path_set()/clear() hooks.This patch enables LSM module to remember vfsmount's pathname so that it can calculate absolute pathname in security_inode_*(). Since actual MAC can be performed after DAC, there will not be any noise in auditing and learning features. This patch currently assumes that the vfsmount's pathname is stored in hash table in LSM module. (Should I use stack memory?)
Quoted 7 lines
Quoted 2 lines
Since security_inode_*() are not always called after
security_path_set(),security_path_clear()hook is needed to free the remembered pathname.Your security_path_set()/security_path_clear() pairs look rather similar to mnt_want_write()/mnt_drop_write() pairs. What if you were to call your hooks from those functions, and then you would only need to add further hook calls in the case of read-only and execute/search checks?
Quoted 6 lines
Right. Locations of inserting security_path_set()/security_path_clear() pairs are subset of mnt_want_write()/mnt_drop_write() pairs. Thus, we can insert security_path_set()/security_path_clear() pairs into mnt_want_write()/mnt_drop_write() pairs, if we can tolerate performance regression. According to our rough measurement, there is about 8 - 22% of performance regression.
... compared to what, exactly?
If having CONFIG_SECURITY_PATH=y but TOMOYO disabled has this kind of regression against just not having CONFIG_SECURITY_PATH, then no that is not acceptable.
-serge
Tetsuo Handa
maintainerRe: [PATCH (mmotm-2008-12-02-17-08)] Introducesecurity_path_set/clear() hooks.
Hello.
Quoted 7 lines · Serge E. Hallyn
Quoted 6 lines
Right. Locations of inserting security_path_set()/security_path_clear() pairs are subset of mnt_want_write()/mnt_drop_write() pairs. Thus, we can insert security_path_set()/security_path_clear() pairs into mnt_want_write()/mnt_drop_write() pairs, if we can tolerate performance regression. According to our rough measurement, there is about 8 - 22% of performance regression.
Quoted 6 lines
... compared to what, exactly?
If having
CONFIG_SECURITY_PATH=ybut TOMOYO disabled has this kind of regression against just not havingCONFIG_SECURITY_PATH, then no that is not acceptable.
Comparison between a module using mnt_path.c and a module not using mnt_path.c . If mp_update_mnt_path() is not called, there is no performance regression. TOMOYO will need mp_update_mnt_path().
Regards.
Re: [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks.
In reply to Tetsuo HandaQuoted 21 lines · Tetsuo Handa
Hello.
Quoted 10 lines · Stephen Smalley
Quoted 9 lines · Kentaro Takeda
Stephen, Serge,
Here is the patch for introducing new security_path_set()/clear() hooks.This patch enables LSM module to remember vfsmount's pathname so that it can calculate absolute pathname in security_inode_*(). Since actual MAC can be performed after DAC, there will not be any noise in auditing and learning features. This patch currently assumes that the vfsmount's pathname is stored in hash table in LSM module. (Should I use stack memory?)
Quoted 7 lines
Quoted 2 lines
Since security_inode_*() are not always called after
security_path_set(),security_path_clear()hook is needed to free the remembered pathname.Your security_path_set()/security_path_clear() pairs look rather similar to mnt_want_write()/mnt_drop_write() pairs. What if you were to call your hooks from those functions, and then you would only need to add further hook calls in the case of read-only and execute/search checks?
Quoted 7 lines
Right. Locations of inserting security_path_set()/security_path_clear() pairs are subset of mnt_want_write()/mnt_drop_write() pairs. Thus, we can insert security_path_set()/security_path_clear() pairs into mnt_want_write()/mnt_drop_write() pairs, if we can tolerate performance regression. According to our rough measurement, there is about 8 - 22% of performance regression. But this approach needs minimum modification to the existing kernel (only two hooks to be inserted).
I assume you also need separate hooks to cover the read-only open case? As for your performance, your implementation of mp_* is clearly non-optimal, so I'd expect there is plenty of room for improvement there.
<snip>
fs/namespace.c int mnt_want_write(struct vfsmount *mnt) · line 260
Quoted 10 lines
fs/namespace.c +4
-0@@ -254,6 +254,10 @@ int mnt_want_write(struct vfsmount *mnt)int ret =0 ;struct mnt_writer *cpu_writer; +#ifdef CONFIG_SECURITY_PATH +if (security_path_set (mnt) <0 ) +return -ENOMEM ; +#endif
No #ifdef's within the functions, of course. That gets handled by security.h.
Signature · 2 lines
Stephen Smalley National Security Agency
Tetsuo Handa
maintainerRe: [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks.
Hello.
Quoted 9 lines · Stephen Smalley
Quoted 7 lines
Right. Locations of inserting security_path_set()/security_path_clear() pairs are subset of mnt_want_write()/mnt_drop_write() pairs. Thus, we can insert security_path_set()/security_path_clear() pairs into mnt_want_write()/mnt_drop_write() pairs, if we can tolerate performance regression. According to our rough measurement, there is about 8 - 22% of performance regression. But this approach needs minimum modification to the existing kernel (only two hooks to be inserted).
I assume you also need separate hooks to cover the read-only open case?
security_dentry_open() receives "struct file *", so I think we don't need separate hooks for open(O_RDONLY).
Quoted 3 lines
As for your performance, your implementation of mp_* is clearly non-optimal, so I'd expect there is plenty of room for improvement there.
Yes. Thus, I want to pass a caller identifier to mnt_want_write() so that we can skip calculating vfsmount's pathname when it is not interested for a LSM module (e.g. mnt_want_write() called for updating atime/ctime/mtime checks).
May I add "int caller_id" to mnt_want_write()?
Quoted 2 lines
No #ifdef's within the functions, of course. That gets handled by
security.h.
OK.
Regards.
Tetsuo Handa
maintainerRFC 1/1 Add "reason" parameter to mnt_want_write().
We want to allow LSM modules to perform MAC which takes an absolute pathname of a requested file into account. Since we can't pass "struct vfsmount" to VFS helper functions, we are trying to somehow pass "struct vfsmount"'s pathnames instead of "struct vfsmount" itself.
The mnt_want_write() and mnt_drop_write() hooks are inserted around VFS helper functions call. Thus, I think we can insert security_path_set() into mnt_want_write() and secuity_path_clear() into mnt_drop_write() rather than scattering security_path_set() and security_path_clear() all around the places.
But, mnt_want_write() and mnt_drop_write() are used for not only VFS helper functions call but also various places. Thus, honestly calculating vfsmount's pathnames for touch_atime()/file_update_time() operations triggers measurable performance regression.
We want to skip calculating vfsmount's pathnames when the mnt_want_write() call is not interested for the LSM modules.
This patch adds "reason" parameter to mnt_want_write() so that LSM modules can calculate vfsmount's pathnames only when needed. The "reason" parameter is one of constants listed below.
/* For subsequent VFS helper functions call. */ MNT_WANT_FOR_VFS_REQUEST, /* vfs_create()/vfs_mkdir() etc. */ /* For implicit write of inode's timestamps. */ MNT_WANT_FOR_UPDATE_ACMTIME, /* touch_atime()/file_update_time() */ /* For explicit write of inode's attributes and timestamps. */ MNT_WANT_FOR_UPDATE_ATTR, /* chmod()/chown()/utimes() etc. */ /* For filesystem's ioctl. */ MNT_WANT_FOR_FS_IOCTL, /* ext3_ioctl() etc. */ /* For opening a file for writing. */ MNT_WANT_FOR_OPEN_WRITE, /* __get_file_write_access() */ /* Needs to be granted since the caller doesn't check errors. */ MNT_WANT_ANYWAY /* init_file() */
Please review.
--- fs/ext2/ioctl.c | 6 +++--- fs/ext3/ioctl.c | 10 +++++----- fs/ext4/ioctl.c | 10 +++++----- fs/fat/file.c | 2 +- fs/file_table.c | 2 +- fs/gfs2/ops_file.c | 2 +- fs/hfsplus/ioctl.c | 2 +- fs/inode.c | 4 ++-- fs/jfs/ioctl.c | 2 +- fs/namei.c | 18 +++++++++--------- fs/namespace.c | 15 ++++++++++++++- fs/ncpfs/ioctl.c | 2 +- fs/nfsd/nfs4proc.c | 3 ++- fs/nfsd/nfs4recover.c | 6 +++--- fs/nfsd/nfs4state.c | 3 ++- fs/nfsd/vfs.c | 21 ++++++++++++++------- fs/ocfs2/ioctl.c | 3 ++- fs/open.c | 16 ++++++++-------- fs/reiserfs/ioctl.c | 5 +++-- fs/ubifs/ioctl.c | 2 +- fs/utimes.c | 2 +- fs/xattr.c | 12 ++++++------ fs/xfs/linux-2.6/xfs_ioctl.c | 6 ++++-- fs/xfs/linux-2.6/xfs_lrw.c | 3 ++- include/linux/mount.h | 17 ++++++++++++++++- ipc/mqueue.c | 4 ++-- net/unix/af_unix.c | 2 +- 27 files changed, 111 insertions(+), 69 deletions(-)
fs/ext2/ioctl.c +3 -3
@@ -36,7 +36,7 @@ long ext2_ioctl(struct file *filp, unsigcase EXT2_IOC_SETFLAGS : {unsigned int oldflags; - ret =mnt_want_write (filp->f_path.mnt); + ret =mnt_want_write (filp->f_path.mnt,MNT_WANT_FOR_FS_IOCTL );if (ret)return ret;
@@ -93,7 +93,7 @@ setflags_out:case EXT2_IOC_SETVERSION :if (!is_owner_or_cap (inode))return -EPERM ; - ret =mnt_want_write (filp->f_path.mnt); + ret =mnt_want_write (filp->f_path.mnt,MNT_WANT_FOR_FS_IOCTL );if (ret)return ret;if (get_user (inode->i_generation, (int __user *) arg)) {
@@ -123,7 +123,7 @@ setflags_out:if (get_user (rsv_window_size, (int __user *)arg))return -EFAULT ; - ret =mnt_want_write (filp->f_path.mnt); + ret =mnt_want_write (filp->f_path.mnt,MNT_WANT_FOR_FS_IOCTL );if (ret)return ret;
fs/ext3/ioctl.c +5 -5
@@ -39,7 +39,7 @@ int ext3_ioctl (struct inode * inode, stunsigned int oldflags;unsigned int jflag; - err =mnt_want_write (filp->f_path.mnt); + err =mnt_want_write (filp->f_path.mnt,MNT_WANT_FOR_FS_IOCTL );if (err)return err;
@@ -141,7 +141,7 @@ flags_out:if (!is_owner_or_cap (inode))return -EPERM ; - err =mnt_want_write (filp->f_path.mnt); + err =mnt_want_write (filp->f_path.mnt,MNT_WANT_FOR_FS_IOCTL );if (err)return err;if (get_user (generation, (int __user *) arg)) {
@@ -202,7 +202,7 @@ setversion_out:if (!test_opt (inode->i_sb,RESERVATION ) ||!S_ISREG (inode->i_mode))return -ENOTTY ; - err =mnt_want_write (filp->f_path.mnt); + err =mnt_want_write (filp->f_path.mnt,MNT_WANT_FOR_FS_IOCTL );if (err)return err;
@@ -244,7 +244,7 @@ setrsvsz_out:if (!capable (CAP_SYS_RESOURCE ))return -EPERM ; - err =mnt_want_write (filp->f_path.mnt); + err =mnt_want_write (filp->f_path.mnt,MNT_WANT_FOR_FS_IOCTL );if (err)return err;
@@ -270,7 +270,7 @@ group_extend_out:if (!capable (CAP_SYS_RESOURCE ))return -EPERM ; - err =mnt_want_write (filp->f_path.mnt); + err =mnt_want_write (filp->f_path.mnt,MNT_WANT_FOR_FS_IOCTL );if (err)return err;
fs/ext4/ioctl.c +5 -5
@@ -44,7 +44,7 @@ long ext4_ioctl(struct file *filp, unsigif (get_user (flags, (int __user *) arg))return -EFAULT ; - err =mnt_want_write (filp->f_path.mnt); + err =mnt_want_write (filp->f_path.mnt,MNT_WANT_FOR_FS_IOCTL );if (err)return err;
@@ -141,7 +141,7 @@ flags_out:if (!is_owner_or_cap (inode))return -EPERM ; - err =mnt_want_write (filp->f_path.mnt); + err =mnt_want_write (filp->f_path.mnt,MNT_WANT_FOR_FS_IOCTL );if (err)return err;if (get_user (generation, (int __user *) arg)) {
@@ -200,7 +200,7 @@ setversion_out:if (get_user (n_blocks_count, (__u32 __user *)arg))return -EFAULT ; - err =mnt_want_write (filp->f_path.mnt); + err =mnt_want_write (filp->f_path.mnt,MNT_WANT_FOR_FS_IOCTL );if (err)return err;
@@ -226,7 +226,7 @@ setversion_out: sizeof(input)))return -EFAULT ; - err =mnt_want_write (filp->f_path.mnt); + err =mnt_want_write (filp->f_path.mnt,MNT_WANT_FOR_FS_IOCTL );if (err)return err;
@@ -247,7 +247,7 @@ setversion_out:if (!is_owner_or_cap (inode))return -EACCES ; - err =mnt_want_write (filp->f_path.mnt); + err =mnt_want_write (filp->f_path.mnt,MNT_WANT_FOR_FS_IOCTL );if (err)return err;/*
fs/fat/file.c +1 -1
@@ -47,7 +47,7 @@ int fat_generic_ioctl(struct inode *inodmutex_lock (&inode->i_mutex); - err =mnt_want_write (filp->f_path.mnt); + err =mnt_want_write (filp->f_path.mnt,MNT_WANT_FOR_FS_IOCTL );if (err) goto up_no_drop_write;
fs/file_table.c +1 -1
@@ -210,7 +210,7 @@ int init_file(struct file *file, struct*/ if ((mode &FMODE_WRITE ) && !special_file (dentry->d_inode->i_mode)) {file_take_write (file); - error =mnt_want_write (mnt); + error =mnt_want_write (mnt,MNT_WANT_ANYWAY );WARN_ON (error); }return error;
fs/gfs2/ops_file.c +1 -1
@@ -211,7 +211,7 @@ static int do_gfs2_set_flags(struct fileint error;u32 new_flags, flags; - error =mnt_want_write (filp->f_path.mnt); + error =mnt_want_write (filp->f_path.mnt,MNT_WANT_FOR_FS_IOCTL );if (error)return error;
fs/hfsplus/ioctl.c +1 -1
@@ -37,7 +37,7 @@ int hfsplus_ioctl(struct inode *inode, sreturn put_user (flags, (int __user *)arg);case HFSPLUS_IOC_EXT2_SETFLAGS : {int err =0 ; - err =mnt_want_write (filp->f_path.mnt); + err =mnt_want_write (filp->f_path.mnt,MNT_WANT_FOR_FS_IOCTL );if (err)return err;
fs/inode.c +2 -2
@@ -1235,7 +1235,7 @@ void touch_atime(struct vfsmount *mnt, sstruct inode *inode = dentry->d_inode;struct timespec now; -if (mnt_want_write (mnt)) +if (mnt_want_write (mnt,MNT_WANT_FOR_UPDATE_ACMTIME ))return ;if (inode->i_flags &S_NOATIME ) goto out;
@@ -1291,7 +1291,7 @@ void file_update_time(struct file *file)if (IS_NOCMTIME (inode))return ; - err =mnt_want_write (file->f_path.mnt); + err =mnt_want_write (file->f_path.mnt,MNT_WANT_FOR_UPDATE_ACMTIME );if (err)return ;
fs/jfs/ioctl.c +1 -1
@@ -68,7 +68,7 @@ long jfs_ioctl(struct file *filp, unsignunsigned int oldflags;int err; - err =mnt_want_write (filp->f_path.mnt); + err =mnt_want_write (filp->f_path.mnt,MNT_WANT_FOR_FS_IOCTL );if (err)return err;
fs/namei.c +9 -9
@@ -1723,7 +1723,7 @@ do_last:* a permanent write count is taken through * the 'struct file' in nameidata_to_filp(). */ - error =mnt_want_write (nd.path.mnt); + error =mnt_want_write (nd.path.mnt,MNT_WANT_FOR_VFS_REQUEST );if (error) goto exit_mutex_unlock; error =__open_namei_create (&nd, &path, flag, mode);
@@ -1775,7 +1775,7 @@ ok:*/ will_write =open_will_write_to_fs (flag, nd.path.dentry->d_inode);if (will_write) { - error =mnt_want_write (nd.path.mnt); + error =mnt_want_write (nd.path.mnt,MNT_WANT_FOR_VFS_REQUEST );if (error) goto exit; }
@@ -1996,7 +1996,7 @@ asmlinkage long sys_mknodat(int dfd, con error =may_mknod (mode);if (error) goto out_dput; - error =mnt_want_write (nd.path.mnt); + error =mnt_want_write (nd.path.mnt,MNT_WANT_FOR_VFS_REQUEST );if (error) goto out_dput;switch (mode &S_IFMT ) {
@@ -2067,7 +2067,7 @@ asmlinkage long sys_mkdirat(int dfd, conif (!IS_POSIXACL (nd.path.dentry->d_inode)) mode &= ~current->fs->umask; - error =mnt_want_write (nd.path.mnt); + error =mnt_want_write (nd.path.mnt,MNT_WANT_FOR_VFS_REQUEST );if (error) goto out_dput; error =vfs_mkdir (nd.path.dentry->d_inode, dentry, mode);
@@ -2177,7 +2177,7 @@ static long do_rmdir(int dfd, const char error =PTR_ERR (dentry);if (IS_ERR (dentry)) goto exit2; - error =mnt_want_write (nd.path.mnt); + error =mnt_want_write (nd.path.mnt,MNT_WANT_FOR_VFS_REQUEST );if (error) goto exit3; error =vfs_rmdir (nd.path.dentry->d_inode, dentry);
@@ -2262,7 +2262,7 @@ static long do_unlinkat(int dfd, const c inode = dentry->d_inode;if (inode)atomic_inc (&inode->i_count); - error =mnt_want_write (nd.path.mnt); + error =mnt_want_write (nd.path.mnt,MNT_WANT_FOR_VFS_REQUEST );if (error) goto exit2; error =vfs_unlink (nd.path.dentry->d_inode, dentry);
@@ -2343,7 +2343,7 @@ asmlinkage long sys_symlinkat(const charif (IS_ERR (dentry)) goto out_unlock; - error =mnt_want_write (nd.path.mnt); + error =mnt_want_write (nd.path.mnt,MNT_WANT_FOR_VFS_REQUEST );if (error) goto out_dput; error =vfs_symlink (nd.path.dentry->d_inode, dentry, from);
@@ -2440,7 +2440,7 @@ asmlinkage long sys_linkat(int olddfd, c error =PTR_ERR (new_dentry);if (IS_ERR (new_dentry)) goto out_unlock; - error =mnt_want_write (nd.path.mnt); + error =mnt_want_write (nd.path.mnt,MNT_WANT_FOR_VFS_REQUEST );if (error) goto out_dput; error =vfs_link (old_path.dentry, nd.path.dentry->d_inode, new_dentry);
@@ -2674,7 +2674,7 @@ asmlinkage long sys_renameat(int olddfd,if (new_dentry == trap) goto exit5; - error =mnt_want_write (oldnd.path.mnt); + error =mnt_want_write (oldnd.path.mnt,MNT_WANT_FOR_VFS_REQUEST );if (error) goto exit5; error = vfs_rename(old_dir->d_inode, old_dentry,
fs/namespace.c +14 -1
@@ -242,6 +242,7 @@ static inline void use_cpu_writer_for_mo/** * mnt_want_write - get write access to a mount * @mnt: the mount on which to take a write +* @reason: reason for this call * * This tells the low-level filesystem that a write is * about to be performed to it, and makes sure that
@@ -249,11 +250,16 @@ static inline void use_cpu_writer_for_mo* the write operation is finished, mnt_drop_write() * must be called. This is effectively a refcount. */ -int mnt_want_write (struct vfsmount *mnt) +int mnt_want_write (struct vfsmount *mnt,const int reason) {int ret =0 ;struct mnt_writer *cpu_writer; +/* +ret = security_path_set(mnt, reason); +if (ret) +return ret; +*/ cpu_writer = &get_cpu_var (mnt_writers);spin_lock (&cpu_writer->lock);if (__mnt_is_readonly (mnt)) {
@@ -265,6 +271,10 @@ int mnt_want_write(struct vfsmount *mnt) out:spin_unlock (&cpu_writer->lock);put_cpu_var (mnt_writers); +/* +if (ret) +security_path_clear(); +*/ return ret; }EXPORT_SYMBOL_GPL (mnt_want_write);
@@ -362,6 +372,9 @@ void mnt_drop_write(struct vfsmount *mnt* we could theoretically wrap __mnt_writers. */ put_cpu_var (mnt_writers); +/* +security_path_clear(); +*/ }EXPORT_SYMBOL_GPL (mnt_drop_write);
fs/ncpfs/ioctl.c +1 -1
@@ -861,7 +861,7 @@ int ncp_ioctl(struct inode *inode, struc* -EACCESS, so it seems consistent to keep * that here. */ -if (mnt_want_write (filp->f_path.mnt)) +if (mnt_want_write (filp->f_path.mnt,MNT_WANT_FOR_FS_IOCTL ))return -EACCES ; } ret =__ncp_ioctl (inode, filp, cmd, arg);
fs/nfsd/nfs4proc.c +2 -1
@@ -661,7 +661,8 @@ nfsd4_setattr(struct svc_rqst *rqstp, streturn status; } } - status =mnt_want_write (cstate->current_fh.fh_export->ex_path.mnt); + status =mnt_want_write (cstate->current_fh.fh_export->ex_path.mnt, +MNT_WANT_FOR_VFS_REQUEST );if (status)return status; status = nfs_ok;
fs/nfsd/nfs4recover.c +3 -3
@@ -162,7 +162,7 @@ nfsd4_create_clid_dir(struct nfs4_clientdprintk ("NFSD: nfsd4_create_clid_dir: DIRECTORY EXISTS\n" ); goto out_put; } - status =mnt_want_write (rec_dir.mnt); + status =mnt_want_write (rec_dir.mnt,MNT_WANT_FOR_VFS_REQUEST );if (status) goto out_put; status =vfs_mkdir (rec_dir.dentry->d_inode, dentry,S_IRWXU );
@@ -326,7 +326,7 @@ nfsd4_remove_clid_dir(struct nfs4_clientif (!rec_dir_init || !clp->cl_firststate)return ; - status =mnt_want_write (rec_dir.mnt); + status =mnt_want_write (rec_dir.mnt,MNT_WANT_FOR_VFS_REQUEST );if (status) goto out; clp->cl_firststate =0 ;
@@ -369,7 +369,7 @@ nfsd4_recdir_purge_old(void) {if (!rec_dir_init)return ; - status =mnt_want_write (rec_dir.mnt); + status =mnt_want_write (rec_dir.mnt,MNT_WANT_FOR_VFS_REQUEST );if (status) goto out; status =nfsd4_list_rec_dir (rec_dir.dentry, purge_old);
fs/nfsd/nfs4state.c +2 -1
@@ -1587,7 +1587,8 @@ nfs4_upgrade_open(struct svc_rqst *rqstpint err =get_write_access (inode);if (err)return nfserrno (err); - err =mnt_want_write (cur_fh->fh_export->ex_path.mnt); + err =mnt_want_write (cur_fh->fh_export->ex_path.mnt, +MNT_WANT_FOR_VFS_REQUEST );if (err)return nfserrno (err);file_take_write (filp);
fs/nfsd/vfs.c +14 -7
@@ -1261,7 +1261,8 @@ nfsd_create(struct svc_rqst *rqstp, stru goto out; } - host_err =mnt_want_write (fhp->fh_export->ex_path.mnt); + host_err =mnt_want_write (fhp->fh_export->ex_path.mnt, +MNT_WANT_FOR_VFS_REQUEST );if (host_err) goto out_nfserr;
@@ -1374,7 +1375,8 @@ nfsd_create_v3(struct svc_rqst *rqstp, s v_atime = verifier[1 ]&0x7fffffff ; } - host_err =mnt_want_write (fhp->fh_export->ex_path.mnt); + host_err =mnt_want_write (fhp->fh_export->ex_path.mnt, +MNT_WANT_FOR_VFS_REQUEST );if (host_err) goto out_nfserr;if (dchild->d_inode) {
@@ -1538,7 +1540,8 @@ nfsd_symlink(struct svc_rqst *rqstp, strif (IS_ERR (dnew)) goto out_nfserr; - host_err =mnt_want_write (fhp->fh_export->ex_path.mnt); + host_err =mnt_want_write (fhp->fh_export->ex_path.mnt, +MNT_WANT_FOR_VFS_REQUEST );if (host_err) goto out_nfserr;
@@ -1614,7 +1617,8 @@ nfsd_link(struct svc_rqst *rqstp, struct dold = tfhp->fh_dentry; dest = dold->d_inode; - host_err =mnt_want_write (tfhp->fh_export->ex_path.mnt); + host_err =mnt_want_write (tfhp->fh_export->ex_path.mnt, +MNT_WANT_FOR_VFS_REQUEST );if (host_err) { err =nfserrno (host_err); goto out_dput;
@@ -1716,7 +1720,8 @@ nfsd_rename(struct svc_rqst *rqstp, stru host_err = -EXDEV ;if (ffhp->fh_export->ex_path.mnt != tfhp->fh_export->ex_path.mnt) goto out_dput_new; - host_err =mnt_want_write (ffhp->fh_export->ex_path.mnt); + host_err =mnt_want_write (ffhp->fh_export->ex_path.mnt, +MNT_WANT_FOR_VFS_REQUEST );if (host_err) goto out_dput_new;
@@ -1787,7 +1792,8 @@ nfsd_unlink(struct svc_rqst *rqstp, struif (!type) type = rdentry->d_inode->i_mode &S_IFMT ; - host_err =mnt_want_write (fhp->fh_export->ex_path.mnt); + host_err =mnt_want_write (fhp->fh_export->ex_path.mnt, +MNT_WANT_FOR_VFS_REQUEST );if (host_err) goto out_nfserr;
@@ -2188,7 +2194,8 @@ nfsd_set_posix_acl(struct svc_fh *fhp, i }else size =0 ; - error =mnt_want_write (fhp->fh_export->ex_path.mnt); + error =mnt_want_write (fhp->fh_export->ex_path.mnt, +MNT_WANT_FOR_VFS_REQUEST );if (error) goto getout;if (size)
fs/ocfs2/ioctl.c +2 -1
@@ -129,7 +129,8 @@ long ocfs2_ioctl(struct file *filp, unsiif (get_user (flags, (int __user *) arg))return -EFAULT ; - status =mnt_want_write (filp->f_path.mnt); + status =mnt_want_write (filp->f_path.mnt, +MNT_WANT_FOR_FS_IOCTL );if (status)return status; status = ocfs2_set_inode_attr(inode, flags,
fs/open.c +8 -8
@@ -247,7 +247,7 @@ static long do_sys_truncate(const char _if (!S_ISREG (inode->i_mode)) goto dput_and_out; - error =mnt_want_write (path.mnt); + error =mnt_want_write (path.mnt,MNT_WANT_FOR_VFS_REQUEST );if (error) goto dput_and_out;
@@ -587,7 +587,7 @@ asmlinkage long sys_fchmod(unsigned intaudit_inode (NULL , dentry); - err =mnt_want_write (file->f_path.mnt); + err =mnt_want_write (file->f_path.mnt,MNT_WANT_FOR_UPDATE_ATTR );if (err) goto out_putf;mutex_lock (&inode->i_mutex);
@@ -617,7 +617,7 @@ asmlinkage long sys_fchmodat(int dfd, co goto out; inode = path.dentry->d_inode; - error =mnt_want_write (path.mnt); + error =mnt_want_write (path.mnt,MNT_WANT_FOR_UPDATE_ATTR );if (error) goto dput_and_out;mutex_lock (&inode->i_mutex);
@@ -672,7 +672,7 @@ asmlinkage long sys_chown(const char __u error =user_path (filename, &path);if (error) goto out; - error =mnt_want_write (path.mnt); + error =mnt_want_write (path.mnt,MNT_WANT_FOR_UPDATE_ATTR );if (error) goto out_release; error =chown_common (path.dentry, user, group);
@@ -697,7 +697,7 @@ asmlinkage long sys_fchownat(int dfd, co error =user_path_at (dfd, filename, follow, &path);if (error) goto out; - error =mnt_want_write (path.mnt); + error =mnt_want_write (path.mnt,MNT_WANT_FOR_UPDATE_ATTR );if (error) goto out_release; error =chown_common (path.dentry, user, group);
@@ -716,7 +716,7 @@ asmlinkage long sys_lchown(const char __ error =user_lpath (filename, &path);if (error) goto out; - error =mnt_want_write (path.mnt); + error =mnt_want_write (path.mnt,MNT_WANT_FOR_UPDATE_ATTR );if (error) goto out_release; error =chown_common (path.dentry, user, group);
@@ -738,7 +738,7 @@ asmlinkage long sys_fchown(unsigned intif (!file) goto out; - error =mnt_want_write (file->f_path.mnt); + error =mnt_want_write (file->f_path.mnt,MNT_WANT_FOR_UPDATE_ATTR );if (error) goto out_fput; dentry = file->f_path.dentry;
@@ -773,7 +773,7 @@ static inline int __get_file_write_acces/* * Balanced in __fput() */ - error =mnt_want_write (mnt); + error =mnt_want_write (mnt,MNT_WANT_FOR_OPEN_WRITE );if (error)put_write_access (inode); }
fs/reiserfs/ioctl.c +3 -2
@@ -48,7 +48,8 @@ int reiserfs_ioctl(struct inode *inode,if (!reiserfs_attrs (inode->i_sb))return -ENOTTY ; - err =mnt_want_write (filp->f_path.mnt); + err =mnt_want_write (filp->f_path.mnt, +MNT_WANT_FOR_FS_IOCTL );if (err)return err;
@@ -97,7 +98,7 @@ setflags_out:case REISERFS_IOC_SETVERSION :if (!is_owner_or_cap (inode))return -EPERM ; - err =mnt_want_write (filp->f_path.mnt); + err =mnt_want_write (filp->f_path.mnt,MNT_WANT_FOR_FS_IOCTL );if (err)return err;if (get_user (inode->i_generation, (int __user *)arg)) {
fs/ubifs/ioctl.c +1 -1
@@ -173,7 +173,7 @@ long ubifs_ioctl(struct file *file, unsi* Make sure the file-system is read-write and make sure it * will not become read-only while we are changing the flags. */ - err =mnt_want_write (file->f_path.mnt); + err =mnt_want_write (file->f_path.mnt,MNT_WANT_FOR_FS_IOCTL );if (err)return err; err =setflags (inode, flags);
fs/utimes.c +1 -1
@@ -54,7 +54,7 @@ static int utimes_common(struct path *pastruct iattr newattrs;struct inode *inode = path->dentry->d_inode; - error =mnt_want_write (path->mnt); + error =mnt_want_write (path->mnt,MNT_WANT_FOR_UPDATE_ATTR );if (error) goto out;
fs/xattr.c +6 -6
@@ -261,7 +261,7 @@ sys_setxattr(const char __user *pathname error =user_path (pathname, &path);if (error)return error; - error =mnt_want_write (path.mnt); + error =mnt_want_write (path.mnt,MNT_WANT_FOR_UPDATE_ATTR );if (!error) { error =setxattr (path.dentry, name, value, size, flags);mnt_drop_write (path.mnt);
@@ -280,7 +280,7 @@ sys_lsetxattr(const char __user *pathnam error =user_lpath (pathname, &path);if (error)return error; - error =mnt_want_write (path.mnt); + error =mnt_want_write (path.mnt,MNT_WANT_FOR_UPDATE_ATTR );if (!error) { error =setxattr (path.dentry, name, value, size, flags);mnt_drop_write (path.mnt);
@@ -302,7 +302,7 @@ sys_fsetxattr(int fd, const char __userreturn error; dentry = f->f_path.dentry;audit_inode (NULL , dentry); - error =mnt_want_write (f->f_path.mnt); + error =mnt_want_write (f->f_path.mnt,MNT_WANT_FOR_UPDATE_ATTR );if (!error) { error =setxattr (dentry, name, value, size, flags);mnt_drop_write (f->f_path.mnt);
@@ -494,7 +494,7 @@ sys_removexattr(const char __user *pathn error =user_path (pathname, &path);if (error)return error; - error =mnt_want_write (path.mnt); + error =mnt_want_write (path.mnt,MNT_WANT_FOR_UPDATE_ATTR );if (!error) { error =removexattr (path.dentry, name);mnt_drop_write (path.mnt);
@@ -512,7 +512,7 @@ sys_lremovexattr(const char __user *path error =user_lpath (pathname, &path);if (error)return error; - error =mnt_want_write (path.mnt); + error =mnt_want_write (path.mnt,MNT_WANT_FOR_UPDATE_ATTR );if (!error) { error =removexattr (path.dentry, name);mnt_drop_write (path.mnt);
@@ -533,7 +533,7 @@ sys_fremovexattr(int fd, const char __usreturn error; dentry = f->f_path.dentry;audit_inode (NULL , dentry); - error =mnt_want_write (f->f_path.mnt); + error =mnt_want_write (f->f_path.mnt,MNT_WANT_FOR_UPDATE_ATTR );if (!error) { error =removexattr (dentry, name);mnt_drop_write (f->f_path.mnt);
fs/xfs/linux-2.6/xfs_ioctl.c +4 -2
@@ -629,7 +629,8 @@ xfs_attrmulti_by_handle( &ops[i].am_length, ops[i].am_flags);break ;case ATTR_OP_SET : - ops[i].am_error =mnt_want_write (parfilp->f_path.mnt); + ops[i].am_error =mnt_want_write (parfilp->f_path.mnt, +MNT_WANT_FOR_FS_IOCTL );if (ops[i].am_error)break ; ops[i].am_error = xfs_attrmulti_attr_set(inode,
@@ -638,7 +639,8 @@ xfs_attrmulti_by_handle(mnt_drop_write (parfilp->f_path.mnt);break ;case ATTR_OP_REMOVE : - ops[i].am_error =mnt_want_write (parfilp->f_path.mnt); + ops[i].am_error =mnt_want_write (parfilp->f_path.mnt, +MNT_WANT_FOR_FS_IOCTL );if (ops[i].am_error)break ; ops[i].am_error = xfs_attrmulti_attr_remove(inode,
fs/xfs/linux-2.6/xfs_lrw.c +2 -1
@@ -673,7 +673,8 @@ start:* filesystems. Throw it away if anyone asks us. */ if (likely (!(ioflags &IO_INVIS ) && - !mnt_want_write (file->f_path.mnt))) { + !mnt_want_write (file->f_path.mnt, +MNT_WANT_FOR_UPDATE_ACMTIME ))) {xfs_ichgtime (xip,XFS_ICHGTIME_MOD |XFS_ICHGTIME_CHG );mnt_drop_write (file->f_path.mnt); }
include/linux/mount.h +16 -1
@@ -78,7 +78,22 @@ static inline struct vfsmount *mntget(streturn mnt; } -extern int mnt_want_write (struct vfsmount *mnt); +enum mnt_want_reasons { +/* For subsequent VFS helper functions call. */ +MNT_WANT_FOR_VFS_REQUEST ,/* vfs_create()/vfs_mkdir() etc. */ +/* For implicit write of inode's timestamps. */ +MNT_WANT_FOR_UPDATE_ACMTIME ,/* touch_atime()/file_update_time() */ +/* For explicit write of inode's attributes and timestamps. */ +MNT_WANT_FOR_UPDATE_ATTR ,/* chmod()/chown()/utimes() etc. */ +/* For filesystem's ioctl. */ +MNT_WANT_FOR_FS_IOCTL ,/* ext3_ioctl() etc. */ +/* For opening a file for writing. */ +MNT_WANT_FOR_OPEN_WRITE ,/* __get_file_write_access() */ +/* Needs to be granted since the caller doesn't check errors. */ +MNT_WANT_ANYWAY /* init_file() */ +}; + +extern int mnt_want_write (struct vfsmount *mnt,const int reason);extern void mnt_drop_write (struct vfsmount *mnt);extern void mntput_no_expire (struct vfsmount *mnt);extern void mnt_pin (struct vfsmount *mnt);
ipc/mqueue.c +2 -2
@@ -611,7 +611,7 @@ static struct file *do_create(struct den } mode &= ~current->fs->umask; - ret =mnt_want_write (mqueue_mnt); + ret =mnt_want_write (mqueue_mnt,MNT_WANT_FOR_VFS_REQUEST );if (ret) goto out; ret =vfs_create (dir->d_inode, dentry, mode,NULL );
@@ -753,7 +753,7 @@ asmlinkage long sys_mq_unlink(const char inode = dentry->d_inode;if (inode)atomic_inc (&inode->i_count); - err =mnt_want_write (mqueue_mnt); + err =mnt_want_write (mqueue_mnt,MNT_WANT_FOR_VFS_REQUEST );if (err) goto out_err; err =vfs_unlink (dentry->d_parent->d_inode, dentry);
net/unix/af_unix.c +1 -1
@@ -833,7 +833,7 @@ static int unix_bind(struct socket *sock*/ mode =S_IFSOCK | (SOCK_INODE (sock)->i_mode & ~current->fs->umask); - err =mnt_want_write (nd.path.mnt); + err =mnt_want_write (nd.path.mnt,MNT_WANT_FOR_VFS_REQUEST );if (err) goto out_mknod_dput; err =vfs_mknod (nd.path.dentry->d_inode, dentry, mode,0 );
Al Viro
maintainerRe: [RFC] Add "reason" parameter to mnt_want_write().
Quoted 9 lines · Tetsuo Handa
We want to allow LSM modules to perform MAC which takes an absolute pathname of a requested file into account. Since we can't pass "struct vfsmount" to VFS helper functions, we are trying to somehow pass "struct vfsmount"'s pathnames instead of "struct vfsmount" itself.
The
mnt_want_write()andmnt_drop_write()hooks are inserted around VFS helper functions call. Thus, I think we can insertsecurity_path_set()intomnt_want_write()andsecuity_path_clear()intomnt_drop_write()rather than scatteringsecurity_path_set()andsecurity_path_clear()all around the places.
No. Use separate set of hooks AND PASS vfsmount DIRECTLY TO THEM. Damnit, people, just how many times does it have to be repeated?
Any version that pulls that class of tricks is no-go. I don't _CARE_ whether you hide vfsmount in task struct, do the same with string, send yourself a datagram over magic socket or mail it to kludges-R-US.webtv.com, downloading it back in LSM hook.
It's not a problem with implementation; it's a problem with the kludge itself *and* with having the effect of vfs_mkdir() et.al. dependent on anything except the arguments it's getting.
Adding global context of that kind is every bit as wrong as passing vfsmount (or absolute pathname, or...) to vfs_mkdir() and its ilk. It's worse, actually, since it has an extra helping of ugliness on top of doing the wrong thing.
Al Viro
maintainerRe: [PATCH (mmotm-2008-12-02-17-08)] Introduce security_path_set/clear() hooks.
In reply to Stephen SmalleyQuoted 8 lines · Stephen Smalley
Quoted 7 lines
Right. Locations of inserting security_path_set()/security_path_clear() pairs are subset of mnt_want_write()/mnt_drop_write() pairs. Thus, we can insert security_path_set()/security_path_clear() pairs into mnt_want_write()/mnt_drop_write() pairs, if we can tolerate performance regression. According to our rough measurement, there is about 8 - 22% of performance regression. But this approach needs minimum modification to the existing kernel (only two hooks to be inserted).
Quoted 4 lines
I assume you also need separate hooks to cover the read-only open case? As for your performance, your implementation of mp_* is clearly non-optimal, so I'd expect there is plenty of room for improvement there.
And just what will happen if you end up with foo_mkdir() calling something that does e.g. pathname resolution in fs-controlled private namespace and creates/removes some files there?
Tetsuo Handa
maintainerPATCH 2/11 Add in_execve flag into task_struct.
In reply to Tetsuo HandaThis patch allows LSM modules to determine whether current process is in an execve operation or not so that they can behave differently while an execve operation is in progress.
This allows TOMOYO to dispense with a readability check on a file to be executed under the process's current credentials, and to do it instead under the proposed credentials.
This is required with the new COW credentials because TOMOYO is no longer allowed to mark the state temporarily in the security struct attached to the task_struct.
--- fs/compat.c | 3 +++ fs/exec.c | 3 +++ include/linux/sched.h | 2 ++ 3 files changed, 8 insertions(+)
fs/compat.c +3 -0
@@ -1396,6 +1396,7 @@ int compat_do_execve(char * filename, retval =mutex_lock_interruptible (¤t->cred_exec_mutex);if (retval <0 ) goto out_free; + current->in_execve =1 ; retval = -ENOMEM ; bprm->cred =prepare_exec_creds ();
@@ -1448,6 +1449,7 @@ int compat_do_execve(char * filename, goto out;/* execve succeeded */ + current->in_execve =0 ;mutex_unlock (¤t->cred_exec_mutex);acct_update_integrals (current);free_bprm (bprm);
@@ -1464,6 +1466,7 @@ out_file: } out_unlock: + current->in_execve =0 ;mutex_unlock (¤t->cred_exec_mutex); out_free:
fs/exec.c +3 -0
@@ -1293,6 +1293,7 @@ int do_execve(char * filename, retval =mutex_lock_interruptible (¤t->cred_exec_mutex);if (retval <0 ) goto out_free; + current->in_execve =1 ; retval = -ENOMEM ; bprm->cred =prepare_exec_creds ();
@@ -1346,6 +1347,7 @@ int do_execve(char * filename, goto out;/* execve succeeded */ + current->in_execve =0 ;mutex_unlock (¤t->cred_exec_mutex);acct_update_integrals (current);free_bprm (bprm);
@@ -1364,6 +1366,7 @@ out_file: } out_unlock: + current->in_execve =0 ;mutex_unlock (¤t->cred_exec_mutex); out_free:
include/linux/sched.h +2 -0
@@ -1095,6 +1095,8 @@ struct task_struct {/* ??? */ unsigned int personality; unsigned did_exec:1; + unsigned in_execve:1; /* Tell the LSMs that the process is doing an +* execve */ pid_t pid;pid_t tgid;
Tetsuo Handa
maintainerPATCH 3/11 Singly linked list implementation.
In reply to Tetsuo HandaThis patch introduces new list structure named "list1".
TOMOYO wants to use "write once read many" list.
Since only two operations
(1) Append an entry to the tail of the list.
(2) Read all entries starting from the head of the list.
are needed for that purpose, this list doesn't have pointer to previous element.
While "hlist" is defined as
struct hlist_head {
struct hlist_node *first;
};
struct hlist_node {
struct hlist_node *next, **pprev;
};
I don't use "hlist_node" because I don't need pointer to previous element.
By ommiting pointer to previous element, the reader becomes read lock free, which is good thing for implementing "write once read many" list.
--- include/linux/list1.h | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+)
include/linux/list1.h new file +81 -0
@@ -0,0 +1,81 @@ +#ifndef _LINUX_LIST1_H +#define _LINUX_LIST1_H + +#include <linux/list.h> +#include <linux/rcupdate.h> + +/* +* Singly linked list implementation. +* +* This list supports only two operations. +* (1) Append an entry to the tail of the list. +* (2) Read all entries starting from the head of the list. +* +* This list is designed for holding "write once, read many" entries. +* This list requires no locks for read operation. +* This list doesn't support "remove an entry from the list" operation. +*/ + +/* To reduce memory usage, this list doesn't use "->prev" pointer. */ +struct list1_head { +struct list1_head *next; +}; + +#define LIST1_HEAD_INIT (name) { &(name) } +#define LIST1_HEAD (name) struct list1_head name = LIST1_HEAD_INIT(name) + +static inline void INIT_LIST1_HEAD (struct list1_head *list) +{ + list->next = list; +} + +/* Reuse list_entry because it doesn't use "->prev" pointer. */ +#define list1_entry list_entry + +/* Reuse list_for_each_rcu because it doesn't use "->prev" pointer. */ +#define list1_for_each list_for_each_rcu +/* Reuse list_for_each_entry_rcu because it doesn't use "->prev" pointer. */ +#define list1_for_each_entry list_for_each_entry_rcu + +/** +* list1_for_each_cookie - iterate over a list with cookie. +* @pos: the &struct list1_head to use as a loop cursor. +* @cookie: the &struct list1_head to use as a cookie. +* @head: the head for your list. +* +* Same with list_for_each_rcu() except that this primitive uses @cookie +* so that we can continue iteration. +* @cookie must be NULL when iteration starts, and @cookie will become +* NULL when iteration finishes. +* +* Since list elements are never removed, we don't need to get a lock +* or a reference count. +*/ +#define list1_for_each_cookie (pos, cookie, head) \ + for (({ if (!cookie) \ + cookie = head; }), \ + pos = rcu_dereference((cookie)->next); \ + prefetch(pos->next), pos != (head) || ((cookie) = NULL); \ + (cookie) = pos, pos = rcu_dereference(pos->next)) + +/** +* list1_add_tail - add a new entry to list1 list. +* @new: new entry to be added. +* @head: list head to add it before. +* +* Same with list_add_tail_rcu() without "->prev" pointer. +* +* Caller must hold a lock for protecting @head. +*/ +static inline void list1_add_tail (struct list1_head *new, +struct list1_head *head) +{ +struct list1_head *prev = head; + + new->next = head; +while (prev->next != head) + prev = prev->next; +rcu_assign_pointer (prev->next, new); +} + +#endif
Tetsuo Handa
maintainerPATCH 4/11 Introduce d_realpath().
In reply to Tetsuo HandaTo remove factors that make pathname based access control difficult (e.g. symbolic links, "..", "//", chroot() etc.), a variant of d_path() which traverses up to the root of the namespace is needed.
This patch introduces d_realpath(), a variant of d_path(). While d_path() stops traversing at current->fs->root, d_realpath() doesn't stop traversiong at current->fs->root.
Three differences compared to d_path().
(1) Ignores current process's root directory.
(2) Trailing '/' is added if the pathname refers to a directory.
(3) /proc/PID/ is represented as /proc/self/ if PID equals current->tgid.
--- fs/dcache.c | 84 +++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/dcache.h | 1 2 files changed, 85 insertions(+)
fs/dcache.c +84 -0
@@ -33,6 +33,7 @@#include <linux/swap.h> #include <linux/bootmem.h> #include <linux/backing-dev.h> +#include <linux/magic.h> #include "internal.h"
@@ -1982,6 +1983,89 @@ Elong: }/** +* d_realpath - Get the realpath of a dentry. +* +* @path: Pointer to "struct path". +* @buffer: Pointer to buffer to return value in. +* @buflen: Sizeof @buffer. +* +* Returns pointer to the realpath on success, an error code othersize. +* +* If @dentry is a directory, trailing '/' is appended. +* /proc/PID/ is replaced by /proc/self/ if PID == task_tgid_nr_ns(current). +*/ +char *d_realpath (struct path *path,char *buffer,int buflen) +{ +struct dentry *dentry = path->dentry; +struct vfsmount *vfsmnt = path->mnt; +char *end = buffer + buflen; + +spin_lock (&dcache_lock); +spin_lock (&vfsmount_lock); +if (buflen <1 ||prepend (&end, &buflen,"" ,1 )) + goto Elong; +/* +* Exception: Add trailing '/' for directory. +*/ +if (dentry->d_inode &&S_ISDIR (dentry->d_inode->i_mode) && +prepend (&end, &buflen,"/" ,1 )) + goto Elong; +for (;;) { +struct dentry *parent; +const char *name; +int name_len; +unsigned long pid; + +if (dentry == vfsmnt->mnt_root ||IS_ROOT (dentry)) { +/* Global root? */ +if (vfsmnt->mnt_parent == vfsmnt) +break ; + dentry = vfsmnt->mnt_mountpoint; + vfsmnt = vfsmnt->mnt_parent; +continue ; + } + parent = dentry->d_parent; +prefetch (parent); +/* +* Exception: Use /proc/self/ rather than /proc/\$/ +* for current process. +*/ + name = dentry->d_name.name; + name_len = dentry->d_name.len; +if (IS_ROOT (parent) && + parent->d_sb->s_magic ==PROC_SUPER_MAGIC && + !strict_strtoul (name,10 , &pid)) { +const pid_t tgid + =task_tgid_nr_ns (current, + dentry->d_sb->s_fs_info); +if (tgid && (pid_t ) pid == tgid) { + name ="self" ; + name_len =4 ; + } + } +if (prepend (&end, &buflen, name, name_len)) + goto Elong; +if (prepend (&end, &buflen,"/" ,1 )) + goto Elong; + dentry = parent; + } +if (*end =='/' ) { +/* hit the slash */ + buflen++; + end++; + } +if (prepend_name (&end, &buflen, &dentry->d_name)) + goto Elong; + out: +spin_unlock (&vfsmount_lock); +spin_unlock (&dcache_lock); +return end; + Elong: + end =ERR_PTR (-ENAMETOOLONG ); + goto out; +} + +/** * d_path - return the path of a dentry * @path: path to report * @buf: buffer to return value in
include/linux/dcache.h +1 -0
@@ -305,6 +305,7 @@ extern char *dynamic_dname(struct dentryextern char *__d_path (const struct path *path,struct path *root,char *,int );extern char *d_path (const struct path *,char *,int );extern char *dentry_path (struct dentry *,char *,int ); +extern char *d_realpath (struct path *,char *,int );/* Allocation counts.. */
Tetsuo Handa
maintainerPATCH 5/11 Memory and pathname management functions.
In reply to Tetsuo HandaTOMOYO Linux performs pathname based access control.
To remove factors that make pathname based access control difficult (e.g. symbolic links, "..", "//" etc.), TOMOYO Linux derives realpath of requested pathname from "struct dentry" and "struct vfsmount".
The maximum length of string data is limited to 4000 including trailing '\0'. Since TOMOYO Linux uses '\ooo' style representation for non ASCII printable characters, may be TOMOYO Linux should be able to support 16336 (which means (NAME_MAX * (PATH_MAX / (NAME_MAX + 1)) * 4 + (PATH_MAX / (NAME_MAX + 1))) including trailing '\0'), but I think 4000 is enough for practical use.
TOMOYO uses only 0x21 - 0x7E (as printable characters) and 0x20 (as word delimiter) and 0x0A (as line delimiter).0x01 - 0x20 and 0x80 - 0xFF is handled in \ooo style representation. The reason to use \ooo is to guarantee that "%s" won't damage logs. Userland program can request
open("/tmp/file granted.\nAccess /tmp/file ", O_WRONLY | O_CREAT, 0600)
and logging such crazy pathname using "Access %s denied.\n" format will cause "fabrication of logs" like
Access /tmp/file granted.
Access /tmp/file denied.
TOMOYO converts such characters to \ooo so that the logs will become
Access /tmp/file\040granted.\012Access\040/tmp/file denied.
and the administrator can read the logs safely using /bin/cat . Likewise, a crazy request like
open("/tmp/\x01\x02\x03\x04\x05\x06\x07\x08\x09", O_WRONLY | O_CREAT, 0600)
will be processed safely by converting to
Access /tmp/\001\002\003\004\005\006\007\010\011 denied.
--- security/tomoyo/realpath.c | 453 +++++++++++++++++++++++++++++++++++++++++++++ security/tomoyo/realpath.h | 63 ++++++ 2 files changed, 516 insertions(+)
security/tomoyo/realpath.c new file +453 -0
@@ -0,0 +1,453 @@ +/* +* security/tomoyo/realpath.c +* +* Get the canonicalized absolute pathnames. The basis for TOMOYO. +* +* Copyright (C) 2005-2008 NTT DATA CORPORATION +* +* Version: 2.2.0-pre 2008/11/11 +* +*/ + +#include <linux/types.h> +#include <linux/mount.h> +#include "common.h" +#include "realpath.h" + +/** +* tomoyo_encode: Convert binary string to ascii string. +* +* @buffer: Buffer for ASCII string. +* @buflen: Size of @buffer. +* @str: Binary string. +* +* Returns 0 on success, -ENOMEM otherwise. +*/ +int tomoyo_encode (char *buffer,int buflen,const char *str) +{ +while (1 ) { +const unsigned char c = *(unsigned char *) str++; + +if (tomoyo_is_valid (c)) { +if (--buflen <=0 ) +break ; + *buffer++ = (char ) c; +if (c !='\\' ) +continue ; +if (--buflen <=0 ) +break ; + *buffer++ = (char ) c; +continue ; + } +if (!c) { +if (--buflen <=0 ) +break ; + *buffer ='\0' ; +return 0 ; + } + buflen -=4 ; +if (buflen <=0 ) +break ; + *buffer++ ='\\' ; + *buffer++ = (c >>6 ) +'0' ; + *buffer++ = ((c >>3 ) &7 ) +'0' ; + *buffer++ = (c &7 ) +'0' ; + } +return -ENOMEM ; +} + +/** +* tomoyo_realpath_from_path2 - Returns realpath(3) of the given dentry but ignores chroot'ed root. +* +* @path: Pointer to "struct path". +* @newname: Pointer to buffer to return value in. +* @newname_len: Size of @newname. +* +* Returns 0 on success, negative value otherwise. +* +* If dentry is a directory, trailing '/' is appended. +* Characters out of 0x20 < c < 0x7F range are converted to +* \ooo style octal string. +* Character \ is converted to \\ string. +*/ +int tomoyo_realpath_from_path2 (struct path *path,char *newname, +int newname_len) +{ +int error = -ENOMEM ; +struct dentry *dentry = path->dentry; +char *sp; + +if (!dentry || !path->mnt || !newname || newname_len <=2048 ) +return -EINVAL ; +if (dentry->d_op && dentry->d_op->d_dname) { +/* For "socket:[\$]" and "pipe:[\$]". */ +static const int offset =1536 ; + sp = dentry->d_op->d_dname (dentry, newname + offset, + newname_len - offset); + }else { +path_get (path); + sp =d_realpath (path, newname, newname_len); +path_put (path); + } +if (IS_ERR (sp)) + error =PTR_ERR (sp); +else + error =tomoyo_encode (newname, sp - newname, sp); +if (error) +printk (KERN_WARNING "tomoyo_realpath: Pathname too long.\n" ); +return error; +} + +/** +* tomoyo_realpath_from_path - Returns realpath(3) of the given pathname but ignores chroot'ed root. +* +* @path: Pointer to "struct path". +* +* Returns the realpath of the given @path on success, NULL otherwise. +* +* These functions use tomoyo_alloc(), so the caller must call tomoyo_free() +* if these functions didn't return NULL. +*/ +char *tomoyo_realpath_from_path (struct path *path) +{ +char *buf =tomoyo_alloc (sizeof (struct tomoyo_page_buffer )); + +BUILD_BUG_ON (sizeof (struct tomoyo_page_buffer ) + <=TOMOYO_MAX_PATHNAME_LEN -1 ); +if (!buf) +return NULL ; +if (tomoyo_realpath_from_path2 (path, buf, +TOMOYO_MAX_PATHNAME_LEN -1 ) ==0 ) +return buf; +tomoyo_free (buf); +return NULL ; +} + +/** +* tomoyo_realpath - Get realpath of a pathname. +* +* @pathname: The pathname to solve. +* +* Returns the realpath of @pathname on success, NULL otherwise. +*/ +char *tomoyo_realpath (const char *pathname) +{ +struct nameidata nd; + +if (pathname &&path_lookup (pathname,LOOKUP_FOLLOW , &nd) ==0 ) { +char *buf =tomoyo_realpath_from_path (&nd.path); +path_put (&nd.path); +return buf; + } +return NULL ; +} + +/** +* tomoyo_realpath_nofollow - Get realpath of a pathname. +* +* @pathname: The pathname to solve. +* +* Returns the realpath of @pathname on success, NULL otherwise. +*/ +char *tomoyo_realpath_nofollow (const char *pathname) +{ +struct nameidata nd; + +if (pathname &&path_lookup (pathname,0 , &nd) ==0 ) { +char *buf =tomoyo_realpath_from_path (&nd.path); +path_put (&nd.path); +return buf; + } +return NULL ; +} + +/* Memory allocated for non-string data. */ +static unsigned int tomoyo_allocated_memory_for_elements; +/* Quota for holding non-string data. */ +static unsigned int tomoyo_quota_for_elements; + +/** +* tomoyo_alloc_element - Allocate permanent memory for structures. +* +* @size: Size in bytes. +* +* Returns pointer to allocated memory on success, NULL otherwise. +* +* Memory has to be zeroed. +* The RAM is chunked, so NEVER try to kfree() the returned pointer. +*/ +void *tomoyo_alloc_element (const unsigned int size) +{ +static char *buf; +static DEFINE_MUTEX (lock); +static unsigned int buf_used_len =PATH_MAX ; +char *ptr =NULL ; +/*Assumes sizeof(void *) >= sizeof(long) is true. */ +const unsigned int word_aligned_size + =roundup (size,max (sizeof (void *),sizeof (long ))); +if (word_aligned_size >PATH_MAX ) +return NULL ; +/***** EXCLUSIVE SECTION START *****/ +mutex_lock (&lock); +if (buf_used_len + word_aligned_size >PATH_MAX ) { +if (!tomoyo_quota_for_elements || + tomoyo_allocated_memory_for_elements + +PATH_MAX <= tomoyo_quota_for_elements) + ptr =kzalloc (PATH_MAX ,GFP_KERNEL ); +if (!ptr) { +printk (KERN_WARNING "ERROR: Out of memory " +"for tomoyo_alloc_element().\n" ); +if (!tomoyo_policy_loaded) +panic ("MAC Initialization failed.\n" ); + }else { + buf = ptr; + tomoyo_allocated_memory_for_elements +=PATH_MAX ; + buf_used_len = word_aligned_size; + ptr = buf; + } + }else if (word_aligned_size) { +int i; + ptr = buf + buf_used_len; + buf_used_len += word_aligned_size; +for (i =0 ; i < word_aligned_size; i++) { +if (!ptr[i]) +continue ; +printk (KERN_ERR "WARNING: Reserved memory was tainted! " +"The system might go wrong.\n" ); + ptr[i] ='\0' ; + } + } +mutex_unlock (&lock); +/***** EXCLUSIVE SECTION END *****/ +return ptr; +} + +/* Memory allocated for string data in bytes. */ +static unsigned int tomoyo_allocated_memory_for_savename; +/* Quota for holding string data in bytes. */ +static unsigned int tomoyo_quota_for_savename; + +/* +* TOMOYO uses this hash only when appending a string into the string +* table. Frequency of appending strings is very low. So we don't need +* large (e.g. 64k) hash size. 256 will be sufficient. +*/ +#define TOMOYO_MAX_HASH 256 + +/* Structure for string data. */ +struct tomoyo_name_entry { +struct list1_head list; +struct tomoyo_path_info entry; +}; + +/* Structure for available memory region. */ +struct tomoyo_free_memory_block_list { +struct list_head list; +char *ptr;/* Pointer to a free area. */ +int len;/* Length of the area. */ +}; + +/* +* The list for "struct tomoyo_name_entry". +* +* This list is updated only inside tomoyo_save_name(), thus +* no global mutex exists. +*/ +static struct list1_head tomoyo_name_list[TOMOYO_MAX_HASH ]; + +/** +* tomoyo_save_name - Allocate permanent memory for string data. +* +* @name: The string to store into the permernent memory. +* +* Returns pointer to "struct tomoyo_path_info" on success, NULL otherwise. +* +* The RAM is shared, so NEVER try to modify or kfree() the returned name. +*/ +const struct tomoyo_path_info *tomoyo_save_name (const char *name) +{ +static LIST_HEAD (fmb_list); +static DEFINE_MUTEX (lock); +struct tomoyo_name_entry *ptr; +unsigned int hash; +/* fmb contains available size in bytes. +fmb is removed from the fmb_list when fmb->len becomes 0. */ +struct tomoyo_free_memory_block_list *fmb; +int len; +char *cp; + +if (!name) +return NULL ; + len =strlen (name) +1 ; +if (len >TOMOYO_MAX_PATHNAME_LEN ) { +printk (KERN_WARNING "ERROR: Name too long " +"for tomoyo_save_name().\n" ); +return NULL ; + } + hash =full_name_hash ((const unsigned char *) name, len -1 ); +/***** EXCLUSIVE SECTION START *****/ +mutex_lock (&lock); +list1_for_each_entry (ptr, &tomoyo_name_list[hash %TOMOYO_MAX_HASH ], + list) { +if (hash == ptr->entry.hash && !strcmp (name, ptr->entry.name)) + goto out; + } +list_for_each_entry (fmb, &fmb_list, list) { +if (len <= fmb->len) + goto ready; + } +if (!tomoyo_quota_for_savename || + tomoyo_allocated_memory_for_savename +PATH_MAX + <= tomoyo_quota_for_savename) + cp =kzalloc (PATH_MAX ,GFP_KERNEL ); +else + cp =NULL ; + fmb =kzalloc (sizeof (*fmb),GFP_KERNEL ); +if (!cp || !fmb) { +kfree (cp); +kfree (fmb); +printk (KERN_WARNING "ERROR: Out of memory " +"for tomoyo_save_name().\n" ); +if (!tomoyo_policy_loaded) +panic ("MAC Initialization failed.\n" ); + ptr =NULL ; + goto out; + } + tomoyo_allocated_memory_for_savename +=PATH_MAX ; +list_add (&fmb->list, &fmb_list); + fmb->ptr = cp; + fmb->len =PATH_MAX ; + ready: + ptr =tomoyo_alloc_element (sizeof (*ptr)); +if (!ptr) + goto out; + ptr->entry.name = fmb->ptr; +memmove (fmb->ptr, name, len); +tomoyo_fill_path_info (&ptr->entry); + fmb->ptr += len; + fmb->len -= len; +list1_add_tail (&ptr->list, &tomoyo_name_list[hash %TOMOYO_MAX_HASH ]); +if (fmb->len ==0 ) { +list_del (&fmb->list); +kfree (fmb); + } + out: +mutex_unlock (&lock); +/***** EXCLUSIVE SECTION END *****/ +return ptr ? &ptr->entry :NULL ; +} + +/** +* tomoyo_realpath_init - Initialize realpath related code. +* +* Returns 0. +*/ +static int __init tomoyo_realpath_init (void ) +{ +int i; + +BUILD_BUG_ON (TOMOYO_MAX_PATHNAME_LEN >PATH_MAX ); +for (i =0 ; i <TOMOYO_MAX_HASH ; i++) +INIT_LIST1_HEAD (&tomoyo_name_list[i]); +INIT_LIST1_HEAD (&tomoyo_kernel_domain.acl_info_list); + tomoyo_kernel_domain.domainname =tomoyo_save_name (TOMOYO_ROOT_NAME ); +list1_add_tail (&tomoyo_kernel_domain.list, &tomoyo_domain_list); +if (tomoyo_find_domain (TOMOYO_ROOT_NAME ) != &tomoyo_kernel_domain) +panic ("Can't register tomoyo_kernel_domain" ); +return 0 ; +} + +security_initcall (tomoyo_realpath_init); + +/* Memory allocated for temporary purpose. */ +static atomic_t tomoyo_dynamic_memory_size; + +/** +* tomoyo_alloc - Allocate memory for temporary purpose. +* +* @size: Size in bytes. +* +* Returns pointer to allocated memory on success, NULL otherwise. +*/ +void *tomoyo_alloc (const size_t size) +{ +void *p =kzalloc (size,GFP_KERNEL ); +if (p) +atomic_add (ksize (p), &tomoyo_dynamic_memory_size); +return p; +} + +/** +* tomoyo_free - Release memory allocated by tomoyo_alloc(). +* +* @p: Pointer returned by tomoyo_alloc(). May be NULL. +* +* Returns nothing. +*/ +void tomoyo_free (const void *p) +{ +if (p) { +atomic_sub (ksize (p), &tomoyo_dynamic_memory_size); +kfree (p); + } +} + +/** +* tomoyo_read_memory_counter - Check for memory usage in bytes. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns memory usage. +*/ +int tomoyo_read_memory_counter (struct tomoyo_io_buffer *head) +{ +if (!head->read_eof) { +const unsigned int shared + = tomoyo_allocated_memory_for_savename; +const unsigned int private + = tomoyo_allocated_memory_for_elements; +const unsigned int dynamic + =atomic_read (&tomoyo_dynamic_memory_size); +char buffer[64 ]; + +memset (buffer,0 ,sizeof (buffer)); +if (tomoyo_quota_for_savename) +snprintf (buffer,sizeof (buffer) -1 , +" (Quota: %10u)" , + tomoyo_quota_for_savename); +else + buffer[0 ] ='\0' ; +tomoyo_io_printf (head,"Shared: %10u%s\n" , shared, buffer); +if (tomoyo_quota_for_elements) +snprintf (buffer,sizeof (buffer) -1 , +" (Quota: %10u)" , + tomoyo_quota_for_elements); +else + buffer[0 ] ='\0' ; +tomoyo_io_printf (head,"Private: %10u%s\n" , private, buffer); +tomoyo_io_printf (head,"Dynamic: %10u\n" , dynamic); +tomoyo_io_printf (head,"Total: %10u\n" , + shared + private + dynamic); + head->read_eof = true; + } +return 0 ; +} + +/** +* tomoyo_write_memory_quota - Set memory quota. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns 0. +*/ +int tomoyo_write_memory_quota (struct tomoyo_io_buffer *head) +{ +char *data = head->write_buf; +unsigned int size; + +if (sscanf (data,"Shared: %u" , &size) ==1 ) + tomoyo_quota_for_savename = size; +else if (sscanf (data,"Private: %u" , &size) ==1 ) + tomoyo_quota_for_elements = size; +return 0 ; +}
security/tomoyo/realpath.h new file +63 -0
63 lines not on this page · raw message
Tetsuo Handa
maintainerPATCH 6/11 Common functions for TOMOYO Linux.
In reply to Tetsuo HandaThis file contains common functions (e.g. policy I/O, pattern matching).
----- About pattern matching -----
In TOMOYO, all wildcard characters are in the form of '\' + one character. Many programs interpret a character with a backslash as literal character, but TOMOYO interprets all characters without a backslash as literal characters.
Since the characters which are interpreted as wildcard are program dependent (e.g. For shells, '*' and '?' are wildcards but '.' isn't. For sed, '.' is a wildcard). Those who worry about some characters being interpreted as wildcard characters will add a backslash to every characters (e.g. \/\t\m\p\/\* ) without knowing whether '/' 't' 'm' 'p' '*' are wildcard characters or not.
Not only this approach requires the interpreter to remove unneeded backslashes but also this approach keeps introduction of new wildcard characters away. For example, until yesterday, 'p' was a literal character and users had been allowed to add a backslash before 'p'. But, from today, '\p' is now a wildcard character and users are no longer allowed to add a backslash before 'p' if the users want to represent literal 'p'.
If the interpreter forces users not to add a backslash to represent literal character, users must know all wildcard characters beforehand.
On the contrary, TOMOYO's approach allows introduction of new wildcard characters as needed, for users won't add a backslash to represent literal characters. If the interpreter forces users to add a backslash to represent wildcard character, users needn't to know all wildcard characters beforehand. Users can learn the meaning of new wildcard characters on first encounter.
You would go crazy with functions that handle string data. But these functions are needed to stay inside the kernel for validating, hashing and comparing string data.
To speeds up string comparison, TOMOYO hashes strings using "depth" (number of '/' characters). This is calculated by tomoyo_path_depth() and used by tomoyo_path_matches_pattern().
To be able to use strncmp() where possible, TOMOYO remembers initial length of strings without wildcard characters. This is calculated by tomoyo_const_part_length().
To be able to use strcmp() where possible, TOMOYO remembers whether strings contains wildcard character or not.
----- About policy interface -----
TOMOYO Linux creates the following files on securityfs (normally mounted on /sys/kernel/security) as interfaces between kernel and userspace. These files are for TOMOYO Linux management tools *only*, not for general programs.
* profile
* exception_policy
* domain_policy
* manager
* meminfo
* self_domain
* version
* .domain_status
* .process_status
** /sys/kernel/security/tomoyo/profile **
This file is used to read or write profiles.
"profile" means a running mode of process. A profile lists up functions and their modes in "$number-$variable=$value" format. The $number is profile number between 0 and 255. Each domain is assigned one profile. To assign profile to domains, use "ccs-setprofile" or "ccs-editpolicy" or "ccs-loadpolicy" commands.
(Example)
[root@tomoyo]# cat /sys/kernel/security/tomoyo/profile 0-COMMENT=-----Disabled Mode----- 0-MAC_FOR_FILE=disabled 0-MAX_ACCEPT_ENTRY=2048 0-TOMOYO_VERBOSE=disabled 1-COMMENT=-----Learning Mode----- 1-MAC_FOR_FILE=learning 1-MAX_ACCEPT_ENTRY=2048 1-TOMOYO_VERBOSE=disabled 2-COMMENT=-----Permissive Mode----- 2-MAC_FOR_FILE=permissive 2-MAX_ACCEPT_ENTRY=2048 2-TOMOYO_VERBOSE=enabled 3-COMMENT=-----Enforcing Mode----- 3-MAC_FOR_FILE=enforcing 3-MAX_ACCEPT_ENTRY=2048 3-TOMOYO_VERBOSE=enabled
- MAC_FOR_FILE:
Specifies access control level regarding file access requests.
- MAX_ACCEPT_ENTRY:
Limits the max number of ACL entries that are automatically appended during learning mode. Default is 2048.
- TOMOYO_VERBOSE:
Specifies whether to print domain policy violation messages or not.
** /sys/kernel/security/tomoyo/manager **
This file is used to read or append the list of programs or domains that can write to /sys/kernel/security/tomoyo interface. By default, only processes with both UID = 0 and EUID = 0 can modify policy via /sys/kernel/security/tomoyo interface. You can use keyword "manage_by_non_root" to allow policy modification by non root user.
(Example)
[root@tomoyo]# cat /sys/kernel/security/tomoyo/manager /usr/lib/ccs/loadpolicy /usr/lib/ccs/editpolicy /usr/lib/ccs/setlevel /usr/lib/ccs/setprofile /usr/lib/ccs/ld-watch /usr/lib/ccs/ccs-queryd
** /sys/kernel/security/tomoyo/exception_policy **
This file is used to read and write system global settings. Each line has a directive and operand pair. Directives are listed below.
- initialize_domain:
To initialize domain transition when specific program is executed, use initialize_domain directive.
* initialize_domain "program" from "domain"
* initialize_domain "program" from "the last program part of domain"
* initialize_domain "program"
If the part "from" and after is not given, the entry is applied to all domain. If the "domain" doesn't start with "<kernel>", the entry is applied to all domain whose domainname ends with "the last program part of domain".
This directive is intended to aggregate domain transitions for daemon program and program that are invoked by the kernel on demand, by transiting to different domain.
- keep_domain
To prevent domain transition when program is executed from specific domain, use keep_domain directive.
* keep_domain "program" from "domain"
* keep_domain "program" from "the last program part of domain"
* keep_domain "domain"
* keep_domain "the last program part of domain"
If the part "from" and before is not given, this entry is applied to all program. If the "domain" doesn't start with "<kernel>", the entry is applied to all domain whose domainname ends with "the last program part of domain".
This directive is intended to reduce total number of domains and memory usage by suppressing unneeded domain transitions. To declare domain keepers, use keep_domain directive followed by domain definition.
Any process that belongs to any domain declared with this directive, the process stays at the same domain unless any program registered with initialize_domain directive is executed.
In order to control domain transition in detail, you can use no_keep_domain/no_initialize_domain keywrods.
- alias:
To allow executing programs using the name of symbolic links, use alias keyword followed by dereferenced pathname and reference pathname. For example, /sbin/pidof is a symbolic link to /sbin/killall5 . In normal case, if /sbin/pidof is executed, the domain is defined as if /sbin/killall5 is executed. By specifying "alias /sbin/killall5 /sbin/pidof", you can run /sbin/pidof in the domain for /sbin/pidof .
(Example)
alias /sbin/killall5 /sbin/pidof
- allow_read:
To grant unconditionally readable permissions, use allow_read keyword followed by canonicalized file. This keyword is intended to reduce size of domain policy by granting read access to library files such as GLIBC and locale files. Exception is, if ignore_global_allow_read keyword is given to a domain, entries specified by this keyword are ignored.
(Example)allow_read /lib/libc-2.5.so
- file_pattern:
To declare pathname pattern, use file_pattern keyword followed by pathname pattern. The pathname pattern must be a canonicalized Pathname. This keyword is not applicable to neither granting execute permissions nor domain definitions.
For example, canonicalized pathname that contains a process ID (i.e. /proc/PID/ files) needs to be grouped in order to make access control work well.
(Example)file_pattern /proc/\$/cmdline
- path_group
To declare pathname group, use path_group keyword followed by name of the group and pathname pattern. For example, if you want to group all files under home directory, you can define
path_group HOME-DIR-FILE /home/\*/\*
path_group HOME-DIR-FILE /home/\*/\*/\*
path_group HOME-DIR-FILE /home/\*/\*/\*/\*
in the exception policy and use like
allow_read @HOME-DIR-FILE
to grant file access permission.
- deny_rewrite:
To deny overwriting already written contents of file (such as log files) by default, use deny_rewrite keyword followed by pathname pattern. Files whose pathname match the patterns are not permitted to open for writing without append mode or truncate unless the pathnames are explicitly granted using allow_rewrite keyword in domain policy. (Example)deny_rewrite /var/log/\*
- aggregator
To deal multiple programs as a single program, use aggregator keyword followed by name of original program and aggregated program. This keyword is intended to aggregate similar programs.
For example, /usr/bin/tac and /bin/cat are similar. By specifying "aggregator /usr/bin/tac /bin/cat", you can run /usr/bin/tac in the domain for /bin/cat .
For example, /usr/sbin/logrotate for Fedora Core 3 generates programs like /tmp/logrotate.\?\?\?\?\?\? and run them, but TOMOYO Linux doesn't allow using patterns for granting execute permission and defining domains. By specifying
"aggregator /tmp/logrotate.\?\?\?\?\?\? /tmp/logrotate.tmp", you can run /tmp/logrotate.\?\?\?\?\?\? as if /tmp/logrotate.tmp is running.
** /sys/kernel/security/tomoyo/domain_policy **
This file contains definition of all domains and permissions that are granted to each domain.
Lines from the next line to a domain definition ( any lines starting with "<kernel>") to the previous line to the next domain definitions are interpreted as access permissions for that domain.
** /sys/kernel/security/tomoyo/meminfo **
This file is to show the total RAM used to keep policy in the kernel by TOMOYO Linux in bytes.
(Example)
[root@tomoyo]# cat /sys/kernel/security/tomoyo/meminfo Shared: 61440 Private: 69632 Dynamic: 768 Total: 131840
You can set memory quota by writing to this file.
(Example)
[root@tomoyo]# echo Shared: 2097152 > /sys/kernel/security/tomoyo/meminfo [root@tomoyo]# echo Private: 2097152 > /sys/kernel/security/tomoyo/meminfo
** /sys/kernel/security/tomoyo/self_domain **
This file is to show the name of domain the caller process belongs to. (Example)
[root@etch]# cat /sys/kernel/security/tomoyo/self_domain <kernel> /usr/sbin/sshd /bin/zsh /bin/cat
** /sys/kernel/security/tomoyo/version **
This file is used for getting TOMOYO Linux's version. (Example)
[root@etch]# cat /sys/kernel/security/tomoyo/version 2.2.0-pre
** /sys/kernel/security/tomoyo/.domain_status **
This is a view (of a DBMS) that contains only profile number and domainnames of domain so that "ccs-setprofile" command can do line-oriented processing easily.
** /sys/kernel/security/tomoyo/.process_status **
This file is used by "ccs-ccstree" command to show "list of processes currently running" and "domains which each process belongs to" and "profile number which the domain is currently assigned" like "pstree" command. This file is writable by programs that aren't registered as policy manager.
--- security/tomoyo/common.c | 2160 +++++++++++++++++++++++++++++++++++++++++++++++ security/tomoyo/common.h | 337 +++++++ 2 files changed, 2497 insertions(+)
security/tomoyo/common.c new file +2160 -0
@@ -0,0 +1,2160 @@ +/* +* security/tomoyo/common.c +* +* Common functions for TOMOYO. +* +* Copyright (C) 2005-2008 NTT DATA CORPORATION +* +* Version: 2.2.0-pre 2008/11/11 +* +*/ + +#include <linux/uaccess.h> +#include <linux/security.h> +#include <linux/hardirq.h> +#include "realpath.h" +#include "common.h" +#include "tomoyo.h" + +/* Has loading policy done? */ +bool tomoyo_policy_loaded; + +/* String table for functionality that takes 4 modes. */ +static const char *tomoyo_mode_4[4 ] = { +"disabled" ,"learning" ,"permissive" ,"enforcing" +}; +/* String table for functionality that takes 2 modes. */ +static const char *tomoyo_mode_2[4 ] = { +"disabled" ,"enabled" ,"enabled" ,"enabled" +}; + +/* Table for profile. */ +static struct { +const char *keyword; +unsigned int current_value; +const unsigned int max_value; +} tomoyo_control_array[TOMOYO_MAX_CONTROL_INDEX ] = { + [TOMOYO_MAC_FOR_FILE ] = {"MAC_FOR_FILE" ,0 ,3 }, + [TOMOYO_MAX_ACCEPT_ENTRY ] = {"MAX_ACCEPT_ENTRY" ,2048 ,INT_MAX }, + [TOMOYO_VERBOSE ] = {"TOMOYO_VERBOSE" ,1 ,1 }, +}; + +/* Profile table. Memory is allocated as needed. */ +static struct tomoyo_profile { +unsigned int value[TOMOYO_MAX_CONTROL_INDEX ]; +const struct tomoyo_path_info *comment; +} *tomoyo_profile_ptr[TOMOYO_MAX_PROFILES ]; + +/* Permit policy management by non-root user? */ +static bool tomoyo_manage_by_non_root; + +/* Utility functions. */ + +/* Open operation for /sys/kernel/security/tomoyo/ interface. */ +static int tomoyo_open_control (const u8 type,struct file *file); +/* Close /sys/kernel/security/tomoyo/ interface. */ +static int tomoyo_close_control (struct file *file); +/* Read operation for /sys/kernel/security/tomoyo/ interface. */ +static int tomoyo_read_control (struct file *file,char __user *buffer, +const int buffer_len); +/* Write operation for /sys/kernel/security/tomoyo/ interface. */ +static int tomoyo_write_control (struct file *file,const char __user *buffer, +const int buffer_len); + +/** +* tomoyo_is_byte_range - Check whether the string isa \ooo style octal value. +* +* @str: Pointer to the string. +* +* Returns true if @str is a \ooo style octal value, false otherwise. +* +* TOMOYO uses \ooo style representation for 0x01 - 0x20 and 0x7F - 0xFF. +* This function verifies that \ooo is in valid range. +*/ +static inline bool tomoyo_is_byte_range (const char *str) +{ +return *str >='0' && *str++ <='3' && + *str >='0' && *str++ <='7' && + *str >='0' && *str <='7' ; +} + +/** +* tomoyo_is_alphabet_char - Check whether the character is an alphabet. +* +* @c: The character to check. +* +* Returns true if @c is an alphabet character, false otherwise. +*/ +static inline bool tomoyo_is_alphabet_char (const char c) +{ +return (c >='A' && c <='Z' ) || (c >='a' && c <='z' ); +} + +/** +* tomoyo_make_byte - Make byte value from three octal characters. +* +* @c1: The first character. +* @c2: The second character. +* @c3: The third character. +* +* Returns byte value. +*/ +static inline u8 tomoyo_make_byte (const u8 c1,const u8 c2,const u8 c3) +{ +return ((c1 -'0' ) <<6 ) + ((c2 -'0' ) <<3 ) + (c3 -'0' ); +} + +/** +* tomoyo_str_starts - Check whether the given string starts with the given keyword. +* +* @src: Pointer to pointer to the string. +* @find: Pointer to the keyword. +* +* Returns true if @src starts with @find, false otherwise. +* +* The @src is updated to point the first character after the @find +* if @src starts with @find. +*/ +static bool tomoyo_str_starts (char **src,const char *find) +{ +const int len =strlen (find); +char *tmp = *src; + +if (strncmp (tmp, find, len)) +return false; + tmp += len; + *src = tmp; +return true; +} + +/** +* tomoyo_normalize_line - Format string. +* +* @buffer: The line to normalize. +* +* Leading and trailing whitespaces are removed. +* Multiple whitespaces are packed into single space. +* +* Returns nothing. +*/ +static void tomoyo_normalize_line (unsigned char *buffer) +{ +unsigned char *sp = buffer; +unsigned char *dp = buffer; +bool first = true; + +while (tomoyo_is_invalid (*sp)) + sp++; +while (*sp) { +if (!first) + *dp++ =' ' ; + first = false; +while (tomoyo_is_valid (*sp)) + *dp++ = *sp++; +while (tomoyo_is_invalid (*sp)) + sp++; + } + *dp ='\0' ; +} + +/** +* tomoyo_is_correct_path - Validate a pathname. +* @filename: The pathname to check. +* @start_type: Should the pathname start with '/'? +* 1 = must / -1 = must not / 0 = don't care +* @pattern_type: Can the pathname contain a wildcard? +* 1 = must / -1 = must not / 0 = don't care +* @end_type: Should the pathname end with '/'? +* 1 = must / -1 = must not / 0 = don't care +* @function: The name of function calling me. +* +* Check whether the given filename follows the naming rules. +* Returns true if @filename follows the naming rules, false otherwise. +*/ +bool tomoyo_is_correct_path (const char *filename,const s8 start_type, +const s8 pattern_type,const s8 end_type, +const char *function) +{ +bool contains_pattern = false; +unsigned char c; +unsigned char d; +unsigned char e; +const char *original_filename = filename; + +if (!filename) + goto out; + c = *filename; +if (start_type ==1 ) {/* Must start with '/' */ +if (c !='/' ) + goto out; + }else if (start_type ==-1 ) {/* Must not start with '/' */ +if (c =='/' ) + goto out; + } +if (c) + c = *(filename +strlen (filename) -1 ); +if (end_type ==1 ) {/* Must end with '/' */ +if (c !='/' ) + goto out; + }else if (end_type ==-1 ) {/* Must not end with '/' */ +if (c =='/' ) + goto out; + } +while ((c = *filename++) !='\0' ) { +if (c =='\\' ) { +switch ((c = *filename++)) { +case '\\' :/* "\\" */ +continue ; +case '$' :/* "\$" */ +case '+' :/* "\+" */ +case '?' :/* "\?" */ +case '*' :/* "\*" */ +case '@' :/* "\@" */ +case 'x' :/* "\x" */ +case 'X' :/* "\X" */ +case 'a' :/* "\a" */ +case 'A' :/* "\A" */ +case '-' :/* "\-" */ +if (pattern_type ==-1 ) +break ;/* Must not contain pattern */ + contains_pattern = true; +continue ; +case '0' :/* "\ooo" */ +case '1' : +case '2' : +case '3' : + d = *filename++; +if (d <'0' || d >'7' ) +break ; + e = *filename++; +if (e <'0' || e >'7' ) +break ; + c =tomoyo_make_byte (c, d, e); +if (tomoyo_is_invalid (c)) +continue ;/* pattern is not \000 */ + } + goto out; + }else if (tomoyo_is_invalid (c)) { + goto out; + } + } +if (pattern_type ==1 ) {/* Must contain pattern */ +if (!contains_pattern) + goto out; + } +return true; + out: +printk (KERN_DEBUG "%s: Invalid pathname '%s'\n" , function, + original_filename); +return false; +} + +/** +* tomoyo_is_correct_domain - Check whether the given domainname follows the naming rules. +* @domainname: The domainname to check. +* @function: The name of function calling me. +* +* Returns true if @domainname follows the naming rules, false otherwise. +*/ +bool tomoyo_is_correct_domain (const unsigned char *domainname, +const char *function) +{ +unsigned char c; +unsigned char d; +unsigned char e; +const char *org_domainname = domainname; + +if (!domainname ||strncmp (domainname,TOMOYO_ROOT_NAME , +TOMOYO_ROOT_NAME_LEN )) + goto out; + domainname +=TOMOYO_ROOT_NAME_LEN ; +if (!*domainname) +return true; +do { +if (*domainname++ !=' ' ) + goto out; +if (*domainname++ !='/' ) + goto out; +while ((c = *domainname) !='\0' && c !=' ' ) { + domainname++; +if (c =='\\' ) { + c = *domainname++; +switch ((c)) { +case '\\' :/* "\\" */ +continue ; +case '0' :/* "\ooo" */ +case '1' : +case '2' : +case '3' : + d = *domainname++; +if (d <'0' || d >'7' ) +break ; + e = *domainname++; +if (e <'0' || e >'7' ) +break ; + c =tomoyo_make_byte (c, d, e); +if (tomoyo_is_invalid (c)) +/* pattern is not \000 */ +continue ; + } + goto out; + }else if (tomoyo_is_invalid (c)) { + goto out; + } + } + }while (*domainname); +return true; + out: +printk (KERN_DEBUG "%s: Invalid domainname '%s'\n" , function, + org_domainname); +return false; +} + +/** +* tomoyo_is_domain_def - Check whether the given token can be a domainname. +* +* @buffer: The token to check. +* +* Returns true if @buffer possibly be a domainname, false otherwise. +*/ +bool tomoyo_is_domain_def (const unsigned char *buffer) +{ +return !strncmp (buffer,TOMOYO_ROOT_NAME ,TOMOYO_ROOT_NAME_LEN ); +} + +/** +* tomoyo_find_domain - Find a domain by the given name. +* +* @domainname: The domainname to find. +* +* Returns pointer to "struct tomoyo_domain_info" if found, NULL otherwise. +*/ +struct tomoyo_domain_info *tomoyo_find_domain (const char *domainname) +{ +struct tomoyo_domain_info *domain; +struct tomoyo_path_info name; + + name.name = domainname; +tomoyo_fill_path_info (&name); +list1_for_each_entry (domain, &tomoyo_domain_list, list) { +if (!domain->is_deleted && + !tomoyo_pathcmp (&name, domain->domainname)) +return domain; + } +return NULL ; +} + +/** +* tomoyo_path_depth - Evaluate the number of '/' in a string. +* +* @pathname: The string to evaluate. +* +* Returns path depth of the string. +* +* I score 2 for each of the '/' in the @pathname +* and score 1 if the @pathname ends with '/'. +*/ +static int tomoyo_path_depth (const char *pathname) +{ +int i =0 ; + +if (pathname) { +const char *ep = pathname +strlen (pathname); +if (pathname < ep--) { +if (*ep !='/' ) + i++; +while (pathname <= ep) +if (*ep-- =='/' ) + i +=2 ; + } + } +return i; +} + +/** +* tomoyo_const_part_length - Evaluate the initial length without a pattern in a token. +* +* @filename: The string to evaluate. +* +* Returns the initial length without a pattern in @filename. +*/ +static int tomoyo_const_part_length (const char *filename) +{ +char c; +int len =0 ; + +if (!filename) +return 0 ; +while ((c = *filename++) !='\0' ) { +if (c !='\\' ) { + len++; +continue ; + } + c = *filename++; +switch (c) { +case '\\' :/* "\\" */ + len +=2 ; +continue ; +case '0' :/* "\ooo" */ +case '1' : +case '2' : +case '3' : + c = *filename++; +if (c <'0' || c >'7' ) +break ; + c = *filename++; +if (c <'0' || c >'7' ) +break ; + len +=4 ; +continue ; + } +break ; + } +return len; +} + +/** +* tomoyo_fill_path_info - Fill in "struct tomoyo_path_info" members. +* +* @ptr: Pointer to "struct tomoyo_path_info" to fill in. +* +* The caller sets "struct tomoyo_path_info"->name. +*/ +void tomoyo_fill_path_info (struct tomoyo_path_info *ptr) +{ +const char *name = ptr->name; +const int len =strlen (name); + + ptr->total_len = len; + ptr->const_len =tomoyo_const_part_length (name); + ptr->is_dir = len && (name[len -1 ] =='/' ); + ptr->is_patterned = (ptr->const_len < len); + ptr->hash =full_name_hash (name, len); + ptr->depth =tomoyo_path_depth (name); +} + +/** +* tomoyo_file_matches_to_pattern2 - Pattern matching without '/' character +* and "\-" pattern. +* +* @filename: The start of string to check. +* @filename_end: The end of string to check. +* @pattern: The start of pattern to compare. +* @pattern_end: The end of pattern to compare. +* +* Returns true if @filename matches @pattern, false otherwise. +*/ +static bool tomoyo_file_matches_to_pattern2 (const char *filename, +const char *filename_end, +const char *pattern, +const char *pattern_end) +{ +while (filename < filename_end && pattern < pattern_end) { +char c; +if (*pattern !='\\' ) { +if (*filename++ != *pattern++) +return false; +continue ; + } + c = *filename; + pattern++; +switch (*pattern) { +int i; +int j; +case '?' : +if (c =='/' ) { +return false; + }else if (c =='\\' ) { +if (filename[1 ] =='\\' ) + filename++; +else if (tomoyo_is_byte_range (filename +1 )) + filename +=3 ; +else +return false; + } +break ; +case '\\' : +if (c !='\\' ) +return false; +if (*++filename !='\\' ) +return false; +break ; +case '+' : +if (!isdigit (c)) +return false; +break ; +case 'x' : +if (!isxdigit (c)) +return false; +break ; +case 'a' : +if (!tomoyo_is_alphabet_char (c)) +return false; +break ; +case '0' : +case '1' : +case '2' : +case '3' : +if (c =='\\' &&tomoyo_is_byte_range (filename +1 ) + &&strncmp (filename +1 , pattern,3 ) ==0 ) { + filename +=3 ; + pattern +=2 ; +break ; + } +return false;/* Not matched. */ +case '*' : +case '@' : +for (i =0 ; i <= filename_end - filename; i++) { +if (tomoyo_file_matches_to_pattern2 ( + filename + i, filename_end, + pattern +1 , pattern_end)) +return true; + c = filename[i]; +if (c =='.' && *pattern =='@' ) +break ; +if (c !='\\' ) +continue ; +if (filename[i +1 ] =='\\' ) + i++; +else if (tomoyo_is_byte_range (filename + i +1 )) + i +=3 ; +else +break ;/* Bad pattern. */ + } +return false;/* Not matched. */ +default : + j =0 ; + c = *pattern; +if (c =='$' ) { +while (isdigit (filename[j])) + j++; + }else if (c =='X' ) { +while (isxdigit (filename[j])) + j++; + }else if (c =='A' ) { +while (tomoyo_is_alphabet_char (filename[j])) + j++; + } +for (i =1 ; i <= j; i++) { +if (tomoyo_file_matches_to_pattern2 ( + filename + i, filename_end, + pattern +1 , pattern_end)) +return true; + } +return false;/* Not matched or bad pattern. */ + } + filename++; + pattern++; + } +while (*pattern =='\\' && + (*(pattern +1 ) =='*' || *(pattern +1 ) =='@' )) + pattern +=2 ; +return filename == filename_end && pattern == pattern_end; +} + +/** +* tomoyo_file_matches_to_pattern - Pattern matching without without '/' character. +* +* @filename: The start of string to check. +* @filename_end: The end of string to check. +* @pattern: The start of pattern to compare. +* @pattern_end: The end of pattern to compare. +* +* Returns true if @filename matches @pattern, false otherwise. +*/ +static bool tomoyo_file_matches_to_pattern (const char *filename, +const char *filename_end, +const char *pattern, +const char *pattern_end) +{ +const char *pattern_start = pattern; +bool first = true; +bool result; + +while (pattern < pattern_end -1 ) { +/* Split at "\-" pattern. */ +if (*pattern++ !='\\' || *pattern++ !='-' ) +continue ; + result =tomoyo_file_matches_to_pattern2 (filename, + filename_end, + pattern_start, + pattern -2 ); +if (first) + result = !result; +if (result) +return false; + first = false; + pattern_start = pattern; + } + result =tomoyo_file_matches_to_pattern2 (filename, filename_end, + pattern_start, pattern_end); +return first ? result : !result; +} + +/** +* tomoyo_path_matches_pattern - Check whether the given filename matches the given pattern. +* @filename: The filename to check. +* @pattern: The pattern to compare. +* +* Returns true if matches, false otherwise. +* +* The following patterns are available. +* \\ \ itself. +* \ooo Octal representation of a byte. +* \* More than or equals to 0 character other than '/'. +* \@ More than or equals to 0 character other than '/' or '.'. +* \? 1 byte character other than '/'. +* \$ More than or equals to 1 decimal digit. +* \+ 1 decimal digit. +* \X More than or equals to 1 hexadecimal digit. +* \x 1 hexadecimal digit. +* \A More than or equals to 1 alphabet character. +* \a 1 alphabet character. +* \- Subtraction operator. +*/ +bool tomoyo_path_matches_pattern (const struct tomoyo_path_info *filename, +const struct tomoyo_path_info *pattern) +{ +/* +if (!filename || !pattern) +return false; +*/ +const char *f = filename->name; +const char *p = pattern->name; +const int len = pattern->const_len; + +/* If @pattern doesn't contain pattern, I can use strcmp(). */ +if (!pattern->is_patterned) +return !tomoyo_pathcmp (filename, pattern); +/* Dont compare if the number of '/' differs. */ +if (filename->depth != pattern->depth) +return false; +/* Compare the initial length without patterns. */ +if (strncmp (f, p, len)) +return false; + f += len; + p += len; +/* Main loop. Compare each directory component. */ +while (*f && *p) { +const char *f_delimiter =strchr (f,'/' ); +const char *p_delimiter =strchr (p,'/' ); +if (!f_delimiter) + f_delimiter = f +strlen (f); +if (!p_delimiter) + p_delimiter = p +strlen (p); +if (!tomoyo_file_matches_to_pattern (f, f_delimiter, + p, p_delimiter)) +return false; + f = f_delimiter; +if (*f) + f++; + p = p_delimiter; +if (*p) + p++; + } +/* Ignore trailing "\*" and "\@" in @pattern. */ +while (*p =='\\' && + (*(p +1 ) =='*' || *(p +1 ) =='@' )) + p +=2 ; +return !*f && !*p; +} + +/** +* tomoyo_io_printf - Transactional printf() to "struct tomoyo_io_buffer" structure. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* @fmt: The printf()'s format string, followed by parameters. +* +* Returns true if output was written, false otherwise. +* +* The snprintf() will truncate, but tomoyo_io_printf() won't. +*/ +bool tomoyo_io_printf (struct tomoyo_io_buffer *head,const char *fmt, ...) +{ +va_list args; +int len; +int pos = head->read_avail; +int size = head->readbuf_size - pos; + +if (size <=0 ) +return false; +va_start (args, fmt); + len =vsnprintf (head->read_buf + pos, size, fmt, args); +va_end (args); +if (pos + len >= head->readbuf_size) +return false; + head->read_avail += len; +return true; +} + +/** +* tomoyo_get_exe - Get tomoyo_realpath() of current process. +* +* Returns the tomoyo_realpath() of current process on success, NULL otherwise. +* +* This function uses tomoyo_alloc(), so the caller must call tomoyo_free() +* if this function didn't return NULL. +*/ +static const char *tomoyo_get_exe (void ) +{ +struct mm_struct *mm = current->mm; +struct vm_area_struct *vma; +const char *cp =NULL ; + +if (!mm) +return NULL ; +down_read (&mm->mmap_sem); +for (vma = mm->mmap; vma; vma = vma->vm_next) { +if ((vma->vm_flags &VM_EXECUTABLE ) && vma->vm_file) { + cp =tomoyo_realpath_from_path (&vma->vm_file->f_path); +break ; + } + } +up_read (&mm->mmap_sem); +return cp; +} + +/** +* tomoyo_get_msg - Get warning message. +* +* @is_enforce: Is it enforcing mode? +* +* Returns "ERROR" or "WARNING". +*/ +const char *tomoyo_get_msg (const bool is_enforce) +{ +if (is_enforce) +return "ERROR" ; +else +return "WARNING" ; +} + +/** +* tomoyo_check_flags - Check mode for specified functionality. +* +* @domain: Pointer to "struct tomoyo_domain_info". +* @index: The functionality to check mode. +* +* TOMOYO checks only process context. +* This code disables TOMOYO's enforcement in case the function is called from +* interrupt context. +*/ +unsigned int tomoyo_check_flags (const struct tomoyo_domain_info *domain, +const u8 index) +{ +const u8 profile = domain->profile; + +if (WARN_ON (in_interrupt ())) +return 0 ; + return tomoyo_policy_loaded && index < TOMOYO_MAX_CONTROL_INDEX +#if TOMOYO_MAX_PROFILES !=256 + && profile < TOMOYO_MAX_PROFILES +#endif + && tomoyo_profile_ptr[profile] ? + tomoyo_profile_ptr[profile]->value[index] :0 ; +} + +/** +* tomoyo_verbose_mode - Check whether TOMOYO is verbose mode. +* +* @domain: Pointer to "struct tomoyo_domain_info". +* +* Returns true if domain policy violation warning should be printed to +* console. +*/ +bool tomoyo_verbose_mode (const struct tomoyo_domain_info *domain) +{ +return tomoyo_check_flags (domain,TOMOYO_VERBOSE ) !=0 ; +} + +/** +* tomoyo_domain_quota_is_ok - Check for domain's quota. +* +* @domain: Pointer to "struct tomoyo_domain_info". +* +* Returns true if the domain is not exceeded quota, false otherwise. +*/ +bool tomoyo_domain_quota_is_ok (struct tomoyo_domain_info *const domain) +{ +unsigned int count =0 ; +struct tomoyo_acl_info *ptr; + +if (!domain) +return true; +list1_for_each_entry (ptr, &domain->acl_info_list, list) { +if (ptr->type &TOMOYO_ACL_DELETED ) +continue ; +switch (tomoyo_acl_type2 (ptr)) { +struct tomoyo_single_path_acl_record *acl1; +struct tomoyo_double_path_acl_record *acl2; +u16 perm; +case TOMOYO_TYPE_SINGLE_PATH_ACL : + acl1 =container_of (ptr, + struct tomoyo_single_path_acl_record, + head); + perm = acl1->perm; +if (perm & (1 <<TOMOYO_TYPE_EXECUTE_ACL )) + count++; +if (perm & + ((1 <<TOMOYO_TYPE_READ_ACL ) | + (1 <<TOMOYO_TYPE_WRITE_ACL ))) + count++; +if (perm & (1 <<TOMOYO_TYPE_CREATE_ACL )) + count++; +if (perm & (1 <<TOMOYO_TYPE_UNLINK_ACL )) + count++; +if (perm & (1 <<TOMOYO_TYPE_MKDIR_ACL )) + count++; +if (perm & (1 <<TOMOYO_TYPE_RMDIR_ACL )) + count++; +if (perm & (1 <<TOMOYO_TYPE_MKFIFO_ACL )) + count++; +if (perm & (1 <<TOMOYO_TYPE_MKSOCK_ACL )) + count++; +if (perm & (1 <<TOMOYO_TYPE_MKBLOCK_ACL )) + count++; +if (perm & (1 <<TOMOYO_TYPE_MKCHAR_ACL )) + count++; +if (perm & (1 <<TOMOYO_TYPE_TRUNCATE_ACL )) + count++; +if (perm & (1 <<TOMOYO_TYPE_SYMLINK_ACL )) + count++; +if (perm & (1 <<TOMOYO_TYPE_REWRITE_ACL )) + count++; +break ; +case TOMOYO_TYPE_DOUBLE_PATH_ACL : + acl2 =container_of (ptr, + struct tomoyo_double_path_acl_record, + head); + perm = acl2->perm; +if (perm & (1 <<TOMOYO_TYPE_LINK_ACL )) + count++; +if (perm & (1 <<TOMOYO_TYPE_RENAME_ACL )) + count++; +break ; + } + } +if (count <tomoyo_check_flags (domain,TOMOYO_MAX_ACCEPT_ENTRY )) +return true; +if (!domain->quota_warned) { + domain->quota_warned = true; +printk (KERN_WARNING "TOMOYO-WARNING: " +"Domain '%s' has so many ACLs to hold. " +"Stopped learning mode.\n" , domain->domainname->name); + } +return false; +} + +/** +* tomoyo_find_or_assign_new_profile - Create a new profile. +* +* @profile: Profile number to create. +* +* Returns pointer to "struct tomoyo_profile" on success, NULL otherwise. +*/ +static struct tomoyo_profile *tomoyo_find_or_assign_new_profile (const unsigned +int profile) +{ +static DEFINE_MUTEX (lock); +struct tomoyo_profile *ptr =NULL ; +int i; + +if (profile >=TOMOYO_MAX_PROFILES ) +return NULL ; +/***** EXCLUSIVE SECTION START *****/ +mutex_lock (&lock); + ptr = tomoyo_profile_ptr[profile]; +if (ptr) + goto ok; + ptr =tomoyo_alloc_element (sizeof (*ptr)); +if (!ptr) + goto ok; +for (i =0 ; i <TOMOYO_MAX_CONTROL_INDEX ; i++) + ptr->value[i] = tomoyo_control_array[i].current_value; +mb ();/* Avoid out-of-order execution. */ + tomoyo_profile_ptr[profile] = ptr; + ok: +mutex_unlock (&lock); +/***** EXCLUSIVE SECTION END *****/ +return ptr; +} + +/** +* tomoyo_write_profile - Write to profile table. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns 0 on success, negative value otherwise. +*/ +static int tomoyo_write_profile (struct tomoyo_io_buffer *head) +{ +char *data = head->write_buf; +unsigned int i; +unsigned int value; +char *cp; +struct tomoyo_profile *profile; +unsigned long num; + + cp =strchr (data,'-' ); +if (cp) + *cp ='\0' ; +if (strict_strtoul (data,10 , &num)) +return -EINVAL ; +if (cp) + data = cp +1 ; + profile =tomoyo_find_or_assign_new_profile (num); +if (!profile) +return -EINVAL ; + cp =strchr (data,'=' ); +if (!cp) +return -EINVAL ; + *cp ='\0' ; +if (!strcmp (data,"COMMENT" )) { + profile->comment =tomoyo_save_name (cp +1 ); +return 0 ; + } +for (i =0 ; i <TOMOYO_MAX_CONTROL_INDEX ; i++) { +if (strcmp (data, tomoyo_control_array[i].keyword)) +continue ; +if (sscanf (cp +1 ,"%u" , &value) !=1 ) { +int j; +const char **modes; +switch (i) { +case TOMOYO_VERBOSE : + modes = tomoyo_mode_2; +break ; +default : + modes = tomoyo_mode_4; +break ; + } +for (j =0 ; j <4 ; j++) { +if (strcmp (cp +1 , modes[j])) +continue ; + value = j; +break ; + } +if (j ==4 ) +return -EINVAL ; + }else if (value > tomoyo_control_array[i].max_value) { + value = tomoyo_control_array[i].max_value; + } + profile->value[i] = value; +return 0 ; + } +return -EINVAL ; +} + +/** +* tomoyo_read_profile - Read from profile table. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns 0. +*/ +static int tomoyo_read_profile (struct tomoyo_io_buffer *head) +{ +static const int total =TOMOYO_MAX_CONTROL_INDEX +1 ; +int step; + +if (head->read_eof) +return 0 ; +for (step = head->read_step; step <TOMOYO_MAX_PROFILES * total; + step++) { +const u8 index = step / total; +u8 type = step % total; +const struct tomoyo_profile *profile + = tomoyo_profile_ptr[index]; + head->read_step = step; +if (!profile) +continue ; +if (!type) {/* Print profile' comment tag. */ +if (!tomoyo_io_printf (head,"%u-COMMENT=%s\n" , + index, profile->comment ? + profile->comment->name :"" )) +break ; +continue ; + } + type--; +if (type <TOMOYO_MAX_CONTROL_INDEX ) { +const unsigned int value = profile->value[type]; +const char **modes =NULL ; +const char *keyword + = tomoyo_control_array[type].keyword; +switch (tomoyo_control_array[type].max_value) { +case 3 : + modes = tomoyo_mode_4; +break ; +case 1 : + modes = tomoyo_mode_2; +break ; + } +if (modes) { +if (!tomoyo_io_printf (head,"%u-%s=%s\n" , index, + keyword, modes[value])) +break ; + }else { +if (!tomoyo_io_printf (head,"%u-%s=%u\n" , index, + keyword, value)) +break ; + } + } + } +if (step ==TOMOYO_MAX_PROFILES * total) + head->read_eof = true; +return 0 ; +} + +/* Structure for policy manager. */ +struct tomoyo_policy_manager_entry { +struct list1_head list; +/* A path to program or a domainname. */ +const struct tomoyo_path_info *manager; +bool is_domain;/* True if manager is a domainname. */ +bool is_deleted;/* True if this entry is deleted. */ +}; + +/* +* The list for "struct tomoyo_policy_manager_entry". +* +* This list is updated only inside update_manager_entry(), thus +* no global mutex exists. +*/ +static LIST1_HEAD (tomoyo_policy_manager_list); + +/** +* tomoyo_update_manager_entry - Add a manager entry. +* +* @manager: The path to manager or the domainnamme. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int tomoyo_update_manager_entry (const char *manager, +const bool is_delete) +{ +struct tomoyo_policy_manager_entry *new_entry; +struct tomoyo_policy_manager_entry *ptr; +static DEFINE_MUTEX (lock); +const struct tomoyo_path_info *saved_manager; +int error = -ENOMEM ; +bool is_domain = false; + +if (tomoyo_is_domain_def (manager)) { +if (!tomoyo_is_correct_domain (manager, __func__)) +return -EINVAL ; + is_domain = true; + }else { +if (!tomoyo_is_correct_path (manager,1 ,-1 ,-1 , __func__)) +return -EINVAL ; + } + saved_manager =tomoyo_save_name (manager); +if (!saved_manager) +return -ENOMEM ; +/***** EXCLUSIVE SECTION START *****/ +mutex_lock (&lock); +list1_for_each_entry (ptr, &tomoyo_policy_manager_list, list) { +if (ptr->manager != saved_manager) +continue ; + ptr->is_deleted = is_delete; + error =0 ; + goto out; + } +if (is_delete) { + error = -ENOENT ; + goto out; + } + new_entry =tomoyo_alloc_element (sizeof (*new_entry)); +if (!new_entry) + goto out; + new_entry->manager = saved_manager; + new_entry->is_domain = is_domain; +list1_add_tail (&new_entry->list, &tomoyo_policy_manager_list); + error =0 ; + out: +mutex_unlock (&lock); +/***** EXCLUSIVE SECTION END *****/ +return error; +} + +/** +* tomoyo_write_manager_policy - Write manager policy. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns 0 on success, negative value otherwise. +*/ +static int tomoyo_write_manager_policy (struct tomoyo_io_buffer *head) +{ +char *data = head->write_buf; +bool is_delete =tomoyo_str_starts (&data,TOMOYO_KEYWORD_DELETE ); + +if (!strcmp (data,"manage_by_non_root" )) { + tomoyo_manage_by_non_root = !is_delete; +return 0 ; + } +return tomoyo_update_manager_entry (data, is_delete); +} + +/** +* tomoyo_read_manager_policy - Read manager policy. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns 0. +*/ +static int tomoyo_read_manager_policy (struct tomoyo_io_buffer *head) +{ +struct list1_head *pos; + +if (head->read_eof) +return 0 ; +list1_for_each_cookie (pos, head->read_var2, + &tomoyo_policy_manager_list) { +struct tomoyo_policy_manager_entry *ptr; + ptr = list1_entry(pos, struct tomoyo_policy_manager_entry, + list); +if (ptr->is_deleted) +continue ; +if (!tomoyo_io_printf (head,"%s\n" , ptr->manager->name)) +return 0 ; + } + head->read_eof = true; +return 0 ; +} + +/** +* tomoyo_is_policy_manager - Check whether the current process is a policy manager. +* +* Returns true if the current process is permitted to modify policy +* via /sys/kernel/security/tomoyo/ interface. +*/ +static bool tomoyo_is_policy_manager (void ) +{ +struct tomoyo_policy_manager_entry *ptr; +const char *exe; +const struct task_struct *task = current; +const struct tomoyo_path_info *domainname =tomoyo_domain ()->domainname; +bool found = false; + +if (!tomoyo_policy_loaded) +return true; +if (!tomoyo_manage_by_non_root && (task->cred->uid || task->cred->euid)) +return false; +list1_for_each_entry (ptr, &tomoyo_policy_manager_list, list) { +if (!ptr->is_deleted && ptr->is_domain + && !tomoyo_pathcmp (domainname, ptr->manager)) +return true; + } + exe =tomoyo_get_exe (); +if (!exe) +return false; +list1_for_each_entry (ptr, &tomoyo_policy_manager_list, list) { +if (!ptr->is_deleted && !ptr->is_domain + && !strcmp (exe, ptr->manager->name)) { + found = true; +break ; + } + } +if (!found) {/* Reduce error messages. */ +static pid_t last_pid; +const pid_t pid = current->pid; +if (last_pid != pid) { +printk (KERN_WARNING "%s ( %s ) is not permitted to " +"update policies.\n" , domainname->name, exe); + last_pid = pid; + } + } +tomoyo_free (exe); +return found; +} + +/** +* tomoyo_is_select_one - Parse select command. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* @data: String to parse. +* +* Returns true on success, false otherwise. +*/ +static bool tomoyo_is_select_one (struct tomoyo_io_buffer *head, +const char *data) +{ +unsigned int pid; +struct tomoyo_domain_info *domain =NULL ; + +if (sscanf (data,"pid=%u" , &pid) ==1 ) { +struct task_struct *p; +/***** CRITICAL SECTION START *****/ +read_lock (&tasklist_lock); + p =find_task_by_vpid (pid); +if (p) + domain =tomoyo_real_domain (p); +read_unlock (&tasklist_lock); +/***** CRITICAL SECTION END *****/ + }else if (!strncmp (data,"domain=" ,7 )) { +if (tomoyo_is_domain_def (data +7 )) + domain =tomoyo_find_domain (data +7 ); + }else +return false; + head->write_var1 = domain; +/* Accessing read_buf is safe because head->io_sem is held. */ +if (!head->read_buf) +return true;/* Do nothing if open(O_WRONLY). */ + head->read_avail =0 ; +tomoyo_io_printf (head,"# select %s\n" , data); + head->read_single_domain = true; + head->read_eof = !domain; +if (domain) { +struct tomoyo_domain_info *d; + head->read_var1 =NULL ; +list1_for_each_entry (d, &tomoyo_domain_list, list) { +if (d == domain) +break ; + head->read_var1 = &d->list; + } + head->read_var2 =NULL ; + head->read_bit =0 ; + head->read_step =0 ; +if (domain->is_deleted) +tomoyo_io_printf (head,"# This is a deleted domain.\n" ); + } +return true; +} + +/** +* tomoyo_write_domain_policy - Write domain policy. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns 0 on success, negative value otherwise. +*/ +static int tomoyo_write_domain_policy (struct tomoyo_io_buffer *head) +{ +char *data = head->write_buf; +struct tomoyo_domain_info *domain = head->write_var1; +bool is_delete = false; +bool is_select = false; +bool is_undelete = false; +unsigned int profile; + +if (tomoyo_str_starts (&data,TOMOYO_KEYWORD_DELETE )) + is_delete = true; +else if (tomoyo_str_starts (&data,TOMOYO_KEYWORD_SELECT )) + is_select = true; +else if (tomoyo_str_starts (&data,TOMOYO_KEYWORD_UNDELETE )) + is_undelete = true; +if (is_select &&tomoyo_is_select_one (head, data)) +return 0 ; +/* Don't allow updating policies by non manager programs. */ +if (!tomoyo_is_policy_manager ()) +return -EPERM ; +if (tomoyo_is_domain_def (data)) { + domain =NULL ; +if (is_delete) +tomoyo_delete_domain (data); +else if (is_select) + domain =tomoyo_find_domain (data); +else if (is_undelete) + domain =tomoyo_undelete_domain (data); +else + domain =tomoyo_find_or_assign_new_domain (data,0 ); + head->write_var1 = domain; +return 0 ; + } +if (!domain) +return -EINVAL ; + +if (sscanf (data,TOMOYO_KEYWORD_USE_PROFILE "%u" , &profile) ==1 + && profile <TOMOYO_MAX_PROFILES ) { +if (tomoyo_profile_ptr[profile] || !tomoyo_policy_loaded) + domain->profile = (u8 ) profile; +return 0 ; + } +if (!strcmp (data,TOMOYO_KEYWORD_IGNORE_GLOBAL_ALLOW_READ )) { +tomoyo_set_domain_flag (domain, is_delete, +TOMOYO_DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ ); +return 0 ; + } +return tomoyo_write_file_policy (data, domain, is_delete); +} + +/** +* tomoyo_print_single_path_acl - Print a single path ACL entry. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* @ptr: Pointer to "struct tomoyo_single_path_acl_record". +* +* Returns true on success, false otherwise. +*/ +static bool tomoyo_print_single_path_acl (struct tomoyo_io_buffer *head, +struct tomoyo_single_path_acl_record * + ptr) +{ +int pos; +u8 bit; +const char *atmark ="" ; +const char *filename; +const u16 perm = ptr->perm; + + filename = ptr->filename->name; +for (bit = head->read_bit; bit <TOMOYO_MAX_SINGLE_PATH_OPERATION ; + bit++) { +const char *msg; +if (!(perm & (1 << bit))) +continue ; +/* Print "read/write" instead of "read" and "write". */ +if ((bit ==TOMOYO_TYPE_READ_ACL || + bit ==TOMOYO_TYPE_WRITE_ACL ) + && (perm & (1 <<TOMOYO_TYPE_READ_WRITE_ACL ))) +continue ; + msg =tomoyo_sp2keyword (bit); + pos = head->read_avail; +if (!tomoyo_io_printf (head,"allow_%s %s%s\n" , msg, + atmark, filename)) + goto out; + } + head->read_bit =0 ; +return true; + out: + head->read_bit = bit; + head->read_avail = pos; +return false; +} + +/** +* tomoyo_print_double_path_acl - Print a double path ACL entry. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* @ptr: Pointer to "struct tomoyo_double_path_acl_record". +* +* Returns true on success, false otherwise. +*/ +static bool tomoyo_print_double_path_acl (struct tomoyo_io_buffer *head, +struct tomoyo_double_path_acl_record * + ptr) +{ +int pos; +const char *atmark1 ="" ; +const char *atmark2 ="" ; +const char *filename1; +const char *filename2; +const u8 perm = ptr->perm; +u8 bit; + + filename1 = ptr->filename1->name; + filename2 = ptr->filename2->name; +for (bit = head->read_bit; bit <TOMOYO_MAX_DOUBLE_PATH_OPERATION ; + bit++) { +const char *msg; +if (!(perm & (1 << bit))) +continue ; + msg =tomoyo_dp2keyword (bit); + pos = head->read_avail; +if (!tomoyo_io_printf (head,"allow_%s %s%s %s%s\n" , msg, + atmark1, filename1, atmark2, filename2)) + goto out; + } + head->read_bit =0 ; +return true; + out: + head->read_bit = bit; + head->read_avail = pos; +return false; +} + +/** +* tomoyo_print_entry - Print an ACL entry. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* @ptr: Pointer to an ACL entry. +* +* Returns true on success, false otherwise. +*/ +static bool tomoyo_print_entry (struct tomoyo_io_buffer *head, +struct tomoyo_acl_info *ptr) +{ +const u8 acl_type =tomoyo_acl_type2 (ptr); + +if (acl_type &TOMOYO_ACL_DELETED ) +return true; +if (acl_type ==TOMOYO_TYPE_SINGLE_PATH_ACL ) { +struct tomoyo_single_path_acl_record *acl + =container_of (ptr, + struct tomoyo_single_path_acl_record, + head); +return tomoyo_print_single_path_acl (head, acl); + } +if (acl_type ==TOMOYO_TYPE_DOUBLE_PATH_ACL ) { +struct tomoyo_double_path_acl_record *acl + =container_of (ptr, + struct tomoyo_double_path_acl_record, + head); +return tomoyo_print_double_path_acl (head, acl); + } +BUG ();/* This must not happen. */ +return false; +} + +/** +* tomoyo_read_domain_policy - Read domain policy. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns 0. +*/ +static int tomoyo_read_domain_policy (struct tomoyo_io_buffer *head) +{ +struct list1_head *dpos; +struct list1_head *apos; + +if (head->read_eof) +return 0 ; +if (head->read_step ==0 ) + head->read_step =1 ; +list1_for_each_cookie (dpos, head->read_var1, &tomoyo_domain_list) { +struct tomoyo_domain_info *domain; +const char *quota_exceeded ="" ; +const char *transition_failed ="" ; +const char *ignore_global_allow_read ="" ; + domain = list1_entry(dpos, struct tomoyo_domain_info, list); +if (head->read_step !=1 ) + goto acl_loop; +if (domain->is_deleted && !head->read_single_domain) +continue ; +/* Print domainname and flags. */ +if (domain->quota_warned) + quota_exceeded ="quota_exceeded\n" ; +if (domain->flags &TOMOYO_DOMAIN_FLAGS_TRANSITION_FAILED ) + transition_failed ="transition_failed\n" ; +if (domain->flags & +TOMOYO_DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ ) + ignore_global_allow_read + =TOMOYO_KEYWORD_IGNORE_GLOBAL_ALLOW_READ "\n" ; +if (!tomoyo_io_printf (head, +"%s\n" TOMOYO_KEYWORD_USE_PROFILE "%u\n" +"%s%s%s\n" , domain->domainname->name, + domain->profile, quota_exceeded, + transition_failed, + ignore_global_allow_read)) +return 0 ; + head->read_step =2 ; +acl_loop: +if (head->read_step ==3 ) + goto tail_mark; +/* Print ACL entries in the domain. */ +list1_for_each_cookie (apos, head->read_var2, + &domain->acl_info_list) { +struct tomoyo_acl_info *ptr + = list1_entry(apos, struct tomoyo_acl_info, + list); +if (!tomoyo_print_entry (head, ptr)) +return 0 ; + } + head->read_step =3 ; +tail_mark: +if (!tomoyo_io_printf (head,"\n" )) +return 0 ; + head->read_step =1 ; +if (head->read_single_domain) +break ; + } + head->read_eof = true; +return 0 ; +} + +/** +*tomoyo_write_domain_profile - Assign profile for specified domain. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns 0 on success, -EINVAL otherwise. +* +* This is equivalent to doing +* +* ( echo "select " $domainname; echo "use_profile " $profile ) | +* /usr/lib/ccs/loadpolicy -d +*/ +static int tomoyo_write_domain_profile (struct tomoyo_io_buffer *head) +{ +char *data = head->write_buf; +char *cp =strchr (data,' ' ); +struct tomoyo_domain_info *domain; +unsigned long profile; + +if (!cp) +return -EINVAL ; + *cp ='\0' ; + domain =tomoyo_find_domain (cp +1 ); +if (strict_strtoul (data,10 , &profile)) +return -EINVAL ; +if (domain && profile <TOMOYO_MAX_PROFILES + && (tomoyo_profile_ptr[profile] || !tomoyo_policy_loaded)) + domain->profile = (u8 ) profile; +return 0 ; +} + +/** +* tomoyo_read_domain_profile - Read only domainname and profile. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns list of profile number and domainname pairs. +* +* This is equivalent to doing +* +* grep -A 1 '^<kernel>' /sys/kernel/security/tomoyo/domain_policy | +* awk ' { if ( domainname == "" ) { if ( $1 == "<kernel>" ) +* domainname = $0; } else if ( $1 == "use_profile" ) { +* print $2 " " domainname; domainname = ""; } } ; ' +*/ +static int tomoyo_read_domain_profile (struct tomoyo_io_buffer *head) +{ +struct list1_head *pos; + +if (head->read_eof) +return 0 ; +list1_for_each_cookie (pos, head->read_var1, &tomoyo_domain_list) { +struct tomoyo_domain_info *domain; + domain = list1_entry(pos, struct tomoyo_domain_info, list); +if (domain->is_deleted) +continue ; +if (!tomoyo_io_printf (head,"%u %s\n" , domain->profile, + domain->domainname->name)) +return 0 ; + } + head->read_eof = true; +return 0 ; +} + +/** +* tomoyo_write_pid: Specify PID to obtain domainname. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns 0. +*/ +static int tomoyo_write_pid (struct tomoyo_io_buffer *head) +{ +unsigned long pid; +/* No error check. */ +strict_strtoul (head->write_buf,10 , &pid); + head->read_step = (int ) pid; + head->read_eof = false; +return 0 ; +} + +/** +* tomoyo_read_pid - Get domainname of the specified PID. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns the domainname which the specified PID is in on success, +* empty string otherwise. +* The PID is specified by tomoyo_write_pid() so that the user can obtain +* using read()/write() interface rather than sysctl() interface. +*/ +static int tomoyo_read_pid (struct tomoyo_io_buffer *head) +{ +if (head->read_avail ==0 && !head->read_eof) { +const int pid = head->read_step; +struct task_struct *p; +struct tomoyo_domain_info *domain =NULL ; +/***** CRITICAL SECTION START *****/ +read_lock (&tasklist_lock); + p =find_task_by_vpid (pid); +if (p) + domain =tomoyo_real_domain (p); +read_unlock (&tasklist_lock); +/***** CRITICAL SECTION END *****/ +if (domain) +tomoyo_io_printf (head,"%d %u %s" , pid, domain->profile, + domain->domainname->name); + head->read_eof = true; + } +return 0 ; +} + +/** +* tomoyo_write_exception_policy - Write exception policy. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns 0 on success, negative value otherwise. +*/ +static int tomoyo_write_exception_policy (struct tomoyo_io_buffer *head) +{ +char *data = head->write_buf; +bool is_delete =tomoyo_str_starts (&data,TOMOYO_KEYWORD_DELETE ); + +if (tomoyo_str_starts (&data,TOMOYO_KEYWORD_KEEP_DOMAIN )) +return tomoyo_write_domain_keeper_policy (data, false, + is_delete); +if (tomoyo_str_starts (&data,TOMOYO_KEYWORD_NO_KEEP_DOMAIN )) +return tomoyo_write_domain_keeper_policy (data, true, is_delete); +if (tomoyo_str_starts (&data,TOMOYO_KEYWORD_INITIALIZE_DOMAIN )) +return tomoyo_write_domain_initializer_policy (data, false, + is_delete); +if (tomoyo_str_starts (&data,TOMOYO_KEYWORD_NO_INITIALIZE_DOMAIN )) +return tomoyo_write_domain_initializer_policy (data, true, + is_delete); +if (tomoyo_str_starts (&data,TOMOYO_KEYWORD_ALIAS )) +return tomoyo_write_alias_policy (data, is_delete); +if (tomoyo_str_starts (&data,TOMOYO_KEYWORD_ALLOW_READ )) +return tomoyo_write_globally_readable_policy (data, is_delete); +if (tomoyo_str_starts (&data,TOMOYO_KEYWORD_FILE_PATTERN )) +return tomoyo_write_pattern_policy (data, is_delete); +if (tomoyo_str_starts (&data,TOMOYO_KEYWORD_DENY_REWRITE )) +return tomoyo_write_no_rewrite_policy (data, is_delete); +return -EINVAL ; +} + +/** +* tomoyo_read_exception_policy - Read exception policy. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns 0 on success, -EINVAL otherwise. +*/ +static int tomoyo_read_exception_policy (struct tomoyo_io_buffer *head) +{ +if (!head->read_eof) { +switch (head->read_step) { +case 0 : + head->read_var2 =NULL ; + head->read_step =1 ; +case 1 : +if (!tomoyo_read_domain_keeper_policy (head)) +break ; + head->read_var2 =NULL ; + head->read_step =2 ; +case 2 : +if (!tomoyo_read_globally_readable_policy (head)) +break ; + head->read_var2 =NULL ; + head->read_step =3 ; +case 3 : + head->read_var2 =NULL ; + head->read_step =4 ; +case 4 : +if (!tomoyo_read_domain_initializer_policy (head)) +break ; + head->read_var2 =NULL ; + head->read_step =5 ; +case 5 : +if (!tomoyo_read_alias_policy (head)) +break ; + head->read_var2 =NULL ; + head->read_step =6 ; +case 6 : + head->read_var2 =NULL ; + head->read_step =7 ; +case 7 : +if (!tomoyo_read_file_pattern (head)) +break ; + head->read_var2 =NULL ; + head->read_step =8 ; +case 8 : +if (!tomoyo_read_no_rewrite_policy (head)) +break ; + head->read_var2 =NULL ; + head->read_step =9 ; +case 9 : + head->read_eof = true; +break ; +default : +return -EINVAL ; + } + } +return 0 ; +} + +/* path to policy loader */ +static const char *tomoyo_loader ="/sbin/tomoyo-init" ; + +/** +* tomoyo_policy_loader_exists - Check whether /sbin/tomoyo-init exists. +* +* Returns true if /sbin/tomoyo-init exists, false otherwise. +*/ +static bool tomoyo_policy_loader_exists (void ) +{ +/* +* Don't activate MAC if the policy loader doesn't exist. +* If the initrd includes /sbin/init but real-root-dev has not +* mounted on / yet, activating MAC will block the system since +* policies are not loaded yet. +* Thus, let do_execve() call this function everytime. +*/ +struct nameidata nd; + +if (path_lookup (tomoyo_loader,LOOKUP_FOLLOW , &nd)) { +printk (KERN_INFO "Not activating Mandatory Access Control now " +"since %s doesn't exist.\n" , tomoyo_loader); +return false; + } +path_put (&nd.path); +return true; +} + +/** +* tomoyo_load_policy - Run external policy loader to load policy. +* +* @filename: The program about to start. +* +* This function checks whether @filename is /sbin/init , and if so +* invoke /sbin/tomoyo-init and wait for the termination of /sbin/tomoyo-init +* and then continues invocation of /sbin/init. +* /sbin/tomoyo-init reads policy files in /etc/tomoyo/ directory and +* writes to /sys/kernel/security/tomoyo/ interfaces. +* +* Returns nothing. +*/ +void tomoyo_load_policy (const char *filename) +{ +char *argv[2 ]; +char *envp[3 ]; + +if (tomoyo_policy_loaded) +return ; +/* +* Check filename is /sbin/init or /sbin/tomoyo-start. +* /sbin/tomoyo-start is a dummy filename in case where /sbin/init can't +* be passed. +* You can create /sbin/tomoyo-start by +* "ln -s /bin/true /sbin/tomoyo-start". +*/ +if (strcmp (filename,"/sbin/init" ) && +strcmp (filename,"/sbin/tomoyo-start" )) +return ; +if (!tomoyo_policy_loader_exists ()) +return ; + +printk (KERN_INFO "Calling %s to load policy. Please wait.\n" , + tomoyo_loader); + argv[0 ] = (char *) tomoyo_loader; + argv[1 ] =NULL ; + envp[0 ] ="HOME=/" ; + envp[1 ] ="PATH=/sbin:/bin:/usr/sbin:/usr/bin" ; + envp[2 ] =NULL ; +call_usermodehelper (argv[0 ], argv, envp,1 ); + +printk (KERN_INFO "TOMOYO: 2.2.0-pre 2008/11/11\n" ); +printk (KERN_INFO "Mandatory Access Control activated.\n" ); + tomoyo_policy_loaded = true; + {/* Check all profiles currently assigned to domains are defined. */ +struct tomoyo_domain_info *domain; +list1_for_each_entry (domain, &tomoyo_domain_list, list) { +const u8 profile = domain->profile; +if (tomoyo_profile_ptr[profile]) +continue ; +panic ("Profile %u (used by '%s') not defined.\n" , + profile, domain->domainname->name); + } + } +} + +/** +* tomoyo_read_version: Get version. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns version information. +*/ +static int tomoyo_read_version (struct tomoyo_io_buffer *head) +{ +if (!head->read_eof) { +tomoyo_io_printf (head,"2.2.0-pre" ); + head->read_eof = true; + } +return 0 ; +} + +/** +* tomoyo_read_self_domain - Get the current process's domainname. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns the current process's domainname. +*/ +static int tomoyo_read_self_domain (struct tomoyo_io_buffer *head) +{ +if (!head->read_eof) { +/* +* tomoyo_domain()->domainname != NULL +* because every process belongs to a domain and +* the domain's name cannot be NULL. +*/ +tomoyo_io_printf (head,"%s" ,tomoyo_domain ()->domainname->name); + head->read_eof = true; + } +return 0 ; +} + +/** +* tomoyo_open_control - open() for /sys/kernel/security/tomoyo/ interface. +* +* @type: Type of interface. +* @file: Pointer to "struct file". +* +* Associates policy handler and returns 0 on success, -ENOMEM otherwise. +*/ +static int tomoyo_open_control (const u8 type,struct file *file) +{ +struct tomoyo_io_buffer *head =tomoyo_alloc (sizeof (*head)); + +if (!head) +return -ENOMEM ; +mutex_init (&head->io_sem); +switch (type) { +case TOMOYO_DOMAINPOLICY : +/* /sys/kernel/security/tomoyo/domain_policy */ + head->write = tomoyo_write_domain_policy; + head->read = tomoyo_read_domain_policy; +break ; +case TOMOYO_EXCEPTIONPOLICY : +/* /sys/kernel/security/tomoyo/exception_policy */ + head->write = tomoyo_write_exception_policy; + head->read = tomoyo_read_exception_policy; +break ; +case TOMOYO_SELFDOMAIN : +/* /sys/kernel/security/tomoyo/self_domain */ + head->read = tomoyo_read_self_domain; +break ; +case TOMOYO_DOMAIN_STATUS : +/* /sys/kernel/security/tomoyo/.domain_status */ + head->write = tomoyo_write_domain_profile; + head->read = tomoyo_read_domain_profile; +break ; +case TOMOYO_PROCESS_STATUS : +/* /sys/kernel/security/tomoyo/.process_status */ + head->write = tomoyo_write_pid; + head->read = tomoyo_read_pid; +break ; +case TOMOYO_VERSION : +/* /sys/kernel/security/tomoyo/version */ + head->read = tomoyo_read_version; + head->readbuf_size =128 ; +break ; +case TOMOYO_MEMINFO : +/* /sys/kernel/security/tomoyo/meminfo */ + head->write = tomoyo_write_memory_quota; + head->read = tomoyo_read_memory_counter; + head->readbuf_size =512 ; +break ; +case TOMOYO_PROFILE : +/* /sys/kernel/security/tomoyo/profile */ + head->write = tomoyo_write_profile; + head->read = tomoyo_read_profile; +break ; +case TOMOYO_MANAGER : +/* /sys/kernel/security/tomoyo/manager */ + head->write = tomoyo_write_manager_policy; + head->read = tomoyo_read_manager_policy; +break ; + } +if (!(file->f_mode &FMODE_READ )) { +/* +* No need to allocate read_buf since it is not opened +* for reading. +*/ + head->read =NULL ; + }else { +if (!head->readbuf_size) + head->readbuf_size =4096 *2 ; + head->read_buf =tomoyo_alloc (head->readbuf_size); +if (!head->read_buf) { +tomoyo_free (head); +return -ENOMEM ; + } + } +if (!(file->f_mode &FMODE_WRITE )) { +/* +* No need to allocate write_buf since it is not opened +* for writing. +*/ + head->write =NULL ; + }else if (head->write) { + head->writebuf_size =4096 *2 ; + head->write_buf =tomoyo_alloc (head->writebuf_size); +if (!head->write_buf) { +tomoyo_free (head->read_buf); +tomoyo_free (head); +return -ENOMEM ; + } + } + file->private_data = head; +/* +* Call the handler now if the file is +* /sys/kernel/security/tomoyo/self_domain +* so that the user can use +* cat < /sys/kernel/security/tomoyo/self_domain" +* to know the current process's domainname. +*/ +if (type ==TOMOYO_SELFDOMAIN ) +tomoyo_read_control (file,NULL ,0 ); +return 0 ; +} + +/** +* tomoyo_read_control - read() for /sys/kernel/security/tomoyo/ interface. +* +* @file: Pointer to "struct file". +* @buffer: Poiner to buffer to write to. +* @buffer_len: Size of @buffer. +* +* Returns bytes read on success, negative value otherwise. +*/ +static int tomoyo_read_control (struct file *file,char __user *buffer, +const int buffer_len) +{ +int len =0 ; +struct tomoyo_io_buffer *head = file->private_data; +char *cp; + +if (!head->read) +return -ENOSYS ; +if (mutex_lock_interruptible (&head->io_sem)) +return -EINTR ; +/* Call the policy handler. */ + len = head->read (head); +if (len <0 ) + goto out; +/* Write to buffer. */ + len = head->read_avail; +if (len > buffer_len) + len = buffer_len; +if (!len) + goto out; +/* head->read_buf changes by some functions. */ + cp = head->read_buf; +if (copy_to_user (buffer, cp, len)) { + len = -EFAULT ; + goto out; + } + head->read_avail -= len; +memmove (cp, cp + len, head->read_avail); + out: +mutex_unlock (&head->io_sem); +return len; +} + +/** +* tomoyo_write_control - write() for /sys/kernel/security/tomoyo/ interface. +* +* @file: Pointer to "struct file". +* @buffer: Pointer to buffer to read from. +* @buffer_len: Size of @buffer. +* +* Returns @buffer_len on success, negative value otherwise. +*/ +static int tomoyo_write_control (struct file *file,const char __user *buffer, +const int buffer_len) +{ +struct tomoyo_io_buffer *head = file->private_data; +int error = buffer_len; +int avail_len = buffer_len; +char *cp0 = head->write_buf; + +if (!head->write) +return -ENOSYS ; +if (!access_ok (VERIFY_READ , buffer, buffer_len)) +return -EFAULT ; +/* Don't allow updating policies by non manager programs. */ +if (head->write != tomoyo_write_pid && + head->write != tomoyo_write_domain_policy && + !tomoyo_is_policy_manager ()) +return -EPERM ; +if (mutex_lock_interruptible (&head->io_sem)) +return -EINTR ; +/* Read a line and dispatch it to the policy handler. */ +while (avail_len >0 ) { +char c; +if (head->write_avail >= head->writebuf_size -1 ) { + error = -ENOMEM ; +break ; + }else if (get_user (c, buffer)) { + error = -EFAULT ; +break ; + } + buffer++; + avail_len--; + cp0[head->write_avail++] = c; +if (c !='\n' ) +continue ; + cp0[head->write_avail -1 ] ='\0' ; + head->write_avail =0 ; +tomoyo_normalize_line (cp0); + head->write (head); + } +mutex_unlock (&head->io_sem); +return error; +} + +/** +* tomoyo_close_control - close() for /sys/kernel/security/tomoyo/ interface. +* +* @file: Pointer to "struct file". +* +* Releases memory and returns 0. +*/ +static int tomoyo_close_control (struct file *file) +{ +struct tomoyo_io_buffer *head = file->private_data; + +/* Release memory used for policy I/O. */ +tomoyo_free (head->read_buf); + head->read_buf =NULL ; +tomoyo_free (head->write_buf); + head->write_buf =NULL ; +tomoyo_free (head); + head =NULL ; + file->private_data =NULL ; +return 0 ; +} + +/** +* tomoyo_alloc_acl_element - Allocate permanent memory for ACL entry. +* +* @acl_type: Type of ACL entry. +* +* Returns pointer to the ACL entry on success, NULL otherwise. +*/ +void *tomoyo_alloc_acl_element (const u8 acl_type) +{ +int len; +struct tomoyo_acl_info *ptr; + +switch (acl_type) { +case TOMOYO_TYPE_SINGLE_PATH_ACL : + len =sizeof (struct tomoyo_single_path_acl_record ); +break ; +case TOMOYO_TYPE_DOUBLE_PATH_ACL : + len =sizeof (struct tomoyo_double_path_acl_record ); +break ; +default : +return NULL ; + } + ptr =tomoyo_alloc_element (len); +if (!ptr) +return NULL ; + ptr->type = acl_type; +return ptr; +} + +/** +* tomoyo_open - open() for /sys/kernel/security/tomoyo/ interface. +* +* @inode: Pointer to "struct inode". +* @file: Pointer to "struct file". +* +* Returns 0 on success, negative value otherwise. +*/ +static int tomoyo_open (struct inode *inode,struct file *file) +{ +const int key = ((u8 *) file->f_path.dentry->d_inode->i_private) + - ((u8 *)NULL ); +return tomoyo_open_control (key, file); +} + +/** +* tomoyo_release - close() for /sys/kernel/security/tomoyo/ interface. +* +* @inode: Pointer to "struct inode". +* @file: Pointer to "struct file". +* +* Returns 0 on success, negative value otherwise. +*/ +static int tomoyo_release (struct inode *inode,struct file *file) +{ +return tomoyo_close_control (file); +} + +/** +* tomoyo_read - read() for /sys/kernel/security/tomoyo/ interface. +* +* @file: Pointer to "struct file". +* @buf: Pointer to buffer. +* @count: Size of @buf. +* @ppos: Unused. +* +* Returns bytes read on success, negative value otherwise. +*/ +static ssize_t tomoyo_read (struct file *file,char __user *buf,size_t count, +loff_t *ppos) +{ +return tomoyo_read_control (file, buf, count); +} + +/** +* tomoyo_write - write() for /sys/kernel/security/tomoyo/ interface. +* +* @file: Pointer to "struct file". +* @buf: Pointer to buffer. +* @count: Size of @buf. +* @ppos: Unused. +* +* Returns @count on success, negative value otherwise. +*/ +static ssize_t tomoyo_write (struct file *file,const char __user *buf, +size_t count,loff_t *ppos) +{ +return tomoyo_write_control (file, buf, count); +} + +/* Operations for /sys/kernel/security/tomoyo/ interface. */ +static struct file_operations tomoyo_operations = { + .open = tomoyo_open, + .release = tomoyo_release, + .read = tomoyo_read, + .write = tomoyo_write, +}; + +/** +* tomoyo_create_entry - Create interface files under /sys/kernel/security/tomoyo/ directory. +* +* @name: The name of the interface file. +* @mode: The permission of the interface file. +* @parent: The parent directory. +* @key: Type of interface. +* +* Returns nothing. +*/ +static void __init tomoyo_create_entry (const char *name,const mode_t mode, +struct dentry *parent,const u8 key) +{ +securityfs_create_file (name, mode, parent, ((u8 *)NULL ) + key, + &tomoyo_operations); +} + +/** +* tomoyo_initerface_init - Initialize /sys/kernel/security/tomoyo/ interface. +* +* Returns 0. +*/ +static int __init tomoyo_initerface_init (void ) +{ +struct dentry *tomoyo_dir; + + tomoyo_dir =securityfs_create_dir ("tomoyo" ,NULL ); +tomoyo_create_entry ("domain_policy" ,0600 , tomoyo_dir, +TOMOYO_DOMAINPOLICY ); +tomoyo_create_entry ("exception_policy" ,0600 , tomoyo_dir, +TOMOYO_EXCEPTIONPOLICY ); +tomoyo_create_entry ("self_domain" ,0400 , tomoyo_dir, +TOMOYO_SELFDOMAIN ); +tomoyo_create_entry (".domain_status" ,0600 , tomoyo_dir, +TOMOYO_DOMAIN_STATUS ); +tomoyo_create_entry (".process_status" ,0600 , tomoyo_dir, +TOMOYO_PROCESS_STATUS ); +tomoyo_create_entry ("meminfo" ,0600 , tomoyo_dir, +TOMOYO_MEMINFO ); +tomoyo_create_entry ("profile" ,0600 , tomoyo_dir, +TOMOYO_PROFILE ); +tomoyo_create_entry ("manager" ,0600 , tomoyo_dir, +TOMOYO_MANAGER ); +tomoyo_create_entry ("version" ,0400 , tomoyo_dir, +TOMOYO_VERSION ); +return 0 ; +} + +fs_initcall (tomoyo_initerface_init);
security/tomoyo/common.h new file +337 -0
337 lines not on this page · raw message
Tetsuo Handa
maintainerPATCH 7/11 File operation restriction part.
In reply to Tetsuo HandaThis file controls file related operations.
--- security/tomoyo/file.c | 1258 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1258 insertions(+)
security/tomoyo/file.c new file +1258 -0
@@ -0,0 +1,1258 @@ +/* +* security/tomoyo/file.c +* +* Implementation of the Domain-Based Mandatory Access Control. +* +* Copyright (C) 2005-2008 NTT DATA CORPORATION +* +* Version: 2.2.0-pre 2008/11/11 +* +*/ + +#include "common.h" +#include "tomoyo.h" +#include "realpath.h" +#define ACC_MODE (x) ("\000\004\002\006"[(x)&O_ACCMODE]) + +/* Structure for "allow_read" keyword. */ +struct tomoyo_globally_readable_file_entry { +struct list1_head list; +const struct tomoyo_path_info *filename; +bool is_deleted; +}; + +/* Structure for "file_pattern" keyword. */ +struct tomoyo_pattern_entry { +struct list1_head list; +const struct tomoyo_path_info *pattern; +bool is_deleted; +}; + +/* Structure for "deny_rewrite" keyword. */ +struct tomoyo_no_rewrite_entry { +struct list1_head list; +const struct tomoyo_path_info *pattern; +bool is_deleted; +}; + +/* Keyword array for single path operations. */ +static const char *tomoyo_sp_keyword[TOMOYO_MAX_SINGLE_PATH_OPERATION ] = { + [TOMOYO_TYPE_READ_WRITE_ACL ] ="read/write" , + [TOMOYO_TYPE_EXECUTE_ACL ] ="execute" , + [TOMOYO_TYPE_READ_ACL ] ="read" , + [TOMOYO_TYPE_WRITE_ACL ] ="write" , + [TOMOYO_TYPE_CREATE_ACL ] ="create" , + [TOMOYO_TYPE_UNLINK_ACL ] ="unlink" , + [TOMOYO_TYPE_MKDIR_ACL ] ="mkdir" , + [TOMOYO_TYPE_RMDIR_ACL ] ="rmdir" , + [TOMOYO_TYPE_MKFIFO_ACL ] ="mkfifo" , + [TOMOYO_TYPE_MKSOCK_ACL ] ="mksock" , + [TOMOYO_TYPE_MKBLOCK_ACL ] ="mkblock" , + [TOMOYO_TYPE_MKCHAR_ACL ] ="mkchar" , + [TOMOYO_TYPE_TRUNCATE_ACL ] ="truncate" , + [TOMOYO_TYPE_SYMLINK_ACL ] ="symlink" , + [TOMOYO_TYPE_REWRITE_ACL ] ="rewrite" , +}; + +/* Keyword array for double path operations. */ +static const char *tomoyo_dp_keyword[TOMOYO_MAX_DOUBLE_PATH_OPERATION ] = { + [TOMOYO_TYPE_LINK_ACL ] ="link" , + [TOMOYO_TYPE_RENAME_ACL ] ="rename" , +}; + +/** +* tomoyo_sp2keyword - Get the name of single path operation. +* +* @operation: Type of operation. +* +* Returns the name of single path operation. +*/ +const char *tomoyo_sp2keyword (const u8 operation) +{ +return (operation <TOMOYO_MAX_SINGLE_PATH_OPERATION ) + ? tomoyo_sp_keyword[operation] :NULL ; +} + +/** +* tomoyo_dp2keyword - Get the name of double path operation. +* +* @operation: Type of operation. +* +* Returns the name of double path operation. +*/ +const char *tomoyo_dp2keyword (const u8 operation) +{ +return (operation <TOMOYO_MAX_DOUBLE_PATH_OPERATION ) + ? tomoyo_dp_keyword[operation] :NULL ; +} + +/** +* tomoyo_strendswith - Check whether the token ends with the given token. +* +* @name: The token to check. +* @tail: The token to find. +* +* Returns true if @name ends with @tail, false otherwise. +*/ +static bool tomoyo_strendswith (const char *name,const char *tail) +{ +int len; + +if (!name || !tail) +return false; + len =strlen (name) -strlen (tail); +return len >=0 && !strcmp (name + len, tail); +} + +/** +* tomoyo_get_path - Get realpath. +* +* @path: Pointer to "struct path". +* +* Returns pointer to "struct tomoyo_path_info" on success, NULL otherwise. +*/ +static struct tomoyo_path_info *tomoyo_get_path (struct path *path) +{ +int error; +struct tomoyo_path_info_with_data *buf =tomoyo_alloc (sizeof (*buf)); + +if (!buf) +return NULL ; +/* Reserve one byte for appending "/". */ + error =tomoyo_realpath_from_path2 (path, buf->body, +sizeof (buf->body) -2 ); +if (!error) { + buf->head.name = buf->body; +tomoyo_fill_path_info (&buf->head); +return &buf->head; + } +tomoyo_free (buf); +return NULL ; +} + +static int tomoyo_update_double_path_acl (const u8 type,const char *filename1, +const char *filename2, +struct tomoyo_domain_info * +const domain,const bool is_delete); +static int tomoyo_update_single_path_acl (const u8 type,const char *filename, +struct tomoyo_domain_info * +const domain,const bool is_delete); + +/** +* tomoyo_add_domain_acl - Add the given ACL to the given domain. +* +* @domain: Pointer to "struct tomoyo_domain_info". May be NULL. +* @acl: Pointer to "struct tomoyo_acl_info". +* +* Returns 0. +*/ +static int tomoyo_add_domain_acl (struct tomoyo_domain_info *domain, +struct tomoyo_acl_info *acl) +{ +if (domain) { +/* +* We need to serialize because this function is called by +* tomoyo_update_single_path_acl() and +* tomoyo_update_double_path_acl(). +*/ +static DEFINE_SPINLOCK (lock); +/***** CRITICAL SECTION START *****/ +spin_lock (&lock); +list1_add_tail (&acl->list, &domain->acl_info_list); +spin_unlock (&lock); +/***** CRITICAL SECTION END *****/ + }else { + acl->type &= ~TOMOYO_ACL_DELETED ; + } +return 0 ; +} + +/** +* tomoyo_del_domain_acl - Delete the given ACL from the domain. +* +* @acl: Pointer to "struct tomoyo_acl_info". May be NULL. +* +* Returns 0. +*/ +static int tomoyo_del_domain_acl (struct tomoyo_acl_info *acl) +{ +if (acl) + acl->type |=TOMOYO_ACL_DELETED ; +return 0 ; +} + +/* +* The list for "struct tomoyo_globally_readable_file_entry". +* +* This list is updated only inside update_globally_readable_entry(), thus +* no global mutex exists. +*/ +static LIST1_HEAD (tomoyo_globally_readable_list); + +/** +* update_globally_readable_entry - Update "struct tomoyo_globally_readable_file_entry" list. +* +* @filename: Filename unconditionally permitted to open() for reading. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int update_globally_readable_entry (const char *filename, +const bool is_delete) +{ +struct tomoyo_globally_readable_file_entry *new_entry; +struct tomoyo_globally_readable_file_entry *ptr; +static DEFINE_MUTEX (lock); +const struct tomoyo_path_info *saved_filename; +int error = -ENOMEM ; + +if (!tomoyo_is_correct_path (filename,1 ,0 ,-1 , __func__)) +return -EINVAL ; + saved_filename =tomoyo_save_name (filename); +if (!saved_filename) +return -ENOMEM ; +/***** EXCLUSIVE SECTION START *****/ +mutex_lock (&lock); +list1_for_each_entry (ptr, &tomoyo_globally_readable_list, list) { +if (ptr->filename != saved_filename) +continue ; + ptr->is_deleted = is_delete; + error =0 ; + goto out; + } +if (is_delete) { + error = -ENOENT ; + goto out; + } + new_entry =tomoyo_alloc_element (sizeof (*new_entry)); +if (!new_entry) + goto out; + new_entry->filename = saved_filename; +list1_add_tail (&new_entry->list, &tomoyo_globally_readable_list); + error =0 ; + out: +mutex_unlock (&lock); +/***** EXCLUSIVE SECTION END *****/ +return error; +} + +/** +* is_globally_readable_file - Check if the file is unconditionnaly permitted to be open()ed for reading. +* +* @filename: The filename to check. +* +* Returns true if any domain can open @filename for reading, false otherwise. +*/ +static bool is_globally_readable_file (const struct tomoyo_path_info *filename) +{ +struct tomoyo_globally_readable_file_entry *ptr; + +list1_for_each_entry (ptr, &tomoyo_globally_readable_list, list) { +if (!ptr->is_deleted && +tomoyo_path_matches_pattern (filename, ptr->filename)) +return true; + } +return false; +} + +/** +* tomoyo_write_globally_readable_policy - Write "struct tomoyo_globally_readable_file_entry" list. +* +* @data: String to parse. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +int tomoyo_write_globally_readable_policy (char *data,const bool is_delete) +{ +return update_globally_readable_entry (data, is_delete); +} + +/** +* tomoyo_read_globally_readable_policy - Read "struct tomoyo_globally_readable_file_entry" list. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns true on success, false otherwise. +*/ +bool tomoyo_read_globally_readable_policy (struct tomoyo_io_buffer *head) +{ +struct list1_head *pos; + +list1_for_each_cookie (pos, head->read_var2, + &tomoyo_globally_readable_list) { +struct tomoyo_globally_readable_file_entry *ptr; + ptr =list1_entry (pos, + struct tomoyo_globally_readable_file_entry, + list); +if (ptr->is_deleted) +continue ; +if (!tomoyo_io_printf (head,TOMOYO_KEYWORD_ALLOW_READ "%s\n" , + ptr->filename->name)) + goto out; + } +return true; + out: +return false; +} + +/* +* The list for "struct tomoyo_pattern_entry". +* +* This list is updated only inside tomoyo_update_file_pattern_entry(), thus +* no global mutex exists. +*/ +static LIST1_HEAD (tomoyo_pattern_list); + +/** +* tomoyo_update_file_pattern_entry - Update "struct tomoyo_pattern_entry" list. +* +* @pattern: Pathname pattern. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int tomoyo_update_file_pattern_entry (const char *pattern, +const bool is_delete) +{ +struct tomoyo_pattern_entry *new_entry; +struct tomoyo_pattern_entry *ptr; +static DEFINE_MUTEX (lock); +const struct tomoyo_path_info *saved_pattern; +int error = -ENOMEM ; + +if (!tomoyo_is_correct_path (pattern,0 ,1 ,0 , __func__)) +return -EINVAL ; + saved_pattern =tomoyo_save_name (pattern); +if (!saved_pattern) +return -ENOMEM ; +/***** EXCLUSIVE SECTION START *****/ +mutex_lock (&lock); +list1_for_each_entry (ptr, &tomoyo_pattern_list, list) { +if (saved_pattern != ptr->pattern) +continue ; + ptr->is_deleted = is_delete; + error =0 ; + goto out; + } +if (is_delete) { + error = -ENOENT ; + goto out; + } + new_entry =tomoyo_alloc_element (sizeof (*new_entry)); +if (!new_entry) + goto out; + new_entry->pattern = saved_pattern; +list1_add_tail (&new_entry->list, &tomoyo_pattern_list); + error =0 ; + out: +mutex_unlock (&lock); +/***** EXCLUSIVE SECTION END *****/ +return error; +} + +/** +* tomoyo_get_file_pattern - Get patterned pathname. +* +* @filename: The filename to find patterned pathname. +* +* Returns pointer to pathname pattern if matched, @filename otherwise. +*/ +static const struct tomoyo_path_info * +tomoyo_get_file_pattern (const struct tomoyo_path_info *filename) +{ +struct tomoyo_pattern_entry *ptr; +const struct tomoyo_path_info *pattern =NULL ; + +list1_for_each_entry (ptr, &tomoyo_pattern_list, list) { +if (ptr->is_deleted) +continue ; +if (!tomoyo_path_matches_pattern (filename, ptr->pattern)) +continue ; + pattern = ptr->pattern; +if (tomoyo_strendswith (pattern->name,"/\\*" )) { +/* Do nothing. Try to find the better match. */ + }else { +/* This would be the better match. Use this. */ +break ; + } + } +if (pattern) + filename = pattern; +return filename; +} + +/** +* tomoyo_write_pattern_policy - Write "struct tomoyo_pattern_entry" list. +* +* @data: String to parse. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +int tomoyo_write_pattern_policy (char *data,const bool is_delete) +{ +return tomoyo_update_file_pattern_entry (data, is_delete); +} + +/** +* tomoyo_read_file_pattern - Read "struct tomoyo_pattern_entry" list. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns true on success, false otherwise. +*/ +bool tomoyo_read_file_pattern (struct tomoyo_io_buffer *head) +{ +struct list1_head *pos; + +list1_for_each_cookie (pos, head->read_var2, &tomoyo_pattern_list) { +struct tomoyo_pattern_entry *ptr; + ptr = list1_entry(pos, struct tomoyo_pattern_entry, list); +if (ptr->is_deleted) +continue ; +if (!tomoyo_io_printf (head,TOMOYO_KEYWORD_FILE_PATTERN "%s\n" , + ptr->pattern->name)) + goto out; + } +return true; + out: +return false; +} + +/* +* The list for "struct tomoyo_no_rewrite_entry". +* +* This list is updated only inside tomoyo_update_no_rewrite_entry(), thus +* no global mutex exists. +*/ +static LIST1_HEAD (tomoyo_no_rewrite_list); + +/** +* tomoyo_update_no_rewrite_entry - Update "struct tomoyo_no_rewrite_entry" list. +* +* @pattern: Pathname pattern that are not rewritable by default. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int tomoyo_update_no_rewrite_entry (const char *pattern, +const bool is_delete) +{ +struct tomoyo_no_rewrite_entry *new_entry, *ptr; +static DEFINE_MUTEX (lock); +const struct tomoyo_path_info *saved_pattern; +int error = -ENOMEM ; + +if (!tomoyo_is_correct_path (pattern,0 ,0 ,0 , __func__)) +return -EINVAL ; + saved_pattern =tomoyo_save_name (pattern); +if (!saved_pattern) +return -ENOMEM ; +/***** EXCLUSIVE SECTION START *****/ +mutex_lock (&lock); +list1_for_each_entry (ptr, &tomoyo_no_rewrite_list, list) { +if (ptr->pattern != saved_pattern) +continue ; + ptr->is_deleted = is_delete; + error =0 ; + goto out; + } +if (is_delete) { + error = -ENOENT ; + goto out; + } + new_entry =tomoyo_alloc_element (sizeof (*new_entry)); +if (!new_entry) + goto out; + new_entry->pattern = saved_pattern; +list1_add_tail (&new_entry->list, &tomoyo_no_rewrite_list); + error =0 ; + out: +mutex_unlock (&lock); +/***** EXCLUSIVE SECTION END *****/ +return error; +} + +/** +* tomoyo_is_no_rewrite_file - Check if the given pathname is not permitted to be rewrited. +* +* @filename: Filename to check. +* +* Returns true if @filename is specified by "deny_rewrite" directive, +* false otherwise. +*/ +static bool tomoyo_is_no_rewrite_file (const struct tomoyo_path_info *filename) +{ +struct tomoyo_no_rewrite_entry *ptr; + +list1_for_each_entry (ptr, &tomoyo_no_rewrite_list, list) { +if (ptr->is_deleted) +continue ; +if (!tomoyo_path_matches_pattern (filename, ptr->pattern)) +continue ; +return true; + } +return false; +} + +/** +* tomoyo_write_no_rewrite_policy - Write "struct tomoyo_no_rewrite_entry" list. +* +* @data: String to parse. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +int tomoyo_write_no_rewrite_policy (char *data,const bool is_delete) +{ +return tomoyo_update_no_rewrite_entry (data, is_delete); +} + +/** +* tomoyo_read_no_rewrite_policy - Read "struct tomoyo_no_rewrite_entry" list. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns true on success, false otherwise. +*/ +bool tomoyo_read_no_rewrite_policy (struct tomoyo_io_buffer *head) +{ +struct list1_head *pos; + +list1_for_each_cookie (pos, head->read_var2, &tomoyo_no_rewrite_list) { +struct tomoyo_no_rewrite_entry *ptr; + ptr = list1_entry(pos, struct tomoyo_no_rewrite_entry, list); +if (ptr->is_deleted) +continue ; +if (!tomoyo_io_printf (head,TOMOYO_KEYWORD_DENY_REWRITE "%s\n" , + ptr->pattern->name)) + goto out; + } +return true; + out: +return false; +} + +/** +* tomoyo_update_file_acl - Update file's read/write/execute ACL. +* +* @filename: Filename. +* @perm: Permission (between 1 to 7). +* @domain: Pointer to "struct tomoyo_domain_info". +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +* +* This is legacy support interface for older policy syntax. +* Current policy syntax uses "allow_read/write" instead of "6", +* "allow_read" instead of "4", "allow_write" instead of "2", +* "allow_execute" instead of "1". +*/ +static int tomoyo_update_file_acl (const char *filename,u8 perm, +struct tomoyo_domain_info *const domain, +const bool is_delete) +{ +if (perm >7 || !perm) { +printk (KERN_DEBUG "%s: Invalid permission '%d %s'\n" , + __func__, perm, filename); +return -EINVAL ; + } +if (filename[0 ] !='@' &&tomoyo_strendswith (filename,"/" )) +/* +* Only 'allow_mkdir' and 'allow_rmdir' are valid for +* directory permissions. +*/ +return 0 ; +if (perm &4 ) +tomoyo_update_single_path_acl (TOMOYO_TYPE_READ_ACL , filename, + domain, is_delete); +if (perm &2 ) +tomoyo_update_single_path_acl (TOMOYO_TYPE_WRITE_ACL , filename, + domain, is_delete); +if (perm &1 ) +tomoyo_update_single_path_acl (TOMOYO_TYPE_EXECUTE_ACL , + filename, domain, is_delete); +return 0 ; +} + +/** +* tomoyo_check_single_path_acl2 - Check permission for single path operation. +* +* @domain: Pointer to "struct tomoyo_domain_info". +* @filename: Filename to check. +* @perm: Permission. +* @may_use_pattern: True if patterned ACL is permitted. +* +* Returns 0 on success, -EPERM otherwise. +*/ +static int tomoyo_check_single_path_acl2 (const struct tomoyo_domain_info * + domain, +const struct tomoyo_path_info * + filename, +const u16 perm, +const bool may_use_pattern) +{ +struct tomoyo_acl_info *ptr; + +list1_for_each_entry (ptr, &domain->acl_info_list, list) { +struct tomoyo_single_path_acl_record *acl; +if (tomoyo_acl_type2 (ptr) !=TOMOYO_TYPE_SINGLE_PATH_ACL ) +continue ; + acl = container_of(ptr, struct tomoyo_single_path_acl_record, + head); +if (!(acl->perm & perm)) +continue ; +if (may_use_pattern || !acl->filename->is_patterned) { +if (!tomoyo_path_matches_pattern (filename, + acl->filename)) +continue ; + }else { +continue ; + } +return 0 ; + } +return -EPERM ; +} + +/** +* check_file_acl - Check permission for opening files. +* +* @domain: Pointer to "struct tomoyo_domain_info". +* @filename: Filename to check. +* @operation: Mode ("read" or "write" or "read/write" or "execute"). +* +* Returns 0 on success, -EPERM otherwise. +*/ +static int check_file_acl (const struct tomoyo_domain_info *domain, +const struct tomoyo_path_info *filename, +const u8 operation) +{ +u16 perm =0 ; + +if (!tomoyo_check_flags (domain,TOMOYO_MAC_FOR_FILE )) +return 0 ; +if (operation ==6 ) + perm =1 <<TOMOYO_TYPE_READ_WRITE_ACL ; +else if (operation ==4 ) + perm =1 <<TOMOYO_TYPE_READ_ACL ; +else if (operation ==2 ) + perm =1 <<TOMOYO_TYPE_WRITE_ACL ; +else if (operation ==1 ) + perm =1 <<TOMOYO_TYPE_EXECUTE_ACL ; +else +BUG (); +return tomoyo_check_single_path_acl2 (domain, filename, perm, + operation !=1 ); +} + +/** +* tomoyo_check_file_perm2 - Check permission for opening files. +* +* @domain: Pointer to "struct tomoyo_domain_info". +* @filename: Filename to check. +* @perm: Mode ("read" or "write" or "read/write" or "execute"). +* @operation: Operation name passed used for verbose mode. +* @mode: Access control mode. +* +* Returns 0 on success, negative value otherwise. +*/ +static int tomoyo_check_file_perm2 (struct tomoyo_domain_info *const domain, +const struct tomoyo_path_info *filename, +const u8 perm,const char *operation, +const u8 mode) +{ +const bool is_enforce = (mode ==3 ); +const char *msg ="<unknown>" ; +int error =0 ; + +if (!filename) +return 0 ; + error =check_file_acl (domain, filename, perm); +if (error && perm ==4 && + (domain->flags &TOMOYO_DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ ) ==0 + &&is_globally_readable_file (filename)) + error =0 ; +if (perm ==6 ) + msg =tomoyo_sp2keyword (TOMOYO_TYPE_READ_WRITE_ACL ); +else if (perm ==4 ) + msg =tomoyo_sp2keyword (TOMOYO_TYPE_READ_ACL ); +else if (perm ==2 ) + msg =tomoyo_sp2keyword (TOMOYO_TYPE_WRITE_ACL ); +else if (perm ==1 ) + msg =tomoyo_sp2keyword (TOMOYO_TYPE_EXECUTE_ACL ); +else +BUG (); +if (!error) +return 0 ; +if (tomoyo_verbose_mode (domain)) +printk (KERN_WARNING "TOMOYO-%s: Access '%s(%s) %s' denied " +"for %s\n" ,tomoyo_get_msg (is_enforce), msg, operation, + filename->name,tomoyo_get_last_name (domain)); +if (is_enforce) +return error; +if (mode ==1 &&tomoyo_domain_quota_is_ok (domain)) { +/* Don't use patterns for execute permission. */ +const struct tomoyo_path_info *patterned_file = (perm !=1 ) ? +tomoyo_get_file_pattern (filename) : filename; +tomoyo_update_file_acl (patterned_file->name, perm, + domain, false); + } +return 0 ; +} + +/** +* tomoyo_write_file_policy - Update file related list. +* +* @data: String to parse. +* @domain: Pointer to "struct tomoyo_domain_info". +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +int tomoyo_write_file_policy (char *data,struct tomoyo_domain_info *domain, +const bool is_delete) +{ +char *filename =strchr (data,' ' ); +char *filename2; +unsigned int perm; +u8 type; + +if (!filename) +return -EINVAL ; + *filename++ ='\0' ; +if (sscanf (data,"%u" , &perm) ==1 ) +return tomoyo_update_file_acl (filename, (u8 ) perm, domain, + is_delete); +if (strncmp (data,"allow_" ,6 )) + goto out; + data +=6 ; +for (type =0 ; type <TOMOYO_MAX_SINGLE_PATH_OPERATION ; type++) { +if (strcmp (data, tomoyo_sp_keyword[type])) +continue ; +return tomoyo_update_single_path_acl (type, filename, + domain, is_delete); + } + filename2 =strchr (filename,' ' ); +if (!filename2) + goto out; + *filename2++ ='\0' ; +for (type =0 ; type <TOMOYO_MAX_DOUBLE_PATH_OPERATION ; type++) { +if (strcmp (data, tomoyo_dp_keyword[type])) +continue ; +return tomoyo_update_double_path_acl (type, filename, filename2, + domain, is_delete); + } + out: +return -EINVAL ; +} + +/** +* tomoyo_update_single_path_acl - Update "struct tomoyo_single_path_acl_record" list. +* +* @type: Type of operation. +* @filename: Filename. +* @domain: Pointer to "struct tomoyo_domain_info". +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int tomoyo_update_single_path_acl (const u8 type,const char *filename, +struct tomoyo_domain_info * +const domain,const bool is_delete) +{ +static DEFINE_MUTEX (lock); +static const u16 rw_mask = + (1 <<TOMOYO_TYPE_READ_ACL ) | (1 <<TOMOYO_TYPE_WRITE_ACL ); +const struct tomoyo_path_info *saved_filename; +struct tomoyo_acl_info *ptr; +struct tomoyo_single_path_acl_record *acl; +int error = -ENOMEM ; +const u16 perm =1 << type; + +if (!domain) +return -EINVAL ; +if (!tomoyo_is_correct_path (filename,0 ,0 ,0 , __func__)) +return -EINVAL ; + saved_filename =tomoyo_save_name (filename); +if (!saved_filename) +return -ENOMEM ; +/***** EXCLUSIVE SECTION START *****/ +mutex_lock (&lock); +if (is_delete) + goto delete; +list1_for_each_entry (ptr, &domain->acl_info_list, list) { +if (tomoyo_acl_type1 (ptr) !=TOMOYO_TYPE_SINGLE_PATH_ACL ) +continue ; + acl = container_of(ptr, struct tomoyo_single_path_acl_record, + head); +if (acl->filename != saved_filename) +continue ; +/* Special case. Clear all bits if marked as deleted. */ +if (ptr->type &TOMOYO_ACL_DELETED ) + acl->perm =0 ; + acl->perm |= perm; +if ((acl->perm & rw_mask) == rw_mask) + acl->perm |=1 <<TOMOYO_TYPE_READ_WRITE_ACL ; +else if (acl->perm & (1 <<TOMOYO_TYPE_READ_WRITE_ACL )) + acl->perm |= rw_mask; + error =tomoyo_add_domain_acl (NULL , ptr); + goto out; + } +/* Not found. Append it to the tail. */ + acl =tomoyo_alloc_acl_element (TOMOYO_TYPE_SINGLE_PATH_ACL ); +if (!acl) + goto out; + acl->perm = perm; + acl->filename = saved_filename; + error =tomoyo_add_domain_acl (domain, &acl->head); + goto out; + delete: + error = -ENOENT ; +list1_for_each_entry (ptr, &domain->acl_info_list, list) { +if (tomoyo_acl_type2 (ptr) !=TOMOYO_TYPE_SINGLE_PATH_ACL ) +continue ; + acl = container_of(ptr, struct tomoyo_single_path_acl_record, + head); +if (acl->filename != saved_filename) +continue ; + acl->perm &= ~perm; +if ((acl->perm & rw_mask) != rw_mask) + acl->perm &= ~(1 <<TOMOYO_TYPE_READ_WRITE_ACL ); +else if (!(acl->perm & (1 <<TOMOYO_TYPE_READ_WRITE_ACL ))) + acl->perm &= ~rw_mask; + error =tomoyo_del_domain_acl (acl->perm ?NULL : ptr); +break ; + } + out: +mutex_unlock (&lock); +/***** EXCLUSIVE SECTION END *****/ +return error; +} + +/** +* tomoyo_update_double_path_acl - Update "struct tomoyo_double_path_acl_record" list. +* +* @type: Type of operation. +* @filename1: First filename. +* @filename2: Second filename. +* @domain: Pointer to "struct tomoyo_domain_info". +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int tomoyo_update_double_path_acl (const u8 type,const char *filename1, +const char *filename2, +struct tomoyo_domain_info * +const domain,const bool is_delete) +{ +static DEFINE_MUTEX (lock); +const struct tomoyo_path_info *saved_filename1; +const struct tomoyo_path_info *saved_filename2; +struct tomoyo_acl_info *ptr; +struct tomoyo_double_path_acl_record *acl; +int error = -ENOMEM ; +const u8 perm =1 << type; + +if (!domain) +return -EINVAL ; +if (!tomoyo_is_correct_path (filename1,0 ,0 ,0 , __func__) || + !tomoyo_is_correct_path (filename2,0 ,0 ,0 , __func__)) +return -EINVAL ; + saved_filename1 =tomoyo_save_name (filename1); + saved_filename2 =tomoyo_save_name (filename2); +if (!saved_filename1 || !saved_filename2) +return -ENOMEM ; +/***** EXCLUSIVE SECTION START *****/ +mutex_lock (&lock); +if (is_delete) + goto delete; +list1_for_each_entry (ptr, &domain->acl_info_list, list) { +if (tomoyo_acl_type1 (ptr) !=TOMOYO_TYPE_DOUBLE_PATH_ACL ) +continue ; + acl = container_of(ptr, struct tomoyo_double_path_acl_record, + head); +if (acl->filename1 != saved_filename1 || + acl->filename2 != saved_filename2) +continue ; +/* Special case. Clear all bits if marked as deleted. */ +if (ptr->type &TOMOYO_ACL_DELETED ) + acl->perm =0 ; + acl->perm |= perm; + error =tomoyo_add_domain_acl (NULL , ptr); + goto out; + } +/* Not found. Append it to the tail. */ + acl =tomoyo_alloc_acl_element (TOMOYO_TYPE_DOUBLE_PATH_ACL ); +if (!acl) + goto out; + acl->perm = perm; + acl->filename1 = saved_filename1; + acl->filename2 = saved_filename2; + error =tomoyo_add_domain_acl (domain, &acl->head); + goto out; + delete: + error = -ENOENT ; +list1_for_each_entry (ptr, &domain->acl_info_list, list) { +if (tomoyo_acl_type2 (ptr) !=TOMOYO_TYPE_DOUBLE_PATH_ACL ) +continue ; + acl = container_of(ptr, struct tomoyo_double_path_acl_record, + head); +if (acl->filename1 != saved_filename1 || + acl->filename2 != saved_filename2) +continue ; + acl->perm &= ~perm; + error =tomoyo_del_domain_acl (acl->perm ?NULL : ptr); +break ; + } + out: +mutex_unlock (&lock); +/***** EXCLUSIVE SECTION END *****/ +return error; +} + +/** +* check_single_path_acl - Check permission for single path operation. +* +* @domain: Pointer to "struct tomoyo_domain_info". +* @type: Type of operation. +* @filename: Filename to check. +* +* Returns 0 on success, negative value otherwise. +*/ +static int check_single_path_acl (struct tomoyo_domain_info *domain, +const u8 type, +const struct tomoyo_path_info *filename) +{ +if (!tomoyo_check_flags (domain,TOMOYO_MAC_FOR_FILE )) +return 0 ; +return tomoyo_check_single_path_acl2 (domain, filename,1 << type,1 ); +} + +/** +* check_double_path_acl - Check permission for double path operation. +* +* @domain: Pointer to "struct tomoyo_domain_info". +* @type: Type of operation. +* @filename1: First filename to check. +* @filename2: Second filename to check. +* +* Returns 0 on success, -EPERM otherwise. +*/ +static int check_double_path_acl (const struct tomoyo_domain_info *domain, +const u8 type, +const struct tomoyo_path_info *filename1, +const struct tomoyo_path_info *filename2) +{ +struct tomoyo_acl_info *ptr; +const u8 perm =1 << type; + +if (!tomoyo_check_flags (domain,TOMOYO_MAC_FOR_FILE )) +return 0 ; +list1_for_each_entry (ptr, &domain->acl_info_list, list) { +struct tomoyo_double_path_acl_record *acl; +if (tomoyo_acl_type2 (ptr) !=TOMOYO_TYPE_DOUBLE_PATH_ACL ) +continue ; + acl = container_of(ptr, struct tomoyo_double_path_acl_record, + head); +if (!(acl->perm & perm)) +continue ; +if (!tomoyo_path_matches_pattern (filename1, acl->filename1)) +continue ; +if (!tomoyo_path_matches_pattern (filename2, acl->filename2)) +continue ; +return 0 ; + } +return -EPERM ; +} + +/** +* tomoyo_check_single_path_permission2 - Check permission for single path operation. +* +* @domain: Pointer to "struct tomoyo_domain_info". +* @operation: Type of operation. +* @filename: Filename to check. +* @mode: Access control mode. +* +* Returns 0 on success, negative value otherwise. +*/ +static int tomoyo_check_single_path_permission2 (struct tomoyo_domain_info * +const domain,u8 operation, +const struct tomoyo_path_info * + filename,const u8 mode) +{ +const char *msg; +int error; +const bool is_enforce = (mode ==3 ); + +if (!mode) +return 0 ; + next: + error =check_single_path_acl (domain, operation, filename); + msg =tomoyo_sp2keyword (operation); +if (!error) + goto ok; +if (tomoyo_verbose_mode (domain)) +printk (KERN_WARNING "TOMOYO-%s: Access '%s %s' denied for %s\n" , +tomoyo_get_msg (is_enforce), msg, filename->name, +tomoyo_get_last_name (domain)); +if (mode ==1 &&tomoyo_domain_quota_is_ok (domain)) { +const char *name =tomoyo_get_file_pattern (filename)->name; +tomoyo_update_single_path_acl (operation, name, domain, false); + } +if (!is_enforce) + error =0 ; + ok: +/* +* Since "allow_truncate" doesn't imply "allow_rewrite" permission, +* we need to check "allow_rewrite" permission if the filename is +* specified by "deny_rewrite" keyword. +*/ +if (!error && operation ==TOMOYO_TYPE_TRUNCATE_ACL && +tomoyo_is_no_rewrite_file (filename)) { + operation =TOMOYO_TYPE_REWRITE_ACL ; + goto next; + } +return error; +} + +/** +* tomoyo_check_file_perm - Check permission for sysctl()'s "read" and "write". +* +* @domain: Pointer to "struct tomoyo_domain_info". +* @filename: Filename to check. +* @perm: Mode ("read" or "write" or "read/write"). +* Returns 0 on success, negative value otherwise. +*/ +int tomoyo_check_file_perm (struct tomoyo_domain_info *domain, +const char *filename,const u8 perm) +{ +struct tomoyo_path_info name; +const u8 mode =tomoyo_check_flags (domain,TOMOYO_MAC_FOR_FILE ); + +if (!mode) +return 0 ; + name.name = filename; +tomoyo_fill_path_info (&name); +return tomoyo_check_file_perm2 (domain, &name, perm,"sysctl" , mode); +} + +/** +* tomoyo_check_exec_perm - Check permission for "execute". +* +* @domain: Pointer to "struct tomoyo_domain_info". +* @filename: Check permission for "execute". +* @tmp: Buffer for temporary use. +* +* Returns 0 on success, negativevalue otherwise. +*/ +int tomoyo_check_exec_perm (struct tomoyo_domain_info *domain, +const struct tomoyo_path_info *filename, +struct tomoyo_page_buffer *tmp) +{ +const u8 mode =tomoyo_check_flags (domain,TOMOYO_MAC_FOR_FILE ); + +if (!mode) +return 0 ; +return tomoyo_check_file_perm2 (domain, filename,1 ,"do_execve" , mode); +} + +/** +* tomoyo_check_open_permission - Check permission for "read" and "write". +* +* @domain: Pointer to "struct tomoyo_domain_info". +* @path: Pointer to "struct path". +* @flag: Flags for open(). +* +* Returns 0 on success, negative value otherwise. +*/ +int tomoyo_check_open_permission (struct tomoyo_domain_info *domain, +struct path *path,const int flag) +{ +const u8 acc_mode =ACC_MODE (flag); +int error = -ENOMEM ; +struct tomoyo_path_info *buf; +const u8 mode =tomoyo_check_flags (domain,TOMOYO_MAC_FOR_FILE ); +const bool is_enforce = (mode ==3 ); + +if (!mode || !path->mnt) +return 0 ; +if (acc_mode ==0 ) +return 0 ; +if (path->dentry->d_inode &&S_ISDIR (path->dentry->d_inode->i_mode)) +/* +* I don't check directories here because mkdir() and rmdir() +* don't call me. +*/ +return 0 ; + buf =tomoyo_get_path (path); +if (!buf) + goto out; + error =0 ; +/* +* If the filename is specified by "deny_rewrite" keyword, +* we need to check "allow_rewrite" permission when the filename is not +* opened for append mode or the filename is truncated at open time. +*/ +if ((acc_mode &MAY_WRITE ) && + ((flag &O_TRUNC ) || !(flag &O_APPEND )) && + (tomoyo_is_no_rewrite_file (buf))) { + error =tomoyo_check_single_path_permission2 (domain, +TOMOYO_TYPE_REWRITE_ACL , + buf, mode); + } +if (!error) + error =tomoyo_check_file_perm2 (domain, buf, acc_mode,"open" , + mode); +if (!error && (flag &O_TRUNC )) + error =tomoyo_check_single_path_permission2 (domain, +TOMOYO_TYPE_TRUNCATE_ACL , + buf, mode); + out: +tomoyo_free (buf); +if (!is_enforce) + error =0 ; +return error; +} + +/** +* tomoyo_check_1path_perm - Check permission for "create", "unlink", "mkdir", "rmdir", "mkfifo", "mksock", "mkblock", "mkchar", "truncate" and "symlink". +* +* @domain: Pointer to "struct tomoyo_domain_info". +* @operation: Type of operation. +* @path: Pointer to "struct path". +* +* Returns 0 on success, negative value otherwise. +*/ +int tomoyo_check_1path_perm (struct tomoyo_domain_info *domain, +const u8 operation,struct path *path) +{ +int error = -ENOMEM ; +struct tomoyo_path_info *buf; +const u8 mode =tomoyo_check_flags (domain,TOMOYO_MAC_FOR_FILE ); +const bool is_enforce = (mode ==3 ); + +if (!mode || !path->mnt) +return 0 ; + buf =tomoyo_get_path (path); +if (!buf) + goto out; +switch (operation) { +case TOMOYO_TYPE_MKDIR_ACL : +case TOMOYO_TYPE_RMDIR_ACL : +if (!buf->is_dir) { +/* +* tomoyo_get_path() reserves space for appending "/." +*/ +strcat ((char *) buf->name,"/" ); +tomoyo_fill_path_info (buf); + } + } + error =tomoyo_check_single_path_permission2 (domain, operation, buf, + mode); + out: +tomoyo_free (buf); +if (!is_enforce) + error =0 ; +return error; +} + +/** +* tomoyo_check_rewrite_permission - Check permission for "rewrite". +* +* @domain: Pointer to "struct tomoyo_domain_info". +* @filp: Pointer to "struct file". +* +* Returns 0 on success, negative value otherwise. +*/ +int tomoyo_check_rewrite_permission (struct tomoyo_domain_info *domain, +struct file *filp) +{ +int error = -ENOMEM ; +const u8 mode =tomoyo_check_flags (domain,TOMOYO_MAC_FOR_FILE ); +const bool is_enforce = (mode ==3 ); +struct tomoyo_path_info *buf; + +if (!mode || !filp->f_path.mnt) +return 0 ; + buf =tomoyo_get_path (&filp->f_path); +if (!buf) + goto out; +if (!tomoyo_is_no_rewrite_file (buf)) { + error =0 ; + goto out; + } + error =tomoyo_check_single_path_permission2 (domain, +TOMOYO_TYPE_REWRITE_ACL , + buf, mode); + out: +tomoyo_free (buf); +if (!is_enforce) + error =0 ; +return error; +} + +/** +* tomoyo_check_2path_perm - Check permission for "rename" and "link". +* +* @domain: Pointer to "struct tomoyo_domain_info". +* @operation: Type of operation. +* @path1: Pointer to "struct path". +* @path2: Pointer to "struct path". +* +* Returns 0 on success, negative value otherwise. +*/ +int tomoyo_check_2path_perm (struct tomoyo_domain_info *const domain, +const u8 operation,struct path *path1, +struct path *path2) +{ +int error = -ENOMEM ; +struct tomoyo_path_info *buf1, *buf2; +const u8 mode =tomoyo_check_flags (domain,TOMOYO_MAC_FOR_FILE ); +const bool is_enforce = (mode ==3 ); +const char *msg; + +if (!mode || !path1->mnt || !path2->mnt) +return 0 ; + buf1 =tomoyo_get_path (path1); + buf2 =tomoyo_get_path (path2); +if (!buf1 || !buf2) + goto out; + { +struct dentry *dentry = path1->dentry; +if (dentry->d_inode &&S_ISDIR (dentry->d_inode->i_mode)) { +/* +* tomoyo_get_path() reserves space for appending "/." +*/ +if (!buf1->is_dir) { +strcat ((char *) buf1->name,"/" ); +tomoyo_fill_path_info (buf1); + } +if (!buf2->is_dir) { +strcat ((char *) buf2->name,"/" ); +tomoyo_fill_path_info (buf2); + } + } + } + error =check_double_path_acl (domain, operation, buf1, buf2); + msg =tomoyo_dp2keyword (operation); +if (!error) + goto out; +if (tomoyo_verbose_mode (domain)) +printk (KERN_WARNING "TOMOYO-%s: Access '%s %s %s' " +"denied for %s\n" ,tomoyo_get_msg (is_enforce), + msg, buf1->name, buf2->name, +tomoyo_get_last_name (domain)); +if (mode ==1 &&tomoyo_domain_quota_is_ok (domain)) { +const char *name1 =tomoyo_get_file_pattern (buf1)->name; +const char *name2 =tomoyo_get_file_pattern (buf2)->name; +tomoyo_update_double_path_acl (operation, name1, name2, domain, + false); + } + out: +tomoyo_free (buf1); +tomoyo_free (buf2); +if (!is_enforce) + error =0 ; +return error; +}
Tetsuo Handa
maintainerPATCH 8/11 Domain transition handler.
In reply to Tetsuo HandaThis file controls domain creation/deletion/transition.
Every process belongs to a domain in TOMOYO Linux. Domain transition occurs when execve(2) is called and the domain is expressed as 'process invocation history', such as '<kernel> /sbin/init /etc/init.d/rc'.
Domain information is stored in current->cred->security field.
--- security/tomoyo/domain.c | 874 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 874 insertions(+)
security/tomoyo/domain.c new file +874 -0
@@ -0,0 +1,874 @@ +/* +* security/tomoyo/domain.c +* +* Implementation of the Domain-Based Mandatory Access Control. +* +* Copyright (C) 2005-2008 NTT DATA CORPORATION +* +* Version: 2.2.0-pre 2008/11/11 +* +*/ + +#include "common.h" +#include "tomoyo.h" +#include "realpath.h" +#include <linux/binfmts.h> + +/* Variables definitions.*/ + +/* The initial domain. */ +struct tomoyo_domain_info tomoyo_kernel_domain; + +/* +* The list for "struct tomoyo_domain_info". +* +* The tomoyo_domain_list_lock mutex protects the tomoyo_domain_list list. +*/ +LIST1_HEAD (tomoyo_domain_list); +static DEFINE_MUTEX (tomoyo_domain_list_lock); + +/* Structure for "initialize_domain" and "no_initialize_domain" keyword. */ +struct tomoyo_domain_initializer_entry { +struct list1_head list; +const struct tomoyo_path_info *domainname;/* This may be NULL */ +const struct tomoyo_path_info *program; +bool is_deleted; +bool is_not;/* True if this entry is "no_initialize_domain". */ +/* True if the domainname is tomoyo_get_last_name(). */ +bool is_last_name; +}; + +/* Structure for "keep_domain" and "no_keep_domain" keyword. */ +struct tomoyo_domain_keeper_entry { +struct list1_head list; +const struct tomoyo_path_info *domainname; +const struct tomoyo_path_info *program;/* This may be NULL */ +bool is_deleted; +bool is_not;/* True if this entry is "no_keep_domain". */ +/* True if the domainname is tomoyo_get_last_name(). */ +bool is_last_name; +}; + +/* Structure for "alias" keyword. */ +struct tomoyo_alias_entry { +struct list1_head list; +const struct tomoyo_path_info *original_name; +const struct tomoyo_path_info *aliased_name; +bool is_deleted; +}; + +/** +* tomoyo_set_domain_flag - Set or clear domain's attribute flags. +* +* @domain: Pointer to "struct tomoyo_domain_info". +* @is_delete: True if it is a delete request. +* @flags: Flags to set or clear. +* +* Returns nothing. +*/ +void tomoyo_set_domain_flag (struct tomoyo_domain_info *domain, +const bool is_delete,const u8 flags) +{ +/* We need to serialize because this is bitfield operation. */ +static DEFINE_SPINLOCK (lock); +/***** CRITICAL SECTION START *****/ +spin_lock (&lock); +if (!is_delete) + domain->flags |= flags; +else + domain->flags &= ~flags; +spin_unlock (&lock); +/***** CRITICAL SECTION END *****/ +} + +/** +* tomoyo_get_last_name - Get last component of a domainname. +* +* @domain: Pointer to "struct tomoyo_domain_info". +* +* Returns the last component of the domainname. +*/ +const char *tomoyo_get_last_name (const struct tomoyo_domain_info *domain) +{ +const char *cp0 = domain->domainname->name; +const char *cp1 =strrchr (cp0,' ' ); + +if (cp1) +return cp1 +1 ; +return cp0; +} + +/* +* The list for "struct tomoyo_domain_initializer_entry". +* +* This list is updated only inside +* tomoyo_update_domain_initializer_entry(), +* thus no global mutex exists. +*/ +static LIST1_HEAD (tomoyo_domain_initializer_list); + +/** +* tomoyo_update_domain_initializer_entry - Update "struct tomoyo_domain_initializer_entry" list. +* +* @domainname: The name of domain. May be NULL. +* @program: The name of program. +* @is_not: True if it is "no_initialize_domain" entry. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int tomoyo_update_domain_initializer_entry (const char *domainname, +const char *program, +const bool is_not, +const bool is_delete) +{ +struct tomoyo_domain_initializer_entry *new_entry; +struct tomoyo_domain_initializer_entry *ptr; +static DEFINE_MUTEX (lock); +const struct tomoyo_path_info *saved_program; +const struct tomoyo_path_info *saved_domainname =NULL ; +int error = -ENOMEM ; +bool is_last_name = false; + +if (!tomoyo_is_correct_path (program,1 ,-1 ,-1 , __func__)) +return -EINVAL ;/* No patterns allowed. */ +if (domainname) { +if (!tomoyo_is_domain_def (domainname) && +tomoyo_is_correct_path (domainname,1 ,-1 ,-1 , __func__)) + is_last_name = true; +else if (!tomoyo_is_correct_domain (domainname, __func__)) +return -EINVAL ; + saved_domainname =tomoyo_save_name (domainname); +if (!saved_domainname) +return -ENOMEM ; + } + saved_program =tomoyo_save_name (program); +if (!saved_program) +return -ENOMEM ; +/***** EXCLUSIVE SECTION START *****/ +mutex_lock (&lock); +list1_for_each_entry (ptr, &tomoyo_domain_initializer_list, list) { +if (ptr->is_not != is_not || + ptr->domainname != saved_domainname || + ptr->program != saved_program) +continue ; + ptr->is_deleted = is_delete; + error =0 ; + goto out; + } +if (is_delete) { + error = -ENOENT ; + goto out; + } + new_entry =tomoyo_alloc_element (sizeof (*new_entry)); +if (!new_entry) + goto out; + new_entry->domainname = saved_domainname; + new_entry->program = saved_program; + new_entry->is_not = is_not; + new_entry->is_last_name = is_last_name; +list1_add_tail (&new_entry->list, &tomoyo_domain_initializer_list); + error =0 ; + out: +mutex_unlock (&lock); +/***** EXCLUSIVE SECTION END *****/ +return error; +} + +/** +* tomoyo_read_domain_initializer_policy - Read "struct tomoyo_domain_initializer_entry" list. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns true on success, false otherwise. +*/ +bool tomoyo_read_domain_initializer_policy (struct tomoyo_io_buffer *head) +{ +struct list1_head *pos; + +list1_for_each_cookie (pos, head->read_var2, + &tomoyo_domain_initializer_list) { +const char *no; +const char *from ="" ; +const char *domain ="" ; +struct tomoyo_domain_initializer_entry *ptr; + ptr = list1_entry(pos, struct tomoyo_domain_initializer_entry, + list); +if (ptr->is_deleted) +continue ; + no = ptr->is_not ?"no_" :"" ; +if (ptr->domainname) { + from =" from " ; + domain = ptr->domainname->name; + } +if (!tomoyo_io_printf (head, +"%s" TOMOYO_KEYWORD_INITIALIZE_DOMAIN +"%s%s%s\n" , no, ptr->program->name, from, + domain)) + goto out; + } +return true; + out: +return false; +} + +/** +* tomoyo_write_domain_initializer_policy - Write "struct tomoyo_domain_initializer_entry" list. +* +* @data: String to parse. +* @is_not: True if it is "no_initialize_domain" entry. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +int tomoyo_write_domain_initializer_policy (char *data,const bool is_not, +const bool is_delete) +{ +char *cp =strstr (data," from " ); + +if (cp) { + *cp ='\0' ; +return tomoyo_update_domain_initializer_entry (cp +6 , data, + is_not, + is_delete); + } +return tomoyo_update_domain_initializer_entry (NULL , data, is_not, + is_delete); +} + +/** +* is_domain_initializer - Check whether the given program causes domainname reinitialization. +* +* @domainname: The name of domain. +* @program: The name of program. +* @last_name: The last component of @domainname. +* +* Returns true if executing @program reinitializes domain transition, +* false otherwise. +*/ +static bool is_domain_initializer (const struct tomoyo_path_info *domainname, +const struct tomoyo_path_info *program, +const struct tomoyo_path_info *last_name) +{ +struct tomoyo_domain_initializer_entry *ptr; +bool flag = false; + +list1_for_each_entry (ptr, &tomoyo_domain_initializer_list, list) { +if (ptr->is_deleted) +continue ; +if (ptr->domainname) { +if (!ptr->is_last_name) { +if (ptr->domainname != domainname) +continue ; + }else { +if (tomoyo_pathcmp (ptr->domainname, last_name)) +continue ; + } + } +if (tomoyo_pathcmp (ptr->program, program)) +continue ; +if (ptr->is_not) +return false; + flag = true; + } +return flag; +} + +/* +* The list for "struct tomoyo_domain_keeper_entry". +* +* This list is updated only inside tomoyo_update_domain_keeper_entry(), +* thus no global mutex exists. +*/ +static LIST1_HEAD (tomoyo_domain_keeper_list); + +/** +* tomoyo_update_domain_keeper_entry - Update "struct tomoyo_domain_keeper_entry" list. +* +* @domainname: The name of domain. +* @program: The name of program. May be NULL. +* @is_not: True if it is "no_keep_domain" entry. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int tomoyo_update_domain_keeper_entry (const char *domainname, +const char *program, +const bool is_not, +const bool is_delete) +{ +struct tomoyo_domain_keeper_entry *new_entry; +struct tomoyo_domain_keeper_entry *ptr; +const struct tomoyo_path_info *saved_domainname; +const struct tomoyo_path_info *saved_program =NULL ; +static DEFINE_MUTEX (lock); +int error = -ENOMEM ; +bool is_last_name = false; + +if (!tomoyo_is_domain_def (domainname) && +tomoyo_is_correct_path (domainname,1 ,-1 ,-1 , __func__)) + is_last_name = true; +else if (!tomoyo_is_correct_domain (domainname, __func__)) +return -EINVAL ; +if (program) { +if (!tomoyo_is_correct_path (program,1 ,-1 ,-1 , __func__)) +return -EINVAL ; + saved_program =tomoyo_save_name (program); +if (!saved_program) +return -ENOMEM ; + } + saved_domainname =tomoyo_save_name (domainname); +if (!saved_domainname) +return -ENOMEM ; +/***** EXCLUSIVE SECTION START *****/ +mutex_lock (&lock); +list1_for_each_entry (ptr, &tomoyo_domain_keeper_list, list) { +if (ptr->is_not != is_not || + ptr->domainname != saved_domainname || + ptr->program != saved_program) +continue ; + ptr->is_deleted = is_delete; + error =0 ; + goto out; + } +if (is_delete) { + error = -ENOENT ; + goto out; + } + new_entry =tomoyo_alloc_element (sizeof (*new_entry)); +if (!new_entry) + goto out; + new_entry->domainname = saved_domainname; + new_entry->program = saved_program; + new_entry->is_not = is_not; + new_entry->is_last_name = is_last_name; +list1_add_tail (&new_entry->list, &tomoyo_domain_keeper_list); + error =0 ; + out: +mutex_unlock (&lock); +/***** EXCLUSIVE SECTION END *****/ +return error; +} + +/** +* tomoyo_write_domain_keeper_policy - Write "struct tomoyo_domain_keeper_entry" list. +* +* @data: String to parse. +* @is_not: True if it is "no_keep_domain" entry. +* @is_delete: True if it is a delete request. +* +*/ +int tomoyo_write_domain_keeper_policy (char *data,const bool is_not, +const bool is_delete) +{ +char *cp =strstr (data," from " ); + +if (cp) { + *cp ='\0' ; +return tomoyo_update_domain_keeper_entry (cp +6 , data, is_not, + is_delete); + } +return tomoyo_update_domain_keeper_entry (data,NULL , is_not, is_delete); +} + +/** +* tomoyo_read_domain_keeper_policy - Read "struct tomoyo_domain_keeper_entry" list. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns true on success, false otherwise. +*/ +bool tomoyo_read_domain_keeper_policy (struct tomoyo_io_buffer *head) +{ +struct list1_head *pos; + +list1_for_each_cookie (pos, head->read_var2, + &tomoyo_domain_keeper_list) { +struct tomoyo_domain_keeper_entry *ptr; +const char *no; +const char *from ="" ; +const char *program ="" ; + + ptr = list1_entry(pos, struct tomoyo_domain_keeper_entry, list); +if (ptr->is_deleted) +continue ; + no = ptr->is_not ?"no_" :"" ; +if (ptr->program) { + from =" from " ; + program = ptr->program->name; + } +if (!tomoyo_io_printf (head, +"%s" TOMOYO_KEYWORD_KEEP_DOMAIN +"%s%s%s\n" , no, program, from, + ptr->domainname->name)) + goto out; + } +return true; + out: +return false; +} + +/** +* is_domain_keeper - Check whether the given program causes domain transition suppression. +* +* @domainname: The name of domain. +* @program: The name of program. +* @last_name: The last component of @domainname. +* +* Returns true if executing @program supresses domain transition, +* false otherwise. +*/ +static bool is_domain_keeper (const struct tomoyo_path_info *domainname, +const struct tomoyo_path_info *program, +const struct tomoyo_path_info *last_name) +{ +struct tomoyo_domain_keeper_entry *ptr; +bool flag = false; + +list1_for_each_entry (ptr, &tomoyo_domain_keeper_list, list) { +if (ptr->is_deleted) +continue ; +if (!ptr->is_last_name) { +if (ptr->domainname != domainname) +continue ; + }else { +if (tomoyo_pathcmp (ptr->domainname, last_name)) +continue ; + } +if (ptr->program &&tomoyo_pathcmp (ptr->program, program)) +continue ; +if (ptr->is_not) +return false; + flag = true; + } +return flag; +} + +/* +* The list for "struct tomoyo_alias_entry". +* +* This list is updated only inside tomoyo_update_alias_entry(), thus +* no global mutex exists. +*/ +static LIST1_HEAD (tomoyo_alias_list); + +/** +* tomoyo_update_alias_entry - Update "struct tomoyo_alias_entry" list. +* +* @original_name: The original program's real name. +* @aliased_name: The symbolic program's symbolic link's name. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int tomoyo_update_alias_entry (const char *original_name, +const char *aliased_name, +const bool is_delete) +{ +struct tomoyo_alias_entry *new_entry; +struct tomoyo_alias_entry *ptr; +static DEFINE_MUTEX (lock); +const struct tomoyo_path_info *saved_original_name; +const struct tomoyo_path_info *saved_aliased_name; +int error = -ENOMEM ; + +if (!tomoyo_is_correct_path (original_name,1 ,-1 ,-1 , __func__) || + !tomoyo_is_correct_path (aliased_name,1 ,-1 ,-1 , __func__)) +return -EINVAL ;/* No patterns allowed. */ + saved_original_name =tomoyo_save_name (original_name); + saved_aliased_name =tomoyo_save_name (aliased_name); +if (!saved_original_name || !saved_aliased_name) +return -ENOMEM ; +/***** EXCLUSIVE SECTION START *****/ +mutex_lock (&lock); +list1_for_each_entry (ptr, &tomoyo_alias_list, list) { +if (ptr->original_name != saved_original_name || + ptr->aliased_name != saved_aliased_name) +continue ; + ptr->is_deleted = is_delete; + error =0 ; + goto out; + } +if (is_delete) { + error = -ENOENT ; + goto out; + } + new_entry =tomoyo_alloc_element (sizeof (*new_entry)); +if (!new_entry) + goto out; + new_entry->original_name = saved_original_name; + new_entry->aliased_name = saved_aliased_name; +list1_add_tail (&new_entry->list, &tomoyo_alias_list); + error =0 ; + out: +mutex_unlock (&lock); +/***** EXCLUSIVE SECTION END *****/ +return error; +} + +/** +* tomoyo_read_alias_policy - Read "struct tomoyo_alias_entry" list. +* +* @head: Pointer to "struct tomoyo_io_buffer". +* +* Returns true on success, false otherwise. +*/ +bool tomoyo_read_alias_policy (struct tomoyo_io_buffer *head) +{ +struct list1_head *pos; + +list1_for_each_cookie (pos, head->read_var2, &tomoyo_alias_list) { +struct tomoyo_alias_entry *ptr; + + ptr = list1_entry(pos, struct tomoyo_alias_entry, list); +if (ptr->is_deleted) +continue ; +if (!tomoyo_io_printf (head,TOMOYO_KEYWORD_ALIAS "%s %s\n" , + ptr->original_name->name, + ptr->aliased_name->name)) + goto out; + } +return true; + out: +return false; +} + +/** +* tomoyo_write_alias_policy - Write "struct tomoyo_alias_entry" list. +* +* @data: String to parse. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +int tomoyo_write_alias_policy (char *data,const bool is_delete) +{ +char *cp =strchr (data,' ' ); + +if (!cp) +return -EINVAL ; + *cp++ ='\0' ; +return tomoyo_update_alias_entry (data, cp, is_delete); +} + +/* Domain create/delete/undelete handler. */ + +/* #define TOMOYO_DEBUG_DOMAIN_UNDELETE */ + +/** +* tomoyo_delete_domain - Delete a domain. +* +* @domainname: The name of domain. +* +* Returns 0. +*/ +int tomoyo_delete_domain (char *domainname) +{ +struct tomoyo_domain_info *domain; +struct tomoyo_path_info name; + + name.name = domainname; +tomoyo_fill_path_info (&name); +/***** EXCLUSIVE SECTION START *****/ +mutex_lock (&tomoyo_domain_list_lock); +#ifdef TOMOYO_DEBUG_DOMAIN_UNDELETE +printk (KERN_DEBUG "tomoyo_delete_domain %s\n" , domainname); +list1_for_each_entry (domain, &tomoyo_domain_list, list) { +if (tomoyo_pathcmp (domain->domainname, &name)) +continue ; +printk (KERN_DEBUG "List: %p %u\n" , domain, domain->is_deleted); + } +#endif +/* Is there an active domain? */ +list1_for_each_entry (domain, &tomoyo_domain_list, list) { +struct tomoyo_domain_info *domain2; +/* Never delete tomoyo_kernel_domain */ +if (domain == &tomoyo_kernel_domain) +continue ; +if (domain->is_deleted || +tomoyo_pathcmp (domain->domainname, &name)) +continue ; +/* Mark already deleted domains as non undeletable. */ +list1_for_each_entry (domain2, &tomoyo_domain_list, list) { +if (!domain2->is_deleted || +tomoyo_pathcmp (domain2->domainname, &name)) +continue ; +#ifdef TOMOYO_DEBUG_DOMAIN_UNDELETE +if (domain2->is_deleted !=255 ) +printk (KERN_DEBUG +"Marked %p as non undeletable\n" , + domain2); +#endif + domain2->is_deleted =255 ; + } +/* Delete and mark active domain as undeletable. */ + domain->is_deleted =1 ; +#ifdef TOMOYO_DEBUG_DOMAIN_UNDELETE +printk (KERN_DEBUG "Marked %p as undeletable\n" , domain); +#endif +break ; + } +mutex_unlock (&tomoyo_domain_list_lock); +/***** EXCLUSIVE SECTION END *****/ +return 0 ; +} + +/** +* tomoyo_undelete_domain - Undelete a domain. +* +* @domainname: The name of domain. +* +* Returns pointer to "struct tomoyo_domain_info" on success, NULL otherwise. +*/ +struct tomoyo_domain_info *tomoyo_undelete_domain (const char *domainname) +{ +struct tomoyo_domain_info *domain; +struct tomoyo_domain_info *candidate_domain =NULL ; +struct tomoyo_path_info name; + + name.name = domainname; +tomoyo_fill_path_info (&name); +/***** EXCLUSIVE SECTION START *****/ +mutex_lock (&tomoyo_domain_list_lock); +#ifdef TOMOYO_DEBUG_DOMAIN_UNDELETE +printk (KERN_DEBUG "tomoyo_undelete_domain %s\n" , domainname); +list1_for_each_entry (domain, &tomoyo_domain_list, list) { +if (tomoyo_pathcmp (domain->domainname, &name)) +continue ; +printk (KERN_DEBUG "List: %p %u\n" , domain, domain->is_deleted); + } +#endif +list1_for_each_entry (domain, &tomoyo_domain_list, list) { +if (tomoyo_pathcmp (&name, domain->domainname)) +continue ; +if (!domain->is_deleted) { +/* This domain is active. I can't undelete. */ + candidate_domain =NULL ; +#ifdef TOMOYO_DEBUG_DOMAIN_UNDELETE +printk (KERN_DEBUG "%p is active. I can't undelete.\n" , + domain); +#endif +break ; + } +/* Is this domain undeletable? */ +if (domain->is_deleted ==1 ) + candidate_domain = domain; + } +if (candidate_domain) { + candidate_domain->is_deleted =0 ; +#ifdef TOMOYO_DEBUG_DOMAIN_UNDELETE +printk (KERN_DEBUG "%p was undeleted.\n" , candidate_domain); +#endif + } +mutex_unlock (&tomoyo_domain_list_lock); +/***** EXCLUSIVE SECTION END *****/ +return candidate_domain; +} + +/** +* tomoyo_find_or_assign_new_domain - Create a domain. +* +* @domainname: The name of domain. +* @profile: Profile number to assign if the domain was newly created. +* +* Returns pointer to "struct tomoyo_domain_info" on success, NULL otherwise. +*/ +struct tomoyo_domain_info *tomoyo_find_or_assign_new_domain (const char * + domainname, +const u8 profile) +{ +struct tomoyo_domain_info *domain =NULL ; +const struct tomoyo_path_info *saved_domainname; + +/***** EXCLUSIVE SECTION START *****/ +mutex_lock (&tomoyo_domain_list_lock); + domain =tomoyo_find_domain (domainname); +if (domain) + goto out; +if (!tomoyo_is_correct_domain (domainname, __func__)) + goto out; + saved_domainname =tomoyo_save_name (domainname); +if (!saved_domainname) + goto out; +/* Can I reuse memory of deleted domain? */ +list1_for_each_entry (domain, &tomoyo_domain_list, list) { +struct task_struct *p; +struct tomoyo_acl_info *ptr; +bool flag; +if (!domain->is_deleted || + domain->domainname != saved_domainname) +continue ; + flag = false; +/***** CRITICAL SECTION START *****/ +read_lock (&tasklist_lock); +for_each_process (p) { +if (tomoyo_real_domain (p) != domain) +continue ; + flag = true; +break ; + } +read_unlock (&tasklist_lock); +/***** CRITICAL SECTION END *****/ +if (flag) +continue ; +#ifdef TOMOYO_DEBUG_DOMAIN_UNDELETE +printk (KERN_DEBUG "Reusing %p %s\n" , domain, + domain->domainname->name); +#endif +list1_for_each_entry (ptr, &domain->acl_info_list, list) { + ptr->type |=TOMOYO_ACL_DELETED ; + } +tomoyo_set_domain_flag (domain, true, domain->flags); + domain->profile = profile; + domain->quota_warned = false; +mb ();/* Avoid out-of-order execution. */ + domain->is_deleted =0 ; + goto out; + } +/* No memory reusable. Create using new memory. */ + domain =tomoyo_alloc_element (sizeof (*domain)); +if (domain) { +INIT_LIST1_HEAD (&domain->acl_info_list); + domain->domainname = saved_domainname; + domain->profile = profile; +list1_add_tail (&domain->list, &tomoyo_domain_list); + } + out: +mutex_unlock (&tomoyo_domain_list_lock); +/***** EXCLUSIVE SECTION END *****/ +return domain; +} + +/** +* tomoyo_find_next_domain - Find a domain. +* +* @bprm: Pointer to "struct linux_binprm". +* @next_domain: Pointer to pointer to "struct tomoyo_domain_info". +* +* Returns 0 on success, negative value otherwise. +*/ +int tomoyo_find_next_domain (struct linux_binprm *bprm, +struct tomoyo_domain_info **next_domain) +{ +/* +* This function assumes that the size of buffer returned by +* tomoyo_realpath() = TOMOYO_MAX_PATHNAME_LEN. +*/ +struct tomoyo_page_buffer *tmp =tomoyo_alloc (sizeof (*tmp)); +struct tomoyo_domain_info *old_domain =tomoyo_domain (); +struct tomoyo_domain_info *domain =NULL ; +const char *old_domain_name = old_domain->domainname->name; +const char *original_name = bprm->filename; +char *new_domain_name =NULL ; +char *real_program_name =NULL ; +char *symlink_program_name =NULL ; +const u8 mode =tomoyo_check_flags (old_domain,TOMOYO_MAC_FOR_FILE ); +const bool is_enforce = (mode ==3 ); +int retval = -ENOMEM ; +struct tomoyo_path_info r;/* real name */ +struct tomoyo_path_info s;/* symlink name */ +struct tomoyo_path_info l;/* last name */ +static bool initialized; + +if (!tmp) + goto out; + +if (!initialized) { +/* +* Built-in initializers. This is needed because policies are +* not loaded until starting /sbin/init. +*/ +tomoyo_update_domain_initializer_entry (NULL ,"/sbin/hotplug" , + false, false); +tomoyo_update_domain_initializer_entry (NULL ,"/sbin/modprobe" , + false, false); + initialized = true; + } + +/* Get tomoyo_realpath of program. */ + retval = -ENOENT ; +/* I hope tomoyo_realpath() won't fail with -ENOMEM. */ + real_program_name =tomoyo_realpath (original_name); +if (!real_program_name) + goto out; +/* Get tomoyo_realpath of symbolic link. */ + symlink_program_name =tomoyo_realpath_nofollow (original_name); +if (!symlink_program_name) + goto out; + + r.name = real_program_name; +tomoyo_fill_path_info (&r); + s.name = symlink_program_name; +tomoyo_fill_path_info (&s); + l.name =tomoyo_get_last_name (old_domain); +tomoyo_fill_path_info (&l); + +/* Check 'alias' directive. */ +if (tomoyo_pathcmp (&r, &s)) { +struct tomoyo_alias_entry *ptr; +/* Is this program allowed to be called via symbolic links? */ +list1_for_each_entry (ptr, &tomoyo_alias_list, list) { +if (ptr->is_deleted || +tomoyo_pathcmp (&r, ptr->original_name) || +tomoyo_pathcmp (&s, ptr->aliased_name)) +continue ; +memset (real_program_name,0 ,TOMOYO_MAX_PATHNAME_LEN ); +strncpy (real_program_name, ptr->aliased_name->name, +TOMOYO_MAX_PATHNAME_LEN -1 ); +tomoyo_fill_path_info (&r); +break ; + } + } + +/* Check execute permission. */ + retval =tomoyo_check_exec_perm (old_domain, &r, tmp); +if (retval <0 ) + goto out; + + new_domain_name = tmp->buffer; +if (is_domain_initializer (old_domain->domainname, &r, &l)) { +/* Transit to the child of tomoyo_kernel_domain domain. */ +snprintf (new_domain_name,TOMOYO_MAX_PATHNAME_LEN +1 , +TOMOYO_ROOT_NAME " " "%s" , real_program_name); + }else if (old_domain == &tomoyo_kernel_domain && + !tomoyo_policy_loaded) { +/* +* Needn't to transit from kernel domain before starting +* /sbin/init. But transit from kernel domain if executing +* initializers because they might start before /sbin/init. +*/ + domain = old_domain; + }else if (is_domain_keeper (old_domain->domainname, &r, &l)) { +/* Keep current domain. */ + domain = old_domain; + }else { +/* Normal domain transition. */ +snprintf (new_domain_name,TOMOYO_MAX_PATHNAME_LEN +1 , +"%s %s" , old_domain_name, real_program_name); + } +if (domain ||strlen (new_domain_name) >=TOMOYO_MAX_PATHNAME_LEN ) + goto done; + domain =tomoyo_find_domain (new_domain_name); +if (domain) + goto done; +if (is_enforce) + goto done; + domain =tomoyo_find_or_assign_new_domain (new_domain_name, + old_domain->profile); + done: +if (domain) + goto out; +printk (KERN_WARNING "TOMOYO-ERROR: Domain '%s' not defined.\n" , + new_domain_name); +if (is_enforce) + retval = -EPERM ; +else +tomoyo_set_domain_flag (old_domain, false, +TOMOYO_DOMAIN_FLAGS_TRANSITION_FAILED ); + out: +tomoyo_free (real_program_name); +tomoyo_free (symlink_program_name); + *next_domain = domain ? domain : old_domain; +tomoyo_free (tmp); +return retval; +}
Tetsuo Handa
maintainerPATCH 9/11 LSM adapter functions.
In reply to Tetsuo HandaThis is LSM adapter for TOMOYO.
This version of TOMOYO assigns only one variable "struct tomoyo_domain_info *" to "current->cred->security".
Future version will require two variables "struct tomoyo_domain_info *" and "u32".
http://tomoyo.sourceforge.jp/cgi-bin/lxr/source/include/linux/sched.h#L1311
Well, how to implement this while "current->cred->security" cannot be modified freely?
To be honest, the only object which TOMOYO needs to assign variables is "struct task_struct". TOMOYO's "T" means "task".
TOMOYO utilizes characteristics of "struct task_struct".
For TOMOYO, use of per task variables is vital.
Removal of security_task_alloc() and security_task_free() by introduction of COW credentials will become a nightmare when TOMOYO tries to add "u32". Of cource, if TOMOYO is allowed to add "u32" to "struct task_struct", COW credentials won't become a nightmare.
--- security/tomoyo/tomoyo.c | 453 +++++++++++++++++++++++++++++++++++++++++++++++ security/tomoyo/tomoyo.h | 107 +++++++++++ 2 files changed, 560 insertions(+)
security/tomoyo/tomoyo.c new file +453 -0
@@ -0,0 +1,453 @@ +/* +* security/tomoyo/tomoyo.c +* +* LSM hooks for TOMOYO Linux. +* +* Copyright (C) 2005-2008 NTT DATA CORPORATION +* +* Version: 2.2.0-pre 2008/11/11 +* +*/ + +#include <linux/security.h> +#include "common.h" +#include "tomoyo.h" +#include "realpath.h" + +static int tomoyo_cred_prepare (struct cred *new,const struct cred *old, +gfp_t gfp) +{ +/* +* Since "struct tomoyo_domain_info *" is a sharable pointer, +* we don't need to duplicate. +*/ + new->security = old->security; +return 0 ; +} + +static int tomoyo_bprm_set_creds (struct linux_binprm *bprm) +{ +/* +* Do only if this function is called for the first time of an execve +* operation. +*/ +if (bprm->cred_prepared) +return 0 ; +/* +* Load policy if /sbin/tomoyo-init exists and /sbin/init is requested +* for the first time. +*/ +if (!tomoyo_policy_loaded) +tomoyo_load_policy (bprm->filename); +/* +* Tell tomoyo_bprm_check_security() is called for the first time of an +* execve operation. +*/ + bprm->cred->security =NULL ; +return 0 ; +} + +static int tomoyo_bprm_check_security (struct linux_binprm *bprm) +{ +struct tomoyo_domain_info *domain = bprm->cred->security; + +/* +* Execute permission is checked against pathname passed to do_execve() +* using current domain. +*/ +if (!domain) { +struct tomoyo_domain_info *next_domain =NULL ; +int retval =tomoyo_find_next_domain (bprm, &next_domain); + +if (!retval) + bprm->cred->security = next_domain; +return retval; + } +/* +* Read permission is checked against interpreters using next domain. +* '1' is the result of open_to_namei_flags(O_RDONLY). +*/ +return tomoyo_check_open_permission (domain, &bprm->file->f_path,1 ); +} + +#ifdef CONFIG_SYSCTL + +static int tomoyo_prepend (char **buffer,int *buflen,const char *str) +{ +int namelen =strlen (str); + +if (*buflen < namelen) +return -ENOMEM ; + *buflen -= namelen; + *buffer -= namelen; +memcpy (*buffer, str, namelen); +return 0 ; +} + +/** +* sysctlpath_from_table - return the realpath of a ctl_table. +* @table: pointer to "struct ctl_table". +* +* Returns realpath(3) of the @table on success. +* Returns NULL on failure. +* +* This function uses tomoyo_alloc(), so the caller must call tomoyo_free() +* if this function didn't return NULL. +*/ +static char *sysctl_path (struct ctl_table *table) +{ +int buflen =TOMOYO_MAX_PATHNAME_LEN ; +char *buf =tomoyo_alloc (buflen); +char *end = buf + buflen; +int error = -ENOMEM ; + +if (!buf) +return NULL ; + + *--end ='\0' ; + buflen--; +while (table) { +char buf[32 ]; +const char *sp = table->procname; + +if (!sp) { +memset (buf,0 ,sizeof (buf)); +snprintf (buf,sizeof (buf) -1 ,"=%d=" , table->ctl_name); + sp = buf; + } +if (tomoyo_prepend (&end, &buflen, sp) || +tomoyo_prepend (&end, &buflen,"/" )) + goto out; + table = table->parent; + } +if (tomoyo_prepend (&end, &buflen,"/proc/sys" )) + goto out; + error =tomoyo_encode (buf, end - buf, end); + out: +if (!error) +return buf; +tomoyo_free (buf); +return NULL ; +} + +static int tomoyo_sysctl (struct ctl_table *table,int op) +{ +int error; +char *name; + + op &=MAY_READ |MAY_WRITE ; +if (!op) +return 0 ; + name =sysctl_path (table); +if (!name) +return -ENOMEM ; + error =tomoyo_check_file_perm (tomoyo_domain (), name, op); +tomoyo_free (name); +return error; +} +#endif + +/** +* tomoyo_update_result - Update error code. +* +* @error: Return code from security_path_*(). +* +* To be able to return DAC's error (if any) to the caller instead of +* MAC's error, we don't return MAC's error at security_path_*(). +* +* We remember MAC's error only if security_path_*() returned an error. +* +* Returns 0 on success, -ENOMEM otherwise if @error != 0. +* Returns previously saved error code and clears it if @error == 0. +*/ +static int tomoyo_update_result (int error) +{ +/* Structure for holding the result of security_path_*(). */ +struct tomoyo_check_result_entry { +struct list_head list; +struct task_struct *task;/* = current */ +int error;/* != 0 */ + }; +static LIST_HEAD (list); +static DEFINE_SPINLOCK (lock); +struct task_struct *task = current; +struct tomoyo_check_result_entry *entry; +if (!error) { +if (!list_empty (&list)) { +struct tomoyo_check_result_entry *p; + entry =NULL ; +/***** CRITICAL SECTION START *****/ +spin_lock (&lock); +list_for_each_entry (p, &list, list) { +if (p->task != task) +continue ; +list_del (&p->list); + entry = p; +break ; + } +spin_unlock (&lock); +/***** CRITICAL SECTION END *****/ +if (entry) { + error = entry->error; +kfree (entry); + } + } +return error; + } + entry =kmalloc (sizeof (*entry),GFP_KERNEL ); +if (!entry) +return -ENOMEM ; + entry->task = task; + entry->error = error; +/***** CRITICAL SECTION START *****/ +spin_lock (&lock); +list_add (&entry->list, &list); +spin_unlock (&lock); +/***** CRITICAL SECTION END *****/ +return 0 ; +} + +/** +* tomoyo_save_result - Remember error code for security_inode_*() if any. +* +* @error: Return code from security_path_*(). +* +* Returns 0 on success, -ENOMEM otherwise. +* +* We don't save if @error == 0. +*/ +static int tomoyo_save_result (const int error) +{ +return error ?tomoyo_update_result (error) :0 ; +} + +/** +* tomoyo_load_result - Fetch error code for security_inode_*(). +* +* Returns error code saved by security_path_*(). +*/ +static int tomoyo_load_result (void ) +{ +return tomoyo_update_result (0 ); +} + +/* Clear error code in case security_inode_*() was not called. */ +static void tomoyo_path_clear (void ) +{ +tomoyo_load_result (); +} + +static int tomoyo_path_truncate (struct path *path,loff_t length, +unsigned int time_attrs,struct file *filp) +{ +const int ret =tomoyo_check_1path_perm (tomoyo_domain (), +TOMOYO_TYPE_TRUNCATE_ACL , + path); +return tomoyo_save_result (ret); +} + +static int tomoyo_path_unlink (struct path *parent,struct dentry *dentry) +{ +struct path path = { parent->mnt, dentry }; +const int ret =tomoyo_check_1path_perm (tomoyo_domain (), +TOMOYO_TYPE_UNLINK_ACL , + &path); +return tomoyo_save_result (ret); +} + +static int tomoyo_path_mkdir (struct path *parent,struct dentry *dentry, +int mode) +{ +struct path path = { parent->mnt, dentry }; +const int ret =tomoyo_check_1path_perm (tomoyo_domain (), +TOMOYO_TYPE_MKDIR_ACL , + &path); +return tomoyo_save_result (ret); +} + +static int tomoyo_path_rmdir (struct path *parent,struct dentry *dentry) +{ +struct path path = { parent->mnt, dentry }; +const int ret =tomoyo_check_1path_perm (tomoyo_domain (), +TOMOYO_TYPE_RMDIR_ACL , + &path); +return tomoyo_save_result (ret); +} + +static int tomoyo_path_symlink (struct path *parent,struct dentry *dentry, +const char *old_name) +{ +struct path path = { parent->mnt, dentry }; +const int ret =tomoyo_check_1path_perm (tomoyo_domain (), +TOMOYO_TYPE_SYMLINK_ACL , + &path); +return tomoyo_save_result (ret); +} + +static int tomoyo_path_mknod (struct path *parent,struct dentry *dentry, +int mode,unsigned int dev) +{ +struct path path = { parent->mnt, dentry }; +int type =TOMOYO_TYPE_CREATE_ACL ; + +switch (mode &S_IFMT ) { +case S_IFCHR : + type =TOMOYO_TYPE_MKCHAR_ACL ; +break ; +case S_IFBLK : + type =TOMOYO_TYPE_MKBLOCK_ACL ; +break ; +case S_IFIFO : + type =TOMOYO_TYPE_MKFIFO_ACL ; +break ; +case S_IFSOCK : + type =TOMOYO_TYPE_MKSOCK_ACL ; +break ; + } +return tomoyo_save_result (tomoyo_check_1path_perm (tomoyo_domain (), + type, &path)); +} + +static int tomoyo_path_link (struct dentry *old_dentry,struct path *new_dir, +struct dentry *new_dentry) +{ +struct path path1 = { new_dir->mnt, old_dentry }; +struct path path2 = { new_dir->mnt, new_dentry }; +const int ret =tomoyo_check_2path_perm (tomoyo_domain (), +TOMOYO_TYPE_LINK_ACL , + &path1, &path2); +return tomoyo_save_result (ret); +} + +static int tomoyo_path_rename (struct path *old_parent, +struct dentry *old_dentry, +struct path *new_parent, +struct dentry *new_dentry) +{ +struct path path1 = { old_parent->mnt, old_dentry }; +struct path path2 = { new_parent->mnt, new_dentry }; +const int ret =tomoyo_check_2path_perm (tomoyo_domain (), +TOMOYO_TYPE_RENAME_ACL , + &path1, &path2); +return tomoyo_save_result (ret); +} + +static int tomoyo_inode_link (struct dentry *old_dentry,struct inode *inode, +struct dentry *new_dentry) +{ +return tomoyo_load_result (); +} + +static int tomoyo_inode_unlink (struct inode *inode,struct dentry *dentry) +{ +return tomoyo_load_result (); +} + +static int tomoyo_inode_symlink (struct inode *inode,struct dentry *dentry, +const char *name) +{ +return tomoyo_load_result (); +} + +static int tomoyo_inode_mkdir (struct inode *inode,struct dentry *dentry, +int mask) +{ +return tomoyo_load_result (); +} + +static int tomoyo_inode_rmdir (struct inode *inode,struct dentry *dentry) +{ +return tomoyo_load_result (); +} + +static int tomoyo_inode_create (struct inode *dir,struct dentry *dentry, +int mode) +{ +return tomoyo_load_result (); +} + +static int tomoyo_inode_mknod (struct inode *inode,struct dentry *dentry, +int mode,dev_t dev) +{ +return tomoyo_load_result (); +} + +static int tomoyo_inode_rename (struct inode *old_inode, +struct dentry *old_dentry, +struct inode *new_inode, +struct dentry *new_dentry) +{ +return tomoyo_load_result (); +} + +static int tomoyo_inode_setattr (struct dentry *dentry,struct iattr *iattr) +{ +return tomoyo_load_result (); +} + +static int tomoyo_file_fcntl (struct file *file,unsigned int cmd, +unsigned long arg) +{ +if (cmd ==F_SETFL && ((arg ^ file->f_flags) &O_APPEND )) +return tomoyo_check_rewrite_permission (tomoyo_domain (), file); +return 0 ; +} + +static int tomoyo_dentry_open (struct file *f,const struct cred *cred) +{ +int flags = f->f_flags; + +if ((flags +1 ) &O_ACCMODE ) + flags++; + flags |= f->f_flags & (O_APPEND |O_TRUNC ); +/* Don't check read permission here if called from do_execve(). */ +if (current->in_execve) +return 0 ; +return tomoyo_check_open_permission (tomoyo_domain (), &f->f_path, flags); +} + +static struct security_operations tomoyo_security_ops = { + .name ="tomoyo" , + .cred_prepare = tomoyo_cred_prepare, + .bprm_set_creds = tomoyo_bprm_set_creds, + .bprm_check_security = tomoyo_bprm_check_security, +#ifdef CONFIG_SYSCTL + .sysctl = tomoyo_sysctl, +#endif + .file_fcntl = tomoyo_file_fcntl, + .dentry_open = tomoyo_dentry_open, + .path_truncate = tomoyo_path_truncate, + .path_unlink = tomoyo_path_unlink, + .path_mkdir = tomoyo_path_mkdir, + .path_rmdir = tomoyo_path_rmdir, + .path_symlink = tomoyo_path_symlink, + .path_mknod = tomoyo_path_mknod, + .path_link = tomoyo_path_link, + .path_rename = tomoyo_path_rename, + .inode_create = tomoyo_inode_create, + .inode_setattr = tomoyo_inode_setattr, + .inode_unlink = tomoyo_inode_unlink, + .inode_mkdir = tomoyo_inode_mkdir, + .inode_rmdir = tomoyo_inode_rmdir, + .inode_symlink = tomoyo_inode_symlink, + .inode_mknod = tomoyo_inode_mknod, + .inode_link = tomoyo_inode_link, + .inode_rename = tomoyo_inode_rename, + .path_clear = tomoyo_path_clear, +}; + +static int __init tomoyo_init (void ) +{ +struct cred *cred = (struct cred *)current_cred (); + +if (!security_module_enable (&tomoyo_security_ops)) +return 0 ; +/* register ourselves with the security framework */ +if (register_security (&tomoyo_security_ops)) +panic ("Failure registering TOMOYO Linux" ); +printk (KERN_INFO "TOMOYO Linux initialized\n" ); + cred->security = &tomoyo_kernel_domain; +return 0 ; +} + +security_initcall (tomoyo_init);
security/tomoyo/tomoyo.h new file +107 -0
107 lines not on this page · raw message
Re: [TOMOYO #13 (mmotm 2008-11-19-02-19) 09/11] LSM adapter functions.
Quoted 16 lines · Tetsuo Handa
plain text document attachment (tomoyo-hooks.patch)
This is LSM adapter for TOMOYO.
This version of TOMOYO assigns only one variable "structtomoyo_domain_info*" to "current->cred->security".Future version will require two variables "struct
tomoyo_domain_info*" and "u32".
http://tomoyo.sourceforge.jp/cgi-bin/lxr/source/include/linux/sched.h#L1311
Well, how to implement this while "current->cred->security" cannot be modified freely?To be honest, the only object which TOMOYO needs to assign variables is "struct
task_struct". TOMOYO's "T" means "task".
TOMOYO utilizes characteristics of "structtask_struct".
For TOMOYO, use of per task variables is vital.
Quoted 4 lines
Removal of
security_task_alloc()andsecurity_task_free()by introduction of COW credentials will become a nightmare when TOMOYO tries to add "u32". Of cource, if TOMOYO is allowed to add "u32" to "structtask_struct", COW credentials won't become a nightmare.
Can you explain exactly how you are using this per-task state and why it is a problem to create a new cred in the situations where you need to modify it? I think that this issue needs to be addressed up front.
Signature · 2 lines
Stephen Smalley National Security Agency
Tetsuo Handa
maintainerRe: [TOMOYO #13 (mmotm 2008-11-19-02-19) 09/11] LSM adapter functions.
Hello.
Quoted 9 lines · Stephen Smalley
Quoted 4 lines
Removal of
security_task_alloc()andsecurity_task_free()by introduction of COW credentials will become a nightmare when TOMOYO tries to add "u32". Of cource, if TOMOYO is allowed to add "u32" to "structtask_struct", COW credentials won't become a nightmare.Can you explain exactly how you are using this per-task state and why it is a problem to create a new cred in the situations where you need to modify it? I think that this issue needs to be addressed up front.
Use of COW credentials saves memory usage as long as per-task variable doesn't change, but creating a new cred after fork() introduces new error paths (i.e. memory allocation failure) when per-task variable changes, and COW credentials will no longer save memory if per-task variable differs each other.
I use this per-task variable for tracking and restricting access requests issued by a process.
http://tomoyo.sourceforge.jp/cgi-bin/lxr/source/fs/tomoyo_audit.c#L284
TOMOYO (version 1.6.x) provides "ability to change what request a process can issue without involving execve()" according to "requests that process issued in the past".
It is similar to AppArmor's chhat(), but it is done by the kernel and it is done without modifying userland programs.
I use this per-task variable for remembering "whether the process is allowed to modify policy or not". Since "the pathname of a program which is allowed to modify policy" can be changed by package manager (e.g. "rpm" and "dpkg"), I have to remember "whether the process ever had a pathname which is allowed to modify policy or not".
http://tomoyo.sourceforge.jp/cgi-bin/lxr/source/fs/ccs_common.c#L1420
http://tomoyo.sourceforge.jp/cgi-bin/lxr/source/fs/tomoyo_domain.c#L1700
I use this per-task variable for holding "type of the process". TOMOYO (version 1.6.x) provides "ability to record/verify/modify parameters passed to execve()" by executing a userland helper instead of a program passed to execve().
http://tomoyo.sourceforge.jp/cgi-bin/lxr/source/fs/tomoyo_domain.c#L1736
And maybe more in future.
Worrying about memory allocation failure complicates things. Thus I want to allocate memory on fork() and not after fork(). For that purpose, directly assigning "u32" to "struct task_struct" is preferable over allocating on demand.
Regards.
Tetsuo Handa
maintainerPATCH 10/11 Kconfig and Makefile
In reply to Tetsuo HandaTOMOYO uses LSM hooks for pathname based access control and securityfs support.
--- security/Kconfig | 1 + security/Makefile | 2 ++ security/tomoyo/Kconfig | 11 +++++++++++ security/tomoyo/Makefile | 1 + 4 files changed, 15 insertions(+)
security/Kconfig +1 -0
@@ -134,6 +134,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 # always enable default capabilities obj-y += commoncap.o
@@ -17,3 +18,4 @@ obj-$(CONFIG_SECURITY_SELINUX) += selin obj-$(CONFIG_SECURITY_SMACK) += smack/built-in.o obj-$(CONFIG_SECURITY_ROOTPLUG) += root_plug.o obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o +obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/built-in.o
security/tomoyo/Kconfig new file +11 -0
@@ -0,0 +1,11 @@ +config SECURITY_TOMOYO + bool "TOMOYO Linux Support" + depends on SECURITY + select SECURITYFS + select SECURITY_PATH + default n + help + This selects TOMOYO Linux, pathname-based access control. + Required userspace tools and further information may be + found at <http://tomoyo.sourceforge.jp/>. + If you are unsure how to answer this question, answer N.
security/tomoyo/Makefile new file +1 -0
@@ -0,0 +1 @@ +obj-y = common.o realpath.o tomoyo.o domain.o file.o
Tetsuo Handa
maintainerPATCH 11/11 MAINTAINERS info
In reply to Tetsuo HandaThe archive of tomoyo-users-en mailing list is available at
http://lists.sourceforge.jp/mailman/archives/tomoyo-users-en/ . Mailing lists for Japanese users are at
http://lists.sourceforge.jp/mailman/archives/tomoyo-users/ and
http://lists.sourceforge.jp/mailman/archives/tomoyo-dev/ .
TOMOYO Linux English portal is at
http://elinux.org/TomoyoLinux .
--- MAINTAINERS | 13 +++++++++++++ 1 file changed, 13 insertions(+)
MAINTAINERS +13 -0
@@ -4193,6 +4193,19 @@ L: [email protected] (sub W: http://sourceforge.net/projects/tlan/ S: Maintained +TOMOYO SECURITY MODULE +P: Kentaro Takeda +M: [email protected] +P: Tetsuo Handa +M: [email protected] +L: [email protected] (kernel issues) +L: [email protected] (subscribers-only, for developers and users in English) +L: [email protected] (subscribers-only, for developers in Japanese) +L: [email protected] (subscribers-only, for users in Japanese) +W: http://tomoyo.sourceforge.jp/ +T: quilt http://svn.sourceforge.jp/svnroot/tomoyo/trunk/2.2.x/tomoyo-lsm/patches/ +S: Maintained + TOSHIBA ACPI EXTRAS DRIVER P: John Belmonte M: [email protected]
Tetsuo Handa
maintainerHello.
As I see no comments on #13, I'd like to ask Andrew Morton to merge #13 and start testing on -mm tree.
Please respond soon if you have any problems/objections. Also, please respond if you can give #13 "Acked-by:" response.
Regards.