TOMOYO Linux
Subsystems (7)
Kentaro Takeda
maintainercover TOMOYO Linux
TOMOYO Linux is a pathname-based MAC extension (LSM module) for the Linux kernel.
Since the latest mmotm (2008-10-02-16-17) lacks CRED patchset by David Howells, we used linux-next (-next-20080919) which includes CRED patchset.
Diffrences from previous version are as follows.
*about LSM interfaces:
-added a new LSM hook security_path_clear() for clearing hash
table after VFS helper functions. It is needed to perform DAC before MAC.
-added a new config option CONFIG_SECURITY_PATH for new LSM hooks.
*about task_struct:
-added in_execve flag to allow 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.
*about TOMOYO body:
-made security_inode_*() return result of security_path_*() and
removed code clone of DAC.
-modified to check permisson of interpreter using
bprm->cred->security and current->in_execve flag.
-modified to use get_task_cred() for reading objective LSM context
of a task.
-modified to use bprm->cred->security to know the first call of
security_bprm_check() .
-modified to pass current->cred->security or bprm->cred->security as
parameter.
Thanks to Serge for sugguesting DAC-before-MAC workaround. Thanks to David for patiently reviewing in_execve patch.
Stephen, James, Chris, please review and respond (hopefully Ack).
Regards,
--
Kentaro Takeda
maintainerPATCH 1/8 Introduce new LSM hooks where vfsmount is available.
----- 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.
(this patch)
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.
(6.1) Introducing security_path_clear() hook.
(this patch)
To perform DAC performed in vfs_foo() before MAC, we let security_path_foo() save a result into our own hash table and return 0, and let security_inode_foo() return the saved result. Since security_inode_foo() is not always called after security_path_foo(), we need security_path_clear() to clear the hash table.
---
fs/namei.c | 46 ++++++++++++++ fs/open.c | 5 + include/linux/security.h | 151 +++++++++++++++++++++++++++++++++++++++++++++++ net/unix/af_unix.c | 5 + security/Kconfig | 9 ++ security/capability.c | 63 +++++++++++++++++++ security/security.c | 73 ++++++++++++++++++++++ 7 files changed, 352 insertions(+)
fs/namei.c +46 -0
@@ -1571,12 +1571,17 @@ 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_truncate (&nd->path,0 , +ATTR_MTIME |ATTR_CTIME |ATTR_OPEN , +NULL );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)
@@ -1601,7 +1606,12 @@ static int __open_namei_create(struct naif (!IS_POSIXACL (dir->d_inode)) mode &= ~current->fs->umask; + error =security_path_mknod (&nd->path, path->dentry, mode,0 ); +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;
@@ -2014,6 +2024,9 @@ asmlinkage long sys_mknodat(int dfd, con error =mnt_want_write (nd.path.mnt);if (error) goto out_dput; + error =security_path_mknod (&nd.path, dentry, mode, dev); +if (error) + goto out_drop_write;switch (mode &S_IFMT ) {case 0 :case S_IFREG : error =vfs_create (nd.path.dentry->d_inode,dentry,mode,&nd);
@@ -2026,6 +2039,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);
@@ -2085,7 +2100,12 @@ asmlinkage long sys_mkdirat(int dfd, con error =mnt_want_write (nd.path.mnt);if (error) goto out_dput; + error =security_path_mkdir (&nd.path, dentry, mode); +if (error) + goto out_drop_write; error =vfs_mkdir (nd.path.dentry->d_inode, dentry, mode); +security_path_clear (); +out_drop_write:mnt_drop_write (nd.path.mnt); out_dput:dput (dentry);
@@ -2192,7 +2212,12 @@ static long do_rmdir(int dfd, const char error =mnt_want_write (nd.path.mnt);if (error) goto exit3; + error =security_path_rmdir (&nd.path, dentry); +if (error) + goto exit4; error =vfs_rmdir (nd.path.dentry->d_inode, dentry); +security_path_clear (); +exit4:mnt_drop_write (nd.path.mnt); exit3:dput (dentry);
@@ -2274,7 +2299,12 @@ static long do_unlinkat(int dfd, const c error =mnt_want_write (nd.path.mnt);if (error) goto exit2; + error =security_path_unlink (&nd.path, dentry); +if (error) + goto exit3; error =vfs_unlink (nd.path.dentry->d_inode, dentry); +security_path_clear (); +exit3:mnt_drop_write (nd.path.mnt); exit2:dput (dentry);
@@ -2355,7 +2385,12 @@ asmlinkage long sys_symlinkat(const char error =mnt_want_write (nd.path.mnt);if (error) goto out_dput; + error =security_path_symlink (&nd.path, dentry, from); +if (error) + goto out_drop_write; error =vfs_symlink (nd.path.dentry->d_inode, dentry, from); +security_path_clear (); +out_drop_write:mnt_drop_write (nd.path.mnt); out_dput:dput (dentry);
@@ -2452,7 +2487,12 @@ asmlinkage long sys_linkat(int olddfd, c error =mnt_want_write (nd.path.mnt);if (error) goto out_dput; + error =security_path_link (old_path.dentry, &nd.path, new_dentry); +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);
@@ -2684,8 +2724,14 @@ asmlinkage long sys_renameat(int olddfd, error =mnt_want_write (oldnd.path.mnt);if (error) goto exit5; + error =security_path_rename (&oldnd.path, old_dentry, + &newnd.path, new_dentry); +if (error) + goto exit6; error =vfs_rename (old_dir->d_inode, old_dentry, new_dir->d_inode, new_dentry); +security_path_clear (); +exit6:mnt_drop_write (oldnd.path.mnt); exit5:dput (new_dentry);
fs/open.c +5 -0
@@ -272,6 +272,8 @@ static long do_sys_truncate(const char _ goto put_write_and_out; error =locks_verify_truncate (inode,NULL , length); +if (!error) + error =security_path_truncate (&path, length,0 ,NULL );if (!error) {DQUOT_INIT (inode); error =do_truncate (path.dentry, length,0 ,NULL );
@@ -329,6 +331,9 @@ static long do_sys_ftruncate(unsigned in error =locks_verify_truncate (inode, file, length);if (!error) + error =security_path_truncate (&file->f_path, length, +ATTR_MTIME |ATTR_CTIME , file); +if (!error) error =do_truncate (dentry, length,ATTR_MTIME |ATTR_CTIME , file); out_putf:fput (file);
include/linux/security.h +151 -0
215 lines not on this page · raw message
net/unix/af_unix.c +5 -0
@@ -828,7 +828,12 @@ static int unix_bind(struct socket *sock err =mnt_want_write (nd.path.mnt);if (err) goto out_mknod_dput; + err =security_path_mknod (&nd.path, dentry, mode,0 ); +if (err) + goto out_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 +9 -0
@@ -81,6 +81,15 @@ 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 enables the security 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 +63 -0
75 lines not on this page · raw message
security/security.c +73 -0
79 lines not on this page · raw message
--
Kentaro Takeda
maintainerPATCH 2/8 Add in_execve flag into task_struct.
In reply to Kentaro TakedaThis 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
@@ -1357,6 +1357,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 ();
@@ -1409,6 +1410,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);
@@ -1425,6 +1427,7 @@ out_file: } out_unlock: + current->in_execve =0 ;mutex_unlock (¤t->cred_exec_mutex); out_free:
fs/exec.c +3 -0
@@ -1305,6 +1305,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 ();
@@ -1358,6 +1359,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);
@@ -1376,6 +1378,7 @@ out_file: } out_unlock: + current->in_execve =0 ;mutex_unlock (¤t->cred_exec_mutex); out_free:
include/linux/sched.h +2 -0
@@ -1052,6 +1052,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;
--
Kentaro Takeda
maintainerPATCH 3/8 LSM adapter functions.
In reply to Kentaro Takeda--- security/tomoyo/tomoyo.c | 319 +++++++++++++++++++++++++++++++++++++++++++++++ security/tomoyo/tomoyo.h | 106 +++++++++++++++ 2 files changed, 425 insertions(+)
security/tomoyo/tomoyo.c new file +319 -0
@@ -0,0 +1,319 @@ +/* +* security/tomoyo/tomoyo.c +* +* LSM hooks for TOMOYO Linux. +* +* Copyright (C) 2005-2008 NTT DATA CORPORATION +* +* Version: 2.2.0-pre 2008/10/01 +* +*/ + +#include <linux/security.h> +#include "common.h" +#include "tomoyo.h" +#include "realpath.h" +#include <linux/audit.h> +#include <linux/device_cgroup.h> + +static int tmy_cred_prepare (struct cred *new,const struct cred *old,gfp_t gfp) +{ + new->security = old->security; +return 0 ; +} + +static int tmy_bprm_set_creds (struct linux_binprm *bprm) +{ +if (bprm->cred_prepared) +return 0 ; +if (!sbin_init_started) +tmy_load_policy (bprm->filename); + bprm->cred->security =NULL ; +return 0 ; +} + +static int tmy_bprm_check_security (struct linux_binprm *bprm) +{ +struct domain_info *domain = bprm->cred->security; +/* +* Execute permission is checked against pathname passed to do_execve() +* using current domain. +*/ +if (!domain) { +struct domain_info *next_domain =NULL ; +int retval =tmy_find_next_domain (bprm, &next_domain); +if (!retval) + bprm->cred->security = next_domain; +return retval; + } +/* +* Read permission is checked against interpreters using next domain. +*/ +return tmy_check_open_permission (domain, bprm->file->f_path.dentry, + bprm->file->f_path.mnt,1 ); +} + +static int tmy_sysctl (struct ctl_table *table,int op) +{ +int error; +char *name; +if ((op &6 ) ==0 ) +return 0 ; + name =sysctlpath_from_table (table); +if (!name) +return -ENOMEM ; + error =tmy_check_file_perm (tmy_domain (), name, op &6 ,"sysctl" ); +tmy_free (name); +return error; +} + +struct check_result_entry { +struct list_head list; +struct task_struct *task; +int error; +}; + +static LIST_HEAD (check_result_list); +static DEFINE_SPINLOCK (check_result_list_lock); + +/* Remember error code for security_inode_*(). */ +static int tmy_save_result (const int error) +{ +struct check_result_entry *entry; +if (!error) +return 0 ; + entry =kmalloc (sizeof (*entry),GFP_KERNEL ); +if (!entry) +return -ENOMEM ; + entry->task = current; + entry->error = error; +spin_lock (&check_result_list_lock); +list_add (&entry->list, &check_result_list); +spin_unlock (&check_result_list_lock); +return 0 ; +} + +/* Fetch error code from security_inode_*(). */ +static int tmy_load_result (void ) +{ +struct task_struct *task = current; +struct check_result_entry *p; +struct check_result_entry *entry =NULL ; +if (list_empty (&check_result_list)) +return 0 ; +spin_lock (&check_result_list_lock); +list_for_each_entry (p, &check_result_list, list) { +if (p->task != task) +continue ; +list_del (&p->list); + entry = p; +break ; + } +spin_unlock (&check_result_list_lock); +if (entry) { +int error = entry->error; +kfree (entry); +return error; + } +return 0 ; +} + +/* Clear error code if security_inode_*() was not called. */ +static void tmy_path_clear (void ) +{ +tmy_load_result (); +} + +static int tmy_path_truncate (struct path *path,loff_t length, +unsigned int time_attrs,struct file *filp) +{ +return tmy_save_result (tmy_check_1path_perm (tmy_domain (), +TMY_TYPE_TRUNCATE_ACL , + path->dentry, path->mnt)); +} + +static int tmy_path_unlink (struct path *parent,struct dentry *dentry) +{ +return tmy_save_result (tmy_check_1path_perm (tmy_domain (), +TMY_TYPE_UNLINK_ACL , + dentry, parent->mnt)); +} + +static int tmy_path_mkdir (struct path *parent,struct dentry *dentry,int mode) +{ +return tmy_save_result (tmy_check_1path_perm (tmy_domain (), +TMY_TYPE_MKDIR_ACL , + dentry, parent->mnt)); +} + +static int tmy_path_rmdir (struct path *parent,struct dentry *dentry) +{ +return tmy_save_result (tmy_check_1path_perm (tmy_domain (), +TMY_TYPE_RMDIR_ACL , + dentry, parent->mnt)); +} + +static int tmy_path_symlink (struct path *parent,struct dentry *dentry, +const char *old_name) +{ +return tmy_save_result (tmy_check_1path_perm (tmy_domain (), +TMY_TYPE_SYMLINK_ACL , + dentry, parent->mnt)); +} + +static int tmy_path_mknod (struct path *parent,struct dentry *dentry,int mode, +unsigned int dev) +{ +struct vfsmount *mnt = parent->mnt; +int type =TMY_TYPE_CREATE_ACL ; +switch (mode &S_IFMT ) { +case S_IFCHR : + type =TMY_TYPE_MKCHAR_ACL ; +break ; +case S_IFBLK : + type =TMY_TYPE_MKBLOCK_ACL ; +break ; +case S_IFIFO : + type =TMY_TYPE_MKFIFO_ACL ; +break ; +case S_IFSOCK : + type =TMY_TYPE_MKSOCK_ACL ; +break ; + } +return tmy_save_result (tmy_check_1path_perm (tmy_domain (), + type, dentry, mnt)); +} + +static int tmy_path_link (struct dentry *old_dentry,struct path *new_dir, +struct dentry *new_dentry) +{ +return tmy_save_result (tmy_check_2path_perm (tmy_domain (), +TMY_TYPE_LINK_ACL , + old_dentry, new_dir->mnt, + new_dentry, new_dir->mnt)); +} + +static int tmy_path_rename (struct path *old_parent,struct dentry *old_dentry, +struct path *new_parent,struct dentry *new_dentry) +{ +return tmy_save_result (tmy_check_2path_perm (tmy_domain (), +TMY_TYPE_RENAME_ACL , + old_dentry, + old_parent->mnt, + new_dentry, + new_parent->mnt)); +} + +static int tmy_inode_link (struct dentry *old_dentry,struct inode *inode, +struct dentry *new_dentry) +{ +return tmy_load_result (); +} + +static int tmy_inode_unlink (struct inode *inode,struct dentry *dentry) +{ +return tmy_load_result (); +} + +static int tmy_inode_symlink (struct inode *inode,struct dentry *dentry, +const char *name) +{ +return tmy_load_result (); +} + +static int tmy_inode_mkdir (struct inode *inode,struct dentry *dentry, +int mask) +{ +return tmy_load_result (); +} + +static int tmy_inode_rmdir (struct inode *inode,struct dentry *dentry) +{ +return tmy_load_result (); +} + +static int tmy_inode_create (struct inode *dir,struct dentry *dentry,int mode) +{ +return tmy_load_result (); +} + +static int tmy_inode_mknod (struct inode *inode,struct dentry *dentry, +int mode,dev_t dev) +{ +return tmy_load_result (); +} + +static int tmy_inode_rename (struct inode *old_inode,struct dentry *old_dentry, +struct inode *new_inode,struct dentry *new_dentry) +{ +return tmy_load_result (); +} + +static int tmy_inode_setattr (struct dentry *dentry,struct iattr *iattr) +{ +return tmy_load_result (); +} + +static int tmy_file_fcntl (struct file *file,unsigned int cmd, +unsigned long arg) +{ +if (cmd ==F_SETFL && ((arg ^ file->f_flags) &O_APPEND )) +return tmy_check_rewrite_permission (tmy_domain (), file); +return 0 ; +} + +static int tmy_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 tmy_check_open_permission (tmy_domain (), f->f_path.dentry, + f->f_path.mnt, flags); +} + +static struct security_operations tomoyo_security_ops = { + .name ="tomoyo" , + .cred_prepare = tmy_cred_prepare, + .bprm_set_creds = tmy_bprm_set_creds, + .bprm_check_security = tmy_bprm_check_security, + .sysctl = tmy_sysctl, + .file_fcntl = tmy_file_fcntl, + .dentry_open = tmy_dentry_open, + .path_truncate = tmy_path_truncate, + .path_unlink = tmy_path_unlink, + .path_mkdir = tmy_path_mkdir, + .path_rmdir = tmy_path_rmdir, + .path_symlink = tmy_path_symlink, + .path_mknod = tmy_path_mknod, + .path_link = tmy_path_link, + .path_rename = tmy_path_rename, + .inode_create = tmy_inode_create, + .inode_setattr = tmy_inode_setattr, + .inode_unlink = tmy_inode_unlink, + .inode_mkdir = tmy_inode_mkdir, + .inode_rmdir = tmy_inode_rmdir, + .inode_symlink = tmy_inode_symlink, + .inode_mknod = tmy_inode_mknod, + .inode_link = tmy_inode_link, + .inode_rename = tmy_inode_rename, + .path_clear = tmy_path_clear, +}; + +static int __init tmy_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 = &KERNEL_DOMAIN ; +return 0 ; +} + +security_initcall (tmy_init);
security/tomoyo/tomoyo.h new file +106 -0
106 lines not on this page · raw message
--
Re: [TOMOYO #10 (linux-next) 3/8] LSM adapter functions.
Hi,
some nitpicks.
Quoted 11 lines · On Thu, 09 Oct 2008 13:28:17 +0900 Kentaro Takeda <[email protected]>…
Signed-off-by Kentaro Takeda <[email protected]>Signed-off-by Tetsuo Handa <[email protected]>Signed-off-by Toshiharu Harada <[email protected]>--- security/tomoyo/tomoyo.c | 319 +++++++++++++++++++++++++++++++++++++++++++++++ security/tomoyo/tomoyo.h | 106 +++++++++++++++ 2 files changed, 425 insertions(+)
security/tomoyo/tomoyo.c line 10
Quoted 10 lines
security/tomoyo/tomoyo.c new file +10
-0@@ -0,0 +1,319 @@ +/* +* security/tomoyo/tomoyo.c +* +* LSM hooks for TOMOYO Linux. +* +* Copyright (C) 2005-2008 NTT DATA CORPORATION +* +* Version: 2.2.0-pre 2008/10/01 +* +*/
What is this Version for ?
Is this exported to userland via some interface ?
Quoted 41 lines
security/tomoyo/tomoyo.c new file +41
-0+ +#include <linux/security.h> +#include "common.h" +#include "tomoyo.h" +#include "realpath.h" +#include <linux/audit.h> +#include <linux/device_cgroup.h> + +static int tmy_cred_prepare (struct cred *new,const struct cred *old,gfp_t gfp) +{ + new->security = old->security; +return 0 ; +} + +static int tmy_bprm_set_creds (struct linux_binprm *bprm) +{ +if (bprm->cred_prepared) +return 0 ; +if (!sbin_init_started) +tmy_load_policy (bprm->filename); + bprm->cred->security =NULL ; +return 0 ; +} + +static int tmy_bprm_check_security (struct linux_binprm *bprm) +{ +struct domain_info *domain = bprm->cred->security; +/* +* Execute permission is checked against pathname passed to do_execve() +* using current domain. +*/ +if (!domain) { +struct domain_info *next_domain =NULL ; +int retval =tmy_find_next_domain (bprm, &next_domain); +if (!retval) + bprm->cred->security = next_domain; +return retval; + } +/* +* Read permission is checked against interpreters using next domain. +*/
security/tomoyo/tomoyo.c line 61
Quoted 10 lines
security/tomoyo/tomoyo.c new file +10
-0+return tmy_check_open_permission (domain, bprm->file->f_path.dentry, + bprm->file->f_path.mnt,1 ); +} + +static int tmy_sysctl (struct ctl_table *table,int op) +{ +int error; +char *name; +if ((op &6 ) ==0 ) +return 0 ;
It seems that you need a blanc line between variable declaration and start of code, like this.
==
char *name;
<<
if ((op & 6) == 0)
return 0;==
BTW, is this "6" need to be "6" rather than some readable macro ?
<snip>
security/tomoyo/tomoyo.c line 277
Quoted 13 lines
security/tomoyo/tomoyo.c new file +13
-0+static int tmy_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 tmy_check_open_permission (tmy_domain (), f->f_path.dentry, + f->f_path.mnt, flags); +} +
This tmy_check_open_permission() is defiend in 7/8.
Because people uses "bisect" in these days, I think it's better to avoid this kind of reversed dependency of patch stack.
Thanks,
-Kame
Kentaro Takeda
maintainerRe: [TOMOYO #10 (linux-next) 3/8] LSM adapter functions.
Quoted 1 line · KAMEZAWA Hiroyuki
Quoted 1 line
+ * Version: 2.2.0-pre 2008/10/01
<snip>
Quoted 2 lines
What is this Version for ?
Is this exported to userland via some interface ?
Yes. This version is exported via /sys/kernel/security/tomoyo/version .
Quoted 10 lines
Review
Quoted 6 lines
(quoted diff) +6
-0+static int tmy_sysctl(struct ctl_table *table, int op) +{ + int error; + char *name; + if ((op & 6) == 0) + return 0;It seems that you need a blanc line between variable declaration and start of code, like this.
Quoted 6 lines
==
char *name; << if ((op & 6) == 0) return 0;==
Fixed.
Quoted 1 line
BTW, is this "6" need to be "6" rather than some readable macro ?
Replaced "6" with "(MAY_READ | MAY_WRITE)" .
Quoted 3 lines
This
tmy_check_open_permission()is defiend in 7/8.
Because people uses "bisect" in these days, I think it's better to avoid this kind of reversed dependency of patch stack.
I see. I'll change patch order next time.
Thanks for your advice. :)
Regards,
---
Subject: LSM adapter functions.
--- security/tomoyo/tomoyo.c | 328 +++++++++++++++++++++++++++++++++++++++++++++++ security/tomoyo/tomoyo.h | 106 +++++++++++++++ 2 files changed, 434 insertions(+)
security/tomoyo/tomoyo.c new file +328 -0
@@ -0,0 +1,328 @@ +/* +* security/tomoyo/tomoyo.c +* +* LSM hooks for TOMOYO Linux. +* +* Copyright (C) 2005-2008 NTT DATA CORPORATION +* +* Version: 2.2.0-pre 2008/10/01 +* +*/ + +#include <linux/security.h> +#include "common.h" +#include "tomoyo.h" +#include "realpath.h" +#include <linux/audit.h> +#include <linux/device_cgroup.h> + +static int tmy_cred_prepare (struct cred *new,const struct cred *old,gfp_t gfp) +{ + new->security = old->security; +return 0 ; +} + +static int tmy_bprm_set_creds (struct linux_binprm *bprm) +{ +if (bprm->cred_prepared) +return 0 ; +if (!sbin_init_started) +tmy_load_policy (bprm->filename); + bprm->cred->security =NULL ; +return 0 ; +} + +static int tmy_bprm_check_security (struct linux_binprm *bprm) +{ +struct domain_info *domain = bprm->cred->security; + +/* +* Execute permission is checked against pathname passed to do_execve() +* using current domain. +*/ +if (!domain) { +struct domain_info *next_domain =NULL ; +int retval =tmy_find_next_domain (bprm, &next_domain); +if (!retval) + bprm->cred->security = next_domain; +return retval; + } +/* +* Read permission is checked against interpreters using next domain. +*/ +return tmy_check_open_permission (domain, bprm->file->f_path.dentry, + bprm->file->f_path.mnt,1 ); +} + +static int tmy_sysctl (struct ctl_table *table,int op) +{ +int error; +char *name; + +if ((op & (MAY_READ |MAY_WRITE )) ==0 ) +return 0 ; + name =sysctlpath_from_table (table); +if (!name) +return -ENOMEM ; + error =tmy_check_file_perm (tmy_domain (), name, + op & (MAY_READ |MAY_WRITE ), +"sysctl" ); +tmy_free (name); +return error; +} + +struct check_result_entry { +struct list_head list; +struct task_struct *task; +int error; +}; + +static LIST_HEAD (check_result_list); +static DEFINE_SPINLOCK (check_result_list_lock); + +/* Remember error code for security_inode_*(). */ +static int tmy_save_result (const int error) +{ +struct check_result_entry *entry; + +if (!error) +return 0 ; + entry =kmalloc (sizeof (*entry),GFP_KERNEL ); +if (!entry) +return -ENOMEM ; + entry->task = current; + entry->error = error; +spin_lock (&check_result_list_lock); +list_add (&entry->list, &check_result_list); +spin_unlock (&check_result_list_lock); +return 0 ; +} + +/* Fetch error code from security_inode_*(). */ +static int tmy_load_result (void ) +{ +struct task_struct *task = current; +struct check_result_entry *p; +struct check_result_entry *entry =NULL ; + +if (list_empty (&check_result_list)) +return 0 ; +spin_lock (&check_result_list_lock); +list_for_each_entry (p, &check_result_list, list) { +if (p->task != task) +continue ; +list_del (&p->list); + entry = p; +break ; + } +spin_unlock (&check_result_list_lock); +if (entry) { +int error = entry->error; +kfree (entry); +return error; + } +return 0 ; +} + +/* Clear error code if security_inode_*() was not called. */ +static void tmy_path_clear (void ) +{ +tmy_load_result (); +} + +static int tmy_path_truncate (struct path *path,loff_t length, +unsigned int time_attrs,struct file *filp) +{ +return tmy_save_result (tmy_check_1path_perm (tmy_domain (), +TMY_TYPE_TRUNCATE_ACL , + path->dentry, path->mnt)); +} + +static int tmy_path_unlink (struct path *parent,struct dentry *dentry) +{ +return tmy_save_result (tmy_check_1path_perm (tmy_domain (), +TMY_TYPE_UNLINK_ACL , + dentry, parent->mnt)); +} + +static int tmy_path_mkdir (struct path *parent,struct dentry *dentry,int mode) +{ +return tmy_save_result (tmy_check_1path_perm (tmy_domain (), +TMY_TYPE_MKDIR_ACL , + dentry, parent->mnt)); +} + +static int tmy_path_rmdir (struct path *parent,struct dentry *dentry) +{ +return tmy_save_result (tmy_check_1path_perm (tmy_domain (), +TMY_TYPE_RMDIR_ACL , + dentry, parent->mnt)); +} + +static int tmy_path_symlink (struct path *parent,struct dentry *dentry, +const char *old_name) +{ +return tmy_save_result (tmy_check_1path_perm (tmy_domain (), +TMY_TYPE_SYMLINK_ACL , + dentry, parent->mnt)); +} + +static int tmy_path_mknod (struct path *parent,struct dentry *dentry,int mode, +unsigned int dev) +{ +struct vfsmount *mnt = parent->mnt; +int type =TMY_TYPE_CREATE_ACL ; + +switch (mode &S_IFMT ) { +case S_IFCHR : + type =TMY_TYPE_MKCHAR_ACL ; +break ; +case S_IFBLK : + type =TMY_TYPE_MKBLOCK_ACL ; +break ; +case S_IFIFO : + type =TMY_TYPE_MKFIFO_ACL ; +break ; +case S_IFSOCK : + type =TMY_TYPE_MKSOCK_ACL ; +break ; + } +return tmy_save_result (tmy_check_1path_perm (tmy_domain (), + type, dentry, mnt)); +} + +static int tmy_path_link (struct dentry *old_dentry,struct path *new_dir, +struct dentry *new_dentry) +{ +return tmy_save_result (tmy_check_2path_perm (tmy_domain (), +TMY_TYPE_LINK_ACL , + old_dentry, new_dir->mnt, + new_dentry, new_dir->mnt)); +} + +static int tmy_path_rename (struct path *old_parent,struct dentry *old_dentry, +struct path *new_parent,struct dentry *new_dentry) +{ +return tmy_save_result (tmy_check_2path_perm (tmy_domain (), +TMY_TYPE_RENAME_ACL , + old_dentry, + old_parent->mnt, + new_dentry, + new_parent->mnt)); +} + +static int tmy_inode_link (struct dentry *old_dentry,struct inode *inode, +struct dentry *new_dentry) +{ +return tmy_load_result (); +} + +static int tmy_inode_unlink (struct inode *inode,struct dentry *dentry) +{ +return tmy_load_result (); +} + +static int tmy_inode_symlink (struct inode *inode,struct dentry *dentry, +const char *name) +{ +return tmy_load_result (); +} + +static int tmy_inode_mkdir (struct inode *inode,struct dentry *dentry, +int mask) +{ +return tmy_load_result (); +} + +static int tmy_inode_rmdir (struct inode *inode,struct dentry *dentry) +{ +return tmy_load_result (); +} + +static int tmy_inode_create (struct inode *dir,struct dentry *dentry,int mode) +{ +return tmy_load_result (); +} + +static int tmy_inode_mknod (struct inode *inode,struct dentry *dentry, +int mode,dev_t dev) +{ +return tmy_load_result (); +} + +static int tmy_inode_rename (struct inode *old_inode,struct dentry *old_dentry, +struct inode *new_inode,struct dentry *new_dentry) +{ +return tmy_load_result (); +} + +static int tmy_inode_setattr (struct dentry *dentry,struct iattr *iattr) +{ +return tmy_load_result (); +} + +static int tmy_file_fcntl (struct file *file,unsigned int cmd, +unsigned long arg) +{ +if (cmd ==F_SETFL && ((arg ^ file->f_flags) &O_APPEND )) +return tmy_check_rewrite_permission (tmy_domain (), file); +return 0 ; +} + +static int tmy_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 tmy_check_open_permission (tmy_domain (), f->f_path.dentry, + f->f_path.mnt, flags); +} + +static struct security_operations tomoyo_security_ops = { + .name ="tomoyo" , + .cred_prepare = tmy_cred_prepare, + .bprm_set_creds = tmy_bprm_set_creds, + .bprm_check_security = tmy_bprm_check_security, + .sysctl = tmy_sysctl, + .file_fcntl = tmy_file_fcntl, + .dentry_open = tmy_dentry_open, + .path_truncate = tmy_path_truncate, + .path_unlink = tmy_path_unlink, + .path_mkdir = tmy_path_mkdir, + .path_rmdir = tmy_path_rmdir, + .path_symlink = tmy_path_symlink, + .path_mknod = tmy_path_mknod, + .path_link = tmy_path_link, + .path_rename = tmy_path_rename, + .inode_create = tmy_inode_create, + .inode_setattr = tmy_inode_setattr, + .inode_unlink = tmy_inode_unlink, + .inode_mkdir = tmy_inode_mkdir, + .inode_rmdir = tmy_inode_rmdir, + .inode_symlink = tmy_inode_symlink, + .inode_mknod = tmy_inode_mknod, + .inode_link = tmy_inode_link, + .inode_rename = tmy_inode_rename, + .path_clear = tmy_path_clear, +}; + +static int __init tmy_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 = &KERNEL_DOMAIN ; +return 0 ; +} + +security_initcall (tmy_init);
security/tomoyo/tomoyo.h new file +106 -0
106 lines not on this page · raw message
Kentaro Takeda
maintainerPATCH 4/8 Memory and pathname management functions.
In reply to Kentaro TakedaTOMOYO Linux performs pathname based access control.
To remove factors that make pathname based access control difficult (e.g. symbolic links, "..", "//" etc.), TOMOYO Linux derives realpath of requested pathname from "struct dentry" and "struct vfsmount".
The maximum length of string data is limited to 4000 including trailing '\0'. Since TOMOYO Linux uses '\ooo' style representation for non ASCII printable characters, may be TOMOYO Linux should be able to support 16336 (which means (NAME_MAX * (PATH_MAX / (NAME_MAX + 1)) * 4 + (PATH_MAX / (NAME_MAX + 1))) including trailing '\0'), but I think 4000 is enough for practical use.
--- security/tomoyo/realpath.c | 723 +++++++++++++++++++++++++++++++++++++++++++++ security/tomoyo/realpath.h | 60 +++ 2 files changed, 783 insertions(+)
security/tomoyo/realpath.c new file +723 -0
@@ -0,0 +1,723 @@ +/* +* 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/10/01 +* +*/ + +#include <linux/types.h> +#include <linux/mount.h> +#include <linux/magic.h> +#include <linux/sysctl.h> +#include "common.h" +#include "realpath.h" + +/** +* get_absolute_path - Get the path of a dentry but ignores chroot'ed root. +* +* @dentry: Pointer to "struct dentry". +* @vfsmnt: Pointer to "struct vfsmount". +* @buffer: Pointer to buffer to return value in. +* @buflen: Sizeof @buffer. +* +* Returns 0 on success, -ENOMEM otherwise. +* +* Caller holds the dcache_lock and vfsmount_lock. +* Based on __d_path() in fs/dcache.c +* +* If dentry is a directory, trailing '/' is appended. +* Characters out of 0x20 < c < 0x7F range are converted to +* \ooo style octal string. +* Character \ is converted to \\ string. +*/ +static int get_absolute_path (struct dentry *dentry,struct vfsmount *vfsmnt, +char *buffer,int buflen) +{ +/***** CRITICAL SECTION START *****/ +char *start = buffer; +char *end = buffer + buflen; +bool is_dir = (dentry->d_inode &&S_ISDIR (dentry->d_inode->i_mode)); + +if (buflen <256 ) + goto out; + + *--end ='\0' ; + buflen--; + +for (;;) { +struct dentry *parent; + +if (dentry == vfsmnt->mnt_root ||IS_ROOT (dentry)) { +/* Global root? */ +if (vfsmnt->mnt_parent == vfsmnt) +break ; + dentry = vfsmnt->mnt_mountpoint; + vfsmnt = vfsmnt->mnt_parent; +continue ; + } +if (is_dir) { + is_dir = false; + *--end ='/' ; + buflen--; + } + parent = dentry->d_parent; + { +const char *sp = dentry->d_name.name; +const char *cp = sp + dentry->d_name.len -1 ; +unsigned char c; + +/* +* Exception: Use /proc/self/ rather than +* /proc/\$/ for current process. +*/ +if (IS_ROOT (parent) && *sp >'0' && *sp <='9' && + parent->d_sb && + parent->d_sb->s_magic ==PROC_SUPER_MAGIC ) { +unsigned long pid; +if (!strict_strtoul (sp,10 , &pid) && + (pid_t ) pid == current->tgid) { + sp ="self" ; + cp = sp +3 ; + } + } + +while (sp <= cp) { + c = *(unsigned char *) cp; +if (c =='\\' ) { + buflen -=2 ; +if (buflen <0 ) + goto out; + *--end ='\\' ; + *--end ='\\' ; + }else if (c >' ' && c <127 ) { +if (--buflen <0 ) + goto out; + *--end = (char ) c; + }else { + buflen -=4 ; +if (buflen <0 ) + goto out; + *--end = (c &7 ) +'0' ; + *--end = ((c >>3 ) &7 ) +'0' ; + *--end = (c >>6 ) +'0' ; + *--end ='\\' ; + } + cp--; + } +if (--buflen <0 ) + goto out; + *--end ='/' ; + } + dentry = parent; + } +if (*end =='/' ) { + buflen++; + end++; + } + { +const char *sp = dentry->d_name.name; +const char *cp = sp + dentry->d_name.len -1 ; +unsigned char c; +while (sp <= cp) { + c = *(unsigned char *) cp; +if (c =='\\' ) { + buflen -=2 ; +if (buflen <0 ) + goto out; + *--end ='\\' ; + *--end ='\\' ; + }else if (c >' ' && c <127 ) { +if (--buflen <0 ) + goto out; + *--end = (char ) c; + }else { + buflen -=4 ; +if (buflen <0 ) + goto out; + *--end = (c &7 ) +'0' ; + *--end = ((c >>3 ) &7 ) +'0' ; + *--end = (c >>6 ) +'0' ; + *--end ='\\' ; + } + cp--; + } + } +/* Move the pathname to the top of the buffer. */ +memmove (start, end,strlen (end) +1 ); +return 0 ; +out: +return -ENOMEM ; +/***** CRITICAL SECTION END *****/ +} + +/** +* tmy_realpath_from_dentry2 - Returns realpath(3) of the given dentry but ignores chroot'ed root. +* +* @dentry: Pointer to "struct dentry". +* @mnt: Pointer to "struct vfsmount". +* @newname: Pointer to buffer to return value in. +* @newname_len: Size of @newname. +* +* Returns 0 on success, negative value otherwise. +*/ +int tmy_realpath_from_dentry2 (struct dentry *dentry,struct vfsmount *mnt, +char *newname,int newname_len) +{ +int error; +struct dentry *d_dentry; +struct vfsmount *d_mnt; +if (!dentry || !mnt || !newname || newname_len <=2048 ) +return -EINVAL ; +if (dentry->d_op && dentry->d_op->d_dname) { +/* For "socket:[\$]" and "pipe:[\$]". */ +static const int offset =1536 ; +char *dp = newname; +char *sp = dentry->d_op->d_dname (dentry, newname + offset, + newname_len - offset); +if (IS_ERR (sp)) { + error =PTR_ERR (sp); + goto out; + } + error = -ENOMEM ; + newname += offset; +while (1 ) { +const unsigned char c = *(unsigned char *) sp++; +if (c =='\\' ) { +if (dp +2 >= newname) +break ; + *dp++ ='\\' ; + *dp++ ='\\' ; + }else if (c >' ' && c <127 ) { +if (dp +1 >= newname) +break ; + *dp++ = (char ) c; + }else if (c) { +if (dp +4 >= newname) +break ; + *dp++ ='\\' ; + *dp++ = (c >>6 ) +'0' ; + *dp++ = ((c >>3 ) &7 ) +'0' ; + *dp++ = (c &7 ) +'0' ; + }else { + *dp ='\0' ; +return 0 ; + } + } + goto out; + } + d_dentry =dget (dentry); + d_mnt =mntget (mnt); +/***** CRITICAL SECTION START *****/ +spin_lock (&dcache_lock); +spin_lock (&vfsmount_lock); + error =get_absolute_path (d_dentry, d_mnt, newname, newname_len); +spin_unlock (&vfsmount_lock); +spin_unlock (&dcache_lock); +/***** CRITICAL SECTION END *****/ +dput (d_dentry); +mntput (d_mnt); + out: +if (error) +printk (KERN_WARNING "tmy_realpath: Pathname too long.\n" ); +return error; +} + +/** +* tmy_realpath_from_dentry - Returns realpath(3) of the given pathname but ignores chroot'ed root. +* +* @dentry: Pointer to "struct dentry". +* @mnt: Pointer to "struct vfsmount". +* +* Returns the realpath of the given @dentry and @mnt on success, +* NULL otherwise. +* +* These functions use tmy_alloc(), so caller must tmy_free() +* if these functions didn't return NULL. +*/ +char *tmy_realpath_from_dentry (struct dentry *dentry,struct vfsmount *mnt) +{ +char *buf =tmy_alloc (sizeof (struct tmy_page_buffer )); +if (buf &&tmy_realpath_from_dentry2 (dentry, mnt, buf, +TMY_MAX_PATHNAME_LEN -1 ) ==0 ) +return buf; +tmy_free (buf); +return NULL ; +} + +/** +* tmy_realpath - Get realpath of a pathname. +* +* @pathname: The pathname to solve. +* +* Returns the realpath of @pathname on success, NULL otherwise. +*/ +char *tmy_realpath (const char *pathname) +{ +struct nameidata nd; +if (pathname &&path_lookup (pathname,LOOKUP_FOLLOW , &nd) ==0 ) { +char *buf =tmy_realpath_from_dentry (nd.path.dentry, + nd.path.mnt); +path_put (&nd.path); +return buf; + } +return NULL ; +} + +/** +* tmy_realpath_nofollow - Get realpath of a pathname. +* +* @pathname: The pathname to solve. +* +* Returns the realpath of @pathname on success, NULL otherwise. +*/ +char *tmy_realpath_nofollow (const char *pathname) +{ +struct nameidata nd; +if (pathname &&path_lookup (pathname,0 , &nd) ==0 ) { +char *buf =tmy_realpath_from_dentry (nd.path.dentry, + nd.path.mnt); +path_put (&nd.path); +return buf; + } +return NULL ; +} + +/** +* round_up - Round up an integer so that the returned pointers are appropriately aligned. +* +* @size: Size in bytes. +* +* Returns rounded value of @size. +* +* FIXME: Are there more requirements that is needed for assigning value +* atomically? +*/ +static inline unsigned int round_up (const unsigned int size) +{ +if (sizeof (void *) >=sizeof (long )) +return ((size +sizeof (void *) -1 ) + /sizeof (void *)) *sizeof (void *); +else +return ((size +sizeof (long ) -1 ) + /sizeof (long )) *sizeof (long ); +} + +static unsigned int allocated_memory_for_elements; +static unsigned int quota_for_elements; + +/** +* tmy_alloc_element - Allocate permanent memory for structures. +* +* @size: Size in bytes. +* +* Returns pointer to allocated memory on success, NULL otherwise. +* +* The RAM is chunked, so NEVER try to kfree() the returned pointer. +*/ +void *tmy_alloc_element (const unsigned int size) +{ +static DEFINE_MUTEX (lock); +static char *buf; +static unsigned int buf_used_len =PAGE_SIZE ; +char *ptr =NULL ; +const unsigned int word_aligned_size =round_up (size); +if (word_aligned_size >PAGE_SIZE ) +return NULL ; +mutex_lock (&lock); +if (buf_used_len + word_aligned_size >PAGE_SIZE ) { +if (!quota_for_elements || allocated_memory_for_elements + +PAGE_SIZE <= quota_for_elements) + ptr =kzalloc (PAGE_SIZE ,GFP_KERNEL ); +if (!ptr) { +printk (KERN_WARNING "ERROR: Out of memory " +"for tmy_alloc_element().\n" ); +if (!sbin_init_started) +panic ("MAC Initialization failed.\n" ); + }else { + buf = ptr; + allocated_memory_for_elements +=PAGE_SIZE ; + buf_used_len = word_aligned_size; + ptr = buf; + } + }else if (word_aligned_size) { +int i; + ptr = buf + buf_used_len; + buf_used_len += word_aligned_size; +for (i =0 ; i < word_aligned_size; i++) { +if (!ptr[i]) +continue ; +printk (KERN_ERR "WARNING: Reserved memory was tainted! " +"The system might go wrong.\n" ); + ptr[i] ='\0' ; + } + } +mutex_unlock (&lock); +return ptr; +} + +static unsigned int allocated_memory_for_savename; +static unsigned int quota_for_savename; + +#define MAX_HASH 256 + +/* Structure for string data. */ +struct name_entry { +struct list1_head list; +struct path_info entry; +}; + +/* Structure for available memory region. */ +struct free_memory_block_list { +struct list_head list; +char *ptr;/* Pointer to a free area. */ +int len;/* Length of the area. */ +}; + +/* The list for "struct name_entry". */ +static struct list1_head name_list[MAX_HASH ]; + +/** +* tmy_save_name - Allocate permanent memory for string data. +* +* @name: The string to store into the permernent memory. +* +* Returns pointer to "struct path_info" on success, NULL otherwise. +* +* The RAM is shared, so NEVER try to modify or kfree() the returned name. +*/ +const struct path_info *tmy_save_name (const char *name) +{ +static LIST_HEAD (fmb_list); +static DEFINE_MUTEX (lock); +struct name_entry *ptr; +unsigned int hash; +struct free_memory_block_list *fmb; +int len; +char *cp; +if (!name) +return NULL ; + len =strlen (name) +1 ; +if (len >TMY_MAX_PATHNAME_LEN ) { +printk (KERN_WARNING "ERROR: Name too long " +"for tmy_save_name().\n" ); +return NULL ; + } + hash =full_name_hash ((const unsigned char *) name, len -1 ); +mutex_lock (&lock); +list1_for_each_entry (ptr, &name_list[hash %MAX_HASH ], list) { +if (hash == ptr->entry.hash && !strcmp (name, ptr->entry.name)) + goto out; + } +list_for_each_entry (fmb, &fmb_list, list) { +if (len <= fmb->len) + goto ready; + } +if (!quota_for_savename || allocated_memory_for_savename +PAGE_SIZE + <= quota_for_savename) + cp =kzalloc (PAGE_SIZE ,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 tmy_save_name().\n" ); +if (!sbin_init_started) +panic ("MAC Initialization failed.\n" ); + ptr =NULL ; + goto out; + } + allocated_memory_for_savename +=PAGE_SIZE ; +list_add (&fmb->list, &fmb_list); + fmb->ptr = cp; + fmb->len =PAGE_SIZE ; +ready: + ptr =tmy_alloc_element (sizeof (*ptr)); +if (!ptr) + goto out; + ptr->entry.name = fmb->ptr; +memmove (fmb->ptr, name, len); +tmy_fill_path_info (&ptr->entry); + fmb->ptr += len; + fmb->len -= len; +list1_add_tail_mb (&ptr->list, &name_list[hash %MAX_HASH ]); +if (fmb->len ==0 ) { +list_del (&fmb->list); +kfree (fmb); + } +out: +mutex_unlock (&lock); +return ptr ? &ptr->entry :NULL ; +} + +/* Structure for temporarily allocated memory. */ +struct cache_entry { +struct list_head list; +void *ptr; +int size; +}; + +static struct kmem_cache *tmy_cachep; + +/** +* tmy_realpath_init - Initialize realpath related code. +* +* Returns 0. +*/ +static int __init tmy_realpath_init (void ) +{ +int i; +if (TMY_MAX_PATHNAME_LEN >PAGE_SIZE ) +panic ("Bad size." ); + tmy_cachep =kmem_cache_create ("tmy_cache" ,sizeof (struct cache_entry ), +0 ,0 ,NULL ); +if (!tmy_cachep) +panic ("Can't create cache.\n" ); +for (i =0 ; i <MAX_HASH ; i++) +INIT_LIST1_HEAD (&name_list[i]); +INIT_LIST1_HEAD (&KERNEL_DOMAIN .acl_info_list); +KERNEL_DOMAIN .domainname =tmy_save_name (ROOT_NAME ); +list1_add_tail_mb (&KERNEL_DOMAIN .list, &domain_list); +if (tmy_find_domain (ROOT_NAME ) != &KERNEL_DOMAIN ) +panic ("Can't register KERNEL_DOMAIN" ); +return 0 ; +} + +security_initcall (tmy_realpath_init); + +/* The list for "struct cache_entry". */ +static LIST_HEAD (cache_list); + +static DEFINE_SPINLOCK (cache_list_lock); + +static unsigned int dynamic_memory_size; + +/** +* tmy_alloc - Allocate memory for temporal purpose. +* +* @size: Size in bytes. +* +* Returns pointer to allocated memory on success, NULL otherwise. +*/ +void *tmy_alloc (const size_t size) +{ +struct cache_entry *new_entry; +void *ret =kzalloc (size,GFP_KERNEL ); +if (!ret) + goto out; + new_entry =kmem_cache_alloc (tmy_cachep,GFP_KERNEL ); +if (!new_entry) { +kfree (ret); + ret =NULL ; + goto out; + } +INIT_LIST_HEAD (&new_entry->list); + new_entry->ptr = ret; + new_entry->size =ksize (ret); +/***** CRITICAL SECTION START *****/ +spin_lock (&cache_list_lock); +list_add_tail (&new_entry->list, &cache_list); + dynamic_memory_size += new_entry->size; +spin_unlock (&cache_list_lock); +/***** CRITICAL SECTION END *****/ +out: +return ret; +} + +/** +* tmy_free - Release memory allocated by tmy_alloc(). +* +* @p: Pointer returned by tmy_alloc(). May be NULL. +* +* Returns nothing. +*/ +void tmy_free (const void *p) +{ +struct list_head *v; +struct cache_entry *entry =NULL ; +if (!p) +return ; +/***** CRITICAL SECTION START *****/ +spin_lock (&cache_list_lock); +list_for_each (v, &cache_list) { + entry = list_entry(v, struct cache_entry, list); +if (entry->ptr != p) { + entry =NULL ; +continue ; + } +list_del (&entry->list); + dynamic_memory_size -= entry->size; +break ; + } +spin_unlock (&cache_list_lock); +/***** CRITICAL SECTION END *****/ +if (entry) { +kfree (p); +kmem_cache_free (tmy_cachep, entry); + }else { +printk (KERN_WARNING "BUG: tmy_free() with invalid pointer.\n" ); + } +} + +static int tmy_print_ascii (const char *sp,const char *cp, +int *buflen0,char **end0) +{ +int buflen = *buflen0; +char *end = *end0; + +while (sp <= cp) { +unsigned char c; + + c = *(unsigned char *) cp; +if (c =='\\' ) { + buflen -=2 ; +if (buflen <0 ) + goto out; + *--end ='\\' ; + *--end ='\\' ; + }else if (c >' ' && c <127 ) { +if (--buflen <0 ) + goto out; + *--end = (char ) c; + }else { + buflen -=4 ; +if (buflen <0 ) + goto out; + *--end = (c &7 ) +'0' ; + *--end = ((c >>3 ) &7 ) +'0' ; + *--end = (c >>6 ) +'0' ; + *--end ='\\' ; + } + cp--; + } + + *buflen0 = buflen; + *end0 = end; + +return 0 ; +out: +return -ENOMEM ; +} + + +/* tmy_get_absolute_path() for "struct ctl_table". */ +static int tmy_sysctl_path (struct ctl_table *table,char *buffer,int buflen) +{ +char *end = buffer + buflen; + +if (buflen <256 ) + goto out; + + *--end ='\0' ; + buflen--; + + buflen -=9 ;/* for "/proc/sys" prefix */ + +while (table) { +char buf[32 ]; +const char *sp = table->procname; +const char *cp; + +if (!sp) { +memset (buf,0 ,sizeof (buf)); +snprintf (buf,sizeof (buf) -1 ,"=%d=" , table->ctl_name); + sp = buf; + } + cp =strchr (sp,'\0' ) -1 ; + +if (tmy_print_ascii (sp, cp, &buflen, &end)) + goto out; + +if (--buflen <0 ) + goto out; + + *--end ='/' ; + table = table->parent; + } + +/* Move the pathname to the top of the buffer. */ +memmove (buffer,"/proc/sys" ,9 ); +memmove (buffer +9 , end,strlen (end) +1 ); +return 0 ; +out: ; +return -ENOMEM ; +} + +/** +* sysctlpath_from_table - return the realpath of a ctl_table. +* @table: pointer to "struct ctl_table". +* +* Returns realpath(3) of the @table on success. +* Returns NULL on failure. +* +* This function uses tmy_alloc(), so caller must call tmy_free() +* if this function didn't return NULL. +*/ +char *sysctlpath_from_table (struct ctl_table *table) +{ +char *buf =tmy_alloc (TMY_MAX_PATHNAME_LEN ); + +if (buf &&tmy_sysctl_path (table, buf,TMY_MAX_PATHNAME_LEN -1 ) ==0 ) +return buf; + +tmy_free (buf); +return NULL ; +} + +/** +* tmy_read_memory_counter - Check for memory usage. +* +* @head: Pointer to "struct tmy_io_buffer". +* +* Returns memory usage. +*/ +int tmy_read_memory_counter (struct tmy_io_buffer *head) +{ +if (!head->read_eof) { +const unsigned int shared = allocated_memory_for_savename; +const unsigned int private = allocated_memory_for_elements; +const unsigned int dynamic = dynamic_memory_size; +char buffer[64 ]; +memset (buffer,0 ,sizeof (buffer)); +if (quota_for_savename) +snprintf (buffer,sizeof (buffer) -1 , +" (Quota: %10u)" , quota_for_savename); +else + buffer[0 ] ='\0' ; +tmy_io_printf (head,"Shared: %10u%s\n" , shared, buffer); +if (quota_for_elements) +snprintf (buffer,sizeof (buffer) -1 , +" (Quota: %10u)" , quota_for_elements); +else + buffer[0 ] ='\0' ; +tmy_io_printf (head,"Private: %10u%s\n" , private, buffer); +tmy_io_printf (head,"Dynamic: %10u\n" , dynamic); +tmy_io_printf (head,"Total: %10u\n" , + shared + private + dynamic); + head->read_eof = true; + } +return 0 ; +} + +/** +* tmy_write_memory_quota - Set memory quota. +* +* @head: Pointer to "struct tmy_io_buffer". +* +* Returns 0. +*/ +int tmy_write_memory_quota (struct tmy_io_buffer *head) +{ +char *data = head->write_buf; +unsigned int size; +if (sscanf (data,"Shared: %u" , &size) ==1 ) + quota_for_savename = size; +else if (sscanf (data,"Private: %u" , &size) ==1 ) + quota_for_elements = size; +return 0 ; +}
security/tomoyo/realpath.h new file +60 -0
60 lines not on this page · raw message
--
Re: [TOMOYO #10 (linux-next) 4/8] Memory and pathname management functions.
a nitpick.
security/tomoyo/realpath.c line 366
Quoted 5 lines · On Thu, 09 Oct 2008 13:28:18 +0900 Kentaro Takeda <[email protected]>…
security/tomoyo/realpath.c new file +5
-0+static unsigned int allocated_memory_for_savename; +static unsigned int quota_for_savename; + +#define MAX_HASH 256 +
Maybe based on typical workload and usage but
It's better to write why hash size is fixed size of "256", when you use hash.
Thanks,
-Kame
Kentaro Takeda
maintainerRe: [TOMOYO #10 (linux-next) 4/8] Memory and pathname management functions.
Quoted 6 lines · KAMEZAWA Hiroyuki
security/tomoyo/realpath.c line 366
Quoted 3 lines
security/tomoyo/realpath.c new file +3
-0+ +#define MAX_HASH 256 +Maybe based on typical workload and usage but
It's better to write why hash size is fixed size of "256", when you use hash.
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.
I'll append above as a comment.
Regards,
Kentaro Takeda
maintainerPATCH 5/8 Common functions for TOMOYO Linux.
In reply to Kentaro TakedaThis file contains common functions (e.g. policy I/O, pattern matching).
--- security/tomoyo/common.c | 2175 +++++++++++++++++++++++++++++++++++++++++++++++ security/tomoyo/common.h | 398 ++++++++ 2 files changed, 2573 insertions(+)
security/tomoyo/common.c new file +2175 -0
@@ -0,0 +1,2175 @@ +/* +* security/tomoyo/common.c +* +* Common functions for TOMOYO. +* +* Copyright (C) 2005-2008 NTT DATA CORPORATION +* +* Version: 2.2.0-pre 2008/10/01 +* +*/ + +#include <linux/uaccess.h> +#include <linux/security.h> +#include <linux/hardirq.h> +#include "realpath.h" +#include "common.h" +#include "tomoyo.h" + +/* Set default specified by the kernel config. */ +#define MAX_ACCEPT_ENTRY 2048 + +/* Has /sbin/init started? */ +bool sbin_init_started; + +/* String table for functionality that takes 4 modes. */ +static const char *mode_4[4 ] = { +"disabled" ,"learning" ,"permissive" ,"enforcing" +}; +/* String table for functionality that takes 2 modes. */ +static const char *mode_2[4 ] = { +"disabled" ,"enabled" ,"enabled" ,"enabled" +}; + +/* Table for profile. */ +static struct { +const char *keyword; +unsigned int current_value; +const unsigned int max_value; +} tmy_control_array[TMY_MAX_CONTROL_INDEX ] = { + [TMY_TOMOYO_MAC_FOR_FILE ] = {"MAC_FOR_FILE" ,0 ,3 }, + [TMY_TOMOYO_MAX_ACCEPT_ENTRY ] + = {"MAX_ACCEPT_ENTRY" ,MAX_ACCEPT_ENTRY ,INT_MAX }, + [TMY_TOMOYO_VERBOSE ] = {"TOMOYO_VERBOSE" ,1 ,1 }, +}; + +/* Profile table. Memory is allocated as needed. */ +static struct profile { +unsigned int value[TMY_MAX_CONTROL_INDEX ]; +const struct path_info *comment; +} *profile_ptr[MAX_PROFILES ]; + +/* Permit policy management by non-root user? */ +static bool manage_by_non_root; + +/* Utility functions. */ + +/* Open operation for /sys/kernel/security/tomoyo/ interface. */ +static int tmy_open_control (const u8 type,struct file *file); +/* Close /sys/kernel/security/tomoyo/ interface. */ +static int tmy_close_control (struct file *file); +/* Read operation for /sys/kernel/security/tomoyo/ interface. */ +static int tmy_read_control (struct file *file,char __user *buffer, +const int buffer_len); +/* Write operation for /sys/kernel/security/tomoyo/ interface. */ +static int tmy_write_control (struct file *file,const char __user *buffer, +const int buffer_len); + +/** +* is_byte_range - Check whether the string isa \ooo style octal value. +* +* @str: Pointer to the string. +* +* Returns true if @str is a \ooo style octal value, false otherwise. +*/ +static bool is_byte_range (const char *str) +{ +return *str >='0' && *str++ <='3' && + *str >='0' && *str++ <='7' && + *str >='0' && *str <='7' ; +} + +/** +* is_decimal - Check whether the character is a decimal character. +* +* @c: The character to check. +* +* Returns true if @c is a decimal character, false otherwise. +*/ +static bool is_decimal (const char c) +{ +return c >='0' && c <='9' ; +} + +/** +* is_hexadecimal - Check whether the character is a hexadecimal character. +* +* @c: The character to check. +* +* Returns true if @c is a hexadecimal character, false otherwise. +*/ +static bool is_hexadecimal (const char c) +{ +return (c >='0' && c <='9' ) || + (c >='A' && c <='F' ) || + (c >='a' && c <='f' ); +} + +/** +* is_alphabet_char - Check whether the character is an alphabet. +* +* @c: The character to check. +* +* Returns true if @c is an alphabet character, false otherwise. +*/ +static bool is_alphabet_char (const char c) +{ +return (c >='A' && c <='F' ) || (c >='a' && c <='f' ); +} + +/** +* make_byte - Make byte value from three octal characters. +* +* @c1: The first character. +* @c2: The second character. +* @c3: The third character. +* +* Returns byte value. +*/ +static u8 make_byte (const u8 c1,const u8 c2,const u8 c3) +{ +return ((c1 -'0' ) <<6 ) + ((c2 -'0' ) <<3 ) + (c3 -'0' ); +} + +/** +* str_starts - Check whether the given string starts with the given keyword. +* +* @src: Pointer to pointer to the string. +* @find: Pointer to the keyword. +* +* Returns true if @src starts with @find, false otherwise. +* +* The @src is updated to point the first character after the @find +* if @src starts with @find. +*/ +static bool str_starts (char **src,const char *find) +{ +const int len =strlen (find); +char *tmp = *src; +if (strncmp (tmp, find, len)) +return false; + tmp += len; + *src = tmp; +return true; +} + +/** +* normalize_line - Format string. +* +* @buffer: The line to normalize. +* +* Leading and trailing whitespaces are removed. +* Multiple whitespaces are packed into single space. +* +* Returns nothing. +*/ +static void normalize_line (unsigned char *buffer) +{ +unsigned char *sp = buffer; +unsigned char *dp = buffer; +bool first = true; +while (*sp && (*sp <=' ' || *sp >=127 )) + sp++; +while (*sp) { +if (!first) + *dp++ =' ' ; + first = false; +while (*sp >' ' && *sp <127 ) + *dp++ = *sp++; +while (*sp && (*sp <=' ' || *sp >=127 )) + sp++; + } + *dp ='\0' ; +} + +/** +* tmy_is_correct_path - Validate a pathname. +* @filename: The pathname to check. +* @start_type: Should the pathname start with '/'? +* 1 = must / -1 = must not / 0 = don't care +* @pattern_type: Can the pathname contain a wildcard? +* 1 = must / -1 = must not / 0 = don't care +* @end_type: Should the pathname end with '/'? +* 1 = must / -1 = must not / 0 = don't care +* @function: The name of function calling me. +* +* Check whether the given filename follows the naming rules. +* Returns true if @filename follows the naming rules, false otherwise. +*/ +bool tmy_is_correct_path (const char *filename,const s8 start_type, +const s8 pattern_type,const s8 end_type, +const char *function) +{ +bool contains_pattern = false; +unsigned char c; +unsigned char d; +unsigned char e; +const char *original_filename = filename; +if (!filename) + goto out; + c = *filename; +if (start_type ==1 ) {/* Must start with '/' */ +if (c !='/' ) + goto out; + }else if (start_type ==-1 ) {/* Must not start with '/' */ +if (c =='/' ) + goto out; + } +if (c) + c = *(strchr (filename,'\0' ) -1 ); +if (end_type ==1 ) {/* Must end with '/' */ +if (c !='/' ) + goto out; + }else if (end_type ==-1 ) {/* Must not end with '/' */ +if (c =='/' ) + goto out; + } +while ((c = *filename++) !='\0' ) { +if (c =='\\' ) { +switch ((c = *filename++)) { +case '\\' :/* "\\" */ +continue ; +case '$' :/* "\$" */ +case '+' :/* "\+" */ +case '?' :/* "\?" */ +case '*' :/* "\*" */ +case '@' :/* "\@" */ +case 'x' :/* "\x" */ +case 'X' :/* "\X" */ +case 'a' :/* "\a" */ +case 'A' :/* "\A" */ +case '-' :/* "\-" */ +if (pattern_type ==-1 ) +break ;/* Must not contain pattern */ + contains_pattern = true; +continue ; +case '0' :/* "\ooo" */ +case '1' : +case '2' : +case '3' : + d = *filename++; +if (d <'0' || d >'7' ) +break ; + e = *filename++; +if (e <'0' || e >'7' ) +break ; + c =make_byte (c, d, e); +if (c && (c <=' ' || c >=127 )) +continue ;/* pattern is not \000 */ + } + goto out; + }else if (c <=' ' || c >=127 ) { + goto out; + } + } +if (pattern_type ==1 ) {/* Must contain pattern */ +if (!contains_pattern) + goto out; + } +return true; +out: +printk (KERN_DEBUG "%s: Invalid pathname '%s'\n" , function, + original_filename); +return false; +} + +/** +* tmy_is_correct_domain - Check whether the given domainname follows the naming rules. +* @domainname: The domainname to check. +* @function: The name of function calling me. +* +* Returns true if @domainname follows the naming rules, false otherwise. +*/ +bool tmy_is_correct_domain (const unsigned char *domainname, +const char *function) +{ +unsigned char c; +unsigned char d; +unsigned char e; +const char *org_domainname = domainname; +if (!domainname ||strncmp (domainname,ROOT_NAME ,ROOT_NAME_LEN )) + goto out; + domainname +=ROOT_NAME_LEN ; +if (!*domainname) +return true; +do { +if (*domainname++ !=' ' ) + goto out; +if (*domainname++ !='/' ) + goto out; +while ((c = *domainname) !='\0' && c !=' ' ) { + domainname++; +if (c =='\\' ) { + c = *domainname++; +switch ((c)) { +case '\\' :/* "\\" */ +continue ; +case '0' :/* "\ooo" */ +case '1' : +case '2' : +case '3' : + d = *domainname++; +if (d <'0' || d >'7' ) +break ; + e = *domainname++; +if (e <'0' || e >'7' ) +break ; + c =make_byte (c, d, e); +if (c && (c <=' ' || c >=127 )) +/* pattern is not \000 */ +continue ; + } + goto out; + }else if (c <' ' || c >=127 ) { + goto out; + } + } + }while (*domainname); +return true; +out: +printk (KERN_DEBUG "%s: Invalid domainname '%s'\n" , function, + org_domainname); +return false; +} + +/** +* tmy_is_domain_def - Check whether the given token can be a domainname. +* +* @buffer: The token to check. +* +* Returns true if @buffer possibly be a domainname, false otherwise. +*/ +bool tmy_is_domain_def (const unsigned char *buffer) +{ +return !strncmp (buffer,ROOT_NAME ,ROOT_NAME_LEN ); +} + +/** +* tmy_find_domain - Find a domain by the given name. +* +* @domainname: The domainname to find. +* +* Returns pointer to "struct domain_info" if found, NULL otherwise. +*/ +struct domain_info *tmy_find_domain (const char *domainname) +{ +struct domain_info *domain; +struct path_info name; + name.name = domainname; +tmy_fill_path_info (&name); +list1_for_each_entry (domain, &domain_list, list) { +if (!domain->is_deleted && + !tmy_pathcmp (&name, domain->domainname)) +return domain; + } +return NULL ; +} + +/** +* path_depth - Evaluate the number of '/' in a string. +* +* @pathname: The string to evaluate. +* +* Returns path depth of the string. +* +* I score 2 for each of the '/' in the @pathname +* and score 1 if the @pathname ends with '/'. +*/ +static int path_depth (const char *pathname) +{ +int i =0 ; +if (pathname) { +char *ep =strchr (pathname,'\0' ); +if (pathname < ep--) { +if (*ep !='/' ) + i++; +while (pathname <= ep) +if (*ep-- =='/' ) + i +=2 ; + } + } +return i; +} + +/** +* const_part_length - Evaluate the initial length without a pattern in a token. +* +* @filename: The string to evaluate. +* +* Returns the initial length without a pattern in @filename. +*/ +static int const_part_length (const char *filename) +{ +char c; +int len =0 ; +if (!filename) +return 0 ; +while ((c = *filename++) !='\0' ) { +if (c !='\\' ) { + len++; +continue ; + } + c = *filename++; +switch (c) { +case '\\' :/* "\\" */ + len +=2 ; +continue ; +case '0' :/* "\ooo" */ +case '1' : +case '2' : +case '3' : + c = *filename++; +if (c <'0' || c >'7' ) +break ; + c = *filename++; +if (c <'0' || c >'7' ) +break ; + len +=4 ; +continue ; + } +break ; + } +return len; +} + +/** +* tmy_fill_path_info - Fill in "struct path_info" members. +* +* @ptr: Pointer to "struct path_info" to fill in. +* +* The caller sets "struct path_info"->name. +*/ +void tmy_fill_path_info (struct path_info *ptr) +{ +const char *name = ptr->name; +const int len =strlen (name); + ptr->total_len = len; + ptr->const_len =const_part_length (name); + ptr->is_dir = len && (name[len -1 ] =='/' ); + ptr->is_patterned = (ptr->const_len < len); + ptr->hash =full_name_hash (name, len); + ptr->depth =path_depth (name); +} + +/** +* file_matches_to_pattern2 - Pattern matching without '/' character +* and "\-" pattern. +* +* @filename: The start of string to check. +* @filename_end: The end of string to check. +* @pattern: The start of pattern to compare. +* @pattern_end: The end of pattern to compare. +* +* Returns true if @filename matches @pattern, false otherwise. +*/ +static bool file_matches_to_pattern2 (const char *filename, +const char *filename_end, +const char *pattern, +const char *pattern_end) +{ +while (filename < filename_end && pattern < pattern_end) { +char c; +if (*pattern !='\\' ) { +if (*filename++ != *pattern++) +return false; +continue ; + } + c = *filename; + pattern++; +switch (*pattern) { +int i; +int j; +case '?' : +if (c =='/' ) { +return false; + }else if (c =='\\' ) { +if (filename[1 ] =='\\' ) + filename++; +else if (is_byte_range (filename +1 )) + filename +=3 ; +else +return false; + } +break ; +case '\\' : +if (c !='\\' ) +return false; +if (*++filename !='\\' ) +return false; +break ; +case '+' : +if (!is_decimal (c)) +return false; +break ; +case 'x' : +if (!is_hexadecimal (c)) +return false; +break ; +case 'a' : +if (!is_alphabet_char (c)) +return false; +break ; +case '0' : +case '1' : +case '2' : +case '3' : +if (c =='\\' &&is_byte_range (filename +1 ) + &&strncmp (filename +1 , pattern,3 ) ==0 ) { + filename +=3 ; + pattern +=2 ; +break ; + } +return false;/* Not matched. */ +case '*' : +case '@' : +for (i =0 ; i <= filename_end - filename; i++) { +if (file_matches_to_pattern2 (filename + i, + filename_end, + pattern +1 , + pattern_end)) +return true; + c = filename[i]; +if (c =='.' && *pattern =='@' ) +break ; +if (c !='\\' ) +continue ; +if (filename[i +1 ] =='\\' ) + i++; +else if (is_byte_range (filename + i +1 )) + i +=3 ; +else +break ;/* Bad pattern. */ + } +return false;/* Not matched. */ +default : + j =0 ; + c = *pattern; +if (c =='$' ) { +while (is_decimal (filename[j])) + j++; + }else if (c =='X' ) { +while (is_hexadecimal (filename[j])) + j++; + }else if (c =='A' ) { +while (is_alphabet_char (filename[j])) + j++; + } +for (i =1 ; i <= j; i++) { +if (file_matches_to_pattern2 (filename + i, + filename_end, + pattern +1 , + pattern_end)) +return true; + } +return false;/* Not matched or bad pattern. */ + } + filename++; + pattern++; + } +while (*pattern =='\\' && + (*(pattern +1 ) =='*' || *(pattern +1 ) =='@' )) + pattern +=2 ; +return filename == filename_end && pattern == pattern_end; +} + +/** +* file_matches_to_pattern - Pattern matching without without '/' character. +* +* @filename: The start of string to check. +* @filename_end: The end of string to check. +* @pattern: The start of pattern to compare. +* @pattern_end: The end of pattern to compare. +* +* Returns true if @filename matches @pattern, false otherwise. +*/ +static bool file_matches_to_pattern (const char *filename, +const char *filename_end, +const char *pattern, +const char *pattern_end) +{ +const char *pattern_start = pattern; +bool first = true; +bool result; +while (pattern < pattern_end -1 ) { +/* Split at "\-" pattern. */ +if (*pattern++ !='\\' || *pattern++ !='-' ) +continue ; + result =file_matches_to_pattern2 (filename, filename_end, + pattern_start, pattern -2 ); +if (first) + result = !result; +if (result) +return false; + first = false; + pattern_start = pattern; + } + result =file_matches_to_pattern2 (filename, filename_end, + pattern_start, pattern_end); +return first ? result : !result; +} + +/** +* tmy_path_matches_pattern - Check whether the given filename matches the given pattern. +* @filename: The filename to check. +* @pattern: The pattern to compare. +* +* Returns true if matches, false otherwise. +* +* The following patterns are available. +* \\ \ itself. +* \ooo Octal representation of a byte. +* \* More than or equals to 0 character other than '/'. +* \@ More than or equals to 0 character other than '/' or '.'. +* \? 1 byte character other than '/'. +* \$ More than or equals to 1 decimal digit. +* \+ 1 decimal digit. +* \X More than or equals to 1 hexadecimal digit. +* \x 1 hexadecimal digit. +* \A More than or equals to 1 alphabet character. +* \a 1 alphabet character. +* \- Subtraction operator. +*/ +bool tmy_path_matches_pattern (const struct path_info *filename, +const struct path_info *pattern) +{ +/* +if (!filename || !pattern) +return false; +*/ +const char *f = filename->name; +const char *p = pattern->name; +const int len = pattern->const_len; +/* If @pattern doesn't contain pattern, I can use strcmp(). */ +if (!pattern->is_patterned) +return !tmy_pathcmp (filename, pattern); +/* Dont compare if the number of '/' differs. */ +if (filename->depth != pattern->depth) +return false; +/* Compare the initial length without patterns. */ +if (strncmp (f, p, len)) +return false; + f += len; + p += len; +/* Main loop. Compare each directory component. */ +while (*f && *p) { +const char *f_delimiter =strchr (f,'/' ); +const char *p_delimiter =strchr (p,'/' ); +if (!f_delimiter) + f_delimiter =strchr (f,'\0' ); +if (!p_delimiter) + p_delimiter =strchr (p,'\0' ); +if (!file_matches_to_pattern (f, f_delimiter, p, p_delimiter)) +return false; + f = f_delimiter; +if (*f) + f++; + p = p_delimiter; +if (*p) + p++; + } +/* Ignore trailing "\*" and "\@" in @pattern. */ +while (*p =='\\' && + (*(p +1 ) =='*' || *(p +1 ) =='@' )) + p +=2 ; +return !*f && !*p; +} + +/** +* tmy_io_printf - Transactional printf() to "struct tmy_io_buffer" structure. +* +* @head: Pointer to "struct tmy_io_buffer". +* @fmt: The printf()'s format string, followed by parameters. +* +* Returns true on success, false otherwise. +* +* The snprintf() will truncate, but tmy_io_printf() won't. +*/ +bool tmy_io_printf (struct tmy_io_buffer *head,const char *fmt, ...) +{ +va_list args; +int len; +int pos = head->read_avail; +int size = head->readbuf_size - pos; +if (size <=0 ) +return false; +va_start (args, fmt); + len =vsnprintf (head->read_buf + pos, size, fmt, args); +va_end (args); +if (pos + len >= head->readbuf_size) +return false; + head->read_avail += len; +return true; +} + +/** +* tmy_get_exe - Get tmy_realpath() of current process. +* +* Returns the tmy_realpath() of current process on success, NULL otherwise. +* +* This function uses tmy_alloc(), so the caller must tmy_free() +* if this function didn't return NULL. +*/ +static const char *tmy_get_exe (void ) +{ +struct mm_struct *mm = current->mm; +struct vm_area_struct *vma; +const char *cp =NULL ; +if (!mm) +return NULL ; +down_read (&mm->mmap_sem); +for (vma = mm->mmap; vma; vma = vma->vm_next) { +if ((vma->vm_flags &VM_EXECUTABLE ) && vma->vm_file) { +struct path *path = &vma->vm_file->f_path; + cp =tmy_realpath_from_dentry (path->dentry, path->mnt); +break ; + } + } +up_read (&mm->mmap_sem); +return cp; +} + +/** +* tmy_get_msg - Get warning message. +* +* @is_enforce: Is it enforcing mode? +* +* Returns "ERROR" or "WARNING". +*/ +const char *tmy_get_msg (const bool is_enforce) +{ +if (is_enforce) +return "ERROR" ; +else +return "WARNING" ; +} + +/** +* tmy_check_flags - Check mode for specified functionality. +* +* @domain: Pointer to "struct domain_info". +* @index: The functionality to check mode. +* +* Returns the mode of specified functionality. +*/ +unsigned int tmy_check_flags (const struct domain_info *domain,const u8 index) +{ +const u8 profile = domain->profile; +if (unlikely (in_interrupt ())) { +static u8 count =20 ; +if (count) { + count--; +printk (KERN_ERR "BUG: sleeping function called " +"from invalid context.\n" ); +dump_stack (); + } +return 0 ; + } + return sbin_init_started && index < TMY_MAX_CONTROL_INDEX +#if MAX_PROFILES !=256 + && profile < MAX_PROFILES +#endif + && profile_ptr[profile] ? + profile_ptr[profile]->value[index] :0 ; +} + +/** +* tmy_verbose_mode - Check whether TOMOYO is verbose mode. +* +* @domain: Pointer to "struct domain_info". +* +* Returns true if domain policy violation warning should be printed to +* console. +*/ +bool tmy_verbose_mode (const struct domain_info *domain) +{ +return tmy_check_flags (domain,TMY_TOMOYO_VERBOSE ) !=0 ; +} + +/** +* tmy_check_domain_quota - Check for domain's quota. +* +* @domain: Pointer to "struct domain_info". +* +* Returns true if the domain is not exceeded quota, false otherwise. +*/ +bool tmy_check_domain_quota (struct domain_info *const domain) +{ +unsigned int count =0 ; +struct acl_info *ptr; +if (!domain) +return true; +list1_for_each_entry (ptr, &domain->acl_info_list, list) { +if (ptr->type &ACL_DELETED ) +continue ; +switch (tmy_acl_type2 (ptr)) { +struct single_path_acl_record *acl1; +struct double_path_acl_record *acl2; +u16 perm; +case TYPE_SINGLE_PATH_ACL : + acl1 = container_of(ptr, struct single_path_acl_record, + head); + perm = acl1->perm; +if (perm & (1 <<TMY_TYPE_EXECUTE_ACL )) + count++; +if (perm & + ((1 <<TMY_TYPE_READ_ACL ) | + (1 <<TMY_TYPE_WRITE_ACL ))) + count++; +if (perm & (1 <<TMY_TYPE_CREATE_ACL )) + count++; +if (perm & (1 <<TMY_TYPE_UNLINK_ACL )) + count++; +if (perm & (1 <<TMY_TYPE_MKDIR_ACL )) + count++; +if (perm & (1 <<TMY_TYPE_RMDIR_ACL )) + count++; +if (perm & (1 <<TMY_TYPE_MKFIFO_ACL )) + count++; +if (perm & (1 <<TMY_TYPE_MKSOCK_ACL )) + count++; +if (perm & (1 <<TMY_TYPE_MKBLOCK_ACL )) + count++; +if (perm & (1 <<TMY_TYPE_MKCHAR_ACL )) + count++; +if (perm & (1 <<TMY_TYPE_TRUNCATE_ACL )) + count++; +if (perm & (1 <<TMY_TYPE_SYMLINK_ACL )) + count++; +if (perm & (1 <<TMY_TYPE_REWRITE_ACL )) + count++; +break ; +case TYPE_DOUBLE_PATH_ACL : + acl2 = container_of(ptr, struct double_path_acl_record, + head); + perm = acl2->perm; +if (perm & (1 <<TMY_TYPE_LINK_ACL )) + count++; +if (perm & (1 <<TMY_TYPE_RENAME_ACL )) + count++; +break ; + } + } +if (count <tmy_check_flags (domain,TMY_TOMOYO_MAX_ACCEPT_ENTRY )) +return true; +if (!domain->quota_warned) { + domain->quota_warned = true; +printk (KERN_WARNING "TOMOYO-WARNING: " +"Domain '%s' has so many ACLs to hold. " +"Stopped learning mode.\n" , domain->domainname->name); + } +return false; +} + +/** +* tmy_find_or_assign_new_profile - Create a new profile. +* +* @profile: Profile number to create. +* +* Returns pointer to "struct profile" on success, NULL otherwise. +*/ +static struct profile *tmy_find_or_assign_new_profile (const unsigned int + profile) +{ +static DEFINE_MUTEX (profile_lock); +struct profile *ptr =NULL ; +mutex_lock (&profile_lock); +if (profile <MAX_PROFILES ) { + ptr = profile_ptr[profile]; +if (ptr) + goto ok; + ptr =tmy_alloc_element (sizeof (*ptr)); +if (ptr) { +int i; +for (i =0 ; i <TMY_MAX_CONTROL_INDEX ; i++) + ptr->value[i] + = tmy_control_array[i].current_value; +mb ();/* Avoid out-of-order execution. */ + profile_ptr[profile] = ptr; + } + } +ok: +mutex_unlock (&profile_lock); +return ptr; +} + +/** +* write_profile - Write profile table. +* +* @head: Pointer to "struct tmy_io_buffer" +* +* Returns 0 on success, negative value otherwise. +*/ +static int write_profile (struct tmy_io_buffer *head) +{ +char *data = head->write_buf; +unsigned int i; +unsigned int value; +char *cp; +struct profile *profile; +unsigned long num; + cp =strchr (data,'-' ); +if (cp) + *cp ='\0' ; +if (strict_strtoul (data,10 , &num)) +return -EINVAL ; +if (cp) + data = cp +1 ; + profile =tmy_find_or_assign_new_profile (num); +if (!profile) +return -EINVAL ; + cp =strchr (data,'=' ); +if (!cp) +return -EINVAL ; + *cp ='\0' ; +tmy_update_counter (TMY_UPDATES_COUNTER_PROFILE ); +if (!strcmp (data,"COMMENT" )) { + profile->comment =tmy_save_name (cp +1 ); +return 0 ; + } +for (i =0 ; i <TMY_MAX_CONTROL_INDEX ; i++) { +if (strcmp (data, tmy_control_array[i].keyword)) +continue ; +if (sscanf (cp +1 ,"%u" , &value) !=1 ) { +int j; +const char **modes; +switch (i) { +case TMY_TOMOYO_VERBOSE : + modes = mode_2; +break ; +default : + modes = mode_4; +break ; + } +for (j =0 ; j <4 ; j++) { +if (strcmp (cp +1 , modes[j])) +continue ; + value = j; +break ; + } +if (j ==4 ) +return -EINVAL ; + }else if (value > tmy_control_array[i].max_value) { + value = tmy_control_array[i].max_value; + } + profile->value[i] = value; +return 0 ; + } +return -EINVAL ; +} + +/** +* read_profile - Read profile table. +* +* @head: Pointer to "struct tmy_io_buffer" +* +* Returns 0. +*/ +static int read_profile (struct tmy_io_buffer *head) +{ +static const int total + =TMY_MAX_CONTROL_INDEX +1 ; +int step; +if (head->read_eof) +return 0 ; +for (step = head->read_step; step <MAX_PROFILES * total; step++) { +const u8 index = step / total; +u8 type = step % total; +const struct profile *profile = profile_ptr[index]; + head->read_step = step; +if (!profile) +continue ; +if (!type) {/* Print profile' comment tag. */ +if (!tmy_io_printf (head,"%u-COMMENT=%s\n" , + index, profile->comment ? + profile->comment->name :"" )) +break ; +continue ; + } + type--; +if (type <TMY_MAX_CONTROL_INDEX ) { +const unsigned int value = profile->value[type]; +const char **modes =NULL ; +const char *keyword = tmy_control_array[type].keyword; +switch (tmy_control_array[type].max_value) { +case 3 : + modes = mode_4; +break ; +case 1 : + modes = mode_2; +break ; + } +if (modes) { +if (!tmy_io_printf (head,"%u-%s=%s\n" , index, + keyword, modes[value])) +break ; + }else { +if (!tmy_io_printf (head,"%u-%s=%u\n" , index, + keyword, value)) +break ; + } + } + } +if (step ==MAX_PROFILES * total) + head->read_eof = true; +return 0 ; +} + +/* Structure for policy manager. */ +struct policy_manager_entry { +struct list1_head list; +/* A path to program or a domainname. */ +const struct path_info *manager; +bool is_domain;/* True if manager is a domainname. */ +bool is_deleted;/* True if this entry is deleted. */ +}; + +/* The list for "struct policy_manager_entry". */ +static LIST1_HEAD (policy_manager_list); + +/** +* update_manager_entry - Add a manager entry. +* +* @manager: The path to manager or the domainnamme. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int update_manager_entry (const char *manager,const bool is_delete) +{ +struct policy_manager_entry *new_entry; +struct policy_manager_entry *ptr; +static DEFINE_MUTEX (lock); +const struct path_info *saved_manager; +int error = -ENOMEM ; +bool is_domain = false; +if (tmy_is_domain_def (manager)) { +if (!tmy_is_correct_domain (manager, __func__)) +return -EINVAL ; + is_domain = true; + }else { +if (!tmy_is_correct_path (manager,1 ,-1 ,-1 , __func__)) +return -EINVAL ; + } + saved_manager =tmy_save_name (manager); +if (!saved_manager) +return -ENOMEM ; +mutex_lock (&lock); +list1_for_each_entry (ptr, &policy_manager_list, list) { +if (ptr->manager != saved_manager) +continue ; + ptr->is_deleted = is_delete; + error =0 ; + goto out; + } +if (is_delete) { + error = -ENOENT ; + goto out; + } + new_entry =tmy_alloc_element (sizeof (*new_entry)); +if (!new_entry) + goto out; + new_entry->manager = saved_manager; + new_entry->is_domain = is_domain; +list1_add_tail_mb (&new_entry->list, &policy_manager_list); + error =0 ; +out: +mutex_unlock (&lock); +if (!error) +tmy_update_counter (TMY_UPDATES_COUNTER_MANAGER ); +return error; +} + +/** +* write_manager_policy - Write manager policy. +* +* @head: Pointer to "struct tmy_io_buffer" +* +* Returns 0 on success, negative value otherwise. +*/ +static int write_manager_policy (struct tmy_io_buffer *head) +{ +char *data = head->write_buf; +bool is_delete =str_starts (&data,KEYWORD_DELETE ); +if (!strcmp (data,"manage_by_non_root" )) { + manage_by_non_root = !is_delete; +return 0 ; + } +return update_manager_entry (data, is_delete); +} + +/** +* read_manager_policy - Read manager policy. +* +* @head: Pointer to "struct tmy_io_buffer" +* +* Returns 0. +*/ +static int read_manager_policy (struct tmy_io_buffer *head) +{ +struct list1_head *pos; +if (head->read_eof) +return 0 ; +list1_for_each_cookie (pos, head->read_var2, &policy_manager_list) { +struct policy_manager_entry *ptr; + ptr = list1_entry(pos, struct policy_manager_entry, list); +if (ptr->is_deleted) +continue ; +if (!tmy_io_printf (head,"%s\n" , ptr->manager->name)) +return 0 ; + } + head->read_eof = true; +return 0 ; +} + +/** +* is_policy_manager - Check whether the current process is a policy manager. +* +* Returns true if the current process is permitted to modify policy +* via /sys/kernel/security/tomoyo/ interface. +*/ +static bool is_policy_manager (void ) +{ +struct policy_manager_entry *ptr; +const char *exe; +const struct task_struct *task = current; +const struct path_info *domainname =tmy_domain ()->domainname; +bool found = false; +if (!sbin_init_started) +return true; +if (!manage_by_non_root && (task->cred->uid || task->cred->euid)) +return false; +list1_for_each_entry (ptr, &policy_manager_list, list) { +if (!ptr->is_deleted && ptr->is_domain + && !tmy_pathcmp (domainname, ptr->manager)) +return true; + } + exe =tmy_get_exe (); +if (!exe) +return false; +list1_for_each_entry (ptr, &policy_manager_list, list) { +if (!ptr->is_deleted && !ptr->is_domain + && !strcmp (exe, ptr->manager->name)) { + found = true; +break ; + } + } +if (!found) {/* Reduce error messages. */ +static pid_t last_pid; +const pid_t pid = current->pid; +if (last_pid != pid) { +printk (KERN_WARNING "%s ( %s ) is not permitted to " +"update policies.\n" , domainname->name, exe); + last_pid = pid; + } + } +tmy_free (exe); +return found; +} + + +/** +* is_select_one - Parse select command. +* +* @head: Pointer to "struct tmy_io_buffer". +* @data: String to parse. +* +* Returns true on success, false otherwise. +*/ +static bool is_select_one (struct tmy_io_buffer *head,const char *data) +{ +unsigned int pid; +struct 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 =tmy_real_domain (p); +read_unlock (&tasklist_lock); +/***** CRITICAL SECTION END *****/ + }else if (!strncmp (data,"domain=" ,7 )) { +if (tmy_is_domain_def (data +7 )) + domain =tmy_find_domain (data +7 ); + }else +return false; + head->read_avail =0 ; +tmy_io_printf (head,"# select %s\n" , data); + head->read_single_domain = true; + head->read_eof = !domain; +if (domain) { +struct domain_info *d; + head->read_var1 =NULL ; +list1_for_each_entry (d, &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) +tmy_io_printf (head,"# This is a deleted domain.\n" ); + } + head->write_var1 = domain; +return true; +} + +/** +* write_domain_policy - Write domain policy. +* +* @head: Pointer to "struct tmy_io_buffer". +* +* Returns 0 on success, negative value otherwise. +*/ +static int write_domain_policy (struct tmy_io_buffer *head) +{ +char *data = head->write_buf; +struct domain_info *domain = head->write_var1; +bool is_delete = false; +bool is_select = false; +bool is_undelete = false; +unsigned int profile; +if (str_starts (&data,KEYWORD_DELETE )) + is_delete = true; +else if (str_starts (&data,KEYWORD_SELECT )) + is_select = true; +else if (str_starts (&data,KEYWORD_UNDELETE )) + is_undelete = true; +if (is_select &&is_select_one (head, data)) +return 0 ; +/* Don't allow updating policies by non manager programs. */ +if (!is_policy_manager ()) +return -EPERM ; +if (tmy_is_domain_def (data)) { + domain =NULL ; +if (is_delete) +tmy_delete_domain (data); +else if (is_select) + domain =tmy_find_domain (data); +else if (is_undelete) + domain =tmy_undelete_domain (data); +else + domain =tmy_find_or_assign_new_domain (data,0 ); + head->write_var1 = domain; +tmy_update_counter (TMY_UPDATES_COUNTER_DOMAIN_POLICY ); +return 0 ; + } +if (!domain) +return -EINVAL ; + +if (sscanf (data,KEYWORD_USE_PROFILE "%u" , &profile) ==1 + && profile <MAX_PROFILES ) { +if (profile_ptr[profile] || !sbin_init_started) + domain->profile = (u8 ) profile; +return 0 ; + } +if (!strcmp (data,KEYWORD_IGNORE_GLOBAL_ALLOW_READ )) { +tmy_set_domain_flag (domain, is_delete, +DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ ); +return 0 ; + } +return tmy_write_file_policy (data, domain, is_delete); +} + +/** +* print_single_path_acl - Print a single path ACL entry. +* +* @head: Pointer to "struct tmy_io_buffer". +* @ptr: Pointer to "struct single_path_acl_record". +* +* Returns true on success, false otherwise. +*/ +static bool print_single_path_acl (struct tmy_io_buffer *head, +struct single_path_acl_record *ptr) +{ +int pos; +u8 bit; +const char *atmark ="" ; +const char *filename; +const u16 perm = ptr->perm; + filename = ptr->filename->name; +for (bit = head->read_bit; bit <MAX_SINGLE_PATH_OPERATION ; bit++) { +const char *msg; +if (!(perm & (1 << bit))) +continue ; +/* Print "read/write" instead of "read" and "write". */ +if ((bit ==TMY_TYPE_READ_ACL || bit ==TMY_TYPE_WRITE_ACL ) + && (perm & (1 <<TMY_TYPE_READ_WRITE_ACL ))) +continue ; + msg =tmy_sp2keyword (bit); + pos = head->read_avail; +if (!tmy_io_printf (head,"allow_%s %s%s\n" , msg, + atmark, filename)) + goto out; + } + head->read_bit =0 ; +return true; +out: + head->read_bit = bit; + head->read_avail = pos; +return false; +} + +/** +* print_double_path_acl - Print a double path ACL entry. +* +* @head: Pointer to "struct tmy_io_buffer". +* @ptr: Pointer to "struct double_path_acl_record". +* +* Returns true on success, false otherwise. +*/ +static bool print_double_path_acl (struct tmy_io_buffer *head, +struct double_path_acl_record *ptr) +{ +int pos; +const char *atmark1 ="" ; +const char *atmark2 ="" ; +const char *filename1; +const char *filename2; +const u8 perm = ptr->perm; +u8 bit; + filename1 = ptr->filename1->name; + filename2 = ptr->filename2->name; +for (bit = head->read_bit; bit <MAX_DOUBLE_PATH_OPERATION ; bit++) { +const char *msg; +if (!(perm & (1 << bit))) +continue ; + msg =tmy_dp2keyword (bit); + pos = head->read_avail; +if (!tmy_io_printf (head,"allow_%s %s%s %s%s\n" , msg, + atmark1, filename1, atmark2, filename2)) + goto out; + } + head->read_bit =0 ; +return true; +out: + head->read_bit = bit; + head->read_avail = pos; +return false; +} + +/** +* print_entry - Print an ACL entry. +* +* @head: Pointer to "struct tmy_io_buffer". +* @ptr: Pointer to an ACL entry. +* +* Returns true on success, false otherwise. +*/ +static bool print_entry (struct tmy_io_buffer *head,struct acl_info *ptr) +{ +const u8 acl_type =tmy_acl_type2 (ptr); +if (acl_type &ACL_DELETED ) +return true; +if (acl_type ==TYPE_SINGLE_PATH_ACL ) { +struct single_path_acl_record *acl + = container_of(ptr, struct single_path_acl_record, + head); +return print_single_path_acl (head, acl); + } +if (acl_type ==TYPE_DOUBLE_PATH_ACL ) { +struct double_path_acl_record *acl + = container_of(ptr, struct double_path_acl_record, + head); +return print_double_path_acl (head, acl); + } +BUG ();/* This must not happen. */ +return false; +} + +/** +* read_domain_policy - Read domain policy. +* +* @head: Pointer to "struct tmy_io_buffer". +* +* Returns 0. +*/ +static int read_domain_policy (struct tmy_io_buffer *head) +{ +struct list1_head *dpos; +struct list1_head *apos; +if (head->read_eof) +return 0 ; +if (head->read_step ==0 ) + head->read_step =1 ; +list1_for_each_cookie (dpos, head->read_var1, &domain_list) { +struct domain_info *domain; +const char *quota_exceeded ="" ; +const char *transition_failed ="" ; +const char *ignore_global_allow_read ="" ; + domain = list1_entry(dpos, struct domain_info, list); +if (head->read_step !=1 ) + goto acl_loop; +if (domain->is_deleted && !head->read_single_domain) +continue ; +/* Print domainname and flags. */ +if (domain->quota_warned) + quota_exceeded ="quota_exceeded\n" ; +if (domain->flags &DOMAIN_FLAGS_TRANSITION_FAILED ) + transition_failed ="transition_failed\n" ; +if (domain->flags &DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ ) + ignore_global_allow_read + =KEYWORD_IGNORE_GLOBAL_ALLOW_READ "\n" ; +if (!tmy_io_printf (head,"%s\n" KEYWORD_USE_PROFILE "%u\n" +"%s%s%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 acl_info *ptr + = list1_entry(apos, struct acl_info, list); +if (!print_entry (head, ptr)) +return 0 ; + } + head->read_step =3 ; +tail_mark: +if (!tmy_io_printf (head,"\n" )) +return 0 ; + head->read_step =1 ; +if (head->read_single_domain) +break ; + } + head->read_eof = true; +return 0 ; +} + +/** +* write_domain_profile - Assign profile for specified domain. +* +* @head: Pointer to "struct tmy_io_buffer". +* +* Returns 0 on success, -EINVAL otherwise. +* +* This is equivalent to doing +* +* ( echo "select " $domainname; echo "use_profile " $profile ) | +* /usr/lib/ccs/loadpolicy -d +*/ +static int write_domain_profile (struct tmy_io_buffer *head) +{ +char *data = head->write_buf; +char *cp =strchr (data,' ' ); +struct domain_info *domain; +unsigned long profile; +if (!cp) +return -EINVAL ; + *cp ='\0' ; + domain =tmy_find_domain (cp +1 ); +strict_strtoul (data,10 , &profile); +if (domain && profile <MAX_PROFILES + && (profile_ptr[profile] || !sbin_init_started)) + domain->profile = (u8 ) profile; +tmy_update_counter (TMY_UPDATES_COUNTER_DOMAIN_POLICY ); +return 0 ; +} + +/** +* read_domain_profile - Read only domainname and profile. +* +* @head: Pointer to "struct tmy_io_buffer". +* +* Returns list of profile number and domainname pairs. +* +* This is equivalent to doing +* +* grep -A 1 '^<kernel>' /sys/kernel/security/tomoyo/domain_policy | +* awk ' { if ( domainname == "" ) { if ( $1 == "<kernel>" ) +* domainname = $0; } else if ( $1 == "use_profile" ) { +* print $2 " " domainname; domainname = ""; } } ; ' +*/ +static int read_domain_profile (struct tmy_io_buffer *head) +{ +struct list1_head *pos; +if (head->read_eof) +return 0 ; +list1_for_each_cookie (pos, head->read_var1, &domain_list) { +struct domain_info *domain; + domain = list1_entry(pos, struct domain_info, list); +if (domain->is_deleted) +continue ; +if (!tmy_io_printf (head,"%u %s\n" , domain->profile, + domain->domainname->name)) +return 0 ; + } + head->read_eof = true; +return 0 ; +} + +/** +* write_pid: Specify PID to obtain domainname. +* +* @head: Pointer to "struct tmy_io_buffer". +* +* Returns 0. +*/ +static int write_pid (struct tmy_io_buffer *head) +{ +unsigned long pid; +strict_strtoul (head->write_buf,10 , &pid); + head->read_step = (int ) pid; + head->read_eof = false; +return 0 ; +} + +/** +* read_pid - Get domainname of the specified PID. +* +* @head: Pointer to "struct tmy_io_buffer". +* +* Returns the domainname which the specified PID is in on success, +* empty string otherwise. +* The PID is specified by write_pid() so that the user can obtain +* using read()/write() interface rather than sysctl() interface. +*/ +static int read_pid (struct tmy_io_buffer *head) +{ +if (head->read_avail ==0 && !head->read_eof) { +const int pid = head->read_step; +struct task_struct *p; +struct domain_info *domain =NULL ; +/***** CRITICAL SECTION START *****/ +read_lock (&tasklist_lock); + p =find_task_by_vpid (pid); +if (p) + domain =tmy_real_domain (p); +read_unlock (&tasklist_lock); +/***** CRITICAL SECTION END *****/ +if (domain) +tmy_io_printf (head,"%d %u %s" , pid, domain->profile, + domain->domainname->name); + head->read_eof = true; + } +return 0 ; +} + +/** +* write_exception_policy - Write exception policy. +* +* @head: Pointer to "struct tmy_io_buffer". +* +* Returns 0 on success, negative value otherwise. +*/ +static int write_exception_policy (struct tmy_io_buffer *head) +{ +char *data = head->write_buf; +bool is_delete =str_starts (&data,KEYWORD_DELETE ); +if (str_starts (&data,KEYWORD_KEEP_DOMAIN )) +return tmy_write_domain_keeper_policy (data, false, is_delete); +if (str_starts (&data,KEYWORD_NO_KEEP_DOMAIN )) +return tmy_write_domain_keeper_policy (data, true, is_delete); +if (str_starts (&data,KEYWORD_INITIALIZE_DOMAIN )) +return tmy_write_domain_initializer_policy (data, false, + is_delete); +if (str_starts (&data,KEYWORD_NO_INITIALIZE_DOMAIN )) +return tmy_write_domain_initializer_policy (data, true, + is_delete); +if (str_starts (&data,KEYWORD_ALIAS )) +return tmy_write_alias_policy (data, is_delete); +if (str_starts (&data,KEYWORD_ALLOW_READ )) +return tmy_write_globally_readable_policy (data, is_delete); +if (str_starts (&data,KEYWORD_FILE_PATTERN )) +return tmy_write_pattern_policy (data, is_delete); +if (str_starts (&data,KEYWORD_DENY_REWRITE )) +return tmy_write_no_rewrite_policy (data, is_delete); +return -EINVAL ; +} + +/** +* read_exception_policy - Read exception policy. +* +* @head: Pointer to "struct tmy_io_buffer". +* +* Returns 0 on success, -EINVAL otherwise. +*/ +static int read_exception_policy (struct tmy_io_buffer *head) +{ +if (!head->read_eof) { +switch (head->read_step) { +case 0 : + head->read_var2 =NULL ; + head->read_step =1 ; +case 1 : +if (!tmy_read_domain_keeper_policy (head)) +break ; + head->read_var2 =NULL ; + head->read_step =2 ; +case 2 : +if (!tmy_read_globally_readable_policy (head)) +break ; + head->read_var2 =NULL ; + head->read_step =3 ; +case 3 : + head->read_var2 =NULL ; + head->read_step =4 ; +case 4 : +if (!tmy_read_domain_initializer_policy (head)) +break ; + head->read_var2 =NULL ; + head->read_step =5 ; +case 5 : +if (!tmy_read_alias_policy (head)) +break ; + head->read_var2 =NULL ; + head->read_step =6 ; +case 6 : + head->read_var2 =NULL ; + head->read_step =7 ; +case 7 : +if (!tmy_read_file_pattern (head)) +break ; + head->read_var2 =NULL ; + head->read_step =8 ; +case 8 : +if (!tmy_read_no_rewrite_policy (head)) +break ; + head->read_var2 =NULL ; + head->read_step =9 ; +case 9 : + head->read_eof = true; +break ; +default : +return -EINVAL ; + } + } +return 0 ; +} + +/* path to policy loader */ +static const char *tmy_loader ="/sbin/tomoyo-init" ; + +/** +* policy_loader_exists - Check whether /sbin/tomoyo-init exists. +* +* Returns true if /sbin/tomoyo-init exists, false otherwise. +*/ +static bool policy_loader_exists (void ) +{ +/* +* Don't activate MAC if the policy loader doesn't exist. +* If the initrd includes /sbin/init but real-root-dev has not +* mounted on / yet, activating MAC will block the system since +* policies are not loaded yet. +* Thus, let do_execve() call this function everytime. +*/ +struct nameidata nd; +if (path_lookup (tmy_loader,LOOKUP_FOLLOW , &nd)) { +printk (KERN_INFO "Not activating Mandatory Access Control now " +"since %s doesn't exist.\n" , tmy_loader); +return false; + } +path_put (&nd.path); +return true; +} + +/** +* tmy_load_policy - Run external policy loader to load policy. +* +* @filename: The program about to start. +* +* This function checks whether @filename is /sbin/init , and if so +* invoke /sbin/tomoyo-init and wait for the termination of /sbin/tomoyo-init +* and then continues invocation of /sbin/init. +* /sbin/tomoyo-init reads policy files in /etc/tomoyo/ directory and +* writes to /sys/kernel/security/tomoyo/ interfaces. +* +* Returns nothing. +*/ +void tmy_load_policy (const char *filename) +{ +char *argv[2 ]; +char *envp[3 ]; +if (sbin_init_started) +return ; +/* +* Check filename is /sbin/init or /sbin/tomoyo-start. +* /sbin/tomoyo-start is a dummy filename in case where /sbin/init can't +* be passed. +* You can create /sbin/tomoyo-start by +* "ln -s /bin/true /sbin/tomoyo-start". +*/ +if (strcmp (filename,"/sbin/init" ) && +strcmp (filename,"/sbin/tomoyo-start" )) +return ; +if (!policy_loader_exists ()) +return ; + +printk (KERN_INFO "Calling %s to load policy. Please wait.\n" , + tmy_loader); + argv[0 ] = (char *) tmy_loader; + argv[1 ] =NULL ; + envp[0 ] ="HOME=/" ; + envp[1 ] ="PATH=/sbin:/bin:/usr/sbin:/usr/bin" ; + envp[2 ] =NULL ; +call_usermodehelper (argv[0 ], argv, envp,1 ); + +printk (KERN_INFO "TOMOYO: 2.2.0-pre 2008/10/01\n" ); +printk (KERN_INFO "Mandatory Access Control activated.\n" ); + sbin_init_started = true; + {/* Check all profiles currently assigned to domains are defined. */ +struct domain_info *domain; +list1_for_each_entry (domain, &domain_list, list) { +const u8 profile = domain->profile; +if (profile_ptr[profile]) +continue ; +panic ("Profile %u (used by '%s') not defined.\n" , + profile, domain->domainname->name); + } + } +} + +/* Policy updates counter. */ +static unsigned int updates_counter[MAX_TMY_UPDATES_COUNTER ]; + +/* Policy updates counter lock. */ +static DEFINE_SPINLOCK (updates_counter_lock); + +/** +* tmy_update_counter - Increment policy change counter. +* +* @index: Type of policy. +* +* Returns nothing. +*/ +void tmy_update_counter (const unsigned char index) +{ +/***** CRITICAL SECTION START *****/ +spin_lock (&updates_counter_lock); +if (index <MAX_TMY_UPDATES_COUNTER ) + updates_counter[index]++; +spin_unlock (&updates_counter_lock); +/***** CRITICAL SECTION END *****/ +} + +/** +* read_updates_counter - Check for policy change counter. +* +* @head: Pointer to "struct tmy_io_buffer". +* +* Returns how many times policy has changed since the previous check. +*/ +static int read_updates_counter (struct tmy_io_buffer *head) +{ +unsigned int counter[MAX_TMY_UPDATES_COUNTER ]; +if (head->read_eof) +return 0 ; +/***** CRITICAL SECTION START *****/ +spin_lock (&updates_counter_lock); +memmove (counter, updates_counter,sizeof (updates_counter)); +memset (updates_counter,0 ,sizeof (updates_counter)); +spin_unlock (&updates_counter_lock); +/***** CRITICAL SECTION END *****/ +tmy_io_printf (head, +"/sys/kernel/security/tomoyo/domain_policy: %10u\n" +"/sys/kernel/security/tomoyo/exception_policy: %10u\n" +"/sys/kernel/security/tomoyo/profile: %10u\n" +"/sys/kernel/security/tomoyo/manager: %10u\n" , + counter[TMY_UPDATES_COUNTER_DOMAIN_POLICY ], + counter[TMY_UPDATES_COUNTER_EXCEPTION_POLICY ], + counter[TMY_UPDATES_COUNTER_PROFILE ], + counter[TMY_UPDATES_COUNTER_MANAGER ]); + head->read_eof = true; +return 0 ; +} + +/** +* read_version: Get version. +* +* @head: Pointer to "struct tmy_io_buffer". +* +* Returns version information. +*/ +static int read_version (struct tmy_io_buffer *head) +{ +if (!head->read_eof) { +tmy_io_printf (head,"2.2.0-pre" ); + head->read_eof = true; + } +return 0 ; +} + +/** +* read_self_domain - Get the current process's domainname. +* +* @head: Pointer to "struct tmy_io_buffer". +* +* Returns the current process's domainname. +*/ +static int read_self_domain (struct tmy_io_buffer *head) +{ +if (!head->read_eof) { +/* +* tmy_domain()->domainname != NULL +* because every process belongs to a domain and +* the domain's name cannot be NULL. +*/ +tmy_io_printf (head,"%s" ,tmy_domain ()->domainname->name); + head->read_eof = true; + } +return 0 ; +} + +/** +* tmy_open_control - open() for /sys/kernel/security/tomoyo/ interface. +* +* @type: Type of interface. +* @file: Pointer to "struct file". +* +* Associates policy handler and returns 0 on success, -ENOMEM otherwise. +*/ +static int tmy_open_control (const u8 type,struct file *file) +{ +struct tmy_io_buffer *head =tmy_alloc (sizeof (*head)); +if (!head) +return -ENOMEM ; +mutex_init (&head->io_sem); +switch (type) { +case TMY_DOMAINPOLICY : +/* /sys/kernel/security/tomoyo/domain_policy */ + head->write = write_domain_policy; + head->read = read_domain_policy; +break ; +case TMY_EXCEPTIONPOLICY : +/* /sys/kernel/security/tomoyo/exception_policy */ + head->write = write_exception_policy; + head->read = read_exception_policy; +break ; +case TMY_SELFDOMAIN : +/* /sys/kernel/security/tomoyo/self_domain */ + head->read = read_self_domain; +break ; +case TMY_DOMAIN_STATUS : +/* /sys/kernel/security/tomoyo/.domain_status */ + head->write = write_domain_profile; + head->read = read_domain_profile; +break ; +case TMY_PROCESS_STATUS : +/* /sys/kernel/security/tomoyo/.process_status */ + head->write = write_pid; + head->read = read_pid; +break ; +case TMY_VERSION : +/* /sys/kernel/security/tomoyo/version */ + head->read = read_version; + head->readbuf_size =128 ; +break ; +case TMY_MEMINFO : +/* /sys/kernel/security/tomoyo/meminfo */ + head->write = tmy_write_memory_quota; + head->read = tmy_read_memory_counter; + head->readbuf_size =512 ; +break ; +case TMY_PROFILE : +/* /sys/kernel/security/tomoyo/profile */ + head->write = write_profile; + head->read = read_profile; +break ; +case TMY_MANAGER : +/* /sys/kernel/security/tomoyo/manager */ + head->write = write_manager_policy; + head->read = read_manager_policy; +break ; +case TMY_UPDATESCOUNTER : +/* /sys/kernel/security/tomoyo/.updates_counter */ + head->read = read_updates_counter; +break ; + } +if (!(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 =tmy_alloc (head->readbuf_size); +if (!head->read_buf) { +tmy_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 =tmy_alloc (head->writebuf_size); +if (!head->write_buf) { +tmy_free (head->read_buf); +tmy_free (head); +return -ENOMEM ; + } + } + file->private_data = head; +/* +* Call the handler now if the file is +* /sys/kernel/security/tomoyo/self_domain +* so that the user can use +* cat < /sys/kernel/security/tomoyo/self_domain" +* to know the current process's domainname. +*/ +if (type ==TMY_SELFDOMAIN ) +tmy_read_control (file,NULL ,0 ); +return 0 ; +} + +/** +* tmy_read_control - read() for /sys/kernel/security/tomoyo/ interface. +* +* @file: Pointer to "struct file". +* @buffer: Poiner to buffer to write to. +* @buffer_len: Size of @buffer. +* +* Returns bytes read on success, negative value otherwise. +*/ +static int tmy_read_control (struct file *file,char __user *buffer, +const int buffer_len) +{ +int len =0 ; +struct tmy_io_buffer *head = file->private_data; +char *cp; +if (!head->read) +return -ENOSYS ; +if (!access_ok (VERIFY_WRITE , buffer, buffer_len)) +return -EFAULT ; +if (mutex_lock_interruptible (&head->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; +} + +/** +* tmy_write_control - write() for /sys/kernel/security/tomoyo/ interface. +* +* @file: Pointer to "struct file". +* @buffer: Pointer to buffer to read from. +* @buffer_len: Size of @buffer. +* +* Returns @buffer_len on success, negative value otherwise. +*/ +static int tmy_write_control (struct file *file,const char __user *buffer, +const int buffer_len) +{ +struct tmy_io_buffer *head = file->private_data; +int error = buffer_len; +int avail_len = buffer_len; +char *cp0 = head->write_buf; +if (!head->write) +return -ENOSYS ; +if (!access_ok (VERIFY_READ , buffer, buffer_len)) +return -EFAULT ; +/* Don't allow updating policies by non manager programs. */ +if (head->write != write_pid && head->write != write_domain_policy && + !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 ; +normalize_line (cp0); + head->write (head); + } +mutex_unlock (&head->io_sem); +return error; +} + +/** +* tmy_close_control - close() for /sys/kernel/security/tomoyo/ interface. +* +* @file: Pointer to "struct file". +* +* Releases memory and returns 0. +*/ +static int tmy_close_control (struct file *file) +{ +struct tmy_io_buffer *head = file->private_data; +/* Release memory used for policy I/O. */ +tmy_free (head->read_buf); + head->read_buf =NULL ; +tmy_free (head->write_buf); + head->write_buf =NULL ; +tmy_free (head); + head =NULL ; + file->private_data =NULL ; +return 0 ; +} + +/** +* tmy_alloc_acl_element - Allocate permanent memory for ACL entry. +* +* @acl_type: Type of ACL entry. +* +* Returns pointer to the ACL entry on success, NULL otherwise. +*/ +void *tmy_alloc_acl_element (const u8 acl_type) +{ +int len; +struct acl_info *ptr; +switch (acl_type) { +case TYPE_SINGLE_PATH_ACL : + len =sizeof (struct single_path_acl_record ); +break ; +case TYPE_DOUBLE_PATH_ACL : + len =sizeof (struct double_path_acl_record ); +break ; +default : +return NULL ; + } + ptr =tmy_alloc_element (len); +if (!ptr) +return NULL ; + ptr->type = acl_type; +return ptr; +} + +/** +* tmy_open - open() for /sys/kernel/security/tomoyo/ interface. +* +* @inode: Pointer to "struct inode". +* @file: Pointer to "struct file". +* +* Returns 0 on success, negative value otherwise. +*/ +static int tmy_open (struct inode *inode,struct file *file) +{ +return tmy_open_control (((u8 *) file->f_path.dentry->d_inode->i_private) + - ((u8 *)NULL ), file); +} + +/** +* tmy_release - close() for /sys/kernel/security/tomoyo/ interface. +* +* @inode: Pointer to "struct inode". +* @file: Pointer to "struct file". +* +* Returns 0 on success, negative value otherwise. +*/ +static int tmy_release (struct inode *inode,struct file *file) +{ +return tmy_close_control (file); +} + +/** +* tmy_read - read() for /sys/kernel/security/tomoyo/ interface. +* +* @file: Pointer to "struct file". +* @buf: Pointer to buffer. +* @count: Size of @buf. +* @ppos: Unused. +* +* Returns bytes read on success, negative value otherwise. +*/ +static ssize_t tmy_read (struct file *file,char __user *buf,size_t count, +loff_t *ppos) +{ +return tmy_read_control (file, buf, count); +} + +/** +* tmy_write - write() for /sys/kernel/security/tomoyo/ interface. +* +* @file: Pointer to "struct file". +* @buf: Pointer to buffer. +* @count: Size of @buf. +* @ppos: Unused. +* +* Returns @count on success, negative value otherwise. +*/ +static ssize_t tmy_write (struct file *file,const char __user *buf, +size_t count,loff_t *ppos) +{ +return tmy_write_control (file, buf, count); +} + +/* Operations for /sys/kernel/security/tomoyo/interface. */ +static struct file_operations tmy_operations = { + .open = tmy_open, + .release = tmy_release, + .read = tmy_read, + .write = tmy_write, +}; + +/** +* create_entry - Create interface files under /sys/kernel/security/tomoyo/ directory. +* +* @name: The name of the interface file. +* @mode: The permission of the interface file. +* @parent: The parent directory. +* @key: Type of interface. +* +* Returns nothing. +*/ +static void __init create_entry (const char *name,const mode_t mode, +struct dentry *parent,const u8 key) +{ +securityfs_create_file (name, mode, parent, ((u8 *)NULL ) + key, + &tmy_operations); +} + +/** +* tmy_initerface_init - Initialize /sys/kernel/security/tomoyo/ interface. +* +* Returns 0. +*/ +static int __init tmy_initerface_init (void ) +{ +struct dentry *tmy_dir; + tmy_dir =securityfs_create_dir ("tomoyo" ,NULL ); +create_entry ("domain_policy" ,0600 , tmy_dir,TMY_DOMAINPOLICY ); +create_entry ("exception_policy" ,0600 , tmy_dir,TMY_EXCEPTIONPOLICY ); +create_entry ("self_domain" ,0400 , tmy_dir,TMY_SELFDOMAIN ); +create_entry (".domain_status" ,0600 , tmy_dir,TMY_DOMAIN_STATUS ); +create_entry (".process_status" ,0600 , tmy_dir,TMY_PROCESS_STATUS ); +create_entry ("meminfo" ,0600 , tmy_dir,TMY_MEMINFO ); +create_entry ("profile" ,0600 , tmy_dir,TMY_PROFILE ); +create_entry ("manager" ,0600 , tmy_dir,TMY_MANAGER ); +create_entry (".updates_counter" ,0400 , tmy_dir,TMY_UPDATESCOUNTER ); +create_entry ("version" ,0400 , tmy_dir,TMY_VERSION ); +return 0 ; +} + +fs_initcall (tmy_initerface_init);
security/tomoyo/common.h new file +398 -0
398 lines not on this page · raw message
--
Kentaro Takeda
maintainerPATCH 6/8 Domain transition handler.
In reply to Kentaro TakedaThis file controls domain creation/deletion/transition.
Every process belongs to a domain in TOMOYO Linux. Domain transition occurs when execve(2) is called
and the domain is expressed as 'process invocation history', such as '<kernel> /sbin/init /etc/init.d/rc'.
Domain information is stored in task_struct->cred->security field.
--- security/tomoyo/domain.c | 854 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 854 insertions(+)
security/tomoyo/domain.c new file +854 -0
@@ -0,0 +1,854 @@ +/* +* 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/10/01 +* +*/ + +#include "common.h" +#include "tomoyo.h" +#include "realpath.h" +#include <linux/binfmts.h> + +/* Variables definitions.*/ + +/* The initial domain. */ +struct domain_info KERNEL_DOMAIN ; + +/* The list for "struct domain_info". */ +LIST1_HEAD (domain_list); + +/* Lock for appending domain's ACL. */ +DEFINE_MUTEX (domain_acl_lock); + +/* Domain creation lock. */ +static DEFINE_MUTEX (new_domain_assign_lock); + +/* Structure for "initialize_domain" and "no_initialize_domain" keyword. */ +struct domain_initializer_entry { +struct list1_head list; +const struct path_info *domainname;/* This may be NULL */ +const struct path_info *program; +bool is_deleted; +bool is_not;/* True if this entry is "no_initialize_domain". */ +bool is_last_name;/* True if the domainname is tmy_get_last_name(). */ +}; + +/* Structure for "keep_domain" and "no_keep_domain" keyword. */ +struct domain_keeper_entry { +struct list1_head list; +const struct path_info *domainname; +const struct path_info *program;/* This may be NULL */ +bool is_deleted; +bool is_not;/* True if this entry is "no_keep_domain". */ +bool is_last_name;/* True if the domainname is tmy_get_last_name(). */ +}; + +/* Structure for "alias" keyword. */ +struct alias_entry { +struct list1_head list; +const struct path_info *original_name; +const struct path_info *aliased_name; +bool is_deleted; +}; + +/** +* tmy_set_domain_flag - Set or clear domain's attribute flags. +* +* @domain: Pointer to "struct domain_info". +* @is_delete: True if it is a delete request. +* @flags: Flags to set or clear. +* +* Returns nothing. +*/ +void tmy_set_domain_flag (struct domain_info *domain,const bool is_delete, +const u8 flags) +{ +mutex_lock (&new_domain_assign_lock); +if (!is_delete) + domain->flags |= flags; +else + domain->flags &= ~flags; +mutex_unlock (&new_domain_assign_lock); +} + +/** +* tmy_get_last_name - Get last component of a domainname. +* +* @domain: Pointer to "struct domain_info". +* +* Returns the last component of the domainname. +*/ +const char *tmy_get_last_name (const struct domain_info *domain) +{ +const char *cp0 = domain->domainname->name, *cp1 =strrchr (cp0,' ' ); +if (cp1) +return cp1 +1 ; +return cp0; +} + +/** +* tmy_add_domain_acl - Add the given ACL to the given domain. +* +* @domain: Pointer to "struct domain_info". May be NULL. +* @acl: Pointer to "struct acl_info". +* +* Returns 0. +*/ +int tmy_add_domain_acl (struct domain_info *domain,struct acl_info *acl) +{ +if (domain) +list1_add_tail_mb (&acl->list, &domain->acl_info_list); +else + acl->type &= ~ACL_DELETED ; +tmy_update_counter (TMY_UPDATES_COUNTER_DOMAIN_POLICY ); +return 0 ; +} + +/** +* tmy_del_domain_acl - Delete the given ACL from the domain. +* +* @acl: Pointer to "struct acl_info". May be NULL. +* +* Returns 0. +*/ +int tmy_del_domain_acl (struct acl_info *acl) +{ +if (acl) + acl->type |=ACL_DELETED ; +tmy_update_counter (TMY_UPDATES_COUNTER_DOMAIN_POLICY ); +return 0 ; +} + +/* The list for "struct domain_initializer_entry". */ +static LIST1_HEAD (domain_initializer_list); + +/** +* update_domain_initializer_entry - Update "struct domain_initializer_entry" list. +* +* @domainname: The name of domain. May be NULL. +* @program: The name of program. +* @is_not: True if it is "no_initialize_domain" entry. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int update_domain_initializer_entry (const char *domainname, +const char *program, +const bool is_not, +const bool is_delete) +{ +struct domain_initializer_entry *new_entry; +struct domain_initializer_entry *ptr; +static DEFINE_MUTEX (lock); +const struct path_info *saved_program; +const struct path_info *saved_domainname =NULL ; +int error = -ENOMEM ; +bool is_last_name = false; +if (!tmy_is_correct_path (program,1 ,-1 ,-1 , __func__)) +return -EINVAL ;/* No patterns allowed. */ +if (domainname) { +if (!tmy_is_domain_def (domainname) && +tmy_is_correct_path (domainname,1 ,-1 ,-1 , __func__)) + is_last_name = true; +else if (!tmy_is_correct_domain (domainname, __func__)) +return -EINVAL ; + saved_domainname =tmy_save_name (domainname); +if (!saved_domainname) +return -ENOMEM ; + } + saved_program =tmy_save_name (program); +if (!saved_program) +return -ENOMEM ; +mutex_lock (&lock); +list1_for_each_entry (ptr, &domain_initializer_list, list) { +if (ptr->is_not != is_not || + ptr->domainname != saved_domainname || + ptr->program != saved_program) +continue ; + ptr->is_deleted = is_delete; + error =0 ; + goto out; + } +if (is_delete) { + error = -ENOENT ; + goto out; + } + new_entry =tmy_alloc_element (sizeof (*new_entry)); +if (!new_entry) + goto out; + new_entry->domainname = saved_domainname; + new_entry->program = saved_program; + new_entry->is_not = is_not; + new_entry->is_last_name = is_last_name; +list1_add_tail_mb (&new_entry->list, &domain_initializer_list); + error =0 ; +out: +mutex_unlock (&lock); +tmy_update_counter (TMY_UPDATES_COUNTER_EXCEPTION_POLICY ); +return error; +} + +/** +* tmy_read_domain_initializer_policy - Read "struct domain_initializer_entry" list. +* +* @head: Pointer to "struct tmy_io_buffer". +* +* Returns true on success, false otherwise. +*/ +bool tmy_read_domain_initializer_policy (struct tmy_io_buffer *head) +{ +struct list1_head *pos; +list1_for_each_cookie (pos, head->read_var2, &domain_initializer_list) { +const char *no; +const char *from ="" ; +const char *domain ="" ; +struct domain_initializer_entry *ptr; + ptr = list1_entry(pos, struct domain_initializer_entry, list); +if (ptr->is_deleted) +continue ; + no = ptr->is_not ?"no_" :"" ; +if (ptr->domainname) { + from =" from " ; + domain = ptr->domainname->name; + } +if (!tmy_io_printf (head, +"%s" KEYWORD_INITIALIZE_DOMAIN "%s%s%s\n" , + no, ptr->program->name, from, domain)) + goto out; + } +return true; +out: +return false; +} + +/** +* tmy_write_domain_initializer_policy - Write "struct domain_initializer_entry" list. +* +* @data: String to parse. +* @is_not: True if it is "no_initialize_domain" entry. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +int tmy_write_domain_initializer_policy (char *data,const bool is_not, +const bool is_delete) +{ +char *cp =strstr (data," from " ); +if (cp) { + *cp ='\0' ; +return update_domain_initializer_entry (cp +6 , data, is_not, + is_delete); + } +return update_domain_initializer_entry (NULL , data, is_not, is_delete); +} + +/** +* is_domain_initializer - Check whether the given program causes domainname reinitialization. +* +* @domainname: The name of domain. +* @program: The name of program. +* @last_name: The last component of @domainname. +* +* Returns true if executing @program reinitializes domain transition, +* false otherwise. +*/ +static bool is_domain_initializer (const struct path_info *domainname, +const struct path_info *program, +const struct path_info *last_name) +{ +struct domain_initializer_entry *ptr; +bool flag = false; +list1_for_each_entry (ptr, &domain_initializer_list, list) { +if (ptr->is_deleted) +continue ; +if (ptr->domainname) { +if (!ptr->is_last_name) { +if (ptr->domainname != domainname) +continue ; + }else { +if (tmy_pathcmp (ptr->domainname, last_name)) +continue ; + } + } +if (tmy_pathcmp (ptr->program, program)) +continue ; +if (ptr->is_not) +return false; + flag = true; + } +return flag; +} + +/* The list for "struct domain_keeper_entry". */ +static LIST1_HEAD (domain_keeper_list); + +/** +* update_domain_keeper_entry - Update "struct domain_keeper_entry" list. +* +* @domainname: The name of domain. +* @program: The name of program. May be NULL. +* @is_not: True if it is "no_keep_domain" entry. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int update_domain_keeper_entry (const char *domainname, +const char *program, +const bool is_not,const bool is_delete) +{ +struct domain_keeper_entry *new_entry; +struct domain_keeper_entry *ptr; +const struct path_info *saved_domainname; +const struct path_info *saved_program =NULL ; +static DEFINE_MUTEX (lock); +int error = -ENOMEM ; +bool is_last_name = false; +if (!tmy_is_domain_def (domainname) && +tmy_is_correct_path (domainname,1 ,-1 ,-1 , __func__)) + is_last_name = true; +else if (!tmy_is_correct_domain (domainname, __func__)) +return -EINVAL ; +if (program) { +if (!tmy_is_correct_path (program,1 ,-1 ,-1 , __func__)) +return -EINVAL ; + saved_program =tmy_save_name (program); +if (!saved_program) +return -ENOMEM ; + } + saved_domainname =tmy_save_name (domainname); +if (!saved_domainname) +return -ENOMEM ; +mutex_lock (&lock); +list1_for_each_entry (ptr, &domain_keeper_list, list) { +if (ptr->is_not != is_not || + ptr->domainname != saved_domainname || + ptr->program != saved_program) +continue ; + ptr->is_deleted = is_delete; + error =0 ; + goto out; + } +if (is_delete) { + error = -ENOENT ; + goto out; + } + new_entry =tmy_alloc_element (sizeof (*new_entry)); +if (!new_entry) + goto out; + new_entry->domainname = saved_domainname; + new_entry->program = saved_program; + new_entry->is_not = is_not; + new_entry->is_last_name = is_last_name; +list1_add_tail_mb (&new_entry->list, &domain_keeper_list); + error =0 ; +out: +mutex_unlock (&lock); +tmy_update_counter (TMY_UPDATES_COUNTER_EXCEPTION_POLICY ); +return error; +} + +/** +* tmy_write_domain_keeper_policy - Write "struct domain_keeper_entry" list. +* +* @data: String to parse. +* @is_not: True if it is "no_keep_domain" entry. +* @is_delete: True if it is a delete request. +* +*/ +int tmy_write_domain_keeper_policy (char *data,const bool is_not, +const bool is_delete) +{ +char *cp =strstr (data," from " ); +if (cp) { + *cp ='\0' ; +return update_domain_keeper_entry (cp +6 , data, + is_not, is_delete); + } +return update_domain_keeper_entry (data,NULL , is_not, is_delete); +} + +/** +* tmy_read_domain_keeper_policy - Read "struct domain_keeper_entry" list. +* +* @head: Pointer to "struct tmy_io_buffer". +* +* Returns true on success, false otherwise. +*/ +bool tmy_read_domain_keeper_policy (struct tmy_io_buffer *head) +{ +struct list1_head *pos; +list1_for_each_cookie (pos, head->read_var2, &domain_keeper_list) { +struct domain_keeper_entry *ptr; +const char *no; +const char *from ="" ; +const char *program ="" ; + ptr = list1_entry(pos, struct domain_keeper_entry, list); +if (ptr->is_deleted) +continue ; + no = ptr->is_not ?"no_" :"" ; +if (ptr->program) { + from =" from " ; + program = ptr->program->name; + } +if (!tmy_io_printf (head, +"%s" KEYWORD_KEEP_DOMAIN "%s%s%s\n" , no, + program, from, ptr->domainname->name)) + goto out; + } +return true; +out: +return false; +} + +/** +* is_domain_keeper - Check whether the given program causes domain transition suppression. +* +* @domainname: The name of domain. +* @program: The name of program. +* @last_name: The last component of @domainname. +* +* Returns true if executing @program supresses domain transition, +* false otherwise. +*/ +static bool is_domain_keeper (const struct path_info *domainname, +const struct path_info *program, +const struct path_info *last_name) +{ +struct domain_keeper_entry *ptr; +bool flag = false; +list1_for_each_entry (ptr, &domain_keeper_list, list) { +if (ptr->is_deleted) +continue ; +if (!ptr->is_last_name) { +if (ptr->domainname != domainname) +continue ; + }else { +if (tmy_pathcmp (ptr->domainname, last_name)) +continue ; + } +if (ptr->program &&tmy_pathcmp (ptr->program, program)) +continue ; +if (ptr->is_not) +return false; + flag = true; + } +return flag; +} + +/* The list for "struct alias_entry". */ +static LIST1_HEAD (alias_list); + +/** +* update_alias_entry - Update "struct alias_entry" list. +* +* @original_name: The original program's real name. +* @aliased_name: The symbolic program's symbolic link's name. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int update_alias_entry (const char *original_name, +const char *aliased_name, +const bool is_delete) +{ +struct alias_entry *new_entry; +struct alias_entry *ptr; +static DEFINE_MUTEX (lock); +const struct path_info *saved_original_name; +const struct path_info *saved_aliased_name; +int error = -ENOMEM ; +if (!tmy_is_correct_path (original_name,1 ,-1 ,-1 , __func__) || + !tmy_is_correct_path (aliased_name,1 ,-1 ,-1 , __func__)) +return -EINVAL ;/* No patterns allowed. */ + saved_original_name =tmy_save_name (original_name); + saved_aliased_name =tmy_save_name (aliased_name); +if (!saved_original_name || !saved_aliased_name) +return -ENOMEM ; +mutex_lock (&lock); +list1_for_each_entry (ptr, &alias_list, list) { +if (ptr->original_name != saved_original_name || + ptr->aliased_name != saved_aliased_name) +continue ; + ptr->is_deleted = is_delete; + error =0 ; + goto out; + } +if (is_delete) { + error = -ENOENT ; + goto out; + } + new_entry =tmy_alloc_element (sizeof (*new_entry)); +if (!new_entry) + goto out; + new_entry->original_name = saved_original_name; + new_entry->aliased_name = saved_aliased_name; +list1_add_tail_mb (&new_entry->list, &alias_list); + error =0 ; +out: +mutex_unlock (&lock); +tmy_update_counter (TMY_UPDATES_COUNTER_EXCEPTION_POLICY ); +return error; +} + +/** +* tmy_read_alias_policy - Read "struct alias_entry" list. +* +* @head: Pointer to "struct tmy_io_buffer". +* +* Returns true on success, false otherwise. +*/ +bool tmy_read_alias_policy (struct tmy_io_buffer *head) +{ +struct list1_head *pos; +list1_for_each_cookie (pos, head->read_var2, &alias_list) { +struct alias_entry *ptr; + ptr = list1_entry(pos, struct alias_entry, list); +if (ptr->is_deleted) +continue ; +if (!tmy_io_printf (head,KEYWORD_ALIAS "%s %s\n" , + ptr->original_name->name, + ptr->aliased_name->name)) + goto out; + } +return true; +out: +return false; +} + +/** +* tmy_write_alias_policy - Write "struct alias_entry" list. +* +* @data: String to parse. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +int tmy_write_alias_policy (char *data,const bool is_delete) +{ +char *cp =strchr (data,' ' ); +if (!cp) +return -EINVAL ; + *cp++ ='\0' ; +return update_alias_entry (data, cp, is_delete); +} + +/* Domain create/delete/undelete handler. */ + +/* #define DEBUG_DOMAIN_UNDELETE */ + +/** +* tmy_delete_domain - Delete a domain. +* +* @domainname: The name of domain. +* +* Returns 0. +*/ +int tmy_delete_domain (char *domainname) +{ +struct domain_info *domain; +struct path_info name; + name.name = domainname; +tmy_fill_path_info (&name); +mutex_lock (&new_domain_assign_lock); +#ifdef DEBUG_DOMAIN_UNDELETE +printk (KERN_DEBUG "tmy_delete_domain %s\n" , domainname); +list1_for_each_entry (domain, &domain_list, list) { +if (tmy_pathcmp (domain->domainname, &name)) +continue ; +printk (KERN_DEBUG "List: %p %u\n" , domain, domain->is_deleted); + } +#endif +/* Is there an active domain? */ +list1_for_each_entry (domain, &domain_list, list) { +struct domain_info *domain2; +/* Never delete KERNEL_DOMAIN */ +if (domain == &KERNEL_DOMAIN ) +continue ; +if (domain->is_deleted || +tmy_pathcmp (domain->domainname, &name)) +continue ; +/* Mark already deleted domains as non undeletable. */ +list1_for_each_entry (domain2, &domain_list, list) { +if (!domain2->is_deleted || +tmy_pathcmp (domain2->domainname, &name)) +continue ; +#ifdef DEBUG_DOMAIN_UNDELETE +if (domain2->is_deleted !=255 ) +printk (KERN_DEBUG +"Marked %p as non undeletable\n" , + domain2); +#endif + domain2->is_deleted =255 ; + } +/* Delete and mark active domain as undeletable. */ + domain->is_deleted =1 ; +#ifdef DEBUG_DOMAIN_UNDELETE +printk (KERN_DEBUG "Marked %p as undeletable\n" , domain); +#endif +break ; + } +mutex_unlock (&new_domain_assign_lock); +return 0 ; +} + +/** +* tmy_undelete_domain - Undelete a domain. +* +* @domainname: The name of domain. +* +* Returns pointer to "struct domain_info" on success, NULL otherwise. +*/ +struct domain_info *tmy_undelete_domain (const char *domainname) +{ +struct domain_info *domain; +struct domain_info *candidate_domain =NULL ; +struct path_info name; + name.name = domainname; +tmy_fill_path_info (&name); +mutex_lock (&new_domain_assign_lock); +#ifdef DEBUG_DOMAIN_UNDELETE +printk (KERN_DEBUG "tmy_undelete_domain %s\n" , domainname); +list1_for_each_entry (domain, &domain_list, list) { +if (tmy_pathcmp (domain->domainname, &name)) +continue ; +printk (KERN_DEBUG "List: %p %u\n" , domain, domain->is_deleted); + } +#endif +list1_for_each_entry (domain, &domain_list, list) { +if (tmy_pathcmp (&name, domain->domainname)) +continue ; +if (!domain->is_deleted) { +/* This domain is active. I can't undelete. */ + candidate_domain =NULL ; +#ifdef DEBUG_DOMAIN_UNDELETE +printk (KERN_DEBUG "%p is active. I can't undelete.\n" , + domain); +#endif +break ; + } +/* Is this domain undeletable? */ +if (domain->is_deleted ==1 ) + candidate_domain = domain; + } +if (candidate_domain) { + candidate_domain->is_deleted =0 ; +#ifdef DEBUG_DOMAIN_UNDELETE +printk (KERN_DEBUG "%p was undeleted.\n" , candidate_domain); +#endif + } +mutex_unlock (&new_domain_assign_lock); +return candidate_domain; +} + +/** +* tmy_find_or_assign_new_domain - Create a domain. +* +* @domainname: The name of domain. +* @profile: Profile number to assign if the domain was newly created. +* +* Returns pointer to "struct domain_info" on success, NULL otherwise. +*/ +struct domain_info *tmy_find_or_assign_new_domain (const char *domainname, +const u8 profile) +{ +struct domain_info *domain =NULL ; +const struct path_info *saved_domainname; +mutex_lock (&new_domain_assign_lock); + domain =tmy_find_domain (domainname); +if (domain) + goto out; +if (!tmy_is_correct_domain (domainname, __func__)) + goto out; + saved_domainname =tmy_save_name (domainname); +if (!saved_domainname) + goto out; +/* Can I reuse memory of deleted domain? */ +list1_for_each_entry (domain, &domain_list, list) { +struct task_struct *p; +struct acl_info *ptr; +bool flag; +if (!domain->is_deleted || + domain->domainname != saved_domainname) +continue ; + flag = false; +/***** CRITICAL SECTION START *****/ +read_lock (&tasklist_lock); +for_each_process (p) { +if (tmy_real_domain (p) != domain) +continue ; + flag = true; +break ; + } +read_unlock (&tasklist_lock); +/***** CRITICAL SECTION END *****/ +if (flag) +continue ; +#ifdef DEBUG_DOMAIN_UNDELETE +printk (KERN_DEBUG "Reusing %p %s\n" , domain, + domain->domainname->name); +#endif +list1_for_each_entry (ptr, &domain->acl_info_list, list) { + ptr->type |=ACL_DELETED ; + } +/* +* Don't use tmy_set_domain_flag() because +* new_domain_assign_lock is held. +*/ + domain->flags =0 ; + domain->profile = profile; + domain->quota_warned = false; +mb ();/* Avoid out-of-order execution. */ + domain->is_deleted =0 ; + goto out; + } +/* No memory reusable. Create using new memory. */ + domain =tmy_alloc_element (sizeof (*domain)); +if (domain) { +INIT_LIST1_HEAD (&domain->acl_info_list); + domain->domainname = saved_domainname; + domain->profile = profile; +list1_add_tail_mb (&domain->list, &domain_list); + } +out: +mutex_unlock (&new_domain_assign_lock); +return domain; +} + +/** +* tmy_find_next_domain - Find a domain. +* +* @bprm: Pointer to "struct linux_binprm". +* @next_domain: Pointer to pointer to "struct domain_info". +* +* Returns 0 on success, negative value otherwise. +*/ +int tmy_find_next_domain (struct linux_binprm *bprm, +struct domain_info **next_domain) +{ +/* +* This function assumes that the size of buffer returned by +* tmy_realpath() = TMY_MAX_PATHNAME_LEN. +*/ +struct tmy_page_buffer *tmp =tmy_alloc (sizeof (*tmp)); +struct domain_info *old_domain =tmy_domain (); +struct domain_info *domain =NULL ; +const char *old_domain_name = old_domain->domainname->name; +const char *original_name = bprm->filename; +char *new_domain_name =NULL ; +char *real_program_name =NULL ; +char *symlink_program_name =NULL ; +const u8 mode =tmy_check_flags (old_domain,TMY_TOMOYO_MAC_FOR_FILE ); +const bool is_enforce = (mode ==3 ); +int retval = -ENOMEM ; +struct path_info r;/* real name */ +struct path_info s;/* symlink name */ +struct path_info l;/* last name */ + +if (!tmp) + goto out; + + { +/* +* Built-in initializers. This is needed because policies are +* not loaded until starting /sbin/init. +*/ +static bool first = true; +if (first) { +update_domain_initializer_entry (NULL ,"/sbin/hotplug" , + false, false); +update_domain_initializer_entry (NULL ,"/sbin/modprobe" , + false, false); + first = false; + } + } + +/* Get tmy_realpath of program. */ + retval = -ENOENT ;/* I hope tmy_realpath() won't fail with -ENOMEM. */ + real_program_name =tmy_realpath (original_name); +if (!real_program_name) + goto out; +/* Get tmy_realpath of symbolic link. */ + symlink_program_name =tmy_realpath_nofollow (original_name); +if (!symlink_program_name) + goto out; + + r.name = real_program_name; +tmy_fill_path_info (&r); + s.name = symlink_program_name; +tmy_fill_path_info (&s); + l.name =tmy_get_last_name (old_domain); +tmy_fill_path_info (&l); + +/* Check 'alias' directive. */ +if (tmy_pathcmp (&r, &s)) { +struct alias_entry *ptr; +/* Is this program allowed to be called via symbolic links? */ +list1_for_each_entry (ptr, &alias_list, list) { +if (ptr->is_deleted || +tmy_pathcmp (&r, ptr->original_name) || +tmy_pathcmp (&s, ptr->aliased_name)) +continue ; +memset (real_program_name,0 ,TMY_MAX_PATHNAME_LEN ); +strncpy (real_program_name, ptr->aliased_name->name, +TMY_MAX_PATHNAME_LEN -1 ); +tmy_fill_path_info (&r); +break ; + } + } + +/* Check execute permission. */ + retval =tmy_check_exec_perm (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 KERNEL_DOMAIN domain. */ +snprintf (new_domain_name,TMY_MAX_PATHNAME_LEN +1 , +ROOT_NAME " " "%s" , real_program_name); + }else if (old_domain == &KERNEL_DOMAIN && !sbin_init_started) { +/* +* Needn't to transit from kernel domain before starting +* /sbin/init. But transit from kernel domain if executing +* initializers because they might start before /sbin/init. +*/ + domain = old_domain; + }else if (is_domain_keeper (old_domain->domainname, &r, &l)) { +/* Keep current domain. */ + domain = old_domain; + }else { +/* Normal domain transition. */ +snprintf (new_domain_name,TMY_MAX_PATHNAME_LEN +1 , +"%s %s" , old_domain_name, real_program_name); + } +if (domain ||strlen (new_domain_name) >=TMY_MAX_PATHNAME_LEN ) + goto done; + domain =tmy_find_domain (new_domain_name); +if (domain) + goto done; +if (is_enforce) + goto done; + domain =tmy_find_or_assign_new_domain (new_domain_name, + old_domain->profile); +done: +if (domain) + goto out; +printk (KERN_WARNING "TOMOYO-ERROR: Domain '%s' not defined.\n" , + new_domain_name); +if (is_enforce) + retval = -EPERM ; +else +tmy_set_domain_flag (old_domain, false, +DOMAIN_FLAGS_TRANSITION_FAILED ); +out: +tmy_free (real_program_name); +tmy_free (symlink_program_name); + *next_domain = domain ? domain : old_domain; +tmy_free (tmp); +return retval; +}
--
Kentaro Takeda
maintainerPATCH 7/8 File operation restriction part.
In reply to Kentaro TakedaThis file controls file related operations.
--- security/tomoyo/file.c | 1149 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1149 insertions(+)
security/tomoyo/file.c new file +1149 -0
@@ -0,0 +1,1149 @@ +/* +* 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/10/01 +* +*/ + +#include "common.h" +#include "tomoyo.h" +#include "realpath.h" +#define ACC_MODE (x) ("\000\004\002\006"[(x)&O_ACCMODE]) + +/* Structure for "allow_read" keyword. */ +struct globally_readable_file_entry { +struct list1_head list; +const struct path_info *filename; +bool is_deleted; +}; + +/* Structure for "file_pattern" keyword. */ +struct pattern_entry { +struct list1_head list; +const struct path_info *pattern; +bool is_deleted; +}; + +/* Structure for "deny_rewrite" keyword. */ +struct no_rewrite_entry { +struct list1_head list; +const struct path_info *pattern; +bool is_deleted; +}; + +/* Keyword array for single path operations. */ +static const char *sp_keyword[MAX_SINGLE_PATH_OPERATION ] = { + [TMY_TYPE_READ_WRITE_ACL ] ="read/write" , + [TMY_TYPE_EXECUTE_ACL ] ="execute" , + [TMY_TYPE_READ_ACL ] ="read" , + [TMY_TYPE_WRITE_ACL ] ="write" , + [TMY_TYPE_CREATE_ACL ] ="create" , + [TMY_TYPE_UNLINK_ACL ] ="unlink" , + [TMY_TYPE_MKDIR_ACL ] ="mkdir" , + [TMY_TYPE_RMDIR_ACL ] ="rmdir" , + [TMY_TYPE_MKFIFO_ACL ] ="mkfifo" , + [TMY_TYPE_MKSOCK_ACL ] ="mksock" , + [TMY_TYPE_MKBLOCK_ACL ] ="mkblock" , + [TMY_TYPE_MKCHAR_ACL ] ="mkchar" , + [TMY_TYPE_TRUNCATE_ACL ] ="truncate" , + [TMY_TYPE_SYMLINK_ACL ] ="symlink" , + [TMY_TYPE_REWRITE_ACL ] ="rewrite" , +}; + +/* Keyword array for double path operations. */ +static const char *dp_keyword[MAX_DOUBLE_PATH_OPERATION ] = { + [TMY_TYPE_LINK_ACL ] ="link" , + [TMY_TYPE_RENAME_ACL ] ="rename" , +}; + +/** +* tmy_sp2keyword - Get the name of single path operation. +* +* @operation: Type of operation. +* +* Returns the name of single path operation. +*/ +const char *tmy_sp2keyword (const u8 operation) +{ +return (operation <MAX_SINGLE_PATH_OPERATION ) + ? sp_keyword[operation] :NULL ; +} + +/** +* tmy_dp2keyword - Get the name of double path operation. +* +* @operation: Type of operation. +* +* Returns the name of double path operation. +*/ +const char *tmy_dp2keyword (const u8 operation) +{ +return (operation <MAX_DOUBLE_PATH_OPERATION ) + ? dp_keyword[operation] :NULL ; +} + +/** +* strendswith - Check whether the token ends with the given token. +* +* @name: The token to check. +* @tail: The token to find. +* +* Returns true if @name ends with @tail, false otherwise. +*/ +static bool strendswith (const char *name,const char *tail) +{ +int len; +if (!name || !tail) +return false; + len =strlen (name) -strlen (tail); +return len >=0 && !strcmp (name + len, tail); +} + +/** +* tmy_get_path - Get realpath. +* +* @dentry: Pointer to "struct dentry". +* @mnt: Pointer to "struct vfsmount". +* +* Returns pointer to "struct path_info" on success, NULL otherwise. +*/ +static struct path_info *tmy_get_path (struct dentry *dentry, +struct vfsmount *mnt) +{ +int error; +struct path_info_with_data *buf =tmy_alloc (sizeof (*buf)); +if (!buf) +return NULL ; +/* Preserve one byte for appending "/". */ + error =tmy_realpath_from_dentry2 (dentry, mnt, buf->body, +sizeof (buf->body) -2 ); +if (!error) { + buf->head.name = buf->body; +tmy_fill_path_info (&buf->head); +return &buf->head; + } +tmy_free (buf); +return NULL ; +} + +static int update_double_path_acl (const u8 type,const char *filename1, +const char *filename2, +struct domain_info *const domain, +const bool is_delete); +static int update_single_path_acl (const u8 type,const char *filename, +struct domain_info *const domain, +const bool is_delete); + +/* The list for "struct globally_readable_file_entry". */ +static LIST1_HEAD (globally_readable_list); + +/** +* update_globally_readable_entry - Update "struct globally_readable_file_entry" list. +* +* @filename: Filename unconditionally permitted to open() for reading. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int update_globally_readable_entry (const char *filename, +const bool is_delete) +{ +struct globally_readable_file_entry *new_entry; +struct globally_readable_file_entry *ptr; +static DEFINE_MUTEX (lock); +const struct path_info *saved_filename; +int error = -ENOMEM ; +if (!tmy_is_correct_path (filename,1 ,0 ,-1 , __func__)) +return -EINVAL ; + saved_filename =tmy_save_name (filename); +if (!saved_filename) +return -ENOMEM ; +mutex_lock (&lock); +list1_for_each_entry (ptr, &globally_readable_list, list) { +if (ptr->filename != saved_filename) +continue ; + ptr->is_deleted = is_delete; + error =0 ; + goto out; + } +if (is_delete) { + error = -ENOENT ; + goto out; + } + new_entry =tmy_alloc_element (sizeof (*new_entry)); +if (!new_entry) + goto out; + new_entry->filename = saved_filename; +list1_add_tail_mb (&new_entry->list, &globally_readable_list); + error =0 ; +out: +mutex_unlock (&lock); +tmy_update_counter (TMY_UPDATES_COUNTER_EXCEPTION_POLICY ); +return error; +} + +/** +* is_globally_readable_file - Check if the file is unconditionnaly permitted to be open()ed for reading. +* +* @filename: The filename to check. +* +* Returns true if any domain can open @filename for reading, false otherwise. +*/ +static bool is_globally_readable_file (const struct path_info *filename) +{ +struct globally_readable_file_entry *ptr; +list1_for_each_entry (ptr, &globally_readable_list, list) { +if (!ptr->is_deleted && +tmy_path_matches_pattern (filename, ptr->filename)) +return true; + } +return false; +} + +/** +* tmy_write_globally_readable_policy - Write "struct globally_readable_file_entry" list. +* +* @data: String to parse. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +int tmy_write_globally_readable_policy (char *data,const bool is_delete) +{ +return update_globally_readable_entry (data, is_delete); +} + +/** +* tmy_read_globally_readable_policy - Read "struct globally_readable_file_entry" list. +* +* @head: Pointer to "struct tmy_io_buffer". +* +* Returns true on success, false otherwise. +*/ +bool tmy_read_globally_readable_policy (struct tmy_io_buffer *head) +{ +struct list1_head *pos; +list1_for_each_cookie (pos, head->read_var2, &globally_readable_list) { +struct globally_readable_file_entry *ptr; + ptr = list1_entry(pos, struct globally_readable_file_entry, + list); +if (ptr->is_deleted) +continue ; +if (!tmy_io_printf (head,KEYWORD_ALLOW_READ "%s\n" , + ptr->filename->name)) + goto out; + } +return true; +out: +return false; +} + +/* The list for "struct pattern_entry". */ +static LIST1_HEAD (pattern_list); + +/** +* update_file_pattern_entry - Update "struct pattern_entry" list. +* +* @pattern: Pathname pattern. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int update_file_pattern_entry (const char *pattern,const bool is_delete) +{ +struct pattern_entry *new_entry; +struct pattern_entry *ptr; +static DEFINE_MUTEX (lock); +const struct path_info *saved_pattern; +int error = -ENOMEM ; +if (!tmy_is_correct_path (pattern,0 ,1 ,0 , __func__)) +return -EINVAL ; + saved_pattern =tmy_save_name (pattern); +if (!saved_pattern) +return -ENOMEM ; +mutex_lock (&lock); +list1_for_each_entry (ptr, &pattern_list, list) { +if (saved_pattern != ptr->pattern) +continue ; + ptr->is_deleted = is_delete; + error =0 ; + goto out; + } +if (is_delete) { + error = -ENOENT ; + goto out; + } + new_entry =tmy_alloc_element (sizeof (*new_entry)); +if (!new_entry) + goto out; + new_entry->pattern = saved_pattern; +list1_add_tail_mb (&new_entry->list, &pattern_list); + error =0 ; +out: +mutex_unlock (&lock); +tmy_update_counter (TMY_UPDATES_COUNTER_EXCEPTION_POLICY ); +return error; +} + +/** +* get_file_pattern - Get patterned pathname. +* +* @filename: The filename to find patterned pathname. +* +* Returns pointer to pathname pattern if matched, @filename otherwise. +*/ +static const struct path_info * +get_file_pattern (const struct path_info *filename) +{ +struct pattern_entry *ptr; +const struct path_info *pattern =NULL ; +list1_for_each_entry (ptr, &pattern_list, list) { +if (ptr->is_deleted) +continue ; +if (!tmy_path_matches_pattern (filename, ptr->pattern)) +continue ; + pattern = ptr->pattern; +if (strendswith (pattern->name,"/\\*" )) { +/* Do nothing. Try to find the better match. */ + }else { +/* This would be the better match. Use this. */ +break ; + } + } +if (pattern) + filename = pattern; +return filename; +} + +/** +* tmy_write_pattern_policy - Write "struct pattern_entry" list. +* +* @data: String to parse. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +int tmy_write_pattern_policy (char *data,const bool is_delete) +{ +return update_file_pattern_entry (data, is_delete); +} + +/** +* tmy_read_file_pattern - Read "struct pattern_entry" list. +* +* @head: Pointer to "struct tmy_io_buffer". +* +* Returns true on success, false otherwise. +*/ +bool tmy_read_file_pattern (struct tmy_io_buffer *head) +{ +struct list1_head *pos; +list1_for_each_cookie (pos, head->read_var2, &pattern_list) { +struct pattern_entry *ptr; + ptr = list1_entry(pos, struct pattern_entry, list); +if (ptr->is_deleted) +continue ; +if (!tmy_io_printf (head,KEYWORD_FILE_PATTERN "%s\n" , + ptr->pattern->name)) + goto out; + } +return true; +out: +return false; +} + +/* The list for "struct no_rewrite_entry". */ +static LIST1_HEAD (no_rewrite_list); + +/** +* update_no_rewrite_entry - Update "struct no_rewrite_entry" list. +* +* @pattern: Pathname pattern that are not rewritable by default. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int update_no_rewrite_entry (const char *pattern,const bool is_delete) +{ +struct no_rewrite_entry *new_entry, *ptr; +static DEFINE_MUTEX (lock); +const struct path_info *saved_pattern; +int error = -ENOMEM ; +if (!tmy_is_correct_path (pattern,0 ,0 ,0 , __func__)) +return -EINVAL ; + saved_pattern =tmy_save_name (pattern); +if (!saved_pattern) +return -ENOMEM ; +mutex_lock (&lock); +list1_for_each_entry (ptr, &no_rewrite_list, list) { +if (ptr->pattern != saved_pattern) +continue ; + ptr->is_deleted = is_delete; + error =0 ; + goto out; + } +if (is_delete) { + error = -ENOENT ; + goto out; + } + new_entry =tmy_alloc_element (sizeof (*new_entry)); +if (!new_entry) + goto out; + new_entry->pattern = saved_pattern; +list1_add_tail_mb (&new_entry->list, &no_rewrite_list); + error =0 ; +out: +mutex_unlock (&lock); +tmy_update_counter (TMY_UPDATES_COUNTER_EXCEPTION_POLICY ); +return error; +} + +/** +* is_no_rewrite_file - Check if the given pathname is not permitted to be rewrited. +* +* @filename: Filename to check. +* +* Returns true if @filename is specified by "deny_rewrite" directive, +* false otherwise. +*/ +static bool is_no_rewrite_file (const struct path_info *filename) +{ +struct no_rewrite_entry *ptr; +list1_for_each_entry (ptr, &no_rewrite_list, list) { +if (ptr->is_deleted) +continue ; +if (!tmy_path_matches_pattern (filename, ptr->pattern)) +continue ; +return true; + } +return false; +} + +/** +* tmy_write_no_rewrite_policy - Write "struct no_rewrite_entry" list. +* +* @data: String to parse. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +int tmy_write_no_rewrite_policy (char *data,const bool is_delete) +{ +return update_no_rewrite_entry (data, is_delete); +} + +/** +* tmy_read_no_rewrite_policy - Read "struct no_rewrite_entry" list. +* +* @head: Pointer to "struct tmy_io_buffer". +* +* Returns true on success, false otherwise. +*/ +bool tmy_read_no_rewrite_policy (struct tmy_io_buffer *head) +{ +struct list1_head *pos; +list1_for_each_cookie (pos, head->read_var2, &no_rewrite_list) { +struct no_rewrite_entry *ptr; + ptr = list1_entry(pos, struct no_rewrite_entry, list); +if (ptr->is_deleted) +continue ; +if (!tmy_io_printf (head,KEYWORD_DENY_REWRITE "%s\n" , + ptr->pattern->name)) + goto out; + } +return true; +out: +return false; +} + +/** +* update_file_acl - Update file's read/write/execute ACL. +* +* @filename: Filename. +* @perm: Permission (between 1 to 7). +* @domain: Pointer to "struct domain_info". +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +* +* This is legacy support interface for older policy syntax. +* Current policy syntax uses "allow_read/write" instead of "6", +* "allow_read" instead of "4", "allow_write" instead of "2", +* "allow_execute" instead of "1". +*/ +static int update_file_acl (const char *filename,u8 perm, +struct domain_info *const domain, +const bool is_delete) +{ +if (perm >7 || !perm) { +printk (KERN_DEBUG "%s: Invalid permission '%d %s'\n" , + __func__, perm, filename); +return -EINVAL ; + } +if (filename[0 ] !='@' &&strendswith (filename,"/" )) +/* +* Only 'allow_mkdir' and 'allow_rmdir' are valid for +* directory permissions. +*/ +return 0 ; +if (perm &4 ) +update_single_path_acl (TMY_TYPE_READ_ACL , filename, domain, + is_delete); +if (perm &2 ) +update_single_path_acl (TMY_TYPE_WRITE_ACL , filename, domain, + is_delete); +if (perm &1 ) +update_single_path_acl (TMY_TYPE_EXECUTE_ACL , filename, domain, + is_delete); +return 0 ; +} + +/** +* check_single_path_acl2 - Check permission for single path operation. +* +* @domain: Pointer to "struct 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 check_single_path_acl2 (const struct domain_info *domain, +const struct path_info *filename, +const u16 perm,const bool may_use_pattern) +{ +struct acl_info *ptr; +list1_for_each_entry (ptr, &domain->acl_info_list, list) { +struct single_path_acl_record *acl; +if (tmy_acl_type2 (ptr) !=TYPE_SINGLE_PATH_ACL ) +continue ; + acl = container_of(ptr, struct single_path_acl_record, head); +if (!(acl->perm & perm)) +continue ; +if (may_use_pattern || !acl->filename->is_patterned) { +if (!tmy_path_matches_pattern (filename, + acl->filename)) +continue ; + }else { +continue ; + } +return 0 ; + } +return -EPERM ; +} + +/** +* check_file_acl - Check permission for opening files. +* +* @domain: Pointer to "struct 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 domain_info *domain, +const struct path_info *filename,const u8 operation) +{ +u16 perm =0 ; +if (!tmy_check_flags (domain,TMY_TOMOYO_MAC_FOR_FILE )) +return 0 ; +if (operation ==6 ) + perm =1 <<TMY_TYPE_READ_WRITE_ACL ; +else if (operation ==4 ) + perm =1 <<TMY_TYPE_READ_ACL ; +else if (operation ==2 ) + perm =1 <<TMY_TYPE_WRITE_ACL ; +else if (operation ==1 ) + perm =1 <<TMY_TYPE_EXECUTE_ACL ; +else +BUG (); +return check_single_path_acl2 (domain, filename, perm, operation !=1 ); +} + +/** +* check_file_perm2 - Check permission for opening files. +* +* @domain: Pointer to "struct 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 check_file_perm2 (struct domain_info *const domain, +const struct 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 &DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ ) ==0 && +is_globally_readable_file (filename)) + error =0 ; +if (perm ==6 ) + msg =tmy_sp2keyword (TMY_TYPE_READ_WRITE_ACL ); +else if (perm ==4 ) + msg =tmy_sp2keyword (TMY_TYPE_READ_ACL ); +else if (perm ==2 ) + msg =tmy_sp2keyword (TMY_TYPE_WRITE_ACL ); +else if (perm ==1 ) + msg =tmy_sp2keyword (TMY_TYPE_EXECUTE_ACL ); +else +BUG (); +if (!error) +return 0 ; +if (tmy_verbose_mode (domain)) +printk (KERN_WARNING "TOMOYO-%s: Access '%s(%s) %s' denied " +"for %s\n" ,tmy_get_msg (is_enforce), msg, operation, + filename->name,tmy_get_last_name (domain)); +if (is_enforce) +return error; +if (mode ==1 &&tmy_check_domain_quota (domain)) { +/* Don't use patterns for execute permission. */ +const struct path_info *patterned_file = (perm !=1 ) ? +get_file_pattern (filename) : filename; +update_file_acl (patterned_file->name, perm, + domain, false); + } +return 0 ; +} + +/** +* tmy_write_file_policy - Update file related list. +* +* @data: String to parse. +* @domain: Pointer to "struct domain_info". +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +int tmy_write_file_policy (char *data,struct domain_info *domain, +const bool is_delete) +{ +char *filename =strchr (data,' ' ); +char *filename2; +unsigned int perm; +u8 type; +if (!filename) +return -EINVAL ; + *filename++ ='\0' ; +if (sscanf (data,"%u" , &perm) ==1 ) +return update_file_acl (filename, (u8 ) perm, domain, is_delete); +if (strncmp (data,"allow_" ,6 )) + goto out; + data +=6 ; +for (type =0 ; type <MAX_SINGLE_PATH_OPERATION ; type++) { +if (strcmp (data, sp_keyword[type])) +continue ; +return update_single_path_acl (type, filename, + domain, is_delete); + } + filename2 =strchr (filename,' ' ); +if (!filename2) + goto out; + *filename2++ ='\0' ; +for (type =0 ; type <MAX_DOUBLE_PATH_OPERATION ; type++) { +if (strcmp (data, dp_keyword[type])) +continue ; +return update_double_path_acl (type, filename, filename2, domain, + is_delete); + } +out: +return -EINVAL ; +} + +/** +* update_single_path_acl - Update "struct single_path_acl_record" list. +* +* @type: Type of operation. +* @filename: Filename. +* @domain: Pointer to "struct domain_info". +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int update_single_path_acl (const u8 type,const char *filename, +struct domain_info *const domain, +const bool is_delete) +{ +static const u16 rw_mask = + (1 <<TMY_TYPE_READ_ACL ) | (1 <<TMY_TYPE_WRITE_ACL ); +const struct path_info *saved_filename; +struct acl_info *ptr; +struct single_path_acl_record *acl; +int error = -ENOMEM ; +const u16 perm =1 << type; +if (!domain) +return -EINVAL ; +if (!tmy_is_correct_path (filename,0 ,0 ,0 , __func__)) +return -EINVAL ; + saved_filename =tmy_save_name (filename); +if (!saved_filename) +return -ENOMEM ; +mutex_lock (&domain_acl_lock); +if (is_delete) + goto delete; +list1_for_each_entry (ptr, &domain->acl_info_list, list) { +if (tmy_acl_type1 (ptr) !=TYPE_SINGLE_PATH_ACL ) +continue ; + acl = container_of(ptr, struct single_path_acl_record, head); +if (acl->filename != saved_filename) +continue ; +/* Special case. Clear all bits if marked as deleted. */ +if (ptr->type &ACL_DELETED ) + acl->perm =0 ; + acl->perm |= perm; +if ((acl->perm & rw_mask) == rw_mask) + acl->perm |=1 <<TMY_TYPE_READ_WRITE_ACL ; +else if (acl->perm & (1 <<TMY_TYPE_READ_WRITE_ACL )) + acl->perm |= rw_mask; + error =tmy_add_domain_acl (NULL , ptr); + goto out; + } +/* Not found. Append it to the tail. */ + acl =tmy_alloc_acl_element (TYPE_SINGLE_PATH_ACL ); +if (!acl) + goto out; + acl->perm = perm; + acl->filename = saved_filename; + error =tmy_add_domain_acl (domain, &acl->head); + goto out; +delete: + error = -ENOENT ; +list1_for_each_entry (ptr, &domain->acl_info_list, list) { +if (tmy_acl_type2 (ptr) !=TYPE_SINGLE_PATH_ACL ) +continue ; + acl = container_of(ptr, struct single_path_acl_record, head); +if (acl->filename != saved_filename) +continue ; + acl->perm &= ~perm; +if ((acl->perm & rw_mask) != rw_mask) + acl->perm &= ~(1 <<TMY_TYPE_READ_WRITE_ACL ); +else if (!(acl->perm & (1 <<TMY_TYPE_READ_WRITE_ACL ))) + acl->perm &= ~rw_mask; + error =tmy_del_domain_acl (acl->perm ?NULL : ptr); +break ; + } +out: +mutex_unlock (&domain_acl_lock); +return error; +} + +/** +* update_double_path_acl - Update "struct double_path_acl_record" list. +* +* @type: Type of operation. +* @filename1: First filename. +* @filename2: Second filename. +* @domain: Pointer to "struct domain_info". +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int update_double_path_acl (const u8 type,const char *filename1, +const char *filename2, +struct domain_info *const domain, +const bool is_delete) +{ +const struct path_info *saved_filename1; +const struct path_info *saved_filename2; +struct acl_info *ptr; +struct double_path_acl_record *acl; +int error = -ENOMEM ; +const u8 perm =1 << type; +if (!domain) +return -EINVAL ; +if (!tmy_is_correct_path (filename1,0 ,0 ,0 , __func__) || + !tmy_is_correct_path (filename2,0 ,0 ,0 , __func__)) +return -EINVAL ; + saved_filename1 =tmy_save_name (filename1); + saved_filename2 =tmy_save_name (filename2); +if (!saved_filename1 || !saved_filename2) +return -ENOMEM ; +mutex_lock (&domain_acl_lock); +if (is_delete) + goto delete; +list1_for_each_entry (ptr, &domain->acl_info_list, list) { +if (tmy_acl_type1 (ptr) !=TYPE_DOUBLE_PATH_ACL ) +continue ; + acl = container_of(ptr, struct double_path_acl_record, head); +if (acl->filename1 != saved_filename1 || + acl->filename2 != saved_filename2) +continue ; +/* Special case. Clear all bits if marked as deleted. */ +if (ptr->type &ACL_DELETED ) + acl->perm =0 ; + acl->perm |= perm; + error =tmy_add_domain_acl (NULL , ptr); + goto out; + } +/* Not found. Append it to the tail. */ + acl =tmy_alloc_acl_element (TYPE_DOUBLE_PATH_ACL ); +if (!acl) + goto out; + acl->perm = perm; + acl->filename1 = saved_filename1; + acl->filename2 = saved_filename2; + error =tmy_add_domain_acl (domain, &acl->head); + goto out; +delete: + error = -ENOENT ; +list1_for_each_entry (ptr, &domain->acl_info_list, list) { +if (tmy_acl_type2 (ptr) !=TYPE_DOUBLE_PATH_ACL ) +continue ; + acl = container_of(ptr, struct double_path_acl_record, head); +if (acl->filename1 != saved_filename1 || + acl->filename2 != saved_filename2) +continue ; + acl->perm &= ~perm; + error =tmy_del_domain_acl (acl->perm ?NULL : ptr); +break ; + } +out: +mutex_unlock (&domain_acl_lock); +return error; +} + +/** +* check_single_path_acl - Check permission for single path operation. +* +* @domain: Pointer to "struct domain_info". +* @type: Type of operation. +* @filename: Filename to check. +* +* Returns 0 on success, negative value otherwise. +*/ +static int check_single_path_acl (struct domain_info *domain,const u8 type, +const struct path_info *filename) +{ +if (!tmy_check_flags (domain,TMY_TOMOYO_MAC_FOR_FILE )) +return 0 ; +return check_single_path_acl2 (domain, filename,1 << type,1 ); +} + +/** +* check_double_path_acl - Check permission for double path operation. +* +* @domain: Pointer to "struct 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 domain_info *domain, +const u8 type, +const struct path_info *filename1, +const struct path_info *filename2) +{ +struct acl_info *ptr; +const u8 perm =1 << type; +if (!tmy_check_flags (domain,TMY_TOMOYO_MAC_FOR_FILE )) +return 0 ; +list1_for_each_entry (ptr, &domain->acl_info_list, list) { +struct double_path_acl_record *acl; +if (tmy_acl_type2 (ptr) !=TYPE_DOUBLE_PATH_ACL ) +continue ; + acl = container_of(ptr, struct double_path_acl_record, head); +if (!(acl->perm & perm)) +continue ; +if (!tmy_path_matches_pattern (filename1, + acl->filename1)) +continue ; +if (!tmy_path_matches_pattern (filename2, + acl->filename2)) +continue ; +return 0 ; + } +return -EPERM ; +} + +/** +* check_single_path_permission2 - Check permission for single path operation. +* +* @domain: Pointer to "struct domain_info". +* @operation: Type of operation. +* @filename: Filename to check. +* @mode: Access control mode. +* +* Returns 0 on success, negative value otherwise. +*/ +static int check_single_path_permission2 (struct domain_info *const domain, +u8 operation, +const struct 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 =tmy_sp2keyword (operation); +if (!error) + goto ok; +if (tmy_verbose_mode (domain)) +printk (KERN_WARNING "TOMOYO-%s: Access '%s %s' denied for %s\n" , +tmy_get_msg (is_enforce), msg, filename->name, +tmy_get_last_name (domain)); +if (mode ==1 &&tmy_check_domain_quota (domain)) +update_single_path_acl (operation, +get_file_pattern (filename)->name, + domain, false); +if (!is_enforce) + error =0 ; +ok: +/* +* Since "allow_truncate" doesn't imply "allow_rewrite" permission, +* we need to check "allow_rewrite" permission if the filename is +* specified by "deny_rewrite" keyword. +*/ +if (!error && operation ==TMY_TYPE_TRUNCATE_ACL && +is_no_rewrite_file (filename)) { + operation =TMY_TYPE_REWRITE_ACL ; + goto next; + } +return error; +} + +/** +* tmy_check_file_perm - Check permission for sysctl()'s "read" and "write". +* +* @domain: Pointer to "struct domain_info". +* @filename: Filename to check. +* @perm: Mode ("read" or "write" or "read/write"). +* @operation: Always "sysctl". +* +* Returns 0 on success, negative value otherwise. +*/ +int tmy_check_file_perm (struct domain_info *domain,const char *filename, +const u8 perm,const char *operation) +{ +struct path_info name; +const u8 mode =tmy_check_flags (domain,TMY_TOMOYO_MAC_FOR_FILE ); +if (!mode) +return 0 ; + name.name = filename; +tmy_fill_path_info (&name); +return check_file_perm2 (domain, &name, perm, operation, mode); +} + +/** +* tmy_check_exec_perm - Check permission for "execute". +* +* @domain: Pointer to "struct domain_info". +* @filename: Check permission for "execute". +* @tmp: Buffer for temporal use. +* +* Returns 0 on success, negativevalue otherwise. +*/ +int tmy_check_exec_perm (struct domain_info *domain, +const struct path_info *filename, +struct tmy_page_buffer *tmp) +{ +const u8 mode =tmy_check_flags (domain,TMY_TOMOYO_MAC_FOR_FILE ); +if (!mode) +return 0 ; +return check_file_perm2 (domain, filename,1 ,"do_execve" , mode); +} + +/** +* tmy_check_open_permission - Check permission for "read" and "write". +* +* @domain: Pointer to "struct domain_info". +* @dentry: Pointer to "struct dentry". +* @mnt: Pointer to "struct vfsmount". +* @flag: Flags for open(). +* +* Returns 0 on success, negative value otherwise. +*/ +int tmy_check_open_permission (struct domain_info *domain, +struct dentry *dentry,struct vfsmount *mnt, +const int flag) +{ +const u8 acc_mode =ACC_MODE (flag); +int error = -ENOMEM ; +struct path_info *buf; +const u8 mode =tmy_check_flags (domain,TMY_TOMOYO_MAC_FOR_FILE ); +const bool is_enforce = (mode ==3 ); +if (!mode || !mnt) +return 0 ; +if (acc_mode ==0 ) +return 0 ; +if (dentry->d_inode &&S_ISDIR (dentry->d_inode->i_mode)) +/* +* I don't check directories here because mkdir() and rmdir() +* don't call me. +*/ +return 0 ; + buf =tmy_get_path (dentry, mnt); +if (!buf) + goto out; + error =0 ; +/* +* If the filename is specified by "deny_rewrite" keyword, +* we need to check "allow_rewrite" permission when the filename is not +* opened for append mode or the filename is truncated at open time. +*/ +if ((acc_mode &MAY_WRITE ) && + ((flag &O_TRUNC ) || !(flag &O_APPEND )) && + (is_no_rewrite_file (buf))) { + error =check_single_path_permission2 (domain, +TMY_TYPE_REWRITE_ACL , + buf, mode); + } +if (!error) + error =check_file_perm2 (domain, buf, acc_mode,"open" , mode); +if (!error && (flag &O_TRUNC )) + error =check_single_path_permission2 (domain, +TMY_TYPE_TRUNCATE_ACL , + buf, mode); +out: +tmy_free (buf); +if (!is_enforce) + error =0 ; +return error; +} + +/** +* tmy_check_1path_perm - Check permission for "create", "unlink", "mkdir", "rmdir", "mkfifo", "mksock", "mkblock", "mkchar", "truncate" and "symlink". +* +* @domain: Pointer to "struct domain_info". +* @operation: Type of operation. +* @dentry: Pointer to "struct dentry". +* @mnt: Pointer to "struct vfsmount". +* +* Returns 0 on success, negative value otherwise. +*/ +int tmy_check_1path_perm (struct domain_info *domain,const u8 operation, +struct dentry *dentry,struct vfsmount *mnt) +{ +int error = -ENOMEM ; +struct path_info *buf; +const u8 mode =tmy_check_flags (domain,TMY_TOMOYO_MAC_FOR_FILE ); +const bool is_enforce = (mode ==3 ); +if (!mode || !mnt) +return 0 ; + buf =tmy_get_path (dentry, mnt); +if (!buf) + goto out; +switch (operation) { +case TMY_TYPE_MKDIR_ACL : +case TMY_TYPE_RMDIR_ACL : +if (!buf->is_dir) { +/* tmy_get_path() preserves space for appending "/." */ +strcat ((char *) buf->name,"/" ); +tmy_fill_path_info (buf); + } + } + error =check_single_path_permission2 (domain, operation, buf, mode); +out: +tmy_free (buf); +if (!is_enforce) + error =0 ; +return error; +} + +/** +* tmy_check_rewrite_permission - Check permission for "rewrite". +* +* @domain: Pointer to "struct domain_info". +* @filp: Pointer to "struct file". +* +* Returns 0 on success, negative value otherwise. +*/ +int tmy_check_rewrite_permission (struct domain_info *domain,struct file *filp) +{ +int error = -ENOMEM ; +const u8 mode =tmy_check_flags (domain,TMY_TOMOYO_MAC_FOR_FILE ); +const bool is_enforce = (mode ==3 ); +struct path_info *buf; +if (!mode || !filp->f_path.mnt) +return 0 ; + buf =tmy_get_path (filp->f_path.dentry, filp->f_path.mnt); +if (!buf) + goto out; +if (!is_no_rewrite_file (buf)) { + error =0 ; + goto out; + } + error =check_single_path_permission2 (domain,TMY_TYPE_REWRITE_ACL , + buf, mode); +out: +tmy_free (buf); +if (!is_enforce) + error =0 ; +return error; +} + +/** +* tmy_check_2path_perm - Check permission for "rename" and "link". +* +* @domain: Pointer to "struct domain_info". +* @operation: Type of operation. +* @dentry1: Pointer to "struct dentry". +* @mnt1: Pointer to "struct vfsmount". +* @dentry2: Pointer to "struct dentry". +* @mnt2: Pointer to "struct vfsmount". +* +* Returns 0 on success, negative value otherwise. +*/ +int tmy_check_2path_perm (struct domain_info *const domain,const u8 operation, +struct dentry *dentry1,struct vfsmount *mnt1, +struct dentry *dentry2,struct vfsmount *mnt2) +{ +int error = -ENOMEM ; +struct path_info *buf1, *buf2; +const u8 mode =tmy_check_flags (domain,TMY_TOMOYO_MAC_FOR_FILE ); +const bool is_enforce = (mode ==3 ); +const char *msg; +if (!mode || !mnt1 || !mnt2) +return 0 ; + buf1 =tmy_get_path (dentry1, mnt1); + buf2 =tmy_get_path (dentry2, mnt2); +if (!buf1 || !buf2) + goto out; +if (operation ==TMY_TYPE_RENAME_ACL ) { +/* TYPE_LINK_ACL can't reach here for directory. */ +if (dentry1->d_inode &&S_ISDIR (dentry1->d_inode->i_mode)) { +/* tmy_get_path() preserves space for appending "/." */ +if (!buf1->is_dir) { +strcat ((char *) buf1->name,"/" ); +tmy_fill_path_info (buf1); + } +if (!buf2->is_dir) { +strcat ((char *) buf2->name,"/" ); +tmy_fill_path_info (buf2); + } + } + } + error =check_double_path_acl (domain, operation, buf1, buf2); + msg =tmy_dp2keyword (operation); +if (!error) + goto out; +if (tmy_verbose_mode (domain)) +printk (KERN_WARNING "TOMOYO-%s: Access '%s %s %s' " +"denied for %s\n" ,tmy_get_msg (is_enforce), + msg, buf1->name, buf2->name,tmy_get_last_name (domain)); +if (mode ==1 &&tmy_check_domain_quota (domain)) +update_double_path_acl (operation, +get_file_pattern (buf1)->name, +get_file_pattern (buf2)->name, + domain, false); +out: +tmy_free (buf1); +tmy_free (buf2); +if (!is_enforce) + error =0 ; +return error; +}
--
Re: [TOMOYO #10 (linux-next) 7/8] File operation restriction part.
Quoting Kentaro Takeda ([email protected]):
I am not happy with the locking.
In a previous patch you mark funtions with 'begin/end critical section'. Please instead put a comment on top listing precisely which locks the fn expects to be held.
As for protecting your own data, please
1. explain at the var declaration what lock protects it
2. define the lock next to the list
For instance, I assume the intent below is for pattern_list to be protected by the static 'lock' mutex defined in update_file_pattern_entry. But get_file_pattern() also walks the list without any locking.
It looks like you only ever append to the list, with a memory barrier, so *maybe* it's safe, but your rationale should be spelled out here.
Ideally you would use something like rcu that everyone can easily recognize. If you insist on introducing your own list walking and locking primitives, then please
1. introduce them separately
2. add them into list.h (not tomoyo/common.h!)
3. and get the right folks to ack it. I'd feel better if Paul McKenney (cc:d) looked and ok'd it, for instance. (Paul, the relevant helpers are defined in a separate patch though)
You could even protect everything with a big dumb mutex for now and optimize that in later patches. That way stuff like this doesn't muck up the review of the main TOMOYO patches.
Quoted 907 lines
security/tomoyo/file.c new file +905
-1+/* The list for "struct pattern_entry". */ +static LIST1_HEAD (pattern_list); + +/** +* update_file_pattern_entry - Update "struct pattern_entry" list. +* +* @pattern: Pathname pattern. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int update_file_pattern_entry (const char *pattern,const bool is_delete) +{ +struct pattern_entry *new_entry; +struct pattern_entry *ptr; +static DEFINE_MUTEX (lock); +const struct path_info *saved_pattern; +int error = -ENOMEM ; +if (!tmy_is_correct_path (pattern,0 ,1 ,0 , __func__)) +return -EINVAL ; + saved_pattern =tmy_save_name (pattern); +if (!saved_pattern) +return -ENOMEM ; +mutex_lock (&lock); +list1_for_each_entry (ptr, &pattern_list, list) { +if (saved_pattern != ptr->pattern) +continue ; + ptr->is_deleted = is_delete; + error =0 ; + goto out; + } +if (is_delete) { + error = -ENOENT ; + goto out; + } + new_entry =tmy_alloc_element (sizeof (*new_entry)); +if (!new_entry) + goto out; + new_entry->pattern = saved_pattern; +list1_add_tail_mb (&new_entry->list, &pattern_list); + error =0 ; +out: +mutex_unlock (&lock); +tmy_update_counter (TMY_UPDATES_COUNTER_EXCEPTION_POLICY ); +return error; +} + +/** +* get_file_pattern - Get patterned pathname. +* +* @filename: The filename to find patterned pathname. +* +* Returns pointer to pathname pattern if matched, @filename otherwise. +*/ +static const struct path_info * +get_file_pattern (const struct path_info *filename) +{ +struct pattern_entry *ptr; +const struct path_info *pattern =NULL ; +list1_for_each_entry (ptr, &pattern_list, list) { +if (ptr->is_deleted) +continue ; +if (!tmy_path_matches_pattern (filename, ptr->pattern)) +continue ; + pattern = ptr->pattern; +if (strendswith (pattern->name,"/\\*" )) { +/* Do nothing. Try to find the better match. */ + }else { +/* This would be the better match. Use this. */ +break ; + } + } +if (pattern) + filename = pattern; +return filename; +} + +/** +* tmy_write_pattern_policy - Write "struct pattern_entry" list. +* +* @data: String to parse. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +int tmy_write_pattern_policy (char *data,const bool is_delete) +{ +return update_file_pattern_entry (data, is_delete); +} + +/** +* tmy_read_file_pattern - Read "struct pattern_entry" list. +* +* @head: Pointer to "struct tmy_io_buffer". +* +* Returns true on success, false otherwise. +*/ +bool tmy_read_file_pattern (struct tmy_io_buffer *head) +{ +struct list1_head *pos; +list1_for_each_cookie (pos, head->read_var2, &pattern_list) { +struct pattern_entry *ptr; + ptr = list1_entry(pos, struct pattern_entry, list); +if (ptr->is_deleted) +continue ; +if (!tmy_io_printf (head,KEYWORD_FILE_PATTERN "%s\n" , + ptr->pattern->name)) + goto out; + } +return true; +out: +return false; +} + +/* The list for "struct no_rewrite_entry". */ +static LIST1_HEAD (no_rewrite_list); + +/** +* update_no_rewrite_entry - Update "struct no_rewrite_entry" list. +* +* @pattern: Pathname pattern that are not rewritable by default. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int update_no_rewrite_entry (const char *pattern,const bool is_delete) +{ +struct no_rewrite_entry *new_entry, *ptr; +static DEFINE_MUTEX (lock); +const struct path_info *saved_pattern; +int error = -ENOMEM ; +if (!tmy_is_correct_path (pattern,0 ,0 ,0 , __func__)) +return -EINVAL ; + saved_pattern =tmy_save_name (pattern); +if (!saved_pattern) +return -ENOMEM ; +mutex_lock (&lock); +list1_for_each_entry (ptr, &no_rewrite_list, list) { +if (ptr->pattern != saved_pattern) +continue ; + ptr->is_deleted = is_delete; + error =0 ; + goto out; + } +if (is_delete) { + error = -ENOENT ; + goto out; + } + new_entry =tmy_alloc_element (sizeof (*new_entry)); +if (!new_entry) + goto out; + new_entry->pattern = saved_pattern; +list1_add_tail_mb (&new_entry->list, &no_rewrite_list); + error =0 ; +out: +mutex_unlock (&lock); +tmy_update_counter (TMY_UPDATES_COUNTER_EXCEPTION_POLICY ); +return error; +} + +/** +* is_no_rewrite_file - Check if the given pathname is not permitted to be rewrited. +* +* @filename: Filename to check. +* +* Returns true if @filename is specified by "deny_rewrite" directive, +* false otherwise. +*/ +static bool is_no_rewrite_file (const struct path_info *filename) +{ +struct no_rewrite_entry *ptr; +list1_for_each_entry (ptr, &no_rewrite_list, list) { +if (ptr->is_deleted) +continue ; +if (!tmy_path_matches_pattern (filename, ptr->pattern)) +continue ; +return true; + } +return false; +} + +/** +* tmy_write_no_rewrite_policy - Write "struct no_rewrite_entry" list. +* +* @data: String to parse. +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +int tmy_write_no_rewrite_policy (char *data,const bool is_delete) +{ +return update_no_rewrite_entry (data, is_delete); +} + +/** +* tmy_read_no_rewrite_policy - Read "struct no_rewrite_entry" list. +* +* @head: Pointer to "struct tmy_io_buffer". +* +* Returns true on success, false otherwise. +*/ +bool tmy_read_no_rewrite_policy (struct tmy_io_buffer *head) +{ +struct list1_head *pos; +list1_for_each_cookie (pos, head->read_var2, &no_rewrite_list) { +struct no_rewrite_entry *ptr; + ptr = list1_entry(pos, struct no_rewrite_entry, list); +if (ptr->is_deleted) +continue ; +if (!tmy_io_printf (head,KEYWORD_DENY_REWRITE "%s\n" , + ptr->pattern->name)) + goto out; + } +return true; +out: +return false; +} + +/** +* update_file_acl - Update file's read/write/execute ACL. +* +* @filename: Filename. +* @perm: Permission (between 1 to 7). +* @domain: Pointer to "struct domain_info". +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +* +* This is legacy support interface for older policy syntax. +* Current policy syntax uses "allow_read/write" instead of "6", +* "allow_read" instead of "4", "allow_write" instead of "2", +* "allow_execute" instead of "1". +*/ +static int update_file_acl (const char *filename,u8 perm, +struct domain_info *const domain, +const bool is_delete) +{ +if (perm >7 || !perm) { +printk (KERN_DEBUG "%s: Invalid permission '%d %s'\n" , + __func__, perm, filename); +return -EINVAL ; + } +if (filename[0 ] !='@' &&strendswith (filename,"/" )) +/* +* Only 'allow_mkdir' and 'allow_rmdir' are valid for +* directory permissions. +*/ +return 0 ; +if (perm &4 ) +update_single_path_acl (TMY_TYPE_READ_ACL , filename, domain, + is_delete); +if (perm &2 ) +update_single_path_acl (TMY_TYPE_WRITE_ACL , filename, domain, + is_delete); +if (perm &1 ) +update_single_path_acl (TMY_TYPE_EXECUTE_ACL , filename, domain, + is_delete); +return 0 ; +} + +/** +* check_single_path_acl2 - Check permission for single path operation. +* +* @domain: Pointer to "struct 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 check_single_path_acl2 (const struct domain_info *domain, +const struct path_info *filename, +const u16 perm,const bool may_use_pattern) +{ +struct acl_info *ptr; +list1_for_each_entry (ptr, &domain->acl_info_list, list) { +struct single_path_acl_record *acl; +if (tmy_acl_type2 (ptr) !=TYPE_SINGLE_PATH_ACL ) +continue ; + acl = container_of(ptr, struct single_path_acl_record, head); +if (!(acl->perm & perm)) +continue ; +if (may_use_pattern || !acl->filename->is_patterned) { +if (!tmy_path_matches_pattern (filename, + acl->filename)) +continue ; + }else { +continue ; + } +return 0 ; + } +return -EPERM ; +} + +/** +* check_file_acl - Check permission for opening files. +* +* @domain: Pointer to "struct 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 domain_info *domain, +const struct path_info *filename,const u8 operation) +{ +u16 perm =0 ; +if (!tmy_check_flags (domain,TMY_TOMOYO_MAC_FOR_FILE )) +return 0 ; +if (operation ==6 ) + perm =1 <<TMY_TYPE_READ_WRITE_ACL ; +else if (operation ==4 ) + perm =1 <<TMY_TYPE_READ_ACL ; +else if (operation ==2 ) + perm =1 <<TMY_TYPE_WRITE_ACL ; +else if (operation ==1 ) + perm =1 <<TMY_TYPE_EXECUTE_ACL ; +else +BUG (); +return check_single_path_acl2 (domain, filename, perm, operation !=1 ); +} + +/** +* check_file_perm2 - Check permission for opening files. +* +* @domain: Pointer to "struct 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 check_file_perm2 (struct domain_info *const domain, +const struct 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 &DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ ) ==0 && +is_globally_readable_file (filename)) + error =0 ; +if (perm ==6 ) + msg =tmy_sp2keyword (TMY_TYPE_READ_WRITE_ACL ); +else if (perm ==4 ) + msg =tmy_sp2keyword (TMY_TYPE_READ_ACL ); +else if (perm ==2 ) + msg =tmy_sp2keyword (TMY_TYPE_WRITE_ACL ); +else if (perm ==1 ) + msg =tmy_sp2keyword (TMY_TYPE_EXECUTE_ACL ); +else +BUG (); +if (!error) +return 0 ; +if (tmy_verbose_mode (domain)) +printk (KERN_WARNING "TOMOYO-%s: Access '%s(%s) %s' denied " +"for %s\n" ,tmy_get_msg (is_enforce), msg, operation, + filename->name,tmy_get_last_name (domain)); +if (is_enforce) +return error; +if (mode ==1 &&tmy_check_domain_quota (domain)) { +/* Don't use patterns for execute permission. */ +const struct path_info *patterned_file = (perm !=1 ) ? +get_file_pattern (filename) : filename; +update_file_acl (patterned_file->name, perm, + domain, false); + } +return 0 ; +} + +/** +* tmy_write_file_policy - Update file related list. +* +* @data: String to parse. +* @domain: Pointer to "struct domain_info". +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +int tmy_write_file_policy (char *data,struct domain_info *domain, +const bool is_delete) +{ +char *filename =strchr (data,' ' ); +char *filename2; +unsigned int perm; +u8 type; +if (!filename) +return -EINVAL ; + *filename++ ='\0' ; +if (sscanf (data,"%u" , &perm) ==1 ) +return update_file_acl (filename, (u8 ) perm, domain, is_delete); +if (strncmp (data,"allow_" ,6 )) + goto out; + data +=6 ; +for (type =0 ; type <MAX_SINGLE_PATH_OPERATION ; type++) { +if (strcmp (data, sp_keyword[type])) +continue ; +return update_single_path_acl (type, filename, + domain, is_delete); + } + filename2 =strchr (filename,' ' ); +if (!filename2) + goto out; + *filename2++ ='\0' ; +for (type =0 ; type <MAX_DOUBLE_PATH_OPERATION ; type++) { +if (strcmp (data, dp_keyword[type])) +continue ; +return update_double_path_acl (type, filename, filename2, domain, + is_delete); + } +out: +return -EINVAL ; +} + +/** +* update_single_path_acl - Update "struct single_path_acl_record" list. +* +* @type: Type of operation. +* @filename: Filename. +* @domain: Pointer to "struct domain_info". +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int update_single_path_acl (const u8 type,const char *filename, +struct domain_info *const domain, +const bool is_delete) +{ +static const u16 rw_mask = + (1 <<TMY_TYPE_READ_ACL ) | (1 <<TMY_TYPE_WRITE_ACL ); +const struct path_info *saved_filename; +struct acl_info *ptr; +struct single_path_acl_record *acl; +int error = -ENOMEM ; +const u16 perm =1 << type; +if (!domain) +return -EINVAL ; +if (!tmy_is_correct_path (filename,0 ,0 ,0 , __func__)) +return -EINVAL ; + saved_filename =tmy_save_name (filename); +if (!saved_filename) +return -ENOMEM ; +mutex_lock (&domain_acl_lock); +if (is_delete) + goto delete; +list1_for_each_entry (ptr, &domain->acl_info_list, list) { +if (tmy_acl_type1 (ptr) !=TYPE_SINGLE_PATH_ACL ) +continue ; + acl = container_of(ptr, struct single_path_acl_record, head); +if (acl->filename != saved_filename) +continue ; +/* Special case. Clear all bits if marked as deleted. */ +if (ptr->type &ACL_DELETED ) + acl->perm =0 ; + acl->perm |= perm; +if ((acl->perm & rw_mask) == rw_mask) + acl->perm |=1 <<TMY_TYPE_READ_WRITE_ACL ; +else if (acl->perm & (1 <<TMY_TYPE_READ_WRITE_ACL )) + acl->perm |= rw_mask; + error =tmy_add_domain_acl (NULL , ptr); + goto out; + } +/* Not found. Append it to the tail. */ + acl =tmy_alloc_acl_element (TYPE_SINGLE_PATH_ACL ); +if (!acl) + goto out; + acl->perm = perm; + acl->filename = saved_filename; + error =tmy_add_domain_acl (domain, &acl->head); + goto out; +delete: + error = -ENOENT ; +list1_for_each_entry (ptr, &domain->acl_info_list, list) { +if (tmy_acl_type2 (ptr) !=TYPE_SINGLE_PATH_ACL ) +continue ; + acl = container_of(ptr, struct single_path_acl_record, head); +if (acl->filename != saved_filename) +continue ; + acl->perm &= ~perm; +if ((acl->perm & rw_mask) != rw_mask) + acl->perm &= ~(1 <<TMY_TYPE_READ_WRITE_ACL ); +else if (!(acl->perm & (1 <<TMY_TYPE_READ_WRITE_ACL ))) + acl->perm &= ~rw_mask; + error =tmy_del_domain_acl (acl->perm ?NULL : ptr); +break ; + } +out: +mutex_unlock (&domain_acl_lock); +return error; +} + +/** +* update_double_path_acl - Update "struct double_path_acl_record" list. +* +* @type: Type of operation. +* @filename1: First filename. +* @filename2: Second filename. +* @domain: Pointer to "struct domain_info". +* @is_delete: True if it is a delete request. +* +* Returns 0 on success, negative value otherwise. +*/ +static int update_double_path_acl (const u8 type,const char *filename1, +const char *filename2, +struct domain_info *const domain, +const bool is_delete) +{ +const struct path_info *saved_filename1; +const struct path_info *saved_filename2; +struct acl_info *ptr; +struct double_path_acl_record *acl; +int error = -ENOMEM ; +const u8 perm =1 << type; +if (!domain) +return -EINVAL ; +if (!tmy_is_correct_path (filename1,0 ,0 ,0 , __func__) || + !tmy_is_correct_path (filename2,0 ,0 ,0 , __func__)) +return -EINVAL ; + saved_filename1 =tmy_save_name (filename1); + saved_filename2 =tmy_save_name (filename2); +if (!saved_filename1 || !saved_filename2) +return -ENOMEM ; +mutex_lock (&domain_acl_lock); +if (is_delete) + goto delete; +list1_for_each_entry (ptr, &domain->acl_info_list, list) { +if (tmy_acl_type1 (ptr) !=TYPE_DOUBLE_PATH_ACL ) +continue ; + acl = container_of(ptr, struct double_path_acl_record, head); +if (acl->filename1 != saved_filename1 || + acl->filename2 != saved_filename2) +continue ; +/* Special case. Clear all bits if marked as deleted. */ +if (ptr->type &ACL_DELETED ) + acl->perm =0 ; + acl->perm |= perm; + error =tmy_add_domain_acl (NULL , ptr); + goto out; + } +/* Not found. Append it to the tail. */ + acl =tmy_alloc_acl_element (TYPE_DOUBLE_PATH_ACL ); +if (!acl) + goto out; + acl->perm = perm; + acl->filename1 = saved_filename1; + acl->filename2 = saved_filename2; + error =tmy_add_domain_acl (domain, &acl->head); + goto out; +delete: + error = -ENOENT ; +list1_for_each_entry (ptr, &domain->acl_info_list, list) { +if (tmy_acl_type2 (ptr) !=TYPE_DOUBLE_PATH_ACL ) +continue ; + acl = container_of(ptr, struct double_path_acl_record, head); +if (acl->filename1 != saved_filename1 || + acl->filename2 != saved_filename2) +continue ; + acl->perm &= ~perm; + error =tmy_del_domain_acl (acl->perm ?NULL : ptr); +break ; + } +out: +mutex_unlock (&domain_acl_lock); +return error; +} + +/** +* check_single_path_acl - Check permission for single path operation. +* +* @domain: Pointer to "struct domain_info". +* @type: Type of operation. +* @filename: Filename to check. +* +* Returns 0 on success, negative value otherwise. +*/ +static int check_single_path_acl (struct domain_info *domain,const u8 type, +const struct path_info *filename) +{ +if (!tmy_check_flags (domain,TMY_TOMOYO_MAC_FOR_FILE )) +return 0 ; +return check_single_path_acl2 (domain, filename,1 << type,1 ); +} + +/** +* check_double_path_acl - Check permission for double path operation. +* +* @domain: Pointer to "struct 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 domain_info *domain, +const u8 type, +const struct path_info *filename1, +const struct path_info *filename2) +{ +struct acl_info *ptr; +const u8 perm =1 << type; +if (!tmy_check_flags (domain,TMY_TOMOYO_MAC_FOR_FILE )) +return 0 ; +list1_for_each_entry (ptr, &domain->acl_info_list, list) { +struct double_path_acl_record *acl; +if (tmy_acl_type2 (ptr) !=TYPE_DOUBLE_PATH_ACL ) +continue ; + acl = container_of(ptr, struct double_path_acl_record, head); +if (!(acl->perm & perm)) +continue ; +if (!tmy_path_matches_pattern (filename1, + acl->filename1)) +continue ; +if (!tmy_path_matches_pattern (filename2, + acl->filename2)) +continue ; +return 0 ; + } +return -EPERM ; +} + +/** +* check_single_path_permission2 - Check permission for single path operation. +* +* @domain: Pointer to "struct domain_info". +* @operation: Type of operation. +* @filename: Filename to check. +* @mode: Access control mode. +* +* Returns 0 on success, negative value otherwise. +*/ +static int check_single_path_permission2 (struct domain_info *const domain, +u8 operation, +const struct 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 =tmy_sp2keyword (operation); +if (!error) + goto ok; +if (tmy_verbose_mode (domain)) +printk (KERN_WARNING "TOMOYO-%s: Access '%s %s' denied for %s\n" , +tmy_get_msg (is_enforce), msg, filename->name, +tmy_get_last_name (domain)); +if (mode ==1 &&tmy_check_domain_quota (domain)) +update_single_path_acl (operation, +get_file_pattern (filename)->name, + domain, false); +if (!is_enforce) + error =0 ; +ok: +/* +* Since "allow_truncate" doesn't imply "allow_rewrite" permission, +* we need to check "allow_rewrite" permission if the filename is +* specified by "deny_rewrite" keyword. +*/ +if (!error && operation ==TMY_TYPE_TRUNCATE_ACL && +is_no_rewrite_file (filename)) { + operation =TMY_TYPE_REWRITE_ACL ; + goto next; + } +return error; +} + +/** +* tmy_check_file_perm - Check permission for sysctl()'s "read" and "write". +* +* @domain: Pointer to "struct domain_info". +* @filename: Filename to check. +* @perm: Mode ("read" or "write" or "read/write"). +* @operation: Always "sysctl". +* +* Returns 0 on success, negative value otherwise. +*/ +int tmy_check_file_perm (struct domain_info *domain,const char *filename, +const u8 perm,const char *operation) +{ +struct path_info name; +const u8 mode =tmy_check_flags (domain,TMY_TOMOYO_MAC_FOR_FILE ); +if (!mode) +return 0 ; + name.name = filename; +tmy_fill_path_info (&name); +return check_file_perm2 (domain, &name, perm, operation, mode); +} + +/** +* tmy_check_exec_perm - Check permission for "execute". +* +* @domain: Pointer to "struct domain_info". +* @filename: Check permission for "execute". +* @tmp: Buffer for temporal use. +* +* Returns 0 on success, negativevalue otherwise. +*/ +int tmy_check_exec_perm (struct domain_info *domain, +const struct path_info *filename, +struct tmy_page_buffer *tmp) +{ +const u8 mode =tmy_check_flags (domain,TMY_TOMOYO_MAC_FOR_FILE ); +if (!mode) +return 0 ; +return check_file_perm2 (domain, filename,1 ,"do_execve" , mode); +} + +/** +* tmy_check_open_permission - Check permission for "read" and "write". +* +* @domain: Pointer to "struct domain_info". +* @dentry: Pointer to "struct dentry". +* @mnt: Pointer to "struct vfsmount". +* @flag: Flags for open(). +* +* Returns 0 on success, negative value otherwise. +*/ +int tmy_check_open_permission (struct domain_info *domain, +struct dentry *dentry,struct vfsmount *mnt, +const int flag) +{ +const u8 acc_mode =ACC_MODE (flag); +int error = -ENOMEM ; +struct path_info *buf; +const u8 mode =tmy_check_flags (domain,TMY_TOMOYO_MAC_FOR_FILE ); +const bool is_enforce = (mode ==3 ); +if (!mode || !mnt) +return 0 ; +if (acc_mode ==0 ) +return 0 ; +if (dentry->d_inode &&S_ISDIR (dentry->d_inode->i_mode)) +/* +* I don't check directories here because mkdir() and rmdir() +* don't call me. +*/ +return 0 ; + buf =tmy_get_path (dentry, mnt); +if (!buf) + goto out; + error =0 ; +/* +* If the filename is specified by "deny_rewrite" keyword, +* we need to check "allow_rewrite" permission when the filename is not +* opened for append mode or the filename is truncated at open time. +*/ +if ((acc_mode &MAY_WRITE ) && + ((flag &O_TRUNC ) || !(flag &O_APPEND )) && + (is_no_rewrite_file (buf))) { + error =check_single_path_permission2 (domain, +TMY_TYPE_REWRITE_ACL , + buf, mode); + } +if (!error) + error =check_file_perm2 (domain, buf, acc_mode,"open" , mode); +if (!error && (flag &O_TRUNC )) + error =check_single_path_permission2 (domain, +TMY_TYPE_TRUNCATE_ACL , + buf, mode); +out: +tmy_free (buf); +if (!is_enforce) + error =0 ; +return error; +} + +/** +* tmy_check_1path_perm - Check permission for "create", "unlink", "mkdir", "rmdir", "mkfifo", "mksock", "mkblock", "mkchar", "truncate" and "symlink". +* +* @domain: Pointer to "struct domain_info". +* @operation: Type of operation. +* @dentry: Pointer to "struct dentry". +* @mnt: Pointer to "struct vfsmount". +* +* Returns 0 on success, negative value otherwise. +*/ +int tmy_check_1path_perm (struct domain_info *domain,const u8 operation, +struct dentry *dentry,struct vfsmount *mnt) +{ +int error = -ENOMEM ; +struct path_info *buf; +const u8 mode =tmy_check_flags (domain,TMY_TOMOYO_MAC_FOR_FILE ); +const bool is_enforce = (mode ==3 ); +if (!mode || !mnt) +return 0 ; + buf =tmy_get_path (dentry, mnt); +if (!buf) + goto out; +switch (operation) { +case TMY_TYPE_MKDIR_ACL : +case TMY_TYPE_RMDIR_ACL : +if (!buf->is_dir) { +/* tmy_get_path() preserves space for appending "/." */ +strcat ((char *) buf->name,"/" ); +tmy_fill_path_info (buf); + } + } + error =check_single_path_permission2 (domain, operation, buf, mode); +out: +tmy_free (buf); +if (!is_enforce) + error =0 ; +return error; +} + +/** +* tmy_check_rewrite_permission - Check permission for "rewrite". +* +* @domain: Pointer to "struct domain_info". +* @filp: Pointer to "struct file". +* +* Returns 0 on success, negative value otherwise. +*/ +int tmy_check_rewrite_permission (struct domain_info *domain,struct file *filp) +{ +int error = -ENOMEM ; +const u8 mode =tmy_check_flags (domain,TMY_TOMOYO_MAC_FOR_FILE ); +const bool is_enforce = (mode ==3 ); +struct path_info *buf; +if (!mode || !filp->f_path.mnt) +return 0 ; + buf =tmy_get_path (filp->f_path.dentry, filp->f_path.mnt); +if (!buf) + goto out; +if (!is_no_rewrite_file (buf)) { + error =0 ; + goto out; + } + error =check_single_path_permission2 (domain,TMY_TYPE_REWRITE_ACL , + buf, mode); +out: +tmy_free (buf); +if (!is_enforce) + error =0 ; +return error; +} + +/** +* tmy_check_2path_perm - Check permission for "rename" and "link". +* +* @domain: Pointer to "struct domain_info". +* @operation: Type of operation. +* @dentry1: Pointer to "struct dentry". +* @mnt1: Pointer to "struct vfsmount". +* @dentry2: Pointer to "struct dentry". +* @mnt2: Pointer to "struct vfsmount". +* +* Returns 0 on success, negative value otherwise. +*/ +int tmy_check_2path_perm (struct domain_info *const domain,const u8 operation, +struct dentry *dentry1,struct vfsmount *mnt1, +struct dentry *dentry2,struct vfsmount *mnt2) +{ +int error = -ENOMEM ; +struct path_info *buf1, *buf2; +const u8 mode =tmy_check_flags (domain,TMY_TOMOYO_MAC_FOR_FILE ); +const bool is_enforce = (mode ==3 ); +const char *msg; +if (!mode || !mnt1 || !mnt2) +return 0 ; + buf1 =tmy_get_path (dentry1, mnt1); + buf2 =tmy_get_path (dentry2, mnt2); +if (!buf1 || !buf2) + goto out; +if (operation ==TMY_TYPE_RENAME_ACL ) { +/* TYPE_LINK_ACL can't reach here for directory. */ +if (dentry1->d_inode &&S_ISDIR (dentry1->d_inode->i_mode)) { +/* tmy_get_path() preserves space for appending "/." */ +if (!buf1->is_dir) { +strcat ((char *) buf1->name,"/" ); +tmy_fill_path_info (buf1); + } +if (!buf2->is_dir) { +strcat ((char *) buf2->name,"/" ); +tmy_fill_path_info (buf2); + } + } + } + error =check_double_path_acl (domain, operation, buf1, buf2); + msg =tmy_dp2keyword (operation); +if (!error) + goto out; +if (tmy_verbose_mode (domain)) +printk (KERN_WARNING "TOMOYO-%s: Access '%s %s %s' " +"denied for %s\n" ,tmy_get_msg (is_enforce), + msg, buf1->name, buf2->name,tmy_get_last_name (domain)); +if (mode ==1 &&tmy_check_domain_quota (domain)) +update_double_path_acl (operation, +get_file_pattern (buf1)->name, +get_file_pattern (buf2)->name, + domain, false); +out: +tmy_free (buf1); +tmy_free (buf2); +if (!is_enforce) + error =0 ; +return error; +} --
Tetsuo Handa
maintainerRe: [TOMOYO #10 (linux-next) 7/8] File operation restriction part.
Hello.
Quoted 7 lines · Serge E. Hallyn
In a previous patch you mark funtions with 'begin/end critical section'. Please instead put a comment on top listing precisely which locks the fn expects to be held.
As for protecting your own data, please
1. explain at the var declaration what lock protects it
2. define the lock next to the list
OK. I added comments and simplified dependencies.
http://svn.sourceforge.jp/cgi-bin/viewcvs.cgi/trunk/2.2.x/tomoyo-lsm/patches/?root=tomoyo
Anything else we can do before reposting as #11?
Summary of locking is listed below.
(1) tmy_real_domain() requires the caller to hold tasklist_lock spinlock.
(2) list1_add_tail_mb() requires the caller to hold a lock for protecting the list.
(3) All other functions can manage necessary locking using local locks declared inside each functions, for read operation requires no locks and write operation is aggregated into single function.
Quoted 7 lines
For instance, I assume the intent below is for
pattern_listto be protected by the static 'lock' mutex defined inupdate_file_pattern_entry. Butget_file_pattern()also walks the list without any locking.It looks like you only ever append to the list, with a memory barrier, so *maybe* it's safe, but your rationale should be spelled out here.
It *is* safe. Below is the introduce-write-once-read-many-linked-list.patch taken from above URL. Paul, please review the below patch.
Regards.
--------------------
Subject: Singly linked list implementation.
--- include/linux/list.h | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+)
include/linux/list.h +95 -0
@@ -692,4 +692,99 @@ static inline void hlist_move_list(struc ({ tpos =hlist_entry (pos,typeof (*tpos), member);1 ;}); \ pos = n) +/* +* 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. +* This list penalize "mb()" for write operation but penalize nothing for read +* operation. +*/ + +/* To keep the reader read lock free, this list doesn't have "prev" field. */ +struct list1_head { +struct list1_head *next; +}; + +#define LIST1_HEAD_INIT (name) { &(name) } +#define LIST1_HEAD (name) struct list1_head name = LIST1_HEAD_INIT(name) + +static inline void INIT_LIST1_HEAD (struct list1_head *list) +{ + list->next = list; +} + +/** +* list1_entry - get the struct for this entry +* @ptr: the &struct list1_head pointer. +* @type: the type of the struct this is embedded in. +* @member: the name of the list1_struct within the struct. +*/ +#define list1_entry (ptr, type, member) container_of(ptr, type, member) + +/** +* list1_for_each - iterate over a list +* @pos: the &struct list1_head to use as a loop cursor. +* @head: the head for your list. +*/ +#define list1_for_each (pos, head) \ + for (pos = (head)->next; prefetch(pos->next), pos != (head); \ + pos = pos->next) + +/** +* list1_for_each_entry - iterate over list of given type +* @pos: the type * to use as a loop cursor. +* @head: the head for your list. +* @member: the name of the list1_struct within the struct. +*/ +#define list1_for_each_entry (pos, head, member) \ + for (pos = list1_entry((head)->next, typeof(*pos), member); \ + prefetch(pos->member.next), &pos->member != (head); \ + pos = list1_entry(pos->member.next, typeof(*pos), member)) + +/** +* list1_for_each_cookie - iterate over a list with cookie. +* @pos: the &struct list1_head to use as a loop cursor. +* @cookie: the &struct list1_head to use as a cookie. +* @head: the head for your list. +* +* Same with list_for_each except that this primitive uses cookie +* so that we can continue iteration. +* 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 = (cookie)->next; \ + prefetch(pos->next), pos != (head) || ((cookie) = NULL); \ + (cookie) = pos, pos = pos->next) + +/** +* list_add_tail_mb - add a new entry with memory barrier. +* @new: new entry to be added. +* @head: list head to add it before. +* +* Same with list_add_tail_rcu() except that this primitive uses mb() +* so that we can traverse forwards using list1_for_each() and +* list1_for_each_cookie() without any locks. +* +* Caller must hold a lock for protecting @head. +*/ +static inline void list1_add_tail_mb (struct list1_head *new, +struct list1_head *head) +{ +struct list1_head *pos = head; + + new->next = head; +mb ();/* Avoid out-of-order execution. */ +while (pos->next != head) + pos = pos->next; + pos->next = new; +} +#endif
Re: [TOMOYO #10 (linux-next) 7/8] File operation restriction part.
Quoted 29 lines · Tetsuo Handa
Hello.
Quoted 7 lines · Serge E. Hallyn
In a previous patch you mark funtions with 'begin/end critical section'. Please instead put a comment on top listing precisely which locks the fn expects to be held.
As for protecting your own data, please
1. explain at the var declaration what lock protects it
2. define the lock next to the listOK. I added comments and simplified dependencies.
http://svn.sourceforge.jp/cgi-bin/viewcvs.cgi/trunk/2.2.x/tomoyo-lsm/patches/?root=tomoyo
Anything else we can do before reposting as #11?Summary of locking is listed below.
(1)
tmy_real_domain()requires the caller to holdtasklist_lockspinlock.
(2)list1_add_tail_mb()requires the caller to hold a lock for protecting the list.
(3) All other functions can manage necessary locking using local locks declared inside each functions, for read operation requires no locks and write operation is aggregated into single function.Quoted 5 lines
For instance, I assume the intent below is for
pattern_listto be protected by the static 'lock' mutex defined inupdate_file_pattern_entry. Butget_file_pattern()also walks the list without any locking.
Quoted 5 lines
Quoted 2 lines
It looks like you only ever append to the list, with a memory barrier, so *maybe* it's safe, but your rationale should be spelled out here.
It *is* safe. Below is the introduce-write-once-read-many-linked-list.patch taken from above URL. Paul, please review the below patch.
The memory barrier on the element-addition side is OK, though could use rcu_assign_pointer(). In any case, it should be changed to smp_ form, as it is not needed in UP builds.
A few comments below -- some rcu_dereference()s are needed.
The general idea looks sound, at least as long as the lists remain short. Otherwise, the list scan in list1_add_tail_mb() will take too long.
Thanx, Paul
Quoted 31 lines
Regards.
--------------------
Subject: Singly linked list implementation.Signed-off-by Tetsuo Handa <[email protected]>--- include/linux/list.h | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+)include/linux/list.h +16
-0@@ -692,4 +692,99 @@ static inline void hlist_move_list(struc ({ tpos =hlist_entry (pos,typeof (*tpos), member);1 ;}); \ pos = n) +/* +* 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. +* This list penalize "mb()" for write operation but penalize nothing for read +* operation. +*/ + +/* To keep the reader read lock free, this list doesn't have "prev" field. */ +struct list1_head {
include/linux/list.h static inline void hlist_move_list(struc · line 720
Quoted 10 lines
include/linux/list.h +10
-0+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; +}
Hmmm... This list is circular.
include/linux/list.h static inline void hlist_move_list(struc · line 728
Quoted 7 lines
include/linux/list.h +7
-0+/** +* list1_entry - get the struct for this entry +* @ptr: the &struct list1_head pointer. +* @type: the type of the struct this is embedded in. +* @member: the name of the list1_struct within the struct. +*/ +#define list1_entry (ptr, type, member) container_of(ptr, type, member)
This is identical to list_entry(). Why have both?
include/linux/list.h static inline void hlist_move_list(struc · line 737
Quoted 8 lines
include/linux/list.h +8
-0+/** +* list1_for_each - iterate over a list +* @pos: the &struct list1_head to use as a loop cursor. +* @head: the head for your list. +*/ +#define list1_for_each (pos, head) \ + for (pos = (head)->next; prefetch(pos->next), pos != (head); \ + pos = pos->next)
Unless there is a strong need for list1_for_each(), I would leave it out. Error prone.
If you do retain it, don't you need rcu_dereference() as follows?
+#define list1_for_each(pos, head) \
+ for (pos = rcu_dereference((head)->next); prefetch(pos->next), pos != (head); \
+ pos = rcu_dereference(pos->next))
include/linux/list.h static inline void hlist_move_list(struc · line 748
Quoted 10 lines
include/linux/list.h +10
-0+/** +* list1_for_each_entry - iterate over list of given type +* @pos: the type * to use as a loop cursor. +* @head: the head for your list. +* @member: the name of the list1_struct within the struct. +*/ +#define list1_for_each_entry (pos, head, member) \ + for (pos = list1_entry((head)->next, typeof(*pos), member); \ + prefetch(pos->member.next), &pos->member != (head); \ + pos = list1_entry(pos->member.next, typeof(*pos), member))
Need rcu_dereference() here as well. Otherwise, compiler optimizations and DEC Alpha can cause failures.
include/linux/list.h static inline void hlist_move_list(struc · line 753
Quoted 4 lines
include/linux/list.h +4
-0+/** +* 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.
And cookie must initially be NULL.
include/linux/list.h static inline void hlist_move_list(struc · line 765
Quoted 12 lines
include/linux/list.h +12
-0+* @head: the head for your list. +* +* Same with list_for_each except that this primitive uses cookie +* so that we can continue iteration. +* 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 = (cookie)->next; \ + prefetch(pos->next), pos != (head) || ((cookie) = NULL); \ + (cookie) = pos, pos = pos->next)
Need rcu_dereference() here as well:
+#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))
Quoted 8 lines
include/linux/list.h +8
-0+/** +* list_add_tail_mb - add a new entry with memory barrier. +* @new: new entry to be added. +* @head: list head to add it before. +* +* Same with list_add_tail_rcu() except that this primitive uses mb() +* so that we can traverse forwards using list1_for_each() and +* list1_for_each_cookie() without any locks.
include/linux/list.h static inline void hlist_move_list(struc · line 784
Quoted 10 lines
include/linux/list.h +10
-0+* +* Caller must hold a lock for protecting @head. +*/ +static inline void list1_add_tail_mb (struct list1_head *new, +struct list1_head *head) +{ +struct list1_head *pos = head; + + new->next = head; +mb ();/* Avoid out-of-order execution. */
smp_wmb() should be sufficient. You could also instead use rcu_assign_pointer() on the assignment to pos->next below.
include/linux/list.h static inline void hlist_move_list(struc · line 788
Quoted 4 lines
include/linux/list.h +4
-0+while (pos->next != head) + pos = pos->next; + pos->next = new; +}
Hope the lists are never too long... ;-)
Another approach would be to maintain an explicit tail pointer, as is done in the RCU callback lists in kernel/rcuclassic.c.
Either way, I suggest simply list1_add_tail() -- the "mb" is implementation. The key point is that you can add to the list even when there are concurrent readers.
Quoted 1 line
include/linux/list.h +0
-0#endif
Kentaro Takeda
maintainerRe: [TOMOYO #10 (linux-next) 7/8] File operation restriction part.
Quoted 1 line · Paul E. McKenney
A few comments below -- some rcu_dereference()s are needed.
This list doesn't use RCU since it is for Write-Once-Read-Many situation (i.e. no-update and no-delete). TOMOYO Linux uses this list for storing policy elements. Most of elements are allocated when the kernel is loaded, and they are referred during lifetime of the kernel.
Since read_lock is not needed when referring this list, code of TOMOYO keeps its simplicity. If TOMOYO used RCU or reader/writer lock, the code would be a jumble of read_lock and it would be almost impossible to maintain and review the code... X-p This is the reason why TOMOYO uses this WORM list.
Though size of policy increases with learning mode, the same pathnames once learned will be reused. So memory usage of TOMOYO doesn't increase infinitely; if still worried, we can set memory quota.
Quoted 3 lines
The general idea looks sound, at least as long as the lists remain short. Otherwise, the list scan in
list1_add_tail_mb()will take too long.
Typically less than 100. The length of list won't matter since the frequency of append is very low.
Paul, would you review this list from the perspective of WORM list?
Regards,
Re: [TOMOYO #10 (linux-next) 7/8] File operation restriction part.
Quoted 19 lines · Kentaro Takeda
Quoted 1 line · Paul E. McKenney
A few comments below -- some rcu_dereference()s are needed.
This list doesn't use RCU since it is for Write-Once-Read-Many situation (i.e. no-update and no-delete). TOMOYO Linux uses this list for storing policy elements. Most of elements are allocated when the kernel is loaded, and they are referred during lifetime of the kernel.
Since
read_lockis not needed when referring this list, code of TOMOYO keeps its simplicity. If TOMOYO used RCU or reader/writer lock, the code would be a jumble ofread_lockand it would be almost impossible to maintain and review the code... X-p This is the reason why TOMOYO uses this WORM list.Though size of policy increases with learning mode, the same pathnames once learned will be reused. So memory usage of TOMOYO doesn't increase infinitely; if still worried, we can set memory quota.
Quoted 6 lines
Quoted 3 lines
The general idea looks sound, at least as long as the lists remain short. Otherwise, the list scan in
list1_add_tail_mb()will take too long.Typically less than 100. The length of list won't matter since the frequency of append is very low.
That sounds small enough to me. If it becomes a problem some time in the future, it will be easy for you to add a tail pointer or some such.
Quoted 1 line
Paul, would you review this list from the perspective of WORM list?
Hmmm... I thought I was reviewing it from that perspective. You never delete anything from the list, so you don't need RCU.
But fair enough. How about the following?
#define worm_dereference() rcu_dereference() #define worm_assign_pointer() rcu_assign_pointer()
Then put worm_dereference() where I told you to put rcu_dereference() and also put worm_assign_pointer() where I suggested using rcu_assign_pointer().
You might not be using RCU, but keep in mind that RCU is primarily about -removing- items from a data structure. When you -insert- items into a data structure that is being concurrently read, as you are doing, you must solve all the same problems that RCU has to solve for list insertion. Please see http://lkml.org/lkml/2008/2/2/255 for examples showing why this is needed. This posting does indeed use RCU terminology, but it is focused only on insertion, and therefore exactly describes the situation that you are in.
I am sure that you will get back to me with any questions or objections that you might have after looking this over. ;-)
Thanx, Paul
Kentaro Takeda
maintainerRe: [TOMOYO #10 (linux-next) 7/8] File operation restriction part.
Quoting from http://lkml.org/lkml/2008/2/2/255
Quoted 5 lines
Similarly, the
smp_read_barrier_depends()is only for initialization of something that is about to enter the list. As with thesmp_wmb()primitive,smp_read_barrier_depends()also is not to protect against freeing. Instead, it isrcu_read_lock()andrcu_read_unlock()that protect against freeing.
We don't need to use rcu_read_lock() and rcu_read_unlock() because we don't free elements in a list. I see.
However, to ensure the reader gets up-to-date value, we need to use smp_read_barrier_depends() (which is expanded to "mb()" for SMP on Alpha, "read_barrier_depends()" for SMP on H8300, "((void)0)" for SMP on M68K-nommu, "((void)0)" for M68K, "do { } while (0)" otherwise) whenever the reader fetches an element in a list.
Quoted 5 lines · Paul E. McKenney
But fair enough. How about the following?
#define worm_dereference() rcu_dereference() #define worm_assign_pointer() rcu_assign_pointer()
So, I understood that the rcu_dereference() and rcu_assign_pointer() are not only for RCU. They are needed to ensure the reader gets up-to-date value. Then, their names should be var_dereference() and var_assign_pointer() or something, shouldn't they? The "rcu_" prefix and comments on rcu_dereference in include/linux/rcupdate.h sound for me that they are used for variables protected by RCU locking mechanism only...
You are suggesting to explicitly call rcu_assign_pointer() (which will call smp_wmb()) and rcu_dereference() (which will call smp_read_barrier_depends()). But I think that the various cache invalidations driven by the workload will call rcu_assign_pointer() and rcu_dereference() sooner or later. So, if the reader can tolerate reading non-up-to-date value (in fact, TOMOYO can), isn't there a choice to omit rcu_assign_pointer() and rcu_dereference() (which will cost "mb()" for SMP on Alpha)?
Regards,
Re: [TOMOYO #10 (linux-next) 7/8] File operation restriction part.
Quoted 9 lines · Kentaro Takeda
Quoting from http://lkml.org/lkml/2008/2/2/255
Quoted 5 lines
Similarly, the
smp_read_barrier_depends()is only for initialization of something that is about to enter the list. As with thesmp_wmb()primitive,smp_read_barrier_depends()also is not to protect against freeing. Instead, it isrcu_read_lock()andrcu_read_unlock()that protect against freeing.We don't need to use
rcu_read_lock()andrcu_read_unlock()because we don't free elements in a list. I see.
Agreed!
Quoted 5 lines
However, to ensure the reader gets up-to-date value, we need to use
smp_read_barrier_depends()(which is expanded to "mb()" for SMP on Alpha, "read_barrier_depends()" for SMP on H8300, "((void)0)" for SMP on M68K-nommu, "((void)0)" for M68K, "do { } while (0)" otherwise) whenever the reader fetches an element in a list.
Yep. You will also need the ACCESS_ONCE() on the pointer fetch in order to suppress aggressive compiler optimizations. The rcu_dereference() primitive packages them up nicely.
Quoted 6 lines
Quoted 5 lines · Paul E. McKenney
But fair enough. How about the following?
#define worm_dereference() rcu_dereference() #define worm_assign_pointer() rcu_assign_pointer()
Quoted 7 lines
So, I understood that the
rcu_dereference()andrcu_assign_pointer()are not only for RCU. They are needed to ensure the reader gets up-to-date value. Then, their names should bevar_dereference()andvar_assign_pointer()or something, shouldn't they? The "rcu_" prefix and comments onrcu_dereferenceininclude/linux/rcupdate.hsound for me that they are used for variables protected by RCU locking mechanism only...
Well, there are 200+ uses of rcu_dereference() for RCU, so it would 99.5%+ accurate to retain the "rcu_" prefix. ;-)
Once we have several non-RCU uses, we can probably do a much better job of coming up with a good name for the underlying independent-of-RCU primitive. So we should stick with rcu_dereference() as the name of the underlying primitive for now, and re-evaluate the naming in a year or after another five non-RCU uses of rcu_dereference() appear, whichever comes later. (My current guess for names are "pointer_subscribe()" for rcu_dereference() and "pointer_publish()" for rcu_assign_pointer(), but who knows?)
Fair enough?
Quoted 8 lines
You are suggesting to explicitly call
rcu_assign_pointer()(which will callsmp_wmb()) andrcu_dereference()(which will callsmp_read_barrier_depends()). But I think that the various cache invalidations driven by the workload will callrcu_assign_pointer()andrcu_dereference()sooner or later. So, if the reader can tolerate reading non-up-to-date value (in fact, TOMOYO can), isn't there a choice to omitrcu_assign_pointer()andrcu_dereference()(which will cost "mb()" for SMP on Alpha)?
TOMOYO can tolerate reading the complete garbage that would appear if the pointer was assigned before the pointed-to fields are initialized? I must confess that I am having a hard time believing that. Please explain how this works.
Thanx, Paul
Tetsuo Handa
maintainerRe: [TOMOYO #10 (linux-next) 7/8] File operation restriction part.
Hello.
Quoted 10 lines · Paul E. McKenney
Quoted 5 lines · Kentaro Takeda
However, to ensure the reader gets up-to-date value, we need to use
smp_read_barrier_depends()(which is expanded to "mb()" for SMP on Alpha, "read_barrier_depends()" for SMP on H8300, "((void)0)" for SMP on M68K-nommu, "((void)0)" for M68K, "do { } while (0)" otherwise) whenever the reader fetches an element in a list.Yep. You will also need the
ACCESS_ONCE()on the pointer fetch in order to suppress aggressive compiler optimizations. Thercu_dereference()primitive packages them up nicely.
So, I should use rcu_dereference() rather than smp_read_barrier_depends(). I see.
Quoted 25 lines
Quoted 5 lines
Quoted 5 lines
But fair enough. How about the following?
#define worm_dereference() rcu_dereference() #define worm_assign_pointer() rcu_assign_pointer()Quoted 7 lines
So, I understood that the
rcu_dereference()andrcu_assign_pointer()are not only for RCU. They are needed to ensure the reader gets up-to-date value. Then, their names should bevar_dereference()andvar_assign_pointer()or something, shouldn't they? The "rcu_" prefix and comments onrcu_dereferenceininclude/linux/rcupdate.hsound for me that they are used for variables protected by RCU locking mechanism only...Well, there are 200+ uses of
rcu_dereference()for RCU, so it would 99.5%+ accurate to retain the "rcu_" prefix. ;-)Once we have several non-RCU uses, we can probably do a much better job of coming up with a good name for the underlying independent-of-RCU primitive. So we should stick with
rcu_dereference()as the name of the underlying primitive for now, and re-evaluate the naming in a year or after another five non-RCU uses ofrcu_dereference()appear, whichever comes later. (My current guess for names are "pointer_subscribe()" forrcu_dereference()and "pointer_publish()" forrcu_assign_pointer(), but who knows?)
Quoted 1 line
Fair enough?
Agreed.
Quoted 4 lines
TOMOYO can tolerate reading the complete garbage that would appear if the pointer was assigned before the pointed-to fields are initialized? I must confess that I am having a hard time believing that. Please explain how this works.
Maybe I'm misunderstanding what "mb()" can do.
I inserted "mb()" between the initialization of the pointed-to field and the assignment of the pointer like
static inline void list1_add_tail_mb(struct list1_head *new,
struct list1_head *head)
{
struct list1_head *pos = head; new->next = head;
mb(); /* Avoid out-of-order execution. */
while (pos->next != head)
pos = pos->next;
pos->next = new;}
to ensure that 'new->next' comes to point to 'head' before 'pos->next' comes to point to 'new', for arch/ia64/include/asm/system.h says:
/*
* Macros to force memory ordering. In these descriptions, "previous"
* and "subsequent" refer to program order; "visible" means that all
* architecturally visible effects of a memory access have occurred
* (at a minimum, this means the memory has been read or written). *
* wmb(): Guarantees that all preceding stores to memory- * like regions are visible before any subsequent * stores and that all following stores will be * visible only after all previous stores. * rmb(): Like wmb(), but for reads. * mb(): wmb()/rmb() combo, i.e., all previous memory * accesses are visible before all subsequent * accesses and vice versa. This is also known as * a "fence."
*
* Note: "mb()" and its variants cannot be used as a fence to order
* accesses to memory mapped I/O registers. For that, mf.a needs to
* be used. However, we don't want to always use mf.a because (a)
* it's (presumably) much slower than mf and (b) mf.a is supported for
* sequential memory pages only.
*/
But http://www.rdrop.com/users/paulmck/scalability/paper/ordering.2007.09.19a.pdf says:
On these systems, there are three orderings that must be accounted for:
1. Program order:
the order of the program's object code as seen by the CPU
2. Execution order:
The execution order can differ from program order due to both compiler and CPU implementation optimizations.
3. Perceived order:
And I noticed that the comment in arch/ia64/include/asm/system.h uses "program order", not "execution order".
Therefore, I have to also care "execution order".
I assumed that the "program order" == "execution order" and I thought:
The reader will read the appended element (i.e. 'new->next') only if the reader can reach (i.e. 'pos->next' == 'new') the appended element. The reader won't read the appended element (i.e. 'new->next') as long as the reader can't reach (i.e. 'pos->next' == 'head') the appended element.
However, since "execution order" != "program order", you are mentioning that I have to call smp_read_barrier_depends() which can control "execution order". Am I right?
Regards.
Re: [TOMOYO #10 (linux-next) 7/8] File operation restriction part.
Quoted 10 lines · Tetsuo Handa
Hello.
Quoted 7 lines · Paul E. McKenney
Quoted 5 lines · Kentaro Takeda
However, to ensure the reader gets up-to-date value, we need to use
smp_read_barrier_depends()(which is expanded to "mb()" for SMP on Alpha, "read_barrier_depends()" for SMP on H8300, "((void)0)" for SMP on M68K-nommu, "((void)0)" for M68K, "do { } while (0)" otherwise) whenever the reader fetches an element in a list.
Quoted 6 lines
Quoted 3 lines
Yep. You will also need the
ACCESS_ONCE()on the pointer fetch in order to suppress aggressive compiler optimizations. Thercu_dereference()primitive packages them up nicely.So, I should use
rcu_dereference()rather thansmp_read_barrier_depends(). I see.
Yep!
Quoted 69 lines
Quoted 25 lines
Quoted 5 lines
Quoted 5 lines
But fair enough. How about the following?
#define worm_dereference() rcu_dereference() #define worm_assign_pointer() rcu_assign_pointer()Quoted 7 lines
So, I understood that the
rcu_dereference()andrcu_assign_pointer()are not only for RCU. They are needed to ensure the reader gets up-to-date value. Then, their names should bevar_dereference()andvar_assign_pointer()or something, shouldn't they? The "rcu_" prefix and comments onrcu_dereferenceininclude/linux/rcupdate.hsound for me that they are used for variables protected by RCU locking mechanism only...Well, there are 200+ uses of
rcu_dereference()for RCU, so it would 99.5%+ accurate to retain the "rcu_" prefix. ;-)Once we have several non-RCU uses, we can probably do a much better job of coming up with a good name for the underlying independent-of-RCU primitive. So we should stick with
rcu_dereference()as the name of the underlying primitive for now, and re-evaluate the naming in a year or after another five non-RCU uses ofrcu_dereference()appear, whichever comes later. (My current guess for names are "pointer_subscribe()" forrcu_dereference()and "pointer_publish()" forrcu_assign_pointer(), but who knows?)Quoted 1 line
Fair enough?
Agreed.
Quoted 4 lines
TOMOYO can tolerate reading the complete garbage that would appear if the pointer was assigned before the pointed-to fields are initialized? I must confess that I am having a hard time believing that. Please explain how this works.
Maybe I'm misunderstanding what "
mb()" can do.I inserted "
mb()" between the initialization of the pointed-to field and the assignment of the pointer likestatic inline void list1_add_tail_mb(struct list1_head *new, struct list1_head *head) { struct list1_head *pos = head;new->next = head; mb(); /* Avoid out-of-order execution. */ while (pos->next != head) pos = pos->next; pos->next = new;}
to ensure that '
new->next' comes to point to 'head' before 'pos->next' comes to point to 'new', forarch/ia64/include/asm/system.hsays:/*
* Macros to force memory ordering. In these descriptions, "previous"
* and "subsequent" refer to program order; "visible" means that all
* architecturally visible effects of a memory access have occurred
* (at a minimum, this means the memory has been read or written). ** wmb(): Guarantees that all preceding stores to memory- * like regions are visible before any subsequent * stores and that all following stores will be * visible only after all previous stores. * rmb(): Like wmb(), but for reads. * mb(): wmb()/rmb() combo, i.e., all previous memory * accesses are visible before all subsequent * accesses and vice versa. This is also known as * a "fence."
Quoted 7 lines
*
* Note: "mb()" and its variants cannot be used as a fence to order
* accesses to memory mapped I/O registers. For that, mf.a needs to
* be used. However, we don't want to always use mf.a because (a)
* it's (presumably) much slower than mf and (b) mf.a is supported for
* sequential memory pages only.
*/
The problem is that while wmb() and mb() do in fact order writes, they cannot order the other task's reads. So if you do the following with all variables initially zero:
a = 1; smp_wmb(); /* use this instead of wmb() unless doing device I/O. */ b = 1;
That will indeed make the assignment to "a" happen before the assignment to "b". But if the reader does the following:
tmp_b = b; tmp_a = a;
There is nothing preventing these two reads from being reordered. The reader might well see tmp_a==0&&tmp_b==1, which is what the smp_wmb() was trying to prevent.
Quoted 14 lines
But http://www.rdrop.com/users/paulmck/scalability/paper/ordering.2007.09.19a.pdf says:
On these systems, there are three orderings that must be accounted for:
1. Program order:
the order of the program's object code as seen by the CPU
2. Execution order:
The execution order can differ from program order due to both compiler and CPU implementation optimizations.
3. Perceived order:And I noticed that the comment in
arch/ia64/include/asm/system.huses "program order", not "execution order".
Therefore, I have to also care "execution order".
Quoted 6 lines
I assumed that the "program order" == "execution order" and I thought:
The reader will read the appended element (i.e. '
new->next') only if the reader can reach (i.e. 'pos->next' == 'new') the appended element. The reader won't read the appended element (i.e. 'new->next') as long as the reader can't reach (i.e. 'pos->next' == 'head') the appended element.
This assumes something called "dependency ordering". Dependency ordering is intuitive, but unfortunately is not respected by DEC Alpha or by some aggressive value-speculation compiler optimizations.
Quoted 3 lines
However, since "execution order" != "program order", you are mentioning that I have to call
smp_read_barrier_depends()which can control "execution order". Am I right?
Partly.
See the section labelled "Alpha" on Page 4 of:
http://www.rdrop.com/users/paulmck/scalability/paper/ordering.2007.09.19a.pdf
for an explanation, though http://lkml.org/lkml/2008/2/2/255
does a much better job of explaining it.
Thanx, Paul
Tetsuo Handa
maintainerRe: [TOMOYO #10 (linux-next) 7/8] File operation restriction part.
Hello.
Quoted 5 lines · Paul E. McKenney
Quoted 1 line
Maybe I'm misunderstanding what "
mb()" can do.The problem is that while
wmb()andmb()do in fact order writes, they cannot order the other task's reads.
I expected that "mb()" can order the other task's reads.
Now, I understood that there is no room for optimizing the reader process by omitting smp_read_barrier_depends() on read side.
OK, let's return to http://lkml.org/lkml/2008/10/14/406 .
Below is the updated version of list1 operations.
As I now use rcu_assign_pointer() and rcu_dereference() which depend on include/linux/rcupdate.h , I separated the code from include/linux/list.h . Did I update correctly?
---
Subject: Singly linked list implementation.
--- 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
---
By the way, quoting from ordering.2007.09.19a.pdf :
| One could place an smp_rmb() primitive between the pointer fetch and | dereference. However, this imposes unneeded overhead on systems (such as | i386, IA64, PPC, and SPARC) that respect data dependencies on the read side. | A smp_read_barrier_depends() primitive has been added to the Linux 2.6 kernel | to eliminate overhead on these systems.
In 2.4 kernels, to support Alpha architecture, people use smp_rmb() which imposes unneeded overhead on non Alpha architecture.
In 2.6 kernels, to support Alpha architecture, people use smp_read_barrier_depends() which does not impose unneeded overhead on non Alpha architecture.
That's nice.
| Alpha is the only CPU where smp_read_barrier_depends() is an smp_mb() rather | than a no-op.
I found
#define smp_read_barrier_depends() read_barrier_depends()
in arch/h8300/include/asm/system.h but couldn't find the definition of read_barrier_depends() within that file.
I hope read_barrier_depends() is defined as a no-op by some other header files.
Regards.
Paul E. McKenney
Reviewed-byRe: [TOMOYO #10 (linux-next) 7/8] File operation restriction part.
Quoted 9 lines · Tetsuo Handa
Hello.
Quoted 5 lines · Paul E. McKenney
Quoted 1 line
Maybe I'm misunderstanding what "
mb()" can do.The problem is that while
wmb()andmb()do in fact order writes, they cannot order the other task's reads.I expected that "
mb()" can order the other task's reads.
So did I, a long time ago. It took an Alpha architect more than an hour face-to-face to convince me otherwise. ;-)
Quoted 11 lines
Now, I understood that there is no room for optimizing the reader process by omitting
smp_read_barrier_depends()on read side.OK, let's return to http://lkml.org/lkml/2008/10/14/406 .
Below is the updated version of list1 operations.
As I now usercu_assign_pointer()andrcu_dereference()which depend oninclude/linux/rcupdate.h, I separated the code frominclude/linux/list.h. Did I update correctly?---
Subject: Singly linked list implementation.
Looks good to me!
Quoted 118 lines
Signed-off-by Tetsuo Handa <[email protected]>--- include/linux/list1.h | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+)include/linux/list1.h new file +81
-1@@ -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 ---By the way, quoting from ordering.2007.09.19a.pdf :
| One could place an smp_rmb() primitive between the pointer fetch and | dereference. However, this imposes unneeded overhead on systems (such as | i386, IA64, PPC, and SPARC) that respect data dependencies on the read side. | A smp_read_barrier_depends() primitive has been added to the Linux 2.6 kernel | to eliminate overhead on these systems.In 2.4 kernels, to support Alpha architecture, people use
smp_rmb()which imposes unneeded overhead on non Alpha architecture.
In 2.6 kernels, to support Alpha architecture, people usesmp_read_barrier_depends()which does not impose unneeded overhead on non Alpha architecture.
That's nice.| Alpha is the only CPU where
smp_read_barrier_depends()is ansmp_mb()rather | than a no-op.I found
#define smp_read_barrier_depends() read_barrier_depends()in
arch/h8300/include/asm/system.hbut couldn't find the definition ofread_barrier_depends()within that file.
I hoperead_barrier_depends()is defined as a no-op by some other header files.Regards.
Re: [TOMOYO #10 (linux-next) 7/8] File operation restriction part.
In reply to Tetsuo HandaQuoted 7 lines · Tetsuo Handa
Hello.
Quoted 4 lines · Serge E. Hallyn
In a previous patch you mark funtions with 'begin/end critical section'. Please instead put a comment on top listing precisely which locks the fn expects to be held.
Quoted 6 lines
Quoted 3 lines
As for protecting your own data, please
1. explain at the var declaration what lock protects it
2. define the lock next to the listOK. I added comments and simplified dependencies.
http://svn.sourceforge.jp/cgi-bin/viewcvs.cgi/trunk/2.2.x/tomoyo-lsm/patches/?root=tomoyo
Cool, thanks.
This, in general, is part of changing your mindset - from that of being a maintainer of out-of-tree code, to being a part of the core community. My dcache comment further down is along the same lines.
Quoted 1 line
Anything else we can do before reposting as #11?
Well I'd like to sit down one day and make sure that your _clean()s in patch 1 cover all the error cases and there are no leaks.
The pathname walking code doesn't seem to be in any way tomoyo-specific, so it really ought to be in fs/dcache.c where the relevant maintainers will see, scrutinize, and update it when necessary. I realize that means we make it look like we encourage others to use the functions which we don't want either. But having them in tomoyo-specific code isn't nice either.
And I haven't really looked at your patches 6-8 yet, and am not sure when I'll get time.
Anyway I think we're well to the point where the patches should be tossed into a tree and tested (once you address Paul's feedback). Actually, one thing which is missing from this patchset is a MAINTAINERS entry. What I'd particularly be interested in is a mailing list entry (with a public readable archive), so we can see that there is in fact a community using this.
-serge
Kentaro Takeda
maintainerPATCH 8/8 Kconfig and Makefile
In reply to Kentaro Takeda--- 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
--